From 3b8cc0653b30425135701736d55d4a996c708d34 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 11 Jun 2024 17:31:18 +0100 Subject: [PATCH 001/405] feat(provider): `BlockReader::sealed_block_with_senders_range` (#8750) --- crates/primitives/src/header.rs | 6 + crates/stages/stages/src/stages/execution.rs | 2 +- .../provider/src/providers/database/mod.rs | 7 + .../src/providers/database/provider.rs | 420 ++++++++++-------- crates/storage/provider/src/providers/mod.rs | 7 + .../src/providers/static_file/manager.rs | 7 + .../storage/provider/src/test_utils/mock.rs | 7 + .../storage/provider/src/test_utils/noop.rs | 7 + crates/storage/storage-api/src/block.rs | 11 +- 9 files changed, 297 insertions(+), 177 deletions(-) diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index 25c846972c8f..abb5e1f34144 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -109,6 +109,12 @@ pub struct Header { pub extra_data: Bytes, } +impl AsRef for Header { + fn as_ref(&self) -> &Self { + self + } +} + impl Default for Header { fn default() -> Self { Self { diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 0bf19cd68933..0f2e5db6510c 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -390,7 +390,7 @@ where // Prepare the input for post unwind commit hook, where an `ExExNotification` will be sent. if self.exex_manager_handle.has_exexs() { // Get the blocks for the unwound range. - let blocks = provider.get_take_block_range::(range.clone())?; + let blocks = provider.sealed_block_with_senders_range(range.clone())?; let previous_input = self.post_unwind_commit_input.replace(Chain::new( blocks, bundle_state_with_receipts, diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 8b2b7bd6d7dd..edf095124290 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -339,6 +339,13 @@ impl BlockReader for ProviderFactory { ) -> ProviderResult> { self.provider()?.block_with_senders_range(range) } + + fn sealed_block_with_senders_range( + &self, + range: RangeInclusive, + ) -> ProviderResult> { + self.provider()?.sealed_block_with_senders_range(range) + } } impl TransactionsProvider for ProviderFactory { diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 5bab40d6273e..ee7eb72ec112 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -123,46 +123,6 @@ impl DatabaseProvider { } } -impl DatabaseProvider { - /// Iterates over read only values in the given table and collects them into a vector. - /// - /// Early-returns if the range is empty, without opening a cursor transaction. - fn cursor_read_collect>( - &self, - range: impl RangeBounds, - ) -> ProviderResult> { - let capacity = match range_size_hint(&range) { - Some(0) | None => return Ok(Vec::new()), - Some(capacity) => capacity, - }; - let mut cursor = self.tx.cursor_read::()?; - self.cursor_collect_with_capacity(&mut cursor, range, capacity) - } - - /// Iterates over read only values in the given table and collects them into a vector. - fn cursor_collect>( - &self, - cursor: &mut impl DbCursorRO, - range: impl RangeBounds, - ) -> ProviderResult> { - let capacity = range_size_hint(&range).unwrap_or(0); - self.cursor_collect_with_capacity(cursor, range, capacity) - } - - fn cursor_collect_with_capacity>( - &self, - cursor: &mut impl DbCursorRO, - range: impl RangeBounds, - capacity: usize, - ) -> ProviderResult> { - let mut items = Vec::with_capacity(capacity); - for entry in cursor.walk_range(range)? { - items.push(entry?.1); - } - Ok(items) - } -} - impl DatabaseProvider { /// Storage provider for state at that given block pub fn state_provider_by_block_number( @@ -314,6 +274,22 @@ impl DatabaseProvider { &self.tx } + /// Returns a reference to the [`ChainSpec`]. + pub fn chain_spec(&self) -> &ChainSpec { + &self.chain_spec + } + + /// Disables long-lived read transaction safety guarantees for leaks prevention and + /// observability improvements. + /// + /// CAUTION: In most of the cases, you want the safety guarantees for long read transactions + /// enabled. Use this only if you're sure that no write transaction is open in parallel, meaning + /// that Reth as a node is offline and not progressing. + pub fn disable_long_read_transaction_safety(mut self) -> Self { + self.tx.disable_long_read_transaction_safety(); + self + } + /// Return full table as Vec pub fn table(&self) -> Result>, DatabaseError> where @@ -325,15 +301,42 @@ impl DatabaseProvider { .collect::, DatabaseError>>() } - /// Disables long-lived read transaction safety guarantees for leaks prevention and - /// observability improvements. + /// Iterates over read only values in the given table and collects them into a vector. /// - /// CAUTION: In most of the cases, you want the safety guarantees for long read transactions - /// enabled. Use this only if you're sure that no write transaction is open in parallel, meaning - /// that Reth as a node is offline and not progressing. - pub fn disable_long_read_transaction_safety(mut self) -> Self { - self.tx.disable_long_read_transaction_safety(); - self + /// Early-returns if the range is empty, without opening a cursor transaction. + fn cursor_read_collect>( + &self, + range: impl RangeBounds, + ) -> ProviderResult> { + let capacity = match range_size_hint(&range) { + Some(0) | None => return Ok(Vec::new()), + Some(capacity) => capacity, + }; + let mut cursor = self.tx.cursor_read::()?; + self.cursor_collect_with_capacity(&mut cursor, range, capacity) + } + + /// Iterates over read only values in the given table and collects them into a vector. + fn cursor_collect>( + &self, + cursor: &mut impl DbCursorRO, + range: impl RangeBounds, + ) -> ProviderResult> { + let capacity = range_size_hint(&range).unwrap_or(0); + self.cursor_collect_with_capacity(cursor, range, capacity) + } + + fn cursor_collect_with_capacity>( + &self, + cursor: &mut impl DbCursorRO, + range: impl RangeBounds, + capacity: usize, + ) -> ProviderResult> { + let mut items = Vec::with_capacity(capacity); + for entry in cursor.walk_range(range)? { + items.push(entry?.1); + } + Ok(items) } fn transactions_by_tx_range_with_cursor( @@ -353,9 +356,162 @@ impl DatabaseProvider { ) } - /// Returns a reference to the [`ChainSpec`]. - pub fn chain_spec(&self) -> &ChainSpec { - &self.chain_spec + /// Returns a range of blocks from the database. + /// + /// Uses the provided `headers_range` to get the headers for the range, and `assemble_block` to + /// construct blocks from the following inputs: + /// – Header + /// - Range of transaction numbers + /// – Ommers + /// – Withdrawals + /// – Requests + /// – Senders + fn block_range( + &self, + range: RangeInclusive, + headers_range: HF, + mut assemble_block: F, + ) -> ProviderResult> + where + H: AsRef
, + HF: FnOnce(RangeInclusive) -> ProviderResult>, + F: FnMut( + H, + Range, + Vec
, + Option, + Option, + ) -> ProviderResult, + { + if range.is_empty() { + return Ok(Vec::new()) + } + + let len = range.end().saturating_sub(*range.start()) as usize; + let mut blocks = Vec::with_capacity(len); + + let headers = 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 { + let header_ref = header.as_ref(); + // 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 information to return the block anyways, so + // we skip the block. + if let Some((_, block_body_indices)) = + block_body_cursor.seek_exact(header_ref.number)? + { + let tx_range = block_body_indices.tx_num_range(); + + // If we are past shanghai, then all blocks should have a withdrawal list, + // even if empty + let withdrawals = + if self.chain_spec.is_shanghai_active_at_timestamp(header_ref.timestamp) { + Some( + withdrawals_cursor + .seek_exact(header_ref.number)? + .map(|(_, w)| w.withdrawals) + .unwrap_or_default(), + ) + } else { + None + }; + let requests = + if self.chain_spec.is_prague_active_at_timestamp(header_ref.timestamp) { + Some(requests_cursor.seek_exact(header_ref.number)?.unwrap_or_default().1) + } else { + None + }; + let ommers = + if self.chain_spec.final_paris_total_difficulty(header_ref.number).is_some() { + Vec::new() + } else { + ommers_cursor + .seek_exact(header_ref.number)? + .map(|(_, o)| o.ommers) + .unwrap_or_default() + }; + + if let Ok(b) = assemble_block(header, tx_range, ommers, withdrawals, requests) { + blocks.push(b); + } + } + } + + Ok(blocks) + } + + /// Returns a range of blocks from the database, along with the senders of each + /// transaction in the blocks. + /// + /// Uses the provided `headers_range` to get the headers for the range, and `assemble_block` to + /// construct blocks from the following inputs: + /// – Header + /// - Transactions + /// – Ommers + /// – Withdrawals + /// – Requests + /// – Senders + fn block_with_senders_range( + &self, + range: RangeInclusive, + headers_range: HF, + assemble_block: BF, + ) -> ProviderResult> + where + H: AsRef
, + HF: Fn(RangeInclusive) -> ProviderResult>, + BF: Fn( + H, + Vec, + Vec
, + Option, + Option, + Vec
, + ) -> ProviderResult, + { + let mut tx_cursor = self.tx.cursor_read::()?; + let mut senders_cursor = self.tx.cursor_read::()?; + + self.block_range(range, headers_range, |header, tx_range, ommers, withdrawals, requests| { + let (body, senders) = if tx_range.is_empty() { + (Vec::new(), Vec::new()) + } else { + let body = self + .transactions_by_tx_range_with_cursor(tx_range.clone(), &mut tx_cursor)? + .into_iter() + .map(Into::into) + .collect::>(); + // fetch senders from the senders table + let known_senders = + senders_cursor + .walk_range(tx_range.clone())? + .collect::, _>>()?; + + let mut senders = Vec::with_capacity(body.len()); + for (tx_num, tx) in tx_range.zip(body.iter()) { + match known_senders.get(&tx_num) { + None => { + // recover the sender from the transaction if not found + let sender = tx + .recover_signer_unchecked() + .ok_or_else(|| ProviderError::SenderRecoveryError)?; + senders.push(sender); + } + Some(sender) => senders.push(*sender), + } + } + + (body, senders) + }; + + assemble_block(header, body, ommers, withdrawals, requests, senders) + }) } } @@ -1316,79 +1472,6 @@ impl BlockNumReader for DatabaseProvider { } } -impl DatabaseProvider { - fn process_block_range( - &self, - range: RangeInclusive, - mut assemble_block: F, - ) -> ProviderResult> - where - F: FnMut( - Range, - Header, - Vec
, - Option, - Option, - ) -> ProviderResult, - { - if range.is_empty() { - return Ok(Vec::new()) - } - - let len = range.end().saturating_sub(*range.start()) as usize; - let mut blocks = Vec::with_capacity(len); - - 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 { - // 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 information to return the block anyways, so - // we skip the block. - if let Some((_, block_body_indices)) = block_body_cursor.seek_exact(header.number)? { - let tx_range = block_body_indices.tx_num_range(); - - // If we are past shanghai, then all blocks should have a withdrawal list, - // even if empty - let withdrawals = - if self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp) { - Some( - withdrawals_cursor - .seek_exact(header.number)? - .map(|(_, w)| w.withdrawals) - .unwrap_or_default(), - ) - } 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() - } else { - ommers_cursor - .seek_exact(header.number)? - .map(|(_, o)| o.ommers) - .unwrap_or_default() - }; - if let Ok(b) = assemble_block(tx_range, header, ommers, withdrawals, requests) { - blocks.push(b); - } - } - } - Ok(blocks) - } -} - impl BlockReader for DatabaseProvider { fn find_block_by_hash(&self, hash: B256, source: BlockSource) -> ProviderResult> { if source.is_database() { @@ -1515,62 +1598,53 @@ 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, requests| { - let body = if tx_range.is_empty() { - Vec::new() - } else { - self.transactions_by_tx_range_with_cursor(tx_range, &mut tx_cursor)? - .into_iter() - .map(Into::into) - .collect() - }; - Ok(Block { header, body, ommers, withdrawals, requests }) - }) + self.block_range( + range, + |range| self.headers_range(range), + |header, tx_range, ommers, withdrawals, requests| { + let body = if tx_range.is_empty() { + Vec::new() + } else { + self.transactions_by_tx_range_with_cursor(tx_range, &mut tx_cursor)? + .into_iter() + .map(Into::into) + .collect() + }; + Ok(Block { header, body, ommers, withdrawals, requests }) + }, + ) } fn block_with_senders_range( &self, range: RangeInclusive, ) -> ProviderResult> { - 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, requests| { - let (body, senders) = if tx_range.is_empty() { - (Vec::new(), Vec::new()) - } else { - let body = self - .transactions_by_tx_range_with_cursor(tx_range.clone(), &mut tx_cursor)? - .into_iter() - .map(Into::into) - .collect::>(); - // fetch senders from the senders table - let known_senders = - senders_cursor - .walk_range(tx_range.clone())? - .collect::, _>>()?; - - let mut senders = Vec::with_capacity(body.len()); - for (tx_num, tx) in tx_range.zip(body.iter()) { - match known_senders.get(&tx_num) { - None => { - // recover the sender from the transaction if not found - let sender = tx - .recover_signer_unchecked() - .ok_or_else(|| ProviderError::SenderRecoveryError)?; - senders.push(sender); - } - Some(sender) => senders.push(*sender), - } - } - - (body, senders) - }; + self.block_with_senders_range( + range, + |range| self.headers_range(range), + |header, body, ommers, withdrawals, requests, senders| { + Block { header, body, ommers, withdrawals, requests } + .try_with_senders_unchecked(senders) + .map_err(|_| ProviderError::SenderRecoveryError) + }, + ) + } - Block { header, body, ommers, withdrawals, requests } - .try_with_senders_unchecked(senders) - .map_err(|_| ProviderError::SenderRecoveryError) - }) + fn sealed_block_with_senders_range( + &self, + range: RangeInclusive, + ) -> ProviderResult> { + self.block_with_senders_range( + range, + |range| self.sealed_headers_range(range), + |header, body, ommers, withdrawals, requests, senders| { + SealedBlockWithSenders::new( + SealedBlock { header, body, ommers, withdrawals, requests }, + senders, + ) + .ok_or(ProviderError::SenderRecoveryError) + }, + ) } } diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index a8e08be62fed..7cf994870c94 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -337,6 +337,13 @@ where ) -> ProviderResult> { self.database.block_with_senders_range(range) } + + fn sealed_block_with_senders_range( + &self, + range: RangeInclusive, + ) -> ProviderResult> { + self.database.sealed_block_with_senders_range(range) + } } impl TransactionsProvider for BlockchainProvider diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index f0b4234e3265..114284f229b7 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -1467,6 +1467,13 @@ impl BlockReader for StaticFileProvider { ) -> ProviderResult> { Err(ProviderError::UnsupportedProvider) } + + fn sealed_block_with_senders_range( + &self, + _range: RangeInclusive, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } } impl WithdrawalsProvider for StaticFileProvider { diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 3a10c27541f6..a255eb28e1be 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -490,6 +490,13 @@ impl BlockReader for MockEthProvider { ) -> ProviderResult> { Ok(vec![]) } + + fn sealed_block_with_senders_range( + &self, + _range: RangeInclusive, + ) -> ProviderResult> { + Ok(vec![]) + } } impl BlockReaderIdExt for MockEthProvider { diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index ce5e18de45a8..8894a68f7ed6 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -123,6 +123,13 @@ impl BlockReader for NoopProvider { ) -> ProviderResult> { Ok(vec![]) } + + fn sealed_block_with_senders_range( + &self, + _range: RangeInclusive, + ) -> ProviderResult> { + Ok(vec![]) + } } impl BlockReaderIdExt for NoopProvider { diff --git a/crates/storage/storage-api/src/block.rs b/crates/storage/storage-api/src/block.rs index 539930f5a5c6..42ab05f22503 100644 --- a/crates/storage/storage-api/src/block.rs +++ b/crates/storage/storage-api/src/block.rs @@ -123,14 +123,19 @@ pub trait BlockReader: /// Note: returns only available blocks fn block_range(&self, range: RangeInclusive) -> ProviderResult>; - /// retrieves a range of blocks from the database, along with the senders of each + /// Returns a range of blocks from the database, along with the senders of each /// transaction in the blocks. - /// - /// The `transaction_kind` parameter determines whether to return its hash fn block_with_senders_range( &self, range: RangeInclusive, ) -> ProviderResult>; + + /// Returns a range of sealed blocks from the database, along with the senders of each + /// transaction in the blocks. + fn sealed_block_with_senders_range( + &self, + range: RangeInclusive, + ) -> ProviderResult>; } /// Trait extension for `BlockReader`, for types that implement `BlockId` conversion. From 5eecc4f9100ad1ae65b69f5ef83d94cd8917582e Mon Sep 17 00:00:00 2001 From: Brian Bland Date: Tue, 11 Jun 2024 10:40:23 -0700 Subject: [PATCH 002/405] fix: Use OptimismBeaconConsensus in the OptimismNode (#8487) --- Cargo.lock | 5 + crates/ethereum/node/Cargo.toml | 4 +- crates/ethereum/node/src/node.rs | 30 ++++- crates/exex/test-utils/Cargo.toml | 1 + crates/exex/test-utils/src/lib.rs | 32 ++++- crates/node/builder/Cargo.toml | 4 +- crates/node/builder/src/builder/mod.rs | 5 + crates/node/builder/src/components/builder.rs | 124 ++++++++++++++---- .../node/builder/src/components/consensus.rs | 32 +++++ crates/node/builder/src/components/mod.rs | 26 +++- crates/node/builder/src/launch/mod.rs | 17 +-- crates/optimism/node/Cargo.toml | 1 + crates/optimism/node/src/node.rs | 23 +++- examples/custom-engine-types/src/main.rs | 4 +- 14 files changed, 262 insertions(+), 46 deletions(-) create mode 100644 crates/node/builder/src/components/consensus.rs diff --git a/Cargo.lock b/Cargo.lock index 8d1e5d5aca95..9f1c5ba7e5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7024,6 +7024,7 @@ dependencies = [ "rand 0.8.5", "reth-blockchain-tree", "reth-config", + "reth-consensus", "reth-db", "reth-db-common", "reth-evm", @@ -7413,7 +7414,10 @@ dependencies = [ "futures", "futures-util", "reth", + "reth-auto-seal-consensus", "reth-basic-payload-builder", + "reth-beacon-consensus", + "reth-consensus", "reth-db", "reth-e2e-test-utils", "reth-ethereum-engine-primitives", @@ -7476,6 +7480,7 @@ dependencies = [ "reth-network", "reth-node-api", "reth-node-builder", + "reth-optimism-consensus", "reth-optimism-payload-builder", "reth-payload-builder", "reth-primitives", diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index 072f91b28fd6..ee1df5565db5 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -22,6 +22,9 @@ reth-provider.workspace = true reth-transaction-pool.workspace = true reth-network.workspace = true reth-evm-ethereum.workspace = true +reth-consensus.workspace = true +reth-auto-seal-consensus.workspace = true +reth-beacon-consensus.workspace = true # misc eyre.workspace = true @@ -38,4 +41,3 @@ futures.workspace = true tokio.workspace = true futures-util.workspace = true serde_json.workspace = true - diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 96698ccfccd4..55592762304a 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -1,12 +1,15 @@ //! Ethereum Node types config. use crate::{EthEngineTypes, EthEvmConfig}; +use reth_auto_seal_consensus::AutoSealConsensus; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; +use reth_beacon_consensus::EthBeaconConsensus; use reth_evm_ethereum::execute::EthExecutorProvider; use reth_network::NetworkHandle; use reth_node_builder::{ components::{ - ComponentsBuilder, ExecutorBuilder, NetworkBuilder, PayloadServiceBuilder, PoolBuilder, + ComponentsBuilder, ConsensusBuilder, ExecutorBuilder, NetworkBuilder, + PayloadServiceBuilder, PoolBuilder, }, node::{FullNodeTypes, NodeTypes}, BuilderContext, Node, PayloadBuilderConfig, @@ -18,6 +21,7 @@ use reth_transaction_pool::{ blobstore::DiskFileBlobStore, EthTransactionPool, TransactionPool, TransactionValidationTaskExecutor, }; +use std::sync::Arc; /// Type configuration for a regular Ethereum node. #[derive(Debug, Default, Clone, Copy)] @@ -32,6 +36,7 @@ impl EthereumNode { EthereumPayloadBuilder, EthereumNetworkBuilder, EthereumExecutorBuilder, + EthereumConsensusBuilder, > where Node: FullNodeTypes, @@ -42,6 +47,7 @@ impl EthereumNode { .payload(EthereumPayloadBuilder::default()) .network(EthereumNetworkBuilder::default()) .executor(EthereumExecutorBuilder::default()) + .consensus(EthereumConsensusBuilder::default()) } } @@ -60,6 +66,7 @@ where EthereumPayloadBuilder, EthereumNetworkBuilder, EthereumExecutorBuilder, + EthereumConsensusBuilder, >; fn components_builder(self) -> Self::ComponentsBuilder { @@ -227,3 +234,24 @@ where Ok(handle) } } + +/// A basic ethereum consensus builder. +#[derive(Debug, Default, Clone, Copy)] +pub struct EthereumConsensusBuilder { + // TODO add closure to modify consensus +} + +impl ConsensusBuilder for EthereumConsensusBuilder +where + Node: FullNodeTypes, +{ + type Consensus = Arc; + + async fn build_consensus(self, ctx: &BuilderContext) -> eyre::Result { + if ctx.is_dev() { + Ok(Arc::new(AutoSealConsensus::new(ctx.chain_spec()))) + } else { + Ok(Arc::new(EthBeaconConsensus::new(ctx.chain_spec()))) + } + } +} diff --git a/crates/exex/test-utils/Cargo.toml b/crates/exex/test-utils/Cargo.toml index 42412db8d9b9..5e00109d8abc 100644 --- a/crates/exex/test-utils/Cargo.toml +++ b/crates/exex/test-utils/Cargo.toml @@ -14,6 +14,7 @@ workspace = true ## reth reth-blockchain-tree.workspace = true reth-config.workspace = true +reth-consensus = { workspace = true, features = ["test-utils"] } reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-evm = { workspace = true, features = ["test-utils"] } diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index 2c7b57172825..76e153067088 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -10,6 +10,7 @@ use futures_util::FutureExt; use reth_blockchain_tree::noop::NoopBlockchainTree; +use reth_consensus::test_utils::TestConsensus; use reth_db::{test_utils::TempDatabase, DatabaseEnv}; use reth_db_common::init::init_genesis; use reth_evm::test_utils::MockExecutorProvider; @@ -18,7 +19,8 @@ use reth_network::{config::SecretKey, NetworkConfigBuilder, NetworkManager}; use reth_node_api::{FullNodeTypes, FullNodeTypesAdapter, NodeTypes}; use reth_node_builder::{ components::{ - Components, ComponentsBuilder, ExecutorBuilder, NodeComponentsBuilder, PoolBuilder, + Components, ComponentsBuilder, ConsensusBuilder, ExecutorBuilder, NodeComponentsBuilder, + PoolBuilder, }, BuilderContext, Node, NodeAdapter, RethFullAdapter, }; @@ -83,6 +85,22 @@ where } } +/// A test [`ConsensusBuilder`] that builds a [`TestConsensus`]. +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct TestConsensusBuilder; + +impl ConsensusBuilder for TestConsensusBuilder +where + Node: FullNodeTypes, +{ + type Consensus = Arc; + + async fn build_consensus(self, _ctx: &BuilderContext) -> eyre::Result { + Ok(Arc::new(TestConsensus::default())) + } +} + /// A test [`Node`]. #[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] @@ -103,6 +121,7 @@ where EthereumPayloadBuilder, EthereumNetworkBuilder, TestExecutorBuilder, + TestConsensusBuilder, >; fn components_builder(self) -> Self::ComponentsBuilder { @@ -112,6 +131,7 @@ where .payload(EthereumPayloadBuilder::default()) .network(EthereumNetworkBuilder::default()) .executor(TestExecutorBuilder::default()) + .consensus(TestConsensusBuilder::default()) } } @@ -198,6 +218,7 @@ pub async fn test_exex_context_with_chain_spec( let transaction_pool = testing_pool(); let evm_config = EthEvmConfig::default(); let executor = MockExecutorProvider::default(); + let consensus = Arc::new(TestConsensus::default()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); let genesis_hash = init_genesis(provider_factory.clone())?; @@ -217,7 +238,14 @@ pub async fn test_exex_context_with_chain_spec( let task_executor = tasks.executor(); let components = NodeAdapter::, _> { - components: Components { transaction_pool, evm_config, executor, network, payload_builder }, + components: Components { + transaction_pool, + evm_config, + executor, + consensus, + network, + payload_builder, + }, task_executor, provider, }; diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index f47ed8e7d4be..45c2a90989d9 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -55,10 +55,10 @@ tokio = { workspace = true, features = [ ] } tokio-stream.workspace = true -# ethereum +## ethereum discv5.workspace = true -# crypto +## crypto secp256k1 = { workspace = true, features = [ "global-context", "rand-std", diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 02d1b0e04a5a..da2f5eb9fcff 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -458,6 +458,11 @@ impl BuilderContext { self.provider().chain_spec() } + /// Returns true if the node is configured as --dev + pub const fn is_dev(&self) -> bool { + self.config().dev.dev + } + /// Returns the transaction pool config of the node. pub fn pool_config(&self) -> PoolConfig { self.config().txpool.pool_config() diff --git a/crates/node/builder/src/components/builder.rs b/crates/node/builder/src/components/builder.rs index 33ecd00bb46f..72d2e6933da5 100644 --- a/crates/node/builder/src/components/builder.rs +++ b/crates/node/builder/src/components/builder.rs @@ -2,11 +2,12 @@ use crate::{ components::{ - Components, ExecutorBuilder, NetworkBuilder, NodeComponents, PayloadServiceBuilder, - PoolBuilder, + Components, ConsensusBuilder, ExecutorBuilder, NetworkBuilder, NodeComponents, + PayloadServiceBuilder, PoolBuilder, }, BuilderContext, ConfigureEvm, FullNodeTypes, }; +use reth_consensus::Consensus; use reth_evm::execute::BlockExecutorProvider; use reth_transaction_pool::TransactionPool; use std::{future::Future, marker::PhantomData}; @@ -31,19 +32,22 @@ use std::{future::Future, marker::PhantomData}; /// All component builders are captured in the builder state and will be consumed once the node is /// launched. #[derive(Debug)] -pub struct ComponentsBuilder { +pub struct ComponentsBuilder { pool_builder: PoolB, payload_builder: PayloadB, network_builder: NetworkB, executor_builder: ExecB, + consensus_builder: ConsB, _marker: PhantomData, } -impl - ComponentsBuilder +impl + ComponentsBuilder { /// Configures the node types. - pub fn node_types(self) -> ComponentsBuilder + pub fn node_types( + self, + ) -> ComponentsBuilder where Types: FullNodeTypes, { @@ -52,6 +56,7 @@ impl payload_builder, network_builder, executor_builder: evm_builder, + consensus_builder, _marker, } = self; ComponentsBuilder { @@ -59,6 +64,7 @@ impl pool_builder, payload_builder, network_builder, + consensus_builder, _marker: Default::default(), } } @@ -70,6 +76,7 @@ impl payload_builder: self.payload_builder, network_builder: self.network_builder, executor_builder: self.executor_builder, + consensus_builder: self.consensus_builder, _marker: self._marker, } } @@ -81,6 +88,7 @@ impl payload_builder: f(self.payload_builder), network_builder: self.network_builder, executor_builder: self.executor_builder, + consensus_builder: self.consensus_builder, _marker: self._marker, } } @@ -92,6 +100,7 @@ impl payload_builder: self.payload_builder, network_builder: f(self.network_builder), executor_builder: self.executor_builder, + consensus_builder: self.consensus_builder, _marker: self._marker, } } @@ -103,13 +112,26 @@ impl payload_builder: self.payload_builder, network_builder: self.network_builder, executor_builder: f(self.executor_builder), + consensus_builder: self.consensus_builder, + _marker: self._marker, + } + } + + /// Apply a function to the consensus builder. + pub fn map_consensus(self, f: impl FnOnce(ConsB) -> ConsB) -> Self { + Self { + pool_builder: self.pool_builder, + payload_builder: self.payload_builder, + network_builder: self.network_builder, + executor_builder: self.executor_builder, + consensus_builder: f(self.consensus_builder), _marker: self._marker, } } } -impl - ComponentsBuilder +impl + ComponentsBuilder where Node: FullNodeTypes, { @@ -120,7 +142,7 @@ where pub fn pool( self, pool_builder: PB, - ) -> ComponentsBuilder + ) -> ComponentsBuilder where PB: PoolBuilder, { @@ -129,6 +151,7 @@ where payload_builder, network_builder, executor_builder: evm_builder, + consensus_builder, _marker, } = self; ComponentsBuilder { @@ -136,13 +159,14 @@ where payload_builder, network_builder, executor_builder: evm_builder, + consensus_builder, _marker, } } } -impl - ComponentsBuilder +impl + ComponentsBuilder where Node: FullNodeTypes, PoolB: PoolBuilder, @@ -154,7 +178,7 @@ where pub fn network( self, network_builder: NB, - ) -> ComponentsBuilder + ) -> ComponentsBuilder where NB: NetworkBuilder, { @@ -163,6 +187,7 @@ where payload_builder, network_builder: _, executor_builder: evm_builder, + consensus_builder, _marker, } = self; ComponentsBuilder { @@ -170,6 +195,7 @@ where payload_builder, network_builder, executor_builder: evm_builder, + consensus_builder, _marker, } } @@ -181,7 +207,7 @@ where pub fn payload( self, payload_builder: PB, - ) -> ComponentsBuilder + ) -> ComponentsBuilder where PB: PayloadServiceBuilder, { @@ -190,6 +216,7 @@ where payload_builder: _, network_builder, executor_builder: evm_builder, + consensus_builder, _marker, } = self; ComponentsBuilder { @@ -197,6 +224,7 @@ where payload_builder, network_builder, executor_builder: evm_builder, + consensus_builder, _marker, } } @@ -208,32 +236,69 @@ where pub fn executor( self, executor_builder: EB, - ) -> ComponentsBuilder + ) -> ComponentsBuilder where EB: ExecutorBuilder, { - let Self { pool_builder, payload_builder, network_builder, executor_builder: _, _marker } = - self; + let Self { + pool_builder, + payload_builder, + network_builder, + executor_builder: _, + consensus_builder, + _marker, + } = self; ComponentsBuilder { pool_builder, payload_builder, network_builder, executor_builder, + consensus_builder, + _marker, + } + } + + /// Configures the consensus builder. + /// + /// This accepts a [`ConsensusBuilder`] instance that will be used to create the node's + /// components for consensus. + pub fn consensus( + self, + consensus_builder: CB, + ) -> ComponentsBuilder + where + CB: ConsensusBuilder, + { + let Self { + pool_builder, + payload_builder, + network_builder, + executor_builder, + consensus_builder: _, + _marker, + } = self; + ComponentsBuilder { + pool_builder, + payload_builder, + network_builder, + executor_builder, + consensus_builder, _marker, } } } -impl NodeComponentsBuilder - for ComponentsBuilder +impl NodeComponentsBuilder + for ComponentsBuilder where Node: FullNodeTypes, PoolB: PoolBuilder, NetworkB: NetworkBuilder, PayloadB: PayloadServiceBuilder, ExecB: ExecutorBuilder, + ConsB: ConsensusBuilder, { - type Components = Components; + type Components = Components; async fn build_components( self, @@ -244,6 +309,7 @@ where payload_builder, network_builder, executor_builder: evm_builder, + consensus_builder, _marker, } = self; @@ -251,18 +317,27 @@ where let pool = pool_builder.build_pool(context).await?; let network = network_builder.build_network(context, pool.clone()).await?; let payload_builder = payload_builder.spawn_payload_service(context, pool.clone()).await?; + let consensus = consensus_builder.build_consensus(context).await?; - Ok(Components { transaction_pool: pool, evm_config, network, payload_builder, executor }) + Ok(Components { + transaction_pool: pool, + evm_config, + network, + payload_builder, + executor, + consensus, + }) } } -impl Default for ComponentsBuilder<(), (), (), (), ()> { +impl Default for ComponentsBuilder<(), (), (), (), (), ()> { fn default() -> Self { Self { pool_builder: (), payload_builder: (), network_builder: (), executor_builder: (), + consensus_builder: (), _marker: Default::default(), } } @@ -288,16 +363,17 @@ pub trait NodeComponentsBuilder: Send { ) -> impl Future> + Send; } -impl NodeComponentsBuilder for F +impl NodeComponentsBuilder for F where Node: FullNodeTypes, F: FnOnce(&BuilderContext) -> Fut + Send, - Fut: Future>> + Send, + Fut: Future>> + Send, Pool: TransactionPool + Unpin + 'static, EVM: ConfigureEvm, Executor: BlockExecutorProvider, + Cons: Consensus + Clone + Unpin + 'static, { - type Components = Components; + type Components = Components; fn build_components( self, diff --git a/crates/node/builder/src/components/consensus.rs b/crates/node/builder/src/components/consensus.rs new file mode 100644 index 000000000000..6c90bda54752 --- /dev/null +++ b/crates/node/builder/src/components/consensus.rs @@ -0,0 +1,32 @@ +//! Consensus component for the node builder. +use crate::{BuilderContext, FullNodeTypes}; +use std::future::Future; + +/// A type that knows how to build the consensus implementation. +pub trait ConsensusBuilder: Send { + /// The consensus implementation to build. + type Consensus: reth_consensus::Consensus + Clone + Unpin + 'static; + + /// Creates the consensus implementation. + fn build_consensus( + self, + ctx: &BuilderContext, + ) -> impl Future> + Send; +} + +impl ConsensusBuilder for F +where + Node: FullNodeTypes, + Consensus: reth_consensus::Consensus + Clone + Unpin + 'static, + F: FnOnce(&BuilderContext) -> Fut + Send, + Fut: Future> + Send, +{ + type Consensus = Consensus; + + fn build_consensus( + self, + ctx: &BuilderContext, + ) -> impl Future> { + self(ctx) + } +} diff --git a/crates/node/builder/src/components/mod.rs b/crates/node/builder/src/components/mod.rs index 8d0494470cb1..0419f7a71c74 100644 --- a/crates/node/builder/src/components/mod.rs +++ b/crates/node/builder/src/components/mod.rs @@ -9,16 +9,19 @@ use crate::{ConfigureEvm, FullNodeTypes}; pub use builder::*; +pub use consensus::*; pub use execute::*; pub use network::*; pub use payload::*; pub use pool::*; +use reth_consensus::Consensus; use reth_evm::execute::BlockExecutorProvider; use reth_network::NetworkHandle; use reth_payload_builder::PayloadBuilderHandle; use reth_transaction_pool::TransactionPool; mod builder; +mod consensus; mod execute; mod network; mod payload; @@ -39,6 +42,9 @@ pub trait NodeComponents: Clone + Unpin + Send + Sync /// The type that knows how to execute blocks. type Executor: BlockExecutorProvider; + /// The consensus type of the node. + type Consensus: Consensus + Clone + Unpin + 'static; + /// Returns the transaction pool of the node. fn pool(&self) -> &Self::Pool; @@ -48,6 +54,9 @@ pub trait NodeComponents: Clone + Unpin + Send + Sync /// Returns the node's executor type. fn block_executor(&self) -> &Self::Executor; + /// Returns the node's consensus type. + fn consensus(&self) -> &Self::Consensus; + /// Returns the handle to the network fn network(&self) -> &NetworkHandle; @@ -59,29 +68,34 @@ pub trait NodeComponents: Clone + Unpin + Send + Sync /// /// This provides access to all the components of the node. #[derive(Debug)] -pub struct Components { +pub struct Components { /// The transaction pool of the node. pub transaction_pool: Pool, /// The node's EVM configuration, defining settings for the Ethereum Virtual Machine. pub evm_config: EVM, /// The node's executor type used to execute individual blocks and batches of blocks. pub executor: Executor, + /// The consensus implementation of the node. + pub consensus: Consensus, /// The network implementation of the node. pub network: NetworkHandle, /// The handle to the payload builder service. pub payload_builder: PayloadBuilderHandle, } -impl NodeComponents for Components +impl NodeComponents + for Components where Node: FullNodeTypes, Pool: TransactionPool + Unpin + 'static, EVM: ConfigureEvm, Executor: BlockExecutorProvider, + Cons: Consensus + Clone + Unpin + 'static, { type Pool = Pool; type Evm = EVM; type Executor = Executor; + type Consensus = Cons; fn pool(&self) -> &Self::Pool { &self.transaction_pool @@ -95,6 +109,10 @@ where &self.executor } + fn consensus(&self) -> &Self::Consensus { + &self.consensus + } + fn network(&self) -> &NetworkHandle { &self.network } @@ -104,18 +122,20 @@ where } } -impl Clone for Components +impl Clone for Components where Node: FullNodeTypes, Pool: TransactionPool, EVM: ConfigureEvm, Executor: BlockExecutorProvider, + Cons: Consensus + Clone, { fn clone(&self) -> Self { Self { transaction_pool: self.transaction_pool.clone(), evm_config: self.evm_config.clone(), executor: self.executor.clone(), + consensus: self.consensus.clone(), network: self.network.clone(), payload_builder: self.payload_builder.clone(), } diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index cd9971ad7806..fde21ad9730e 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -8,10 +8,9 @@ use crate::{ BuilderContext, NodeBuilderWithComponents, NodeHandle, }; use futures::{future::Either, stream, stream_select, StreamExt}; -use reth_auto_seal_consensus::AutoSealConsensus; use reth_beacon_consensus::{ hooks::{EngineHooks, PruneHook, StaticFileHook}, - BeaconConsensusEngine, EthBeaconConsensus, + BeaconConsensusEngine, }; use reth_blockchain_tree::{ noop::NoopBlockchainTree, BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, @@ -29,6 +28,7 @@ use reth_node_core::{ version::{CARGO_PKG_VERSION, CLIENT_CODE, NAME_CLIENT, VERGEN_GIT_SHA}, }; use reth_node_events::{cl::ConsensusLayerHealthEvents, node}; + use reth_primitives::format_ether; use reth_provider::providers::BlockchainProvider; use reth_rpc_engine_api::EngineApi; @@ -130,13 +130,6 @@ where info!(target: "reth::cli", "\n{}", this.chain_spec().display_hardforks()); }); - // setup the consensus instance - let consensus: Arc = if ctx.is_dev() { - Arc::new(AutoSealConsensus::new(ctx.chain_spec())) - } else { - Arc::new(EthBeaconConsensus::new(ctx.chain_spec())) - }; - debug!(target: "reth::cli", "Spawning stages metrics listener task"); let (sync_metrics_tx, sync_metrics_rx) = unbounded_channel(); let sync_metrics_listener = reth_stages::MetricsListener::new(sync_metrics_rx); @@ -169,6 +162,8 @@ where debug!(target: "reth::cli", "creating components"); let components = components_builder.build_components(&builder_ctx).await?; + let consensus: Arc = Arc::new(components.consensus().clone()); + let tree_externals = TreeExternals::new( ctx.provider_factory().clone(), consensus.clone(), @@ -258,7 +253,7 @@ where ctx.node_config(), &ctx.toml_config().stages, client.clone(), - Arc::clone(&consensus), + consensus.clone(), ctx.provider_factory().clone(), ctx.task_executor(), sync_metrics_tx, @@ -281,7 +276,7 @@ where ctx.node_config(), &ctx.toml_config().stages, network_client.clone(), - Arc::clone(&consensus), + consensus.clone(), ctx.provider_factory().clone(), ctx.task_executor(), sync_metrics_tx, diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index f3ca7cf96b7e..224607674a43 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -29,6 +29,7 @@ reth-evm.workspace = true reth-revm.workspace = true reth-evm-optimism.workspace = true reth-beacon-consensus.workspace = true +reth-optimism-consensus.workspace = true revm-primitives.workspace = true reth-discv5.workspace = true diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index c62cb598580d..aabd7b815de6 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -11,11 +11,13 @@ use reth_evm_optimism::{OpExecutorProvider, OptimismEvmConfig}; use reth_network::{NetworkHandle, NetworkManager}; use reth_node_builder::{ components::{ - ComponentsBuilder, ExecutorBuilder, NetworkBuilder, PayloadServiceBuilder, PoolBuilder, + ComponentsBuilder, ConsensusBuilder, ExecutorBuilder, NetworkBuilder, + PayloadServiceBuilder, PoolBuilder, }, node::{FullNodeTypes, NodeTypes}, BuilderContext, Node, PayloadBuilderConfig, }; +use reth_optimism_consensus::OptimismBeaconConsensus; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::CanonStateSubscriptions; use reth_tracing::tracing::{debug, info}; @@ -47,6 +49,7 @@ impl OptimismNode { OptimismPayloadBuilder, OptimismNetworkBuilder, OptimismExecutorBuilder, + OptimismConsensusBuilder, > where Node: FullNodeTypes, @@ -61,6 +64,7 @@ impl OptimismNode { )) .network(OptimismNetworkBuilder { disable_txpool_gossip }) .executor(OptimismExecutorBuilder::default()) + .consensus(OptimismConsensusBuilder::default()) } } @@ -74,6 +78,7 @@ where OptimismPayloadBuilder, OptimismNetworkBuilder, OptimismExecutorBuilder, + OptimismConsensusBuilder, >; fn components_builder(self) -> Self::ComponentsBuilder { @@ -302,3 +307,19 @@ where Ok(handle) } } + +/// A basic optimism consensus builder. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct OptimismConsensusBuilder; + +impl ConsensusBuilder for OptimismConsensusBuilder +where + Node: FullNodeTypes, +{ + type Consensus = OptimismBeaconConsensus; + + async fn build_consensus(self, ctx: &BuilderContext) -> eyre::Result { + Ok(OptimismBeaconConsensus::new(ctx.chain_spec())) + } +} diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index de6d38870230..d13177e053ce 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -43,7 +43,7 @@ use reth_node_api::{ }; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::node::{ - EthereumExecutorBuilder, EthereumNetworkBuilder, EthereumPoolBuilder, + EthereumConsensusBuilder, EthereumExecutorBuilder, EthereumNetworkBuilder, EthereumPoolBuilder, }; use reth_payload_builder::{ error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes, PayloadBuilderHandle, @@ -204,6 +204,7 @@ where CustomPayloadServiceBuilder, EthereumNetworkBuilder, EthereumExecutorBuilder, + EthereumConsensusBuilder, >; fn components_builder(self) -> Self::ComponentsBuilder { @@ -213,6 +214,7 @@ where .payload(CustomPayloadServiceBuilder::default()) .network(EthereumNetworkBuilder::default()) .executor(EthereumExecutorBuilder::default()) + .consensus(EthereumConsensusBuilder::default()) } } From 64d6853fc321d04acec3a82c4b554d33d7f6dceb Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Tue, 11 Jun 2024 21:09:13 +0200 Subject: [PATCH 003/405] fix: add `requests` in `ExecutionOutcome` extend (#8761) --- crates/evm/execution-types/src/bundle.rs | 97 ++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/crates/evm/execution-types/src/bundle.rs b/crates/evm/execution-types/src/bundle.rs index 09f950a624d5..6f1e5995da48 100644 --- a/crates/evm/execution-types/src/bundle.rs +++ b/crates/evm/execution-types/src/bundle.rs @@ -288,6 +288,7 @@ impl ExecutionOutcome { pub fn extend(&mut self, other: Self) { self.bundle.extend(other.bundle); self.receipts.extend(other.receipts.receipt_vec); + self.requests.extend(other.requests); } /// Prepends present the state with the given `BundleState`. @@ -591,4 +592,100 @@ mod tests { // Assert that exec_res_empty_receipts is empty assert!(exec_res_empty_receipts.is_empty()); } + + #[test] + fn test_revert_to() { + // Create a Receipts object with a vector of receipt vectors + let receipts = Receipts { + receipt_vec: vec![vec![Some(Receipt { + tx_type: TxType::Legacy, + cumulative_gas_used: 46913, + logs: vec![], + success: true, + #[cfg(feature = "optimism")] + deposit_nonce: Some(18), + #[cfg(feature = "optimism")] + deposit_receipt_version: Some(34), + })]], + }; + + // Define the first block number + let first_block = 123; + + // Create a ExecutionOutcome object with the created bundle, receipts, requests, and + // first_block + let mut exec_res = ExecutionOutcome { + bundle: Default::default(), + receipts: receipts.clone(), + requests: vec![], + first_block, + }; + + // Assert that the revert_to method returns true when reverting to the initial block number. + assert!(exec_res.revert_to(123)); + + // Assert that the receipts remain unchanged after reverting to the initial block number. + assert_eq!(exec_res.receipts, receipts); + + // Assert that the revert_to method returns false when attempting to revert to a block + // number greater than the initial block number. + assert!(!exec_res.revert_to(133)); + + // Assert that the revert_to method returns false when attempting to revert to a block + // number less than the initial block number. + assert!(!exec_res.revert_to(10)); + } + + #[test] + fn test_extend_execution_outcome() { + // Create a Receipt object with specific attributes. + let receipt = Receipt { + tx_type: TxType::Legacy, + cumulative_gas_used: 46913, + logs: vec![], + success: true, + #[cfg(feature = "optimism")] + deposit_nonce: Some(18), + #[cfg(feature = "optimism")] + deposit_receipt_version: Some(34), + }; + + // Create a Receipts object containing the receipt. + let receipts = Receipts { receipt_vec: vec![vec![Some(receipt.clone())]] }; + + // Create a DepositRequest object with specific attributes. + let request = Request::DepositRequest(DepositRequest { + pubkey: FixedBytes::<48>::from([1; 48]), + withdrawal_credentials: B256::from([0; 32]), + amount: 1111, + signature: FixedBytes::<96>::from([2; 96]), + index: 222, + }); + + // Create a vector of Requests containing the request. + let requests = vec![Requests(vec![request])]; + + // Define the initial block number. + let first_block = 123; + + // Create an ExecutionOutcome object. + let mut exec_res = + ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block }; + + // Extend the ExecutionOutcome object by itself. + exec_res.extend(exec_res.clone()); + + // Assert the extended ExecutionOutcome matches the expected outcome. + assert_eq!( + exec_res, + ExecutionOutcome { + bundle: Default::default(), + receipts: Receipts { + receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt)]] + }, + requests: vec![Requests(vec![request]), Requests(vec![request])], + first_block: 123, + } + ); + } } From 92d2f29ab83a1b4fe36ef16e3d4c3c156a8b5179 Mon Sep 17 00:00:00 2001 From: Brian Bland Date: Tue, 11 Jun 2024 12:31:00 -0700 Subject: [PATCH 004/405] chore: Remove optimism flag from eth consensus crate (#8760) --- crates/consensus/beacon/Cargo.toml | 1 - crates/ethereum/consensus/Cargo.toml | 3 --- crates/ethereum/consensus/src/lib.rs | 13 ++----------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index 7693890cc762..3f617d1d0a3a 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -76,6 +76,5 @@ optimism = [ "reth-primitives/optimism", "reth-provider/optimism", "reth-blockchain-tree/optimism", - "reth-ethereum-consensus/optimism", "reth-rpc/optimism", ] diff --git a/crates/ethereum/consensus/Cargo.toml b/crates/ethereum/consensus/Cargo.toml index f1ee25085ef2..3eae6aaa7f58 100644 --- a/crates/ethereum/consensus/Cargo.toml +++ b/crates/ethereum/consensus/Cargo.toml @@ -17,6 +17,3 @@ reth-primitives.workspace = true reth-consensus.workspace = true tracing.workspace = true - -[features] -optimism = ["reth-primitives/optimism"] diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index cbef399cce57..2772bd8f0f70 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -52,27 +52,18 @@ impl Consensus for EthBeaconConsensus { Ok(()) } - #[allow(unused_assignments)] - #[allow(unused_mut)] fn validate_header_with_total_difficulty( &self, header: &Header, total_difficulty: U256, ) -> Result<(), ConsensusError> { - let mut is_post_merge = self + let is_post_merge = self .chain_spec .fork(Hardfork::Paris) .active_at_ttd(total_difficulty, header.difficulty); - #[cfg(feature = "optimism")] - { - // If OP-Stack then bedrock activation number determines when TTD (eth Merge) has been - // reached. - is_post_merge = self.chain_spec.is_bedrock_active_at_block(header.number); - } - if is_post_merge { - if !self.chain_spec.is_optimism() && !header.is_zero_difficulty() { + if !header.is_zero_difficulty() { return Err(ConsensusError::TheMergeDifficultyIsNotZero) } From 268e768d822a0d4eb8ed365dc6390862f759a849 Mon Sep 17 00:00:00 2001 From: jn Date: Tue, 11 Jun 2024 13:46:01 -0700 Subject: [PATCH 005/405] feat: introduce payload types (#8756) Co-authored-by: Matthias Seitz --- crates/e2e-test-utils/src/node.rs | 13 +++++++------ crates/engine-primitives/src/lib.rs | 19 +++++-------------- crates/ethereum/engine-primitives/src/lib.rs | 7 +++++-- crates/optimism/node/src/engine.rs | 7 +++++-- crates/payload/primitives/src/lib.rs | 11 +++++++++++ examples/custom-engine-types/src/main.rs | 6 +++++- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index 5c09f09d7be0..ed2f22bf0102 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -9,6 +9,7 @@ use futures_util::Future; use reth::{ api::{BuiltPayload, EngineTypes, FullNodeComponents, PayloadBuilderAttributes}, builder::FullNode, + payload::PayloadTypes, providers::{BlockReader, BlockReaderIdExt, CanonStateSubscriptions, StageCheckpointReader}, rpc::types::engine::PayloadStatusEnum, }; @@ -65,12 +66,12 @@ where &mut self, length: u64, tx_generator: impl Fn(u64) -> Pin>>, - attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes + attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes + Copy, ) -> eyre::Result< Vec<( ::BuiltPayload, - ::PayloadBuilderAttributes, + ::PayloadBuilderAttributes, )>, > where @@ -96,10 +97,10 @@ where /// It triggers the resolve payload via engine api and expects the built payload event. pub async fn new_payload( &mut self, - attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, + attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, ) -> eyre::Result<( <::Engine as EngineTypes>::BuiltPayload, - <::Engine as EngineTypes>::PayloadBuilderAttributes, + <::Engine as PayloadTypes>::PayloadBuilderAttributes, )> where ::ExecutionPayloadV3: @@ -121,10 +122,10 @@ where pub async fn advance_block( &mut self, versioned_hashes: Vec, - attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, + attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, ) -> eyre::Result<( ::BuiltPayload, - <::Engine as EngineTypes>::PayloadBuilderAttributes, + <::Engine as PayloadTypes>::PayloadBuilderAttributes, )> where ::ExecutionPayloadV3: diff --git a/crates/engine-primitives/src/lib.rs b/crates/engine-primitives/src/lib.rs index c10156fb786a..fd64030fc1ec 100644 --- a/crates/engine-primitives/src/lib.rs +++ b/crates/engine-primitives/src/lib.rs @@ -9,26 +9,17 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use core::fmt; -use reth_primitives::ChainSpec; - use reth_payload_primitives::{ - BuiltPayload, EngineApiMessageVersion, EngineObjectValidationError, PayloadAttributes, - PayloadBuilderAttributes, PayloadOrAttributes, + BuiltPayload, EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes, + PayloadTypes, }; - +use reth_primitives::ChainSpec; use serde::{de::DeserializeOwned, ser::Serialize}; + /// The types that are used by the engine API. pub trait EngineTypes: - DeserializeOwned + Serialize + fmt::Debug + Unpin + Send + Sync + Clone + PayloadTypes + 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; - - /// The payload attributes type that contains information about a running payload job. - type PayloadBuilderAttributes: PayloadBuilderAttributes - + Clone - + Unpin; - /// The built payload type. type BuiltPayload: BuiltPayload + Clone diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index 441f83f9ac8a..e3b22a650d26 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -14,7 +14,7 @@ pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes}; use reth_engine_primitives::EngineTypes; use reth_payload_primitives::{ validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, - PayloadOrAttributes, + PayloadOrAttributes, PayloadTypes, }; use reth_primitives::ChainSpec; use reth_rpc_types::{ @@ -30,9 +30,12 @@ use reth_rpc_types::{ #[non_exhaustive] pub struct EthEngineTypes; -impl EngineTypes for EthEngineTypes { +impl PayloadTypes for EthEngineTypes { type PayloadAttributes = EthPayloadAttributes; type PayloadBuilderAttributes = EthPayloadBuilderAttributes; +} + +impl EngineTypes for EthEngineTypes { type BuiltPayload = EthBuiltPayload; type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index 8321b9777567..51dc7a5b3e49 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -1,7 +1,7 @@ use reth_node_api::{ payload::{ validate_parent_beacon_block_root_presence, EngineApiMessageVersion, - EngineObjectValidationError, MessageValidationKind, PayloadOrAttributes, + EngineObjectValidationError, MessageValidationKind, PayloadOrAttributes, PayloadTypes, VersionSpecificValidationError, }, EngineTypes, @@ -21,9 +21,12 @@ use reth_rpc_types::{ #[non_exhaustive] pub struct OptimismEngineTypes; -impl EngineTypes for OptimismEngineTypes { +impl PayloadTypes for OptimismEngineTypes { type PayloadAttributes = OptimismPayloadAttributes; type PayloadBuilderAttributes = OptimismPayloadBuilderAttributes; +} + +impl EngineTypes for OptimismEngineTypes { type BuiltPayload = OptimismBuiltPayload; type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index 2cc4fadf65a2..6bc8b1b11884 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -22,6 +22,17 @@ pub use payload::PayloadOrAttributes; use reth_primitives::ChainSpec; use std::fmt::Debug; +/// The types that are used by the engine API. +pub trait PayloadTypes: Send + Sync + Unpin { + /// The RPC payload attributes type the CL node emits via the engine API. + type PayloadAttributes: PayloadAttributes + Unpin; + + /// The payload attributes type that contains information about a running payload job. + type PayloadBuilderAttributes: PayloadBuilderAttributes + + Clone + + Unpin; +} + /// Validates the timestamp depending on the version called: /// /// * If V2, this ensures that the payload timestamp is pre-Cancun. diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index d13177e053ce..21db41204017 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use reth::{ + api::PayloadTypes, builder::{ components::{ComponentsBuilder, PayloadServiceBuilder}, node::NodeTypes, @@ -162,9 +163,12 @@ impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes { #[non_exhaustive] pub struct CustomEngineTypes; -impl EngineTypes for CustomEngineTypes { +impl PayloadTypes for CustomEngineTypes { type PayloadAttributes = CustomPayloadAttributes; type PayloadBuilderAttributes = CustomPayloadBuilderAttributes; +} + +impl EngineTypes for CustomEngineTypes { type BuiltPayload = EthBuiltPayload; type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; From 2bc16420491eaced802938ffd666ab52f29cffd8 Mon Sep 17 00:00:00 2001 From: 0xAtreides <103257861+JackG-eth@users.noreply.github.com> Date: Wed, 12 Jun 2024 00:21:48 +0100 Subject: [PATCH 006/405] feat: support `no_std` for `reth-execution-errors` (#8729) Co-authored-by: Matthias Seitz --- Cargo.lock | 22 +++++++++++++++++++++- Cargo.toml | 1 + crates/evm/execution-errors/Cargo.toml | 7 ++++++- crates/evm/execution-errors/src/lib.rs | 20 ++++++++++++++------ crates/evm/execution-errors/src/trie.rs | 2 +- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f1c5ba7e5d6..2c4cfb1b669d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6979,7 +6979,7 @@ dependencies = [ "reth-primitives", "reth-prune-types", "reth-storage-errors", - "thiserror", + "thiserror-no-std", ] [[package]] @@ -9544,6 +9544,26 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", +] + [[package]] name = "thread_local" version = "1.1.8" diff --git a/Cargo.toml b/Cargo.toml index b8269979a504..76deae8c50ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -381,6 +381,7 @@ generic-array = "0.14" tracing = "0.1.0" tracing-appender = "0.2" thiserror = "1.0" +thiserror-no-std = { version = "2.0.2", default-features = false } serde_json = "1.0.94" serde = { version = "1.0", default-features = false } serde_with = "3.3.0" diff --git a/crates/evm/execution-errors/Cargo.toml b/crates/evm/execution-errors/Cargo.toml index 4a23156848a3..920eb7a95e0b 100644 --- a/crates/evm/execution-errors/Cargo.toml +++ b/crates/evm/execution-errors/Cargo.toml @@ -16,4 +16,9 @@ reth-consensus.workspace = true reth-primitives.workspace = true reth-storage-errors.workspace = true reth-prune-types.workspace = true -thiserror.workspace = true +thiserror-no-std = { workspace = true, default-features = false } + + +[features] +default = ["std"] +std = ["thiserror-no-std/std"] \ No newline at end of file diff --git a/crates/evm/execution-errors/src/lib.rs b/crates/evm/execution-errors/src/lib.rs index 3c5088028fbd..3954fe982657 100644 --- a/crates/evm/execution-errors/src/lib.rs +++ b/crates/evm/execution-errors/src/lib.rs @@ -7,19 +7,24 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; use reth_consensus::ConsensusError; use reth_primitives::{revm_primitives::EVMError, BlockNumHash, B256}; use reth_prune_types::PruneSegmentError; use reth_storage_errors::provider::ProviderError; -use std::fmt::Display; -use thiserror::Error; + +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, string::String}; pub mod trie; pub use trie::{StateRootError, StorageRootError}; /// Transaction validation errors -#[derive(Error, Debug, Clone, PartialEq, Eq)] +#[derive(thiserror_no_std::Error, Debug, Clone, PartialEq, Eq)] pub enum BlockValidationError { /// EVM error with transaction hash and message #[error("EVM reported invalid transaction ({hash}): {error}")] @@ -99,7 +104,7 @@ pub enum BlockValidationError { } /// `BlockExecutor` Errors -#[derive(Error, Debug)] +#[derive(thiserror_no_std::Error, Debug)] pub enum BlockExecutionError { /// Validation error, transparently wrapping `BlockValidationError` #[error(transparent)] @@ -119,7 +124,7 @@ pub enum BlockExecutionError { /// Transaction error on commit with inner details #[error("transaction error on commit: {inner}")] CanonicalCommit { - /// The inner error message + /// The inner error message. inner: String, }, /// Error when appending chain on fork is not possible @@ -136,12 +141,14 @@ pub enum BlockExecutionError { #[error(transparent)] LatestBlock(#[from] ProviderError), /// Arbitrary Block Executor Errors + #[cfg(feature = "std")] #[error(transparent)] Other(Box), } impl BlockExecutionError { /// Create a new `BlockExecutionError::Other` variant. + #[cfg(feature = "std")] pub fn other(error: E) -> Self where E: std::error::Error + Send + Sync + 'static, @@ -150,7 +157,8 @@ impl BlockExecutionError { } /// Create a new [`BlockExecutionError::Other`] from a given message. - pub fn msg(msg: impl Display) -> Self { + #[cfg(feature = "std")] + pub fn msg(msg: impl std::fmt::Display) -> Self { Self::Other(msg.to_string().into()) } diff --git a/crates/evm/execution-errors/src/trie.rs b/crates/evm/execution-errors/src/trie.rs index ee511611b998..fd3533977ab2 100644 --- a/crates/evm/execution-errors/src/trie.rs +++ b/crates/evm/execution-errors/src/trie.rs @@ -1,7 +1,7 @@ //! Errors when computing the state root. use reth_storage_errors::db::DatabaseError; -use thiserror::Error; +use thiserror_no_std::Error; /// State root errors. #[derive(Error, Debug, PartialEq, Eq, Clone)] From 1c148e7f030f0b6b3610564e0142c799756666f4 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 12 Jun 2024 01:56:03 +0200 Subject: [PATCH 007/405] chore: dont depend on reth-primitives (#8762) --- Cargo.lock | 4 +++- crates/evm/execution-errors/Cargo.toml | 6 +++++- crates/evm/execution-errors/src/lib.rs | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c4cfb1b669d..c3a40a53aebb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6975,10 +6975,12 @@ dependencies = [ name = "reth-execution-errors" version = "0.2.0-beta.9" dependencies = [ + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-primitives", "reth-consensus", - "reth-primitives", "reth-prune-types", "reth-storage-errors", + "revm-primitives", "thiserror-no-std", ] diff --git a/crates/evm/execution-errors/Cargo.toml b/crates/evm/execution-errors/Cargo.toml index 920eb7a95e0b..8ec3a7024cb5 100644 --- a/crates/evm/execution-errors/Cargo.toml +++ b/crates/evm/execution-errors/Cargo.toml @@ -13,9 +13,13 @@ workspace = true [dependencies] # reth reth-consensus.workspace = true -reth-primitives.workspace = true reth-storage-errors.workspace = true reth-prune-types.workspace = true + +alloy-primitives.workspace = true +alloy-eips.workspace = true +revm-primitives.workspace = true + thiserror-no-std = { workspace = true, default-features = false } diff --git a/crates/evm/execution-errors/src/lib.rs b/crates/evm/execution-errors/src/lib.rs index 3954fe982657..9cc4d2ec1285 100644 --- a/crates/evm/execution-errors/src/lib.rs +++ b/crates/evm/execution-errors/src/lib.rs @@ -12,10 +12,12 @@ #[cfg(not(feature = "std"))] extern crate alloc; +use alloy_eips::BlockNumHash; +use alloy_primitives::B256; use reth_consensus::ConsensusError; -use reth_primitives::{revm_primitives::EVMError, BlockNumHash, B256}; use reth_prune_types::PruneSegmentError; use reth_storage_errors::provider::ProviderError; +use revm_primitives::EVMError; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, string::String}; From c2650388bd8c479b155d29eaa8920ec3ac5e04c6 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:28:13 +0200 Subject: [PATCH 008/405] fix: add `start_time` to `ProcessUID` on `StorageLock` (#8753) --- crates/storage/db/Cargo.toml | 1 + crates/storage/db/src/lockfile.rs | 105 ++++++++++++++++++++++++------ testing/ef-tests/Cargo.toml | 2 +- 3 files changed, 88 insertions(+), 20 deletions(-) diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index ca27a9a40739..8fb417fb3e3c 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -79,6 +79,7 @@ mdbx = ["reth-libmdbx"] bench = [] arbitrary = ["reth-primitives/arbitrary", "reth-db-api/arbitrary"] optimism = [] +disable-lock = [] [[bench]] name = "hash_keys" diff --git a/crates/storage/db/src/lockfile.rs b/crates/storage/db/src/lockfile.rs index 2d7704506bae..e0da20348a53 100644 --- a/crates/storage/db/src/lockfile.rs +++ b/crates/storage/db/src/lockfile.rs @@ -1,5 +1,7 @@ //! Storage lock utils. +#![cfg_attr(feature = "disable-lock", allow(dead_code))] + use reth_storage_errors::lockfile::StorageLockError; use reth_tracing::tracing::error; use std::{ @@ -7,7 +9,7 @@ use std::{ process, sync::Arc, }; -use sysinfo::System; +use sysinfo::{ProcessRefreshKind, RefreshKind, System}; /// File lock name. const LOCKFILE_NAME: &str = "lock"; @@ -28,15 +30,31 @@ impl StorageLock { /// Note: In-process exclusivity is not on scope. If called from the same process (or another /// with the same PID), it will succeed. pub fn try_acquire(path: &Path) -> Result { - let path = path.join(LOCKFILE_NAME); + let file_path = path.join(LOCKFILE_NAME); - if let Some(pid) = parse_lock_file_pid(&path)? { - if pid != (process::id() as usize) && System::new_all().process(pid.into()).is_some() { - return Err(StorageLockError::Taken(pid)) - } + #[cfg(feature = "disable-lock")] + { + // Too expensive for ef-tests to write/read lock to/from disk. + Ok(Self(Arc::new(StorageLockInner { file_path }))) } - Ok(Self(Arc::new(StorageLockInner::new(path)?))) + #[cfg(not(feature = "disable-lock"))] + { + if let Some(process_lock) = ProcessUID::parse(&file_path)? { + if process_lock.pid != (process::id() as usize) && process_lock.is_active() { + error!( + target: "reth::db::lockfile", + path = ?file_path, + pid = process_lock.pid, + start_time = process_lock.start_time, + "Storage lock already taken." + ); + return Err(StorageLockError::Taken(process_lock.pid)) + } + } + + Ok(Self(Arc::new(StorageLockInner::new(file_path)?))) + } } } @@ -66,19 +84,61 @@ impl StorageLockInner { reth_fs_util::create_dir_all(parent)?; } - reth_fs_util::write(&file_path, format!("{}", process::id()))?; + // Write this process unique identifier (pid & start_time) to file + ProcessUID::own().write(&file_path)?; Ok(Self { file_path }) } } -/// Parses the PID from the lock file if it exists. -fn parse_lock_file_pid(path: &Path) -> Result, StorageLockError> { - if path.exists() { - let contents = reth_fs_util::read_to_string(path)?; - return Ok(contents.trim().parse().ok()) +#[derive(Debug)] +struct ProcessUID { + /// OS process identifier + pid: usize, + /// Process start time + start_time: u64, +} + +impl ProcessUID { + /// Creates [`Self`] for the provided PID. + fn new(pid: usize) -> Option { + System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())) + .process(pid.into()) + .map(|process| Self { pid, start_time: process.start_time() }) + } + + /// Creates [`Self`] from own process. + fn own() -> Self { + Self::new(process::id() as usize).expect("own process") + } + + /// Parses [`Self`] from a file. + fn parse(path: &Path) -> Result, StorageLockError> { + if path.exists() { + if let Ok(contents) = reth_fs_util::read_to_string(path) { + let mut lines = contents.lines(); + if let (Some(Ok(pid)), Some(Ok(start_time))) = ( + lines.next().map(str::trim).map(str::parse), + lines.next().map(str::trim).map(str::parse), + ) { + return Ok(Some(Self { pid, start_time })); + } + } + } + Ok(None) + } + + /// Whether a process with this `pid` and `start_time` exists. + fn is_active(&self) -> bool { + System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())) + .process(self.pid.into()) + .is_some_and(|p| p.start_time() == self.start_time) + } + + /// Writes `pid` and `start_time` to a file. + fn write(&self, path: &Path) -> Result<(), StorageLockError> { + Ok(reth_fs_util::write(path, format!("{}\n{}", self.pid, self.start_time))?) } - Ok(None) } #[cfg(test)] @@ -101,12 +161,19 @@ mod tests { while system.process(fake_pid.into()).is_some() { fake_pid += 1; } - reth_fs_util::write(&lock_file, format!("{}", fake_pid)).unwrap(); - assert_eq!(Ok(lock), StorageLock::try_acquire(temp_dir.path())); + ProcessUID { pid: fake_pid, start_time: u64::MAX }.write(&lock_file).unwrap(); + assert_eq!(Ok(lock.clone()), StorageLock::try_acquire(temp_dir.path())); + + let mut pid_1 = ProcessUID::new(1).unwrap(); - // A lock of a different but existing PID cannot be acquired. - reth_fs_util::write(&lock_file, "1").unwrap(); + // If a parsed `ProcessUID` exists, the lock can NOT be acquired. + pid_1.write(&lock_file).unwrap(); assert_eq!(Err(StorageLockError::Taken(1)), StorageLock::try_acquire(temp_dir.path())); + + // A lock of a different but existing PID can be acquired ONLY IF the start_time differs. + pid_1.start_time += 1; + pid_1.write(&lock_file).unwrap(); + assert_eq!(Ok(lock), StorageLock::try_acquire(temp_dir.path())); } #[test] @@ -116,8 +183,8 @@ mod tests { let lock = StorageLock::try_acquire(temp_dir.path()).unwrap(); + assert!(lock_file.exists()); drop(lock); - assert!(!lock_file.exists()); } } diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index ecacc0a67f7c..ea991402e0ce 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -17,7 +17,7 @@ asm-keccak = ["reth-primitives/asm-keccak"] [dependencies] reth-primitives.workspace = true -reth-db = { workspace = true, features = ["mdbx", "test-utils"] } +reth-db = { workspace = true, features = ["mdbx", "test-utils", "disable-lock"] } reth-db-api.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } reth-stages.workspace = true From 0a49d47dc33058cafe5d3decfce85a3a81de62f9 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:22:01 +0200 Subject: [PATCH 009/405] fix: ensure that a new offset file ends with the zero data file length (#8770) --- crates/storage/nippy-jar/src/writer.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/storage/nippy-jar/src/writer.rs b/crates/storage/nippy-jar/src/writer.rs index da6c13cc6c2c..695fd6642e54 100644 --- a/crates/storage/nippy-jar/src/writer.rs +++ b/crates/storage/nippy-jar/src/writer.rs @@ -130,10 +130,19 @@ impl NippyJarWriter { } let mut offsets_file = OpenOptions::new().read(true).write(true).open(offsets)?; + if is_created { + let mut buf = Vec::with_capacity(1 + OFFSET_SIZE_BYTES as usize); - // First byte of the offset file is the size of one offset in bytes - offsets_file.write_all(&[OFFSET_SIZE_BYTES])?; - offsets_file.seek(SeekFrom::End(0))?; + // First byte of the offset file is the size of one offset in bytes + buf.write_all(&[OFFSET_SIZE_BYTES])?; + + // The last offset should always represent the data file len, which is 0 on + // creation. + buf.write_all(&[0; OFFSET_SIZE_BYTES as usize])?; + + offsets_file.write_all(&buf)?; + offsets_file.seek(SeekFrom::End(0))?; + } Ok((data_file, offsets_file, is_created)) } From 571c261a2f437282ad6d3920c7d737f1022acb60 Mon Sep 17 00:00:00 2001 From: Marquis Shanahan <29431502+9547@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:29:31 +0800 Subject: [PATCH 010/405] fix(rpc/trace): trace_filter check block range (#8766) Signed-off-by: 9547 <29431502+9547@users.noreply.github.com> Co-authored-by: Alexey Shekhirin --- crates/rpc/rpc/src/trace.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index e73e2adbfdc4..fb3bcdb4dac5 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -239,7 +239,7 @@ where filter: TraceFilter, ) -> EthResult> { let matcher = filter.matcher(); - let TraceFilter { from_block, to_block, after: _after, count: _count, .. } = filter; + let TraceFilter { from_block, to_block, .. } = filter; let start = from_block.unwrap_or(0); let end = if let Some(to_block) = to_block { to_block @@ -247,6 +247,12 @@ where self.provider().best_block_number()? }; + if start > end { + return Err(EthApiError::InvalidParams( + "invalid parameters: fromBlock cannot be greater than toBlock".to_string(), + )) + } + // ensure that the range is not too large, since we need to fetch all blocks in the range let distance = end.saturating_sub(start); if distance > 100 { From e9a76917471f3d931ce436200b187dc73b0601a1 Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 12 Jun 2024 22:36:06 +0800 Subject: [PATCH 011/405] fix(rpc/trace): wrong calculate of block ommer rewards (#8767) Signed-off-by: jsvisa --- crates/rpc/rpc/src/trace.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index fb3bcdb4dac5..589a3037208d 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -6,7 +6,7 @@ use crate::eth::{ }; use async_trait::async_trait; use jsonrpsee::core::RpcResult as Result; -use reth_consensus_common::calc::{base_block_reward, block_reward}; +use reth_consensus_common::calc::{base_block_reward, block_reward, ommer_reward}; use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, SealedHeader, B256, U256}; use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; @@ -369,25 +369,25 @@ where block.header.difficulty, header_td, ) { + let block_reward = block_reward(base_block_reward, block.ommers.len()); traces.push(reward_trace( &block.header, RewardAction { author: block.header.beneficiary, reward_type: RewardType::Block, - value: U256::from(base_block_reward), + value: U256::from(block_reward), }, )); - if !block.ommers.is_empty() { + for uncle in &block.ommers { + let uncle_reward = + ommer_reward(base_block_reward, block.header.number, uncle.number); traces.push(reward_trace( &block.header, RewardAction { - author: block.header.beneficiary, + author: uncle.beneficiary, reward_type: RewardType::Uncle, - value: U256::from( - block_reward(base_block_reward, block.ommers.len()) - - base_block_reward, - ), + value: U256::from(uncle_reward), }, )); } From fcd28f69a848a7a8fd0069a9a990cdce4165e534 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 12 Jun 2024 10:38:42 -0400 Subject: [PATCH 012/405] feat: introduce reth benchmark command (#8284) Co-authored-by: Emilia Hane --- Cargo.lock | 124 +++++++- Cargo.toml | 9 + bin/reth-bench/Cargo.toml | 106 +++++++ bin/reth-bench/README.md | 66 +++++ bin/reth-bench/src/authenticated_transport.rs | 267 +++++++++++++++++ bin/reth-bench/src/bench/context.rs | 111 +++++++ bin/reth-bench/src/bench/mod.rs | 53 ++++ bin/reth-bench/src/bench/new_payload_fcu.rs | 179 ++++++++++++ bin/reth-bench/src/bench/new_payload_only.rs | 136 +++++++++ bin/reth-bench/src/bench/output.rs | 216 ++++++++++++++ bin/reth-bench/src/bench_mode.rs | 37 +++ bin/reth-bench/src/main.rs | 34 +++ bin/reth-bench/src/valid_payload.rs | 276 ++++++++++++++++++ crates/node-core/src/args/benchmark_args.rs | 62 ++++ crates/node-core/src/args/mod.rs | 4 + 15 files changed, 1672 insertions(+), 8 deletions(-) create mode 100644 bin/reth-bench/Cargo.toml create mode 100644 bin/reth-bench/README.md create mode 100644 bin/reth-bench/src/authenticated_transport.rs create mode 100644 bin/reth-bench/src/bench/context.rs create mode 100644 bin/reth-bench/src/bench/mod.rs create mode 100644 bin/reth-bench/src/bench/new_payload_fcu.rs create mode 100644 bin/reth-bench/src/bench/new_payload_only.rs create mode 100644 bin/reth-bench/src/bench/output.rs create mode 100644 bin/reth-bench/src/bench_mode.rs create mode 100644 bin/reth-bench/src/main.rs create mode 100644 bin/reth-bench/src/valid_payload.rs create mode 100644 crates/node-core/src/args/benchmark_args.rs diff --git a/Cargo.lock b/Cargo.lock index c3a40a53aebb..8657d840207a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,7 +140,7 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-primitives", @@ -189,7 +189,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -214,7 +214,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" dependencies = [ "alloy-primitives", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -319,6 +319,7 @@ dependencies = [ "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-engine", "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-rpc-types-trace", "alloy-transport", @@ -416,7 +417,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" dependencies = [ "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -488,7 +489,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" 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)", @@ -528,7 +529,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" dependencies = [ "alloy-primitives", "serde", @@ -668,6 +669,24 @@ dependencies = [ "url", ] +[[package]] +name = "alloy-transport-ipc" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess 2.1.1", + "pin-project", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "alloy-transport-ws" version = "0.1.0" @@ -2168,6 +2187,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctr" version = "0.7.0" @@ -2601,6 +2641,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "downcast" version = "0.11.0" @@ -3079,7 +3125,7 @@ dependencies = [ [[package]] name = "foundry-blob-explorers" version = "0.1.0" -source = "git+https://github.com/foundry-rs/block-explorers#d5fdf79cd62f378448907663fc4ba9d085393b35" +source = "git+https://github.com/foundry-rs/block-explorers#af29524f4fc7dc25f59e8ae38652022f47ebee9b" dependencies = [ "alloy-chains", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -4060,6 +4106,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "interprocess" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f2533e1f1a70bec71ea7a85d1c0a4dab141c314035ce76e51a19a2f48be708" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "intmap" version = "0.7.1" @@ -6067,6 +6128,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -6383,6 +6450,47 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-bench" +version = "0.2.0-beta.9" +dependencies = [ + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-json-rpc", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-engine", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-trait", + "clap", + "csv", + "eyre", + "futures", + "libc", + "reqwest", + "reth-cli-runner", + "reth-db", + "reth-node-api", + "reth-node-core", + "reth-primitives", + "reth-provider", + "reth-rpc-types", + "reth-rpc-types-compat", + "reth-tracing", + "serde", + "serde_json", + "thiserror", + "tikv-jemallocator", + "tokio", + "tokio-util", + "tower", + "tracing", +] + [[package]] name = "reth-blockchain-tree" version = "0.2.0-beta.9" @@ -7068,7 +7176,7 @@ dependencies = [ "bytes", "futures", "futures-util", - "interprocess", + "interprocess 1.2.1", "jsonrpsee", "pin-project", "rand 0.8.5", diff --git a/Cargo.toml b/Cargo.toml index 76deae8c50ee..410d2e123890 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ exclude = [".github/"] [workspace] members = [ + "bin/reth-bench/", "bin/reth/", "crates/blockchain-tree/", "crates/blockchain-tree-api/", @@ -239,6 +240,7 @@ incremental = false [workspace.dependencies] # reth reth = { path = "bin/reth" } +reth-bench = { path = "bin/reth-bench" } reth-auto-seal-consensus = { path = "crates/consensus/auto-seal" } reth-basic-payload-builder = { path = "crates/payload/basic" } reth-beacon-consensus = { path = "crates/consensus/beacon" } @@ -366,6 +368,13 @@ alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", features = ["reqwest-rustls-tls"], default-features = false } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } # misc auto_impl = "1" diff --git a/bin/reth-bench/Cargo.toml b/bin/reth-bench/Cargo.toml new file mode 100644 index 000000000000..bca2cf4f8d24 --- /dev/null +++ b/bin/reth-bench/Cargo.toml @@ -0,0 +1,106 @@ +[package] +name = "reth-bench" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Benchmarking for ethereum nodes" +default-run = "reth-bench" + +[lints] +workspace = true + +[dependencies] +# reth +reth-provider = { workspace = true } +reth-cli-runner.workspace = true +reth-db = { workspace = true, features = ["mdbx"] } +reth-node-core.workspace = true +reth-node-api.workspace = true +reth-rpc-types.workspace = true +reth-rpc-types-compat.workspace = true +reth-primitives = { workspace = true, features = ["clap", "alloy-compat"] } +reth-tracing.workspace = true + +# alloy +alloy-provider = { workspace = true, features = ["engine-api", "reqwest-rustls-tls"], default-features = false } +alloy-rpc-types-engine.workspace = true +alloy-transport.workspace = true +alloy-transport-http.workspace = true +alloy-transport-ws.workspace = true +alloy-transport-ipc.workspace = true +alloy-pubsub.workspace = true +alloy-json-rpc.workspace = true +alloy-rpc-client.workspace = true +alloy-consensus.workspace = true +alloy-eips.workspace = true + +# reqwest +reqwest = { workspace = true, default-features = false, features = [ + "rustls-tls-native-roots", +] } + +# tower +tower.workspace = true + +# tracing +tracing.workspace = true + +# io +serde.workspace = true +serde_json.workspace = true + +# async +tokio = { workspace = true, features = [ + "sync", + "macros", + "time", + "rt-multi-thread", +] } +tokio-util.workspace = true +futures.workspace = true +async-trait.workspace = true + +# misc +eyre.workspace = true +thiserror.workspace = true +clap = { workspace = true, features = ["derive", "env"] } + +# for writing data +csv = "1.3.0" + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { version = "0.5.0", optional = true } +libc = "0.2" + +[dev-dependencies] +reth-tracing.workspace = true + +[features] +default = ["jemalloc"] + +asm-keccak = ["reth-primitives/asm-keccak"] + +jemalloc = ["dep:tikv-jemallocator", "reth-node-core/jemalloc"] +jemalloc-prof = ["jemalloc", "tikv-jemallocator?/profiling"] + +min-error-logs = ["tracing/release_max_level_error"] +min-warn-logs = ["tracing/release_max_level_warn"] +min-info-logs = ["tracing/release_max_level_info"] +min-debug-logs = ["tracing/release_max_level_debug"] +min-trace-logs = ["tracing/release_max_level_trace"] + +optimism = [ + "reth-primitives/optimism", + "reth-provider/optimism", + "reth-node-core/optimism", +] + +# no-op feature flag for switching between the `optimism` and default functionality in CI matrices +ethereum = [] + +[[bin]] +name = "reth-bench" +path = "src/main.rs" diff --git a/bin/reth-bench/README.md b/bin/reth-bench/README.md new file mode 100644 index 000000000000..97c9572a1c6d --- /dev/null +++ b/bin/reth-bench/README.md @@ -0,0 +1,66 @@ +# Benchmarking reth live sync with `reth-bench` + +The binary contained in this directory, `reth-bench`, is a tool that can be used to benchmark the performance of the reth live sync. `reth-bench` is a general tool, and can be used for benchmarking node performance, as long as the node supports the engine API. + +### A recap on node synchronization +Reth uses two primary methods for synchronizing the chain: + * Historical sync, which is used to synchronize the chain from genesis to a known finalized block. This involves re-executing the entire chain history. + * Live sync, which is used to synchronize the chain from a finalized block to the current head. This involves processing new blocks as they are produced. + +Benchmarking historical sync for reth is fairly easy, because historical sync is a long-running, deterministic process. +Reth specifically contains the `--debug.tip` argument, which allows for running the historical sync pipeline to a specific block. +However, reth's historical sync applies optimizations that are not always possible when syncing new blocks. + + +Live sync, on the other hand, is a more complex process that is harder to benchmark. It is also more sensitive to network conditions of the CL. +In order to benchmark live sync, we need to simulate a CL in a controlled manner, so reth can use the same code paths it would when syncing new blocks. + +### The `reth-bench` tool +The `reth-bench` tool is designed to benchmark performance of reth live sync. +It can also be used for debugging client spec implementations, as it replays historical blocks by mocking a CL client. +Performance is measured by latency and gas used in a block, as well as the computed gas used per second. +As long as the data is representative of real-world load, or closer to worst-case load test, the gas per second gives a rough sense of how much throughput the node would be able to handle. + +## Prerequisites + +If you will be collecting CPU profiles, make sure `reth` is compiled with the `debug-fast` profile. +For collecting memory profiles, make sure `reth` is also compiled with the `--features profiling` flag. +Otherwise, running `make maxperf` at the root of the repo should be sufficient for collecting accurate performance metrics. + +## Command Usage + +`reth-bench` contains different commands to benchmark different patterns of engine API calls. +The `reth-bench new-payload-fcu` command is the most representative of ethereum mainnet live sync, alternating between sending `engine_newPayload` calls and `engine_forkchoiceUpdated` calls. + +Below is an overview of how to execute a benchmark: + + 1. **Setup**: Make sure `reth` is running in the background with the proper configuration. This setup involves ensuring the node is at the correct state, setting up profiling tools, and possibly more depending on the purpose of the benchmark's. + + 2. **Run the Benchmark**: + ```bash + reth-bench new-payload-fcu --rpc-url http://:8545 --from --to --jwtsecret + ``` + + Replace ``, ``, ``, and `` with the appropriate values for your testing environment. + Note that this assumes that the benchmark node's engine API is running on `http://127.0.0.1:8545`, which is set as a default value in `reth-bench`. To configure this value, use the `--engine-rpc-url` flag. + + 3. **Observe Outputs**: Upon running the command, `reth-bench` will output benchmark results, showing processing speeds and gas usage, which are crucial for analyzing the node's performance. + + Example output: + ``` + 2024-05-30T00:45:20.806691Z INFO Running benchmark using data from RPC URL: http://:8545 + // ... logs per block + 2024-05-30T00:45:34.203172Z INFO Total Ggas/s: 0.15 total_duration=5.085704882s total_gas_used=741620668.0 + ``` + + 4. **Stop and Review**: Once the benchmark completes, terminate the `reth` process and review the logs and performance metrics collected, if any. + 5. **Repeat**. + +## Additional Considerations + +- **RPC Configuration**: The RPC endpoints should be accessible and configured correctly, specifically the RPC endpoint must support `eth_getBlockByNumber` and support fetching full transactions. The benchmark will make one RPC query per block as fast as possible, so ensure the RPC endpoint does not rate limit or block requests after a certain volume. +- **Reproducibility**: Ensure that the node is at the same state before attempting to retry a benchmark. The `new-payload-fcu` command specifically will commit to the database, so the node must be rolled back using `reth stage unwind` to reproducibly retry benchmarks. +- **Profiling tools**: If you are collecting CPU profiles, tools like [`samply`](https://github.com/mstange/samply) and [`perf`](https://perf.wiki.kernel.org/index.php/Main_Page) can be useful for analyzing node performance. +- **Benchmark Data**: `reth-bench` additionally contains a `--benchmark.output` flag, which will output gas used benchmarks across the benchmark range in CSV format. This may be useful for further data analysis. +- **Platform Information**: To ensure accurate and reproducible benchmarking, document the platform details, including hardware specifications, OS version, and any other relevant information before publishing any benchmarks. + diff --git a/bin/reth-bench/src/authenticated_transport.rs b/bin/reth-bench/src/authenticated_transport.rs new file mode 100644 index 000000000000..e92b581bc5fa --- /dev/null +++ b/bin/reth-bench/src/authenticated_transport.rs @@ -0,0 +1,267 @@ +//! This contains an authenticated rpc transport that can be used to send engine API newPayload +//! requests. + +use std::sync::Arc; + +use alloy_json_rpc::{RequestPacket, ResponsePacket}; +use alloy_pubsub::{PubSubConnect, PubSubFrontend}; +use alloy_rpc_types_engine::{Claims, JwtSecret}; +use alloy_transport::{ + utils::guess_local_url, Authorization, Pbf, TransportConnect, TransportError, + TransportErrorKind, TransportFut, +}; +use alloy_transport_http::{reqwest::Url, Http, ReqwestTransport}; +use alloy_transport_ipc::IpcConnect; +use alloy_transport_ws::WsConnect; +use futures::FutureExt; +use reqwest::header::HeaderValue; +use std::task::{Context, Poll}; +use tokio::sync::RwLock; +use tower::Service; + +/// An enum representing the different transports that can be used to connect to a runtime. +/// Only meant to be used internally by [`AuthenticatedTransport`]. +#[derive(Clone, Debug)] +pub enum InnerTransport { + /// HTTP transport + Http(ReqwestTransport), + /// `WebSocket` transport + Ws(PubSubFrontend), + /// IPC transport + Ipc(PubSubFrontend), +} + +impl InnerTransport { + /// Connects to a transport based on the given URL and JWT. Returns an [`InnerTransport`] and + /// the [`Claims`] generated from the jwt. + async fn connect( + url: Url, + jwt: JwtSecret, + ) -> Result<(Self, Claims), AuthenticatedTransportError> { + match url.scheme() { + "http" | "https" => Self::connect_http(url, jwt).await, + "ws" | "wss" => Self::connect_ws(url, jwt).await, + "file" => Ok((Self::connect_ipc(url).await?, Claims::default())), + _ => Err(AuthenticatedTransportError::BadScheme(url.scheme().to_string())), + } + } + + /// Connects to an HTTP [`alloy_transport_http::Http`] transport. Returns an [`InnerTransport`] + /// and the [Claims] generated from the jwt. + async fn connect_http( + url: Url, + jwt: JwtSecret, + ) -> Result<(Self, Claims), AuthenticatedTransportError> { + let mut client_builder = + reqwest::Client::builder().tls_built_in_root_certs(url.scheme() == "https"); + let mut headers = reqwest::header::HeaderMap::new(); + + // Add the JWT it to the headers if we can decode it. + let (auth, claims) = + build_auth(jwt).map_err(|e| AuthenticatedTransportError::InvalidJwt(e.to_string()))?; + + let mut auth_value: HeaderValue = + HeaderValue::from_str(&auth.to_string()).expect("Header should be valid string"); + auth_value.set_sensitive(true); + + headers.insert(reqwest::header::AUTHORIZATION, auth_value); + client_builder = client_builder.default_headers(headers); + + let client = + client_builder.build().map_err(AuthenticatedTransportError::HttpConstructionError)?; + + let inner = Self::Http(Http::with_client(client, url)); + Ok((inner, claims)) + } + + /// Connects to a `WebSocket` [`alloy_transport_ws::WsConnect`] transport. Returns an + /// [`InnerTransport`] and the [`Claims`] generated from the jwt. + async fn connect_ws( + url: Url, + jwt: JwtSecret, + ) -> Result<(Self, Claims), AuthenticatedTransportError> { + // Add the JWT it to the headers if we can decode it. + let (auth, claims) = + build_auth(jwt).map_err(|e| AuthenticatedTransportError::InvalidJwt(e.to_string()))?; + + let inner = WsConnect { url: url.to_string(), auth: Some(auth) } + .into_service() + .await + .map(Self::Ws) + .map_err(|e| AuthenticatedTransportError::TransportError(e, url.to_string()))?; + + Ok((inner, claims)) + } + + /// Connects to an IPC [`alloy_transport_ipc::IpcConnect`] transport. Returns an + /// [`InnerTransport`]. Does not return any [`Claims`] because IPC does not require them. + async fn connect_ipc(url: Url) -> Result { + // IPC, even for engine, typically does not require auth because it's local + IpcConnect::new(url.to_string()) + .into_service() + .await + .map(InnerTransport::Ipc) + .map_err(|e| AuthenticatedTransportError::TransportError(e, url.to_string())) + } +} + +/// An authenticated transport that can be used to send requests that contain a jwt bearer token. +#[derive(Debug, Clone)] +pub struct AuthenticatedTransport { + /// The inner actual transport used. + /// + /// Also contains the current claims being used. This is used to determine whether or not we + /// should create another client. + inner_and_claims: Arc>, + /// The current jwt being used. This is so we can recreate claims. + jwt: JwtSecret, + /// The current URL being used. This is so we can recreate the client if needed. + url: Url, +} + +/// An error that can occur when creating an authenticated transport. +#[derive(Debug, thiserror::Error)] +pub enum AuthenticatedTransportError { + /// The URL is invalid. + #[error("The URL is invalid")] + InvalidUrl, + /// Failed to lock transport + #[error("Failed to lock transport")] + LockFailed, + /// The JWT is invalid. + #[error("The JWT is invalid: {0}")] + InvalidJwt(String), + /// The transport failed to connect. + #[error("The transport failed to connect to {1}, transport error: {0}")] + TransportError(TransportError, String), + /// The http client could not be built. + #[error("The http client could not be built")] + HttpConstructionError(reqwest::Error), + /// The scheme is invalid. + #[error("The URL scheme is invalid: {0}")] + BadScheme(String), +} + +impl AuthenticatedTransport { + /// Create a new builder with the given URL. + pub async fn connect(url: Url, jwt: JwtSecret) -> Result { + let (inner, claims) = InnerTransport::connect(url.clone(), jwt).await?; + Ok(Self { inner_and_claims: Arc::new(RwLock::new((inner, claims))), jwt, url }) + } + + /// Sends a request using the underlying transport. + /// + /// For sending the actual request, this action is delegated down to the underlying transport + /// through Tower's [`tower::Service::call`]. See tower's [`tower::Service`] trait for more + /// information. + fn request(&self, req: RequestPacket) -> TransportFut<'static> { + let this = self.clone(); + + Box::pin(async move { + let mut inner_and_claims = this.inner_and_claims.write().await; + + // shift the iat forward by one second so there is some buffer time + let mut shifted_claims = inner_and_claims.1; + shifted_claims.iat -= 1; + + // if the claims are out of date, reset the inner transport + if !shifted_claims.is_within_time_window() { + let (new_inner, new_claims) = + InnerTransport::connect(this.url.clone(), this.jwt).await.map_err(|e| { + TransportError::Transport(TransportErrorKind::Custom(Box::new(e))) + })?; + *inner_and_claims = (new_inner, new_claims); + } + + match inner_and_claims.0 { + InnerTransport::Http(ref http) => { + let mut http = http; + http.call(req) + } + InnerTransport::Ws(ref ws) => { + let mut ws = ws; + ws.call(req) + } + InnerTransport::Ipc(ref ipc) => { + let mut ipc = ipc; + // we don't need to recreate the client for IPC + ipc.call(req) + } + } + .await + }) + } +} + +fn build_auth(secret: JwtSecret) -> eyre::Result<(Authorization, Claims)> { + // Generate claims (iat with current timestamp), this happens by default using the Default trait + // for Claims. + let claims = Claims::default(); + let token = secret.encode(&claims)?; + let auth = Authorization::Bearer(token); + + Ok((auth, claims)) +} + +/// This specifies how to connect to an authenticated transport. +#[derive(Clone, Debug)] +pub struct AuthenticatedTransportConnect { + /// The URL to connect to. + url: Url, + /// The JWT secret used to authenticate the transport. + jwt: JwtSecret, +} + +impl AuthenticatedTransportConnect { + /// Create a new builder with the given URL. + pub const fn new(url: Url, jwt: JwtSecret) -> Self { + Self { url, jwt } + } +} + +impl TransportConnect for AuthenticatedTransportConnect { + type Transport = AuthenticatedTransport; + + fn is_local(&self) -> bool { + guess_local_url(&self.url) + } + + fn get_transport<'a: 'b, 'b>(&'a self) -> Pbf<'b, Self::Transport, TransportError> { + AuthenticatedTransport::connect(self.url.clone(), self.jwt) + .map(|res| match res { + Ok(transport) => Ok(transport), + Err(err) => { + Err(TransportError::Transport(TransportErrorKind::Custom(Box::new(err)))) + } + }) + .boxed() + } +} + +impl tower::Service for AuthenticatedTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} + +impl tower::Service for &AuthenticatedTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} diff --git a/bin/reth-bench/src/bench/context.rs b/bin/reth-bench/src/bench/context.rs new file mode 100644 index 000000000000..7f45ee6adfe8 --- /dev/null +++ b/bin/reth-bench/src/bench/context.rs @@ -0,0 +1,111 @@ +//! This contains the [`BenchContext`], which is information that all replay-based benchmarks need. +//! The initialization code is also the same, so this can be shared across benchmark commands. + +use crate::{authenticated_transport::AuthenticatedTransportConnect, bench_mode::BenchMode}; +use alloy_eips::BlockNumberOrTag; +use alloy_provider::{ + network::{AnyNetwork, Ethereum}, + Provider, ProviderBuilder, RootProvider, +}; +use alloy_rpc_client::ClientBuilder; +use alloy_rpc_types_engine::JwtSecret; +use alloy_transport::BoxTransport; +use alloy_transport_http::Http; +use reqwest::{Client, Url}; +use reth_node_core::args::BenchmarkArgs; +use tracing::info; + +/// This is intended to be used by benchmarks that replay blocks from an RPC. +/// +/// It contains an authenticated provider for engine API queries, a block provider for block +/// queries, a [`BenchMode`] to determine whether the benchmark should run for a closed or open +/// range of blocks, and the next block to fetch. +pub(crate) struct BenchContext { + /// The auth provider used for engine API queries. + pub(crate) auth_provider: RootProvider, + /// The block provider used for block queries. + pub(crate) block_provider: RootProvider, Ethereum>, + /// The benchmark mode, which defines whether the benchmark should run for a closed or open + /// range of blocks. + pub(crate) benchmark_mode: BenchMode, + /// The next block to fetch. + pub(crate) next_block: u64, +} + +impl BenchContext { + /// This is the initialization code for most benchmarks, taking in a [`BenchmarkArgs`] and + /// returning the providers needed to run a benchmark. + pub(crate) async fn new(bench_args: &BenchmarkArgs, rpc_url: String) -> eyre::Result { + info!("Running benchmark using data from RPC URL: {}", rpc_url); + + // Ensure that output directory is a directory + if let Some(output) = &bench_args.output { + if output.is_file() { + return Err(eyre::eyre!("Output path must be a directory")); + } + } + + // set up alloy client for blocks + let block_provider = ProviderBuilder::new().on_http(rpc_url.parse()?); + + // If neither `--from` nor `--to` are provided, we will run the benchmark continuously, + // starting at the latest block. + let mut benchmark_mode = BenchMode::new(bench_args.from, bench_args.to)?; + + // construct the authenticated provider + let auth_jwt = bench_args.auth_jwtsecret.clone().ok_or_else(|| { + eyre::eyre!("--auth-jwtsecret must be provided for authenticated RPC") + })?; + + // fetch jwt from file + // + // the jwt is hex encoded so we will decode it after + let jwt = std::fs::read_to_string(auth_jwt)?; + let jwt = JwtSecret::from_hex(jwt)?; + + // get engine url + let auth_url = Url::parse(&bench_args.engine_rpc_url)?; + + // construct the authed transport + info!("Connecting to Engine RPC at {} for replay", auth_url); + let auth_transport = AuthenticatedTransportConnect::new(auth_url, jwt); + let client = ClientBuilder::default().connect_boxed(auth_transport).await?; + let auth_provider = RootProvider::<_, AnyNetwork>::new(client); + + let first_block = match benchmark_mode { + BenchMode::Continuous => { + // fetch Latest block + block_provider.get_block_by_number(BlockNumberOrTag::Latest, true).await?.unwrap() + } + BenchMode::Range(ref mut range) => { + match range.next() { + Some(block_number) => { + // fetch first block in range + block_provider + .get_block_by_number(block_number.into(), true) + .await? + .unwrap() + } + None => { + return Err(eyre::eyre!( + "Benchmark mode range is empty, please provide a larger range" + )); + } + } + } + }; + + let next_block = match first_block.header.number { + Some(number) => { + // fetch next block + number + 1 + } + None => { + // this should never happen + return Err(eyre::eyre!("First block number is None")); + } + }; + + Ok(Self { auth_provider, block_provider, benchmark_mode, next_block }) + } +} diff --git a/bin/reth-bench/src/bench/mod.rs b/bin/reth-bench/src/bench/mod.rs new file mode 100644 index 000000000000..076dbb4af7d4 --- /dev/null +++ b/bin/reth-bench/src/bench/mod.rs @@ -0,0 +1,53 @@ +//! `reth benchmark` command. Collection of various benchmarking routines. + +use clap::{Parser, Subcommand}; +use reth_cli_runner::CliContext; +use reth_node_core::args::LogArgs; +use reth_tracing::FileWorkerGuard; + +mod context; +mod new_payload_fcu; +mod new_payload_only; +mod output; + +/// `reth bench` command +#[derive(Debug, Parser)] +pub struct BenchmarkCommand { + #[command(subcommand)] + command: Subcommands, + + #[command(flatten)] + logs: LogArgs, +} + +/// `reth benchmark` subcommands +#[derive(Subcommand, Debug)] +pub enum Subcommands { + /// Benchmark which calls `newPayload`, then `forkchoiceUpdated`. + NewPayloadFcu(new_payload_fcu::Command), + + /// Benchmark which only calls subsequent `newPayload` calls. + NewPayloadOnly(new_payload_only::Command), +} + +impl BenchmarkCommand { + /// Execute `benchmark` command + pub async fn execute(self, ctx: CliContext) -> eyre::Result<()> { + // Initialize tracing + let _guard = self.init_tracing()?; + + match self.command { + Subcommands::NewPayloadFcu(command) => command.execute(ctx).await, + Subcommands::NewPayloadOnly(command) => command.execute(ctx).await, + } + } + + /// Initializes tracing with the configured options. + /// + /// If file logging is enabled, this function returns a guard that must be kept alive to ensure + /// that all logs are flushed to disk. + pub fn init_tracing(&self) -> eyre::Result> { + let guard = self.logs.init_tracing()?; + Ok(guard) + } +} diff --git a/bin/reth-bench/src/bench/new_payload_fcu.rs b/bin/reth-bench/src/bench/new_payload_fcu.rs new file mode 100644 index 000000000000..c7ea5683175f --- /dev/null +++ b/bin/reth-bench/src/bench/new_payload_fcu.rs @@ -0,0 +1,179 @@ +//! Runs the `reth bench` command, calling first newPayload for each block, then calling +//! forkchoiceUpdated. + +use crate::{ + bench::{ + context::BenchContext, + output::{ + CombinedResult, NewPayloadResult, TotalGasOutput, TotalGasRow, COMBINED_OUTPUT_SUFFIX, + GAS_OUTPUT_SUFFIX, + }, + }, + valid_payload::{call_forkchoice_updated, call_new_payload}, +}; +use alloy_provider::Provider; +use alloy_rpc_types_engine::ForkchoiceState; +use clap::Parser; +use csv::Writer; +use reth_cli_runner::CliContext; +use reth_node_core::args::BenchmarkArgs; +use reth_primitives::{Block, B256}; +use reth_rpc_types_compat::engine::payload::block_to_payload; +use std::time::Instant; +use tracing::{debug, info}; + +/// `reth benchmark new-payload-fcu` command +#[derive(Debug, Parser)] +pub struct Command { + /// The RPC url to use for getting data. + #[arg(long, value_name = "RPC_URL", verbatim_doc_comment)] + rpc_url: String, + + #[command(flatten)] + benchmark: BenchmarkArgs, +} + +impl Command { + /// Execute `benchmark new-payload-fcu` command + pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> { + let cloned_args = self.benchmark.clone(); + let BenchContext { benchmark_mode, block_provider, auth_provider, mut next_block } = + BenchContext::new(&cloned_args, self.rpc_url).await?; + + let (sender, mut receiver) = tokio::sync::mpsc::channel(1000); + tokio::task::spawn(async move { + while benchmark_mode.contains(next_block) { + let block_res = block_provider.get_block_by_number(next_block.into(), true).await; + let block = block_res.unwrap().unwrap(); + let block = match block.header.hash { + Some(block_hash) => { + // we can reuse the hash in the response + Block::try_from(block).unwrap().seal(block_hash) + } + None => { + // we don't have the hash, so let's just hash it + Block::try_from(block).unwrap().seal_slow() + } + }; + + let head_block_hash = block.hash(); + let safe_block_hash = + block_provider.get_block_by_number((block.number - 32).into(), false); + + let finalized_block_hash = + block_provider.get_block_by_number((block.number - 64).into(), false); + + let (safe, finalized) = tokio::join!(safe_block_hash, finalized_block_hash,); + + let safe_block_hash = safe + .unwrap() + .expect("finalized block exists") + .header + .hash + .expect("finalized block has hash"); + let finalized_block_hash = finalized + .unwrap() + .expect("finalized block exists") + .header + .hash + .expect("finalized block has hash"); + + next_block += 1; + sender + .send((block, head_block_hash, safe_block_hash, finalized_block_hash)) + .await + .unwrap(); + } + }); + + // put results in a summary vec so they can be printed at the end + let mut results = Vec::new(); + let total_benchmark_duration = Instant::now(); + + while let Some((block, head, safe, finalized)) = receiver.recv().await { + // just put gas used here + let gas_used = block.header.gas_used; + let block_number = block.header.number; + + let versioned_hashes: Vec = + block.blob_versioned_hashes().into_iter().copied().collect(); + let (payload, parent_beacon_block_root) = block_to_payload(block); + + debug!(?block_number, "Sending payload",); + + // construct fcu to call + let forkchoice_state = ForkchoiceState { + head_block_hash: head, + safe_block_hash: safe, + finalized_block_hash: finalized, + }; + + let start = Instant::now(); + let message_version = call_new_payload( + &auth_provider, + payload, + parent_beacon_block_root, + versioned_hashes, + ) + .await?; + + let new_payload_result = NewPayloadResult { gas_used, latency: start.elapsed() }; + + call_forkchoice_updated(&auth_provider, message_version, forkchoice_state, None) + .await?; + + // calculate the total duration and the fcu latency, record + let total_latency = start.elapsed(); + let fcu_latency = total_latency - new_payload_result.latency; + let combined_result = CombinedResult { new_payload_result, fcu_latency, total_latency }; + + // current duration since the start of the benchmark + let current_duration = total_benchmark_duration.elapsed(); + + // convert gas used to gigagas, then compute gigagas per second + info!(%combined_result); + + // record the current result + let gas_row = TotalGasRow { block_number, gas_used, time: current_duration }; + results.push((gas_row, combined_result)); + } + + let (gas_output_results, combined_results): (_, Vec) = + results.into_iter().unzip(); + + // write the csv output to files + if let Some(path) = self.benchmark.output { + // first write the combined results to a file + let output_path = path.join(COMBINED_OUTPUT_SUFFIX); + info!("Writing engine api call latency output to file: {:?}", output_path); + let mut writer = Writer::from_path(output_path)?; + for result in combined_results { + writer.serialize(result)?; + } + writer.flush()?; + + // now write the gas output to a file + let output_path = path.join(GAS_OUTPUT_SUFFIX); + info!("Writing total gas output to file: {:?}", output_path); + let mut writer = Writer::from_path(output_path)?; + for row in &gas_output_results { + writer.serialize(row)?; + } + writer.flush()?; + + info!("Finished writing benchmark output files to {:?}.", path); + } + + // accumulate the results and calculate the overall Ggas/s + let gas_output = TotalGasOutput::new(gas_output_results); + info!( + total_duration=?gas_output.total_duration, + total_gas_used=?gas_output.total_gas_used, + blocks_processed=?gas_output.blocks_processed, + "Total Ggas/s: {:.4}", + gas_output.total_gigagas_per_second() + ); + + Ok(()) + } +} diff --git a/bin/reth-bench/src/bench/new_payload_only.rs b/bin/reth-bench/src/bench/new_payload_only.rs new file mode 100644 index 000000000000..3fa85e5749ac --- /dev/null +++ b/bin/reth-bench/src/bench/new_payload_only.rs @@ -0,0 +1,136 @@ +//! Runs the `reth bench` command, sending only newPayload, without a forkchoiceUpdated call. + +use crate::{ + bench::{ + context::BenchContext, + output::{ + NewPayloadResult, TotalGasOutput, TotalGasRow, GAS_OUTPUT_SUFFIX, + NEW_PAYLOAD_OUTPUT_SUFFIX, + }, + }, + valid_payload::call_new_payload, +}; +use alloy_provider::Provider; +use clap::Parser; +use csv::Writer; +use reth_cli_runner::CliContext; +use reth_node_core::args::BenchmarkArgs; +use reth_primitives::{Block, B256}; +use reth_rpc_types_compat::engine::payload::block_to_payload; +use std::time::Instant; +use tracing::{debug, info}; + +/// `reth benchmark new-payload-only` command +#[derive(Debug, Parser)] +pub struct Command { + /// The RPC url to use for getting data. + #[arg(long, value_name = "RPC_URL", verbatim_doc_comment)] + rpc_url: String, + + #[command(flatten)] + benchmark: BenchmarkArgs, +} + +impl Command { + /// Execute `benchmark new-payload-only` command + pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> { + let cloned_args = self.benchmark.clone(); + // TODO: this could be just a function I guess, but destructuring makes the code slightly + // more readable than a 4 element tuple. + let BenchContext { benchmark_mode, block_provider, auth_provider, mut next_block } = + BenchContext::new(&cloned_args, self.rpc_url).await?; + + let (sender, mut receiver) = tokio::sync::mpsc::channel(1000); + tokio::task::spawn(async move { + while benchmark_mode.contains(next_block) { + let block_res = block_provider.get_block_by_number(next_block.into(), true).await; + let block = block_res.unwrap().unwrap(); + let block = match block.header.hash { + Some(block_hash) => { + // we can reuse the hash in the response + Block::try_from(block).unwrap().seal(block_hash) + } + None => { + // we don't have the hash, so let's just hash it + Block::try_from(block).unwrap().seal_slow() + } + }; + + next_block += 1; + sender.send(block).await.unwrap(); + } + }); + + // put results in a summary vec so they can be printed at the end + let mut results = Vec::new(); + let total_benchmark_duration = Instant::now(); + + while let Some(block) = receiver.recv().await { + // just put gas used here + let gas_used = block.header.gas_used; + + let versioned_hashes: Vec = + block.blob_versioned_hashes().into_iter().copied().collect(); + let (payload, parent_beacon_block_root) = block_to_payload(block); + + let block_number = payload.block_number(); + + debug!( + number=?payload.block_number(), + "Sending payload to engine", + ); + + let start = Instant::now(); + call_new_payload(&auth_provider, payload, parent_beacon_block_root, versioned_hashes) + .await?; + + let new_payload_result = NewPayloadResult { gas_used, latency: start.elapsed() }; + info!(%new_payload_result); + + // current duration since the start of the benchmark + let current_duration = total_benchmark_duration.elapsed(); + + // record the current result + let row = TotalGasRow { block_number, gas_used, time: current_duration }; + results.push((row, new_payload_result)); + } + + let (gas_output_results, new_payload_results): (_, Vec) = + results.into_iter().unzip(); + + // write the csv output to files + if let Some(path) = self.benchmark.output { + // first write the new payload results to a file + let output_path = path.join(NEW_PAYLOAD_OUTPUT_SUFFIX); + info!("Writing newPayload call latency output to file: {:?}", output_path); + let mut writer = Writer::from_path(output_path)?; + for result in new_payload_results { + writer.serialize(result)?; + } + writer.flush()?; + + // now write the gas output to a file + let output_path = path.join(GAS_OUTPUT_SUFFIX); + info!("Writing total gas output to file: {:?}", output_path); + let mut writer = Writer::from_path(output_path)?; + for row in &gas_output_results { + writer.serialize(row)?; + } + writer.flush()?; + + info!("Finished writing benchmark output files to {:?}.", path); + } + + // accumulate the results and calculate the overall Ggas/s + let gas_output = TotalGasOutput::new(gas_output_results); + info!( + total_duration=?gas_output.total_duration, + total_gas_used=?gas_output.total_gas_used, + blocks_processed=?gas_output.blocks_processed, + "Total Ggas/s: {:.4}", + gas_output.total_gigagas_per_second() + ); + + Ok(()) + } +} diff --git a/bin/reth-bench/src/bench/output.rs b/bin/reth-bench/src/bench/output.rs new file mode 100644 index 000000000000..f976b5d28496 --- /dev/null +++ b/bin/reth-bench/src/bench/output.rs @@ -0,0 +1,216 @@ +//! Contains various benchmark output formats, either for logging or for +//! serialization to / from files. +//! +//! This also contains common constants for units, for example [GIGAGAS]. + +use serde::{ser::SerializeStruct, Serialize}; +use std::time::Duration; + +/// Represents one Kilogas, or `1_000` gas. +const KILOGAS: u64 = 1_000; + +/// Represents one Megagas, or `1_000_000` gas. +const MEGAGAS: u64 = KILOGAS * 1_000; + +/// Represents one Gigagas, or `1_000_000_000` gas. +const GIGAGAS: u64 = MEGAGAS * 1_000; + +/// This is the suffix for gas output csv files. +pub(crate) const GAS_OUTPUT_SUFFIX: &str = "total_gas.csv"; + +/// This is the suffix for combined output csv files. +pub(crate) const COMBINED_OUTPUT_SUFFIX: &str = "combined_latency.csv"; + +/// This is the suffix for new payload output csv files. +pub(crate) const NEW_PAYLOAD_OUTPUT_SUFFIX: &str = "new_payload_latency.csv"; + +/// This represents the results of a single `newPayload` call in the benchmark, containing the gas +/// used and the `newPayload` latency. +#[derive(Debug)] +pub(crate) struct NewPayloadResult { + /// The gas used in the `newPayload` call. + pub(crate) gas_used: u64, + /// The latency of the `newPayload` call. + pub(crate) latency: Duration, +} + +impl NewPayloadResult { + /// Returns the gas per second processed in the `newPayload` call. + pub(crate) fn gas_per_second(&self) -> f64 { + self.gas_used as f64 / self.latency.as_secs_f64() + } +} + +impl std::fmt::Display for NewPayloadResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "New payload processed at {:.4} Ggas/s, used {} total gas. Latency: {:?}", + self.gas_per_second() / GIGAGAS as f64, + self.gas_used, + self.latency + ) + } +} + +/// This is another [`Serialize`] implementation for the [`NewPayloadResult`] struct, serializing +/// the duration as microseconds because the csv writer would fail otherwise. +impl Serialize for NewPayloadResult { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + // convert the time to microseconds + let time = self.latency.as_micros(); + let mut state = serializer.serialize_struct("NewPayloadResult", 3)?; + state.serialize_field("gas_used", &self.gas_used)?; + state.serialize_field("latency", &time)?; + state.end() + } +} + +/// This represents the combined results of a `newPayload` call and a `forkchoiceUpdated` call in +/// the benchmark, containing the gas used, the `newPayload` latency, and the `forkchoiceUpdated` +/// latency. +#[derive(Debug)] +pub(crate) struct CombinedResult { + /// The `newPayload` result. + pub(crate) new_payload_result: NewPayloadResult, + /// The latency of the `forkchoiceUpdated` call. + pub(crate) fcu_latency: Duration, + /// The latency of both calls combined. + pub(crate) total_latency: Duration, +} + +impl CombinedResult { + /// Returns the gas per second, including the `newPayload` _and_ `forkchoiceUpdated` duration. + pub(crate) fn combined_gas_per_second(&self) -> f64 { + self.new_payload_result.gas_used as f64 / self.total_latency.as_secs_f64() + } +} + +impl std::fmt::Display for CombinedResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Payload processed at {:.4} Ggas/s, used {} total gas. Combined gas per second: {:.4} Ggas/s. fcu latency: {:?}, newPayload latency: {:?}", + self.new_payload_result.gas_per_second() / GIGAGAS as f64, + self.new_payload_result.gas_used, + self.combined_gas_per_second() / GIGAGAS as f64, + self.fcu_latency, + self.new_payload_result.latency + ) + } +} + +/// This is a [`Serialize`] implementation for the [`CombinedResult`] struct, serializing the +/// durations as microseconds because the csv writer would fail otherwise. +impl Serialize for CombinedResult { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + // convert the time to microseconds + let fcu_latency = self.fcu_latency.as_micros(); + let new_payload_latency = self.new_payload_result.latency.as_micros(); + let total_latency = self.total_latency.as_micros(); + let mut state = serializer.serialize_struct("CombinedResult", 4)?; + + // flatten the new payload result because this is meant for CSV writing + state.serialize_field("gas_used", &self.new_payload_result.gas_used)?; + state.serialize_field("new_payload_latency", &new_payload_latency)?; + state.serialize_field("fcu_latency", &fcu_latency)?; + state.serialize_field("total_latency", &total_latency)?; + state.end() + } +} + +/// This represents a row of total gas data in the benchmark. +#[derive(Debug)] +pub(crate) struct TotalGasRow { + /// The block number of the block being processed. + #[allow(dead_code)] + pub(crate) block_number: u64, + /// The total gas used in the block. + pub(crate) gas_used: u64, + /// Time since the start of the benchmark. + pub(crate) time: Duration, +} + +/// This represents the aggregated output, meant to show gas per second metrics, of a benchmark run. +#[derive(Debug)] +pub(crate) struct TotalGasOutput { + /// The total gas used in the benchmark. + pub(crate) total_gas_used: u64, + /// The total duration of the benchmark. + pub(crate) total_duration: Duration, + /// The total gas used per second. + pub(crate) total_gas_per_second: f64, + /// The number of blocks processed. + pub(crate) blocks_processed: u64, +} + +impl TotalGasOutput { + /// Create a new [`TotalGasOutput`] from a list of [`TotalGasRow`]. + pub(crate) fn new(rows: Vec) -> Self { + // the duration is obtained from the last row + let total_duration = + rows.last().map(|row| row.time).expect("the row has at least one element"); + let blocks_processed = rows.len() as u64; + let total_gas_used: u64 = rows.into_iter().map(|row| row.gas_used).sum(); + let total_gas_per_second = total_gas_used as f64 / total_duration.as_secs_f64(); + + Self { total_gas_used, total_duration, total_gas_per_second, blocks_processed } + } + + /// Return the total gigagas per second. + pub(crate) fn total_gigagas_per_second(&self) -> f64 { + self.total_gas_per_second / GIGAGAS as f64 + } +} + +/// This serializes the `time` field of the [`TotalGasRow`] to microseconds. +/// +/// This is essentially just for the csv writer, which would have headers +impl Serialize for TotalGasRow { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + // convert the time to microseconds + let time = self.time.as_micros(); + let mut state = serializer.serialize_struct("TotalGasRow", 3)?; + state.serialize_field("block_number", &self.block_number)?; + state.serialize_field("gas_used", &self.gas_used)?; + state.serialize_field("time", &time)?; + state.end() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use csv::Writer; + use std::io::BufRead; + + #[test] + fn test_write_total_gas_row_csv() { + let row = TotalGasRow { block_number: 1, gas_used: 1_000, time: Duration::from_secs(1) }; + + let mut writer = Writer::from_writer(vec![]); + writer.serialize(row).unwrap(); + let result = writer.into_inner().unwrap(); + + // parse into Lines + let mut result = result.as_slice().lines(); + + // assert header + let expected_first_line = "block_number,gas_used,time"; + let first_line = result.next().unwrap().unwrap(); + assert_eq!(first_line, expected_first_line); + + let expected_second_line = "1,1000,1000000"; + let second_line = result.next().unwrap().unwrap(); + assert_eq!(second_line, expected_second_line); + } +} diff --git a/bin/reth-bench/src/bench_mode.rs b/bin/reth-bench/src/bench_mode.rs new file mode 100644 index 000000000000..ae66ba53822f --- /dev/null +++ b/bin/reth-bench/src/bench_mode.rs @@ -0,0 +1,37 @@ +//! The benchmark mode defines whether the benchmark should run for a closed or open range of +//! blocks. +use std::ops::RangeInclusive; + +/// Whether or not the benchmark should run as a continuous stream of payloads. +#[derive(Debug, PartialEq, Eq)] +pub enum BenchMode { + // TODO: just include the start block in `Continuous` + /// Run the benchmark as a continuous stream of payloads, until the benchmark is interrupted. + Continuous, + /// Run the benchmark for a specific range of blocks. + Range(RangeInclusive), +} + +impl BenchMode { + /// Check if the block number is in the range + pub fn contains(&self, block_number: u64) -> bool { + match self { + Self::Continuous => true, + Self::Range(range) => range.contains(&block_number), + } + } + + /// Create a [`BenchMode`] from optional `from` and `to` fields. + pub fn new(from: Option, to: Option) -> Result { + // If neither `--from` nor `--to` are provided, we will run the benchmark continuously, + // starting at the latest block. + match (from, to) { + (Some(from), Some(to)) => Ok(Self::Range(from..=to)), + (None, None) => Ok(Self::Continuous), + _ => { + // both or neither are allowed, everything else is ambiguous + Err(eyre::eyre!("`from` and `to` must be provided together, or not at all.")) + } + } + } +} diff --git a/bin/reth-bench/src/main.rs b/bin/reth-bench/src/main.rs new file mode 100644 index 000000000000..8cb7dbd07b91 --- /dev/null +++ b/bin/reth-bench/src/main.rs @@ -0,0 +1,34 @@ +//! # reth-benchmark +//! +//! This is a tool that converts existing blocks into a stream of blocks for benchmarking purposes. +//! These blocks are then fed into reth as a stream of execution payloads. + +// We use jemalloc for performance reasons. +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +pub mod authenticated_transport; +pub mod bench; +pub mod bench_mode; +pub mod valid_payload; + +use bench::BenchmarkCommand; +use clap::Parser; +use reth_cli_runner::CliRunner; + +fn main() { + // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + // Run until either exit or sigint or sigterm + let runner = CliRunner::default(); + runner + .run_command_until_exit(|ctx| { + let command = BenchmarkCommand::parse(); + command.execute(ctx) + }) + .unwrap(); +} diff --git a/bin/reth-bench/src/valid_payload.rs b/bin/reth-bench/src/valid_payload.rs new file mode 100644 index 000000000000..67bd1bc1e360 --- /dev/null +++ b/bin/reth-bench/src/valid_payload.rs @@ -0,0 +1,276 @@ +//! This is an extension trait for any provider that implements the engine API, to wait for a VALID +//! response. This is useful for benchmarking, as it allows us to wait for a payload to be valid +//! before sending additional calls. + +use alloy_provider::{ext::EngineApi, Network}; +use alloy_rpc_types_engine::{ + ExecutionPayloadInputV2, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, + PayloadStatusEnum, +}; +use alloy_transport::{Transport, TransportResult}; +use reth_node_api::EngineApiMessageVersion; +use reth_primitives::B256; +use reth_rpc_types::{ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV3}; +use tracing::error; + +/// An extension trait for providers that implement the engine API, to wait for a VALID response. +#[async_trait::async_trait] +pub trait EngineApiValidWaitExt: Send + Sync { + /// Calls `engine_newPayloadV1` with the given [ExecutionPayloadV1], and waits until the + /// response is VALID. + async fn new_payload_v1_wait( + &self, + payload: ExecutionPayloadV1, + ) -> TransportResult; + + /// Calls `engine_newPayloadV2` with the given [ExecutionPayloadInputV2], and waits until the + /// response is VALID. + async fn new_payload_v2_wait( + &self, + payload: ExecutionPayloadInputV2, + ) -> TransportResult; + + /// Calls `engine_newPayloadV3` with the given [ExecutionPayloadV3], parent beacon block root, + /// and versioned hashes, and waits until the response is VALID. + async fn new_payload_v3_wait( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> TransportResult; + + /// Calls `engine_forkChoiceUpdatedV1` with the given [ForkchoiceState] and optional + /// [PayloadAttributes], and waits until the response is VALID. + async fn fork_choice_updated_v1_wait( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult; + + /// Calls `engine_forkChoiceUpdatedV2` with the given [ForkchoiceState] and optional + /// [PayloadAttributes], and waits until the response is VALID. + async fn fork_choice_updated_v2_wait( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult; + + /// Calls `engine_forkChoiceUpdatedV3` with the given [ForkchoiceState] and optional + /// [PayloadAttributes], and waits until the response is VALID. + async fn fork_choice_updated_v3_wait( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult; +} + +#[async_trait::async_trait] +impl EngineApiValidWaitExt for P +where + N: Network, + T: Transport + Clone, + P: EngineApi, +{ + async fn new_payload_v1_wait( + &self, + payload: ExecutionPayloadV1, + ) -> TransportResult { + let mut status = self.new_payload_v1(payload.clone()).await?; + while status.status != PayloadStatusEnum::Valid { + if status.status.is_invalid() { + error!(?status, ?payload, "Invalid newPayloadV1",); + panic!("Invalid newPayloadV1: {status:?}"); + } + status = self.new_payload_v1(payload.clone()).await?; + } + Ok(status) + } + + async fn new_payload_v2_wait( + &self, + payload: ExecutionPayloadInputV2, + ) -> TransportResult { + let mut status = self.new_payload_v2(payload.clone()).await?; + while status.status != PayloadStatusEnum::Valid { + if status.status.is_invalid() { + error!(?status, ?payload, "Invalid newPayloadV2",); + panic!("Invalid newPayloadV2: {status:?}"); + } + status = self.new_payload_v2(payload.clone()).await?; + } + Ok(status) + } + + async fn new_payload_v3_wait( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> TransportResult { + let mut status = self + .new_payload_v3(payload.clone(), versioned_hashes.clone(), parent_beacon_block_root) + .await?; + while status.status != PayloadStatusEnum::Valid { + if status.status.is_invalid() { + error!( + ?status, + ?payload, + ?versioned_hashes, + ?parent_beacon_block_root, + "Invalid newPayloadV3", + ); + panic!("Invalid newPayloadV3: {status:?}"); + } + status = self + .new_payload_v3(payload.clone(), versioned_hashes.clone(), parent_beacon_block_root) + .await?; + } + Ok(status) + } + + async fn fork_choice_updated_v1_wait( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult { + let mut status = + self.fork_choice_updated_v1(fork_choice_state, payload_attributes.clone()).await?; + + while status.payload_status.status != PayloadStatusEnum::Valid { + if status.payload_status.status.is_invalid() { + error!( + ?status, + ?fork_choice_state, + ?payload_attributes, + "Invalid forkchoiceUpdatedV1 message", + ); + panic!("Invalid forkchoiceUpdatedV1: {status:?}"); + } + status = + self.fork_choice_updated_v1(fork_choice_state, payload_attributes.clone()).await?; + } + + Ok(status) + } + + async fn fork_choice_updated_v2_wait( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult { + let mut status = + self.fork_choice_updated_v2(fork_choice_state, payload_attributes.clone()).await?; + + while status.payload_status.status != PayloadStatusEnum::Valid { + if status.payload_status.status.is_invalid() { + error!( + ?status, + ?fork_choice_state, + ?payload_attributes, + "Invalid forkchoiceUpdatedV2 message", + ); + panic!("Invalid forkchoiceUpdatedV2: {status:?}"); + } + status = + self.fork_choice_updated_v2(fork_choice_state, payload_attributes.clone()).await?; + } + + Ok(status) + } + + async fn fork_choice_updated_v3_wait( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> TransportResult { + let mut status = + self.fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()).await?; + + while status.payload_status.status != PayloadStatusEnum::Valid { + if status.payload_status.status.is_invalid() { + error!( + ?status, + ?fork_choice_state, + ?payload_attributes, + "Invalid forkchoiceUpdatedV3 message", + ); + panic!("Invalid forkchoiceUpdatedV3: {status:?}"); + } + status = + self.fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()).await?; + } + + Ok(status) + } +} + +/// Calls the correct `engine_newPayload` method depending on the given [`ExecutionPayload`] and its +/// versioned variant. Returns the [`EngineApiMessageVersion`] depending on the payload's version. +/// +/// # Panics +/// If the given payload is a V3 payload, but a parent beacon block root is provided as `None`. +pub(crate) async fn call_new_payload>( + provider: P, + payload: ExecutionPayload, + parent_beacon_block_root: Option, + versioned_hashes: Vec, +) -> TransportResult { + match payload { + ExecutionPayload::V4(_payload) => { + todo!("V4 payloads not supported yet"); + // auth_provider + // .new_payload_v4_wait(payload, versioned_hashes, parent_beacon_block_root, ...) + // .await?; + // + // Ok(EngineApiMessageVersion::V4) + } + ExecutionPayload::V3(payload) => { + // We expect the caller + let parent_beacon_block_root = parent_beacon_block_root + .expect("parent_beacon_block_root is required for V3 payloads"); + provider + .new_payload_v3_wait(payload, versioned_hashes, parent_beacon_block_root) + .await?; + + Ok(EngineApiMessageVersion::V3) + } + ExecutionPayload::V2(payload) => { + let input = ExecutionPayloadInputV2 { + execution_payload: payload.payload_inner, + withdrawals: Some(payload.withdrawals), + }; + + provider.new_payload_v2_wait(input).await?; + + Ok(EngineApiMessageVersion::V2) + } + ExecutionPayload::V1(payload) => { + provider.new_payload_v1_wait(payload).await?; + + Ok(EngineApiMessageVersion::V1) + } + } +} + +/// Calls the correct `engine_forkchoiceUpdated` method depending on the given +/// `EngineApiMessageVersion`, using the provided forkchoice state and payload attributes for the +/// actual engine api message call. +pub(crate) async fn call_forkchoice_updated>( + provider: P, + message_version: EngineApiMessageVersion, + forkchoice_state: ForkchoiceState, + payload_attributes: Option, +) -> TransportResult { + match message_version { + EngineApiMessageVersion::V4 => todo!("V4 payloads not supported yet"), + EngineApiMessageVersion::V3 => { + provider.fork_choice_updated_v3_wait(forkchoice_state, payload_attributes).await + } + EngineApiMessageVersion::V2 => { + provider.fork_choice_updated_v2_wait(forkchoice_state, payload_attributes).await + } + EngineApiMessageVersion::V1 => { + provider.fork_choice_updated_v1_wait(forkchoice_state, payload_attributes).await + } + } +} diff --git a/crates/node-core/src/args/benchmark_args.rs b/crates/node-core/src/args/benchmark_args.rs new file mode 100644 index 000000000000..1ff49c9c84d1 --- /dev/null +++ b/crates/node-core/src/args/benchmark_args.rs @@ -0,0 +1,62 @@ +//! clap [Args](clap::Args) for benchmark configuration + +use clap::Args; +use std::path::PathBuf; + +/// Parameters for benchmark configuration +#[derive(Debug, Args, PartialEq, Eq, Default, Clone)] +#[command(next_help_heading = "Benchmark")] +pub struct BenchmarkArgs { + /// Run the benchmark from a specific block. + #[arg(long, verbatim_doc_comment)] + pub from: Option, + + /// Run the benchmark to a specific block. + #[arg(long, verbatim_doc_comment)] + pub to: Option, + + /// Path to a JWT secret to use for the authenticated engine-API RPC server. + /// + /// This will perform JWT authentication for all requests to the given engine RPC url. + /// + /// If no path is provided, a secret will be generated and stored in the datadir under + /// `//jwt.hex`. For mainnet this would be `~/.reth/mainnet/jwt.hex` by default. + #[arg(long = "jwtsecret", value_name = "PATH", global = true, required = false)] + pub auth_jwtsecret: Option, + + /// The RPC url to use for sending engine requests. + #[arg( + long, + value_name = "ENGINE_RPC_URL", + verbatim_doc_comment, + default_value = "http://localhost:8551" + )] + pub engine_rpc_url: String, + + /// The path to the output directory for granular benchmark results. + #[arg(long, short, value_name = "BENCHMARK_OUTPUT", verbatim_doc_comment)] + pub output: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + + /// A helper type to parse Args more easily + #[derive(Parser)] + struct CommandParser { + #[command(flatten)] + args: T, + } + + #[test] + fn test_parse_benchmark_args() { + let default_args = BenchmarkArgs { + engine_rpc_url: "http://localhost:8551".to_string(), + ..Default::default() + }; + let args = CommandParser::::parse_from(["reth-bench"]).args; + assert_eq!(args, default_args); + } +} diff --git a/crates/node-core/src/args/mod.rs b/crates/node-core/src/args/mod.rs index 04fc02217d08..469ff72ea159 100644 --- a/crates/node-core/src/args/mod.rs +++ b/crates/node-core/src/args/mod.rs @@ -55,6 +55,10 @@ pub use pruning::PruningArgs; mod datadir_args; pub use datadir_args::DatadirArgs; +/// BenchmarkArgs struct for configuring the benchmark to run +mod benchmark_args; +pub use benchmark_args::BenchmarkArgs; + pub mod utils; pub mod types; From 0de932d1746b05f695ba3ca8420dac0102227f12 Mon Sep 17 00:00:00 2001 From: Pelle <78560773+PelleKrab@users.noreply.github.com> Date: Wed, 12 Jun 2024 09:09:45 -0600 Subject: [PATCH 013/405] chore: remove HeaderSyncMode::Continuous & debug.continuous (#8714) Co-authored-by: Matthias Seitz Co-authored-by: Oliver Nordbjerg --- bin/reth/src/commands/common.rs | 11 +++++---- bin/reth/src/commands/debug_cmd/execution.rs | 5 ++-- .../src/commands/debug_cmd/replay_engine.rs | 1 - bin/reth/src/commands/import.rs | 6 ++--- bin/reth/src/commands/stage/unwind.rs | 5 ++-- book/cli/reth/node.md | 5 ---- crates/consensus/beacon/src/engine/mod.rs | 9 -------- crates/consensus/beacon/src/engine/sync.rs | 23 +++---------------- .../consensus/beacon/src/engine/test_utils.rs | 5 ++-- crates/node-core/src/args/debug.rs | 8 +------ crates/node-core/src/node_config.rs | 21 ----------------- crates/node/builder/src/launch/common.rs | 20 ++++------------ crates/node/builder/src/launch/mod.rs | 5 +--- crates/node/builder/src/setup.rs | 17 +++----------- crates/stages/stages/src/lib.rs | 3 +-- crates/stages/stages/src/sets.rs | 22 ++++++++++-------- crates/stages/stages/src/stages/headers.rs | 16 ++++++------- .../provider/src/providers/database/mod.rs | 20 ++++++++-------- .../src/providers/database/provider.rs | 12 ++++------ .../provider/src/traits/header_sync_gap.rs | 15 ++---------- crates/storage/provider/src/traits/mod.rs | 2 +- 21 files changed, 66 insertions(+), 165 deletions(-) diff --git a/bin/reth/src/commands/common.rs b/bin/reth/src/commands/common.rs index 5fd81d1637dd..ed6a92cdbc68 100644 --- a/bin/reth/src/commands/common.rs +++ b/bin/reth/src/commands/common.rs @@ -14,13 +14,12 @@ use reth_node_core::{ }, dirs::{ChainPath, DataDirPath}, }; -use reth_primitives::ChainSpec; -use reth_provider::{ - providers::StaticFileProvider, HeaderSyncMode, ProviderFactory, StaticFileProviderFactory, -}; +use reth_primitives::{ChainSpec, B256}; +use reth_provider::{providers::StaticFileProvider, ProviderFactory, StaticFileProviderFactory}; use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget}; use reth_static_file::StaticFileProducer; use std::{path::PathBuf, sync::Arc}; +use tokio::sync::watch; use tracing::{debug, info, warn}; /// Struct to hold config and datadir paths @@ -127,11 +126,13 @@ impl EnvironmentArgs { info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check."); + let (_tip_tx, tip_rx) = watch::channel(B256::ZERO); + // Builds and executes an unwind-only pipeline let mut pipeline = Pipeline::builder() .add_stages(DefaultStages::new( factory.clone(), - HeaderSyncMode::Continuous, + tip_rx, Arc::new(EthBeaconConsensus::new(self.chain.clone())), NoopHeaderDownloader::default(), NoopBodiesDownloader::default(), diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index 200f212d030f..c1fd4cfa5fa2 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -24,7 +24,7 @@ use reth_network_api::NetworkInfo; use reth_network_p2p::{bodies::client::BodiesClient, headers::client::HeadersClient}; use reth_primitives::{BlockHashOrNumber, BlockNumber, B256}; use reth_provider::{ - BlockExecutionWriter, ChainSpecProvider, HeaderSyncMode, ProviderFactory, StageCheckpointReader, + BlockExecutionWriter, ChainSpecProvider, ProviderFactory, StageCheckpointReader, }; use reth_prune_types::PruneModes; use reth_stages::{ @@ -86,13 +86,12 @@ impl Command { let (tip_tx, tip_rx) = watch::channel(B256::ZERO); let executor = block_executor!(provider_factory.chain_spec()); - let header_mode = HeaderSyncMode::Tip(tip_rx); let pipeline = Pipeline::builder() .with_tip_sender(tip_tx) .add_stages( DefaultStages::new( provider_factory.clone(), - header_mode, + tip_rx, Arc::clone(&consensus), header_downloader, body_downloader, diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 7d8e179d2fb6..26c8a3558e73 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -155,7 +155,6 @@ impl Command { Box::new(ctx.task_executor.clone()), Box::new(network), None, - false, payload_builder, None, u64::MAX, diff --git a/bin/reth/src/commands/import.rs b/bin/reth/src/commands/import.rs index 156e8f4d23ca..baf194714573 100644 --- a/bin/reth/src/commands/import.rs +++ b/bin/reth/src/commands/import.rs @@ -24,8 +24,8 @@ use reth_network_p2p::{ use reth_node_events::node::NodeEvent; use reth_primitives::B256; use reth_provider::{ - BlockNumReader, ChainSpecProvider, HeaderProvider, HeaderSyncMode, ProviderError, - ProviderFactory, StageCheckpointReader, + BlockNumReader, ChainSpecProvider, HeaderProvider, ProviderError, ProviderFactory, + StageCheckpointReader, }; use reth_prune_types::PruneModes; use reth_stages::{prelude::*, Pipeline, StageId, StageSet}; @@ -208,7 +208,7 @@ where .add_stages( DefaultStages::new( provider_factory.clone(), - HeaderSyncMode::Tip(tip_rx), + tip_rx, consensus.clone(), header_downloader, body_downloader, diff --git a/bin/reth/src/commands/stage/unwind.rs b/bin/reth/src/commands/stage/unwind.rs index 89131e5aac11..157f33bff7cb 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/bin/reth/src/commands/stage/unwind.rs @@ -11,7 +11,7 @@ use reth_node_core::args::NetworkArgs; use reth_primitives::{BlockHashOrNumber, BlockNumber, B256}; use reth_provider::{ BlockExecutionWriter, BlockNumReader, ChainSpecProvider, FinalizedBlockReader, - FinalizedBlockWriter, HeaderSyncMode, ProviderFactory, StaticFileProviderFactory, + FinalizedBlockWriter, ProviderFactory, StaticFileProviderFactory, }; use reth_prune_types::PruneModes; use reth_stages::{ @@ -105,13 +105,12 @@ impl Command { let (tip_tx, tip_rx) = watch::channel(B256::ZERO); let executor = block_executor!(provider_factory.chain_spec()); - let header_mode = HeaderSyncMode::Tip(tip_rx); let pipeline = Pipeline::builder() .with_tip_sender(tip_tx) .add_stages( DefaultStages::new( provider_factory.clone(), - header_mode, + tip_rx, Arc::clone(&consensus), NoopHeaderDownloader::default(), NoopBodiesDownloader::default(), diff --git a/book/cli/reth/node.md b/book/cli/reth/node.md index cd07d0692f58..575fe18cc8f9 100644 --- a/book/cli/reth/node.md +++ b/book/cli/reth/node.md @@ -447,11 +447,6 @@ Builder: [default: 3] Debug: - --debug.continuous - Prompt the downloader to download blocks one at a time. - - NOTE: This is for testing purposes only. - --debug.terminate Flag indicating whether the node should be terminated after the pipeline sync diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 2badb73f4117..a0ac2dbea008 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -240,7 +240,6 @@ where task_spawner: Box, sync_state_updater: Box, max_block: Option, - run_pipeline_continuously: bool, payload_builder: PayloadBuilderHandle, target: Option, pipeline_run_threshold: u64, @@ -254,7 +253,6 @@ where task_spawner, sync_state_updater, max_block, - run_pipeline_continuously, payload_builder, target, pipeline_run_threshold, @@ -285,7 +283,6 @@ where task_spawner: Box, sync_state_updater: Box, max_block: Option, - run_pipeline_continuously: bool, payload_builder: PayloadBuilderHandle, target: Option, pipeline_run_threshold: u64, @@ -299,7 +296,6 @@ where pipeline, client, task_spawner.clone(), - run_pipeline_continuously, max_block, blockchain.chain_spec(), event_sender.clone(), @@ -1448,11 +1444,6 @@ where return Ok(()) } - // update the canon chain if continuous is enabled - if self.sync.run_pipeline_continuously() { - self.set_canonical_head(ctrl.block_number().unwrap_or_default())?; - } - let sync_target_state = match self.forkchoice_state_tracker.sync_target_state() { Some(current_state) => current_state, None => { diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index 80e8633400ab..5299a972f982 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -54,8 +54,6 @@ where /// Buffered blocks from downloads - this is a min-heap of blocks, using the block number for /// ordering. This means the blocks will be popped from the heap with ascending block numbers. range_buffered_blocks: BinaryHeap>, - /// If enabled, the pipeline will be triggered continuously, as soon as it becomes idle - run_pipeline_continuously: bool, /// Max block after which the consensus engine would terminate the sync. Used for debugging /// purposes. max_block: Option, @@ -73,7 +71,6 @@ where pipeline: Pipeline, client: Client, pipeline_task_spawner: Box, - run_pipeline_continuously: bool, max_block: Option, chain_spec: Arc, event_sender: EventSender, @@ -89,7 +86,6 @@ where inflight_full_block_requests: Vec::new(), inflight_block_range_requests: Vec::new(), range_buffered_blocks: BinaryHeap::new(), - run_pipeline_continuously, event_sender, max_block, metrics: EngineSyncMetrics::default(), @@ -122,11 +118,6 @@ where self.update_block_download_metrics(); } - /// Returns whether or not the sync controller is set to run the pipeline continuously. - pub(crate) const fn run_pipeline_continuously(&self) -> bool { - self.run_pipeline_continuously - } - /// Returns `true` if a pipeline target is queued and will be triggered on the next `poll`. #[allow(dead_code)] pub(crate) const fn is_pipeline_sync_pending(&self) -> bool { @@ -271,20 +262,14 @@ where fn try_spawn_pipeline(&mut self) -> Option { match &mut self.pipeline_state { PipelineState::Idle(pipeline) => { - let target = self.pending_pipeline_target.take(); - - if target.is_none() && !self.run_pipeline_continuously { - // nothing to sync - return None - } - + let target = self.pending_pipeline_target.take()?; let (tx, rx) = oneshot::channel(); let pipeline = pipeline.take().expect("exists"); self.pipeline_task_spawner.spawn_critical_blocking( "pipeline task", Box::pin(async move { - let result = pipeline.run_as_fut(target).await; + let result = pipeline.run_as_fut(Some(target)).await; let _ = tx.send(result); }), ); @@ -294,7 +279,7 @@ where // outdated (included in the range the pipeline is syncing anyway) self.clear_block_download_requests(); - Some(EngineSyncEvent::PipelineStarted(target)) + Some(EngineSyncEvent::PipelineStarted(Some(target))) } PipelineState::Running(_) => None, } @@ -550,8 +535,6 @@ mod tests { pipeline, client, Box::::default(), - // run_pipeline_continuously: false here until we want to test this - false, self.max_block, chain_spec, Default::default(), diff --git a/crates/consensus/beacon/src/engine/test_utils.rs b/crates/consensus/beacon/src/engine/test_utils.rs index 05164d010f18..b040f87d4bcb 100644 --- a/crates/consensus/beacon/src/engine/test_utils.rs +++ b/crates/consensus/beacon/src/engine/test_utils.rs @@ -25,7 +25,7 @@ use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_primitives::{BlockNumber, ChainSpec, B256}; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, - ExecutionOutcome, HeaderSyncMode, + ExecutionOutcome, }; use reth_prune::Pruner; use reth_prune_types::PruneModes; @@ -371,7 +371,7 @@ where Pipeline::builder().add_stages(DefaultStages::new( provider_factory.clone(), - HeaderSyncMode::Tip(tip_rx.clone()), + tip_rx.clone(), Arc::clone(&consensus), header_downloader, body_downloader, @@ -418,7 +418,6 @@ where Box::::default(), Box::::default(), None, - false, payload_builder, None, self.base_config.pipeline_run_threshold.unwrap_or(MIN_BLOCKS_FOR_PIPELINE_RUN), diff --git a/crates/node-core/src/args/debug.rs b/crates/node-core/src/args/debug.rs index b132eb3a611d..432bacf7d683 100644 --- a/crates/node-core/src/args/debug.rs +++ b/crates/node-core/src/args/debug.rs @@ -8,12 +8,6 @@ use std::path::PathBuf; #[derive(Debug, Clone, Args, PartialEq, Eq, Default)] #[command(next_help_heading = "Debug")] pub struct DebugArgs { - /// Prompt the downloader to download blocks one at a time. - /// - /// NOTE: This is for testing purposes only. - #[arg(long = "debug.continuous", help_heading = "Debug", conflicts_with = "tip")] - pub continuous: bool, - /// Flag indicating whether the node should be terminated after the pipeline sync. #[arg(long = "debug.terminate", help_heading = "Debug")] pub terminate: bool, @@ -21,7 +15,7 @@ pub struct DebugArgs { /// Set the chain tip manually for testing purposes. /// /// NOTE: This is a temporary flag - #[arg(long = "debug.tip", help_heading = "Debug", conflicts_with = "continuous")] + #[arg(long = "debug.tip", help_heading = "Debug")] pub tip: Option, /// Runs the sync only up to the specified block. diff --git a/crates/node-core/src/node_config.rs b/crates/node-core/src/node_config.rs index b8b1ebc8be34..5e6fb5e94653 100644 --- a/crates/node-core/src/node_config.rs +++ b/crates/node-core/src/node_config.rs @@ -239,27 +239,6 @@ impl NodeConfig { self } - /// Returns the initial pipeline target, based on whether or not the node is running in - /// `debug.tip` mode, `debug.continuous` mode, or neither. - /// - /// If running in `debug.tip` mode, the configured tip is returned. - /// Otherwise, if running in `debug.continuous` mode, the genesis hash is returned. - /// Otherwise, `None` is returned. This is what the node will do by default. - pub fn initial_pipeline_target(&self, genesis_hash: B256) -> Option { - if let Some(tip) = self.debug.tip { - // Set the provided tip as the initial pipeline target. - debug!(target: "reth::cli", %tip, "Tip manually set"); - Some(tip) - } else if self.debug.continuous { - // Set genesis as the initial pipeline target. - // This will allow the downloader to start - debug!(target: "reth::cli", "Continuous sync mode enabled"); - Some(genesis_hash) - } else { - None - } - } - /// Returns pruning configuration. pub fn prune_config(&self) -> Option { self.pruning.prune_config(&self.chain) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 395e5e19eb12..4689d83467e8 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -16,9 +16,7 @@ use reth_node_core::{ node_config::NodeConfig, }; use reth_primitives::{BlockNumber, Chain, ChainSpec, Head, B256}; -use reth_provider::{ - providers::StaticFileProvider, HeaderSyncMode, ProviderFactory, StaticFileProviderFactory, -}; +use reth_provider::{providers::StaticFileProvider, ProviderFactory, StaticFileProviderFactory}; use reth_prune::{PruneModes, PrunerBuilder}; use reth_rpc_builder::config::RethRpcServerConfig; use reth_rpc_layer::JwtSecret; @@ -27,7 +25,7 @@ use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; use reth_tracing::tracing::{debug, error, info, warn}; use std::{sync::Arc, thread::available_parallelism}; -use tokio::sync::{mpsc::Receiver, oneshot}; +use tokio::sync::{mpsc::Receiver, oneshot, watch}; /// Reusable setup for launching a node. /// @@ -316,16 +314,6 @@ impl LaunchContextWith> { .timeout(PrunerBuilder::DEFAULT_TIMEOUT) } - /// Returns the initial pipeline target, based on whether or not the node is running in - /// `debug.tip` mode, `debug.continuous` mode, or neither. - /// - /// If running in `debug.tip` mode, the configured tip is returned. - /// Otherwise, if running in `debug.continuous` mode, the genesis hash is returned. - /// Otherwise, `None` is returned. This is what the node will do by default. - pub fn initial_pipeline_target(&self) -> Option { - self.node_config().initial_pipeline_target(self.genesis_hash()) - } - /// Loads the JWT secret for the engine API pub fn auth_jwt_secret(&self) -> eyre::Result { let default_jwt_path = self.data_dir().jwt(); @@ -377,11 +365,13 @@ where info!(target: "reth::cli", unwind_target = %unwind_target, "Executing an unwind after a failed storage consistency check."); + let (_tip_tx, tip_rx) = watch::channel(B256::ZERO); + // Builds an unwind-only pipeline let pipeline = Pipeline::builder() .add_stages(DefaultStages::new( factory.clone(), - HeaderSyncMode::Continuous, + tip_rx, Arc::new(EthBeaconConsensus::new(self.chain_spec())), NoopHeaderDownloader::default(), NoopBodiesDownloader::default(), diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index fde21ad9730e..7cb1b2703753 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -250,7 +250,6 @@ where .build(); let pipeline = crate::setup::build_networked_pipeline( - ctx.node_config(), &ctx.toml_config().stages, client.clone(), consensus.clone(), @@ -273,7 +272,6 @@ where (pipeline, Either::Left(client)) } else { let pipeline = crate::setup::build_networked_pipeline( - ctx.node_config(), &ctx.toml_config().stages, network_client.clone(), consensus.clone(), @@ -293,7 +291,7 @@ where let pipeline_events = pipeline.events(); - let initial_target = ctx.initial_pipeline_target(); + let initial_target = ctx.node_config().debug.tip; let mut pruner_builder = ctx.pruner_builder().max_reorg_depth(tree_config.max_reorg_depth() as usize); @@ -316,7 +314,6 @@ where Box::new(ctx.task_executor().clone()), Box::new(node_adapter.components.network().clone()), max_block, - ctx.node_config().debug.continuous, node_adapter.components.payload_builder().clone(), initial_target, reth_beacon_consensus::MIN_BLOCKS_FOR_PIPELINE_RUN, diff --git a/crates/node/builder/src/setup.rs b/crates/node/builder/src/setup.rs index b4a6fef16b64..cf4d090dcbe7 100644 --- a/crates/node/builder/src/setup.rs +++ b/crates/node/builder/src/setup.rs @@ -13,11 +13,8 @@ use reth_network_p2p::{ bodies::{client::BodiesClient, downloader::BodyDownloader}, headers::{client::HeadersClient, downloader::HeaderDownloader}, }; -use reth_node_core::{ - node_config::NodeConfig, - primitives::{BlockNumber, B256}, -}; -use reth_provider::{HeaderSyncMode, ProviderFactory}; +use reth_node_core::primitives::{BlockNumber, B256}; +use reth_provider::ProviderFactory; use reth_stages::{prelude::DefaultStages, stages::ExecutionStage, Pipeline, StageSet}; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; @@ -28,7 +25,6 @@ use tokio::sync::watch; /// Constructs a [Pipeline] that's wired to the network #[allow(clippy::too_many_arguments)] pub async fn build_networked_pipeline( - node_config: &NodeConfig, config: &StageConfig, client: Client, consensus: Arc, @@ -56,7 +52,6 @@ where .into_task_with(task_executor); let pipeline = build_pipeline( - node_config, provider_factory, config, header_downloader, @@ -77,7 +72,6 @@ where /// Builds the [Pipeline] with the given [`ProviderFactory`] and downloaders. #[allow(clippy::too_many_arguments)] pub async fn build_pipeline( - node_config: &NodeConfig, provider_factory: ProviderFactory, stage_config: &StageConfig, header_downloader: H, @@ -107,18 +101,13 @@ where let prune_modes = prune_config.map(|prune| prune.segments).unwrap_or_default(); - let header_mode = if node_config.debug.continuous { - HeaderSyncMode::Continuous - } else { - HeaderSyncMode::Tip(tip_rx) - }; let pipeline = builder .with_tip_sender(tip_tx) .with_metrics_tx(metrics_tx.clone()) .add_stages( DefaultStages::new( provider_factory.clone(), - header_mode, + tip_rx, Arc::clone(&consensus), header_downloader, body_downloader, diff --git a/crates/stages/stages/src/lib.rs b/crates/stages/stages/src/lib.rs index 4e60e168a1bb..ca44e586f7c4 100644 --- a/crates/stages/stages/src/lib.rs +++ b/crates/stages/stages/src/lib.rs @@ -26,7 +26,6 @@ //! # use reth_evm_ethereum::EthEvmConfig; //! # use reth_provider::ProviderFactory; //! # use reth_provider::StaticFileProviderFactory; -//! # use reth_provider::HeaderSyncMode; //! # use reth_provider::test_utils::create_test_provider_factory; //! # use reth_static_file::StaticFileProducer; //! # use reth_config::config::StageConfig; @@ -57,7 +56,7 @@ //! .with_tip_sender(tip_tx) //! .add_stages(DefaultStages::new( //! provider_factory.clone(), -//! HeaderSyncMode::Tip(tip_rx), +//! tip_rx, //! consensus, //! headers_downloader, //! bodies_downloader, diff --git a/crates/stages/stages/src/sets.rs b/crates/stages/stages/src/sets.rs index 8c82902bf2a4..772f562a224d 100644 --- a/crates/stages/stages/src/sets.rs +++ b/crates/stages/stages/src/sets.rs @@ -46,9 +46,11 @@ use reth_consensus::Consensus; use reth_db_api::database::Database; use reth_evm::execute::BlockExecutorProvider; use reth_network_p2p::{bodies::downloader::BodyDownloader, headers::downloader::HeaderDownloader}; -use reth_provider::{HeaderSyncGapProvider, HeaderSyncMode}; +use reth_primitives::B256; +use reth_provider::HeaderSyncGapProvider; use reth_prune_types::PruneModes; use std::sync::Arc; +use tokio::sync::watch; /// A set containing all stages to run a fully syncing instance of reth. /// @@ -88,7 +90,7 @@ impl DefaultStages { #[allow(clippy::too_many_arguments)] pub fn new( provider: Provider, - header_mode: HeaderSyncMode, + tip: watch::Receiver, consensus: Arc, header_downloader: H, body_downloader: B, @@ -102,7 +104,7 @@ impl DefaultStages { Self { online: OnlineStages::new( provider, - header_mode, + tip, consensus, header_downloader, body_downloader, @@ -159,8 +161,8 @@ where pub struct OnlineStages { /// Sync gap provider for the headers stage. provider: Provider, - /// The sync mode for the headers stage. - header_mode: HeaderSyncMode, + /// The tip for the headers stage. + tip: watch::Receiver, /// The consensus engine used to validate incoming data. consensus: Arc, /// The block header downloader @@ -175,13 +177,13 @@ impl OnlineStages { /// Create a new set of online stages with default values. pub fn new( provider: Provider, - header_mode: HeaderSyncMode, + tip: watch::Receiver, consensus: Arc, header_downloader: H, body_downloader: B, stages_config: StageConfig, ) -> Self { - Self { provider, header_mode, consensus, header_downloader, body_downloader, stages_config } + Self { provider, tip, consensus, header_downloader, body_downloader, stages_config } } } @@ -203,7 +205,7 @@ where pub fn builder_with_bodies( bodies: BodyStage, provider: Provider, - mode: HeaderSyncMode, + tip: watch::Receiver, header_downloader: H, consensus: Arc, stages_config: StageConfig, @@ -212,7 +214,7 @@ where .add_stage(HeaderStage::new( provider, header_downloader, - mode, + tip, consensus.clone(), stages_config.etl, )) @@ -232,7 +234,7 @@ where .add_stage(HeaderStage::new( self.provider, self.header_downloader, - self.header_mode, + self.tip, self.consensus.clone(), self.stages_config.etl.clone(), )) diff --git a/crates/stages/stages/src/stages/headers.rs b/crates/stages/stages/src/stages/headers.rs index 21d3ad4ab628..58c578e34d99 100644 --- a/crates/stages/stages/src/stages/headers.rs +++ b/crates/stages/stages/src/stages/headers.rs @@ -10,11 +10,10 @@ use reth_db_api::{ }; use reth_etl::Collector; use reth_network_p2p::headers::{downloader::HeaderDownloader, error::HeadersDownloaderError}; -use reth_primitives::{BlockHash, BlockNumber, SealedHeader, StaticFileSegment}; +use reth_primitives::{BlockHash, BlockNumber, SealedHeader, StaticFileSegment, B256}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, BlockHashReader, DatabaseProviderRW, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, - HeaderSyncMode, }; use reth_stages_api::{ BlockErrorKind, CheckpointBlockRange, EntitiesCheckpoint, ExecInput, ExecOutput, @@ -25,6 +24,7 @@ use std::{ sync::Arc, task::{ready, Context, Poll}, }; +use tokio::sync::watch; use tracing::*; /// The headers stage. @@ -44,8 +44,8 @@ pub struct HeaderStage { provider: Provider, /// Strategy for downloading the headers downloader: Downloader, - /// The sync mode for the stage. - mode: HeaderSyncMode, + /// The tip for the stage. + tip: watch::Receiver, /// Consensus client implementation consensus: Arc, /// Current sync gap. @@ -68,14 +68,14 @@ where pub fn new( database: Provider, downloader: Downloader, - mode: HeaderSyncMode, + tip: watch::Receiver, consensus: Arc, etl_config: EtlConfig, ) -> Self { Self { provider: database, downloader, - mode, + tip, consensus, sync_gap: None, hash_collector: Collector::new(etl_config.file_size / 2, etl_config.dir.clone()), @@ -206,7 +206,7 @@ where } // Lookup the head and tip of the sync range - let gap = self.provider.sync_gap(self.mode.clone(), current_checkpoint.block_number)?; + let gap = self.provider.sync_gap(self.tip.clone(), current_checkpoint.block_number)?; let tip = gap.target.tip(); self.sync_gap = Some(gap.clone()); @@ -436,7 +436,7 @@ mod tests { HeaderStage::new( self.db.factory.clone(), (*self.downloader_factory)(), - HeaderSyncMode::Tip(self.channel.1.clone()), + self.channel.1.clone(), self.consensus.clone(), EtlConfig::default(), ) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index edf095124290..683b50dce075 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -3,10 +3,9 @@ use crate::{ to_range, traits::{BlockSource, ReceiptProvider}, BlockHashReader, BlockNumReader, BlockReader, ChainSpecProvider, DatabaseProviderFactory, - EvmEnvProvider, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HeaderSyncMode, - ProviderError, PruneCheckpointReader, RequestsProvider, StageCheckpointReader, - StateProviderBox, StaticFileProviderFactory, TransactionVariant, TransactionsProvider, - WithdrawalsProvider, + EvmEnvProvider, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, ProviderError, + PruneCheckpointReader, RequestsProvider, StageCheckpointReader, StateProviderBox, + StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; use reth_db::{init_db, mdbx::DatabaseArguments, DatabaseEnv}; use reth_db_api::{database::Database, models::StoredBlockBodyIndices}; @@ -27,6 +26,7 @@ use std::{ path::Path, sync::Arc, }; +use tokio::sync::watch; use tracing::trace; mod metrics; @@ -165,10 +165,10 @@ impl StaticFileProviderFactory for ProviderFactory { impl HeaderSyncGapProvider for ProviderFactory { fn sync_gap( &self, - mode: HeaderSyncMode, + tip: watch::Receiver, highest_uninterrupted_block: BlockNumber, ) -> ProviderResult { - self.provider()?.sync_gap(mode, highest_uninterrupted_block) + self.provider()?.sync_gap(tip, highest_uninterrupted_block) } } @@ -592,8 +592,7 @@ mod tests { use crate::{ providers::{StaticFileProvider, StaticFileWriter}, test_utils::create_test_provider_factory, - BlockHashReader, BlockNumReader, BlockWriter, HeaderSyncGapProvider, HeaderSyncMode, - TransactionsProvider, + BlockHashReader, BlockNumReader, BlockWriter, HeaderSyncGapProvider, TransactionsProvider, }; use alloy_rlp::Decodable; use assert_matches::assert_matches; @@ -748,7 +747,6 @@ mod tests { let mut rng = generators::rng(); let consensus_tip = rng.gen(); let (_tip_tx, tip_rx) = watch::channel(consensus_tip); - let mode = HeaderSyncMode::Tip(tip_rx); // Genesis let checkpoint = 0; @@ -756,7 +754,7 @@ mod tests { // Empty database assert_matches!( - provider.sync_gap(mode.clone(), checkpoint), + provider.sync_gap(tip_rx.clone(), checkpoint), Err(ProviderError::HeaderNotFound(block_number)) if block_number.as_number().unwrap() == checkpoint ); @@ -768,7 +766,7 @@ mod tests { static_file_writer.commit().unwrap(); drop(static_file_writer); - let gap = provider.sync_gap(mode, checkpoint).unwrap(); + let gap = provider.sync_gap(tip_rx, checkpoint).unwrap(); assert_eq!(gap.local_head, head); assert_eq!(gap.target.tip(), consensus_tip.into()); } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index ee7eb72ec112..01c481659905 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -7,8 +7,8 @@ use crate::{ }, AccountReader, BlockExecutionWriter, BlockHashReader, BlockNumReader, BlockReader, BlockWriter, Chain, EvmEnvProvider, FinalizedBlockReader, FinalizedBlockWriter, HashingWriter, - HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HeaderSyncMode, HistoricalStateProvider, - HistoryWriter, LatestStateProvider, OriginalValuesKnown, ProviderError, PruneCheckpointReader, + HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HistoricalStateProvider, HistoryWriter, + LatestStateProvider, OriginalValuesKnown, ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RequestsProvider, StageCheckpointReader, StateProviderBox, StateWriter, StatsReader, StorageReader, TransactionVariant, TransactionsProvider, TransactionsProviderExt, WithdrawalsProvider, @@ -55,6 +55,7 @@ use std::{ sync::{mpsc, Arc}, time::{Duration, Instant}, }; +use tokio::sync::watch; use tracing::{debug, error, warn}; /// A [`DatabaseProvider`] that holds a read-only database transaction. @@ -1272,7 +1273,7 @@ impl ChangeSetReader for DatabaseProvider { impl HeaderSyncGapProvider for DatabaseProvider { fn sync_gap( &self, - mode: HeaderSyncMode, + tip: watch::Receiver, highest_uninterrupted_block: BlockNumber, ) -> ProviderResult { let static_file_provider = self.static_file_provider(); @@ -1307,10 +1308,7 @@ impl HeaderSyncGapProvider for DatabaseProvider { .sealed_header(highest_uninterrupted_block)? .ok_or_else(|| ProviderError::HeaderNotFound(highest_uninterrupted_block.into()))?; - let target = match mode { - HeaderSyncMode::Tip(rx) => SyncTarget::Tip(*rx.borrow()), - HeaderSyncMode::Continuous => SyncTarget::TipNum(highest_uninterrupted_block + 1), - }; + let target = SyncTarget::Tip(*tip.borrow()); Ok(HeaderSyncGap { local_head, target }) } diff --git a/crates/storage/provider/src/traits/header_sync_gap.rs b/crates/storage/provider/src/traits/header_sync_gap.rs index 54556101aec5..faa02b39e96b 100644 --- a/crates/storage/provider/src/traits/header_sync_gap.rs +++ b/crates/storage/provider/src/traits/header_sync_gap.rs @@ -3,17 +3,6 @@ use reth_primitives::{BlockHashOrNumber, BlockNumber, SealedHeader, B256}; use reth_storage_errors::provider::ProviderResult; use tokio::sync::watch; -/// The header sync mode. -#[derive(Clone, Debug)] -pub enum HeaderSyncMode { - /// A sync mode in which the stage continuously requests the downloader for - /// next blocks. - Continuous, - /// A sync mode in which the stage polls the receiver for the next tip - /// to download from. - Tip(watch::Receiver), -} - /// Represents a gap to sync: from `local_head` to `target` #[derive(Clone, Debug)] pub struct HeaderSyncGap { @@ -38,13 +27,13 @@ impl HeaderSyncGap { /// Client trait for determining the current headers sync gap. #[auto_impl::auto_impl(&, Arc)] pub trait HeaderSyncGapProvider: Send + Sync { - /// Find a current sync gap for the headers depending on the [HeaderSyncMode] and the last + /// Find a current sync gap for the headers depending on the last /// uninterrupted block number. Last uninterrupted block represents the block number before /// which there are no gaps. It's up to the caller to ensure that last uninterrupted block is /// determined correctly. fn sync_gap( &self, - mode: HeaderSyncMode, + tip: watch::Receiver, highest_uninterrupted_block: BlockNumber, ) -> ProviderResult; } diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index 283ba5a48eed..bf69eda03b0b 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -13,7 +13,7 @@ mod chain_info; pub use chain_info::CanonChainTracker; mod header_sync_gap; -pub use header_sync_gap::{HeaderSyncGap, HeaderSyncGapProvider, HeaderSyncMode}; +pub use header_sync_gap::{HeaderSyncGap, HeaderSyncGapProvider}; mod state; pub use state::StateWriter; From b8cd7be6c92a71aea5341cdeba685f124c6de540 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 12 Jun 2024 18:53:54 +0200 Subject: [PATCH 014/405] chore: bump version 1.0.0-rc.1 (#8775) --- Cargo.lock | 192 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8657d840207a..e70c29b75992 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2724,7 +2724,7 @@ dependencies = [ [[package]] name = "ef-tests" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "rayon", @@ -6273,7 +6273,7 @@ dependencies = [ [[package]] name = "reth" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "ahash", "alloy-rlp", @@ -6358,7 +6358,7 @@ dependencies = [ [[package]] name = "reth-auto-seal-consensus" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "futures-util", "reth-beacon-consensus", @@ -6382,7 +6382,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "futures-core", @@ -6403,7 +6403,7 @@ dependencies = [ [[package]] name = "reth-beacon-consensus" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "assert_matches", "futures", @@ -6452,7 +6452,7 @@ dependencies = [ [[package]] name = "reth-bench" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", @@ -6493,7 +6493,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "aquamarine", "assert_matches", @@ -6524,7 +6524,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -6535,7 +6535,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-tasks", "tokio", @@ -6544,7 +6544,7 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", @@ -6563,7 +6563,7 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "convert_case 0.6.0", "proc-macro2", @@ -6574,7 +6574,7 @@ dependencies = [ [[package]] name = "reth-config" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "confy", "humantime-serde", @@ -6587,7 +6587,7 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "auto_impl", "reth-primitives", @@ -6596,7 +6596,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "mockall", "rand 0.8.5", @@ -6607,7 +6607,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", @@ -6629,7 +6629,7 @@ dependencies = [ [[package]] name = "reth-db" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "arbitrary", "assert_matches", @@ -6667,7 +6667,7 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "arbitrary", "assert_matches", @@ -6696,7 +6696,7 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "eyre", "reth-codecs", @@ -6716,7 +6716,7 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "assert_matches", @@ -6741,7 +6741,7 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "derive_more", @@ -6765,7 +6765,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "data-encoding", @@ -6790,7 +6790,7 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "assert_matches", @@ -6823,7 +6823,7 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-network", @@ -6852,7 +6852,7 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "aes 0.8.4", "alloy-rlp", @@ -6884,7 +6884,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-payload-primitives", "reth-primitives", @@ -6893,7 +6893,7 @@ dependencies = [ [[package]] name = "reth-errors" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-blockchain-tree-api", "reth-consensus", @@ -6905,7 +6905,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "arbitrary", @@ -6939,7 +6939,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "arbitrary", @@ -6961,7 +6961,7 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-consensus", "reth-consensus-common", @@ -6971,7 +6971,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "reth-engine-primitives", @@ -6987,7 +6987,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7002,7 +7002,7 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-basic-payload-builder", "reth-errors", @@ -7019,7 +7019,7 @@ dependencies = [ [[package]] name = "reth-etl" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "rayon", "reth-db-api", @@ -7029,7 +7029,7 @@ dependencies = [ [[package]] name = "reth-evm" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "auto_impl", "futures-util", @@ -7045,7 +7045,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-sol-types", @@ -7063,7 +7063,7 @@ dependencies = [ [[package]] name = "reth-evm-optimism" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-consensus-common", "reth-evm", @@ -7081,7 +7081,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-primitives", @@ -7094,7 +7094,7 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-primitives", @@ -7106,7 +7106,7 @@ dependencies = [ [[package]] name = "reth-exex" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "eyre", "metrics", @@ -7127,7 +7127,7 @@ dependencies = [ [[package]] name = "reth-exex-test-utils" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "eyre", "futures-util", @@ -7155,14 +7155,14 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-fs-util" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "serde_json", "thiserror", @@ -7170,7 +7170,7 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "async-trait", "bytes", @@ -7192,7 +7192,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "bitflags 2.5.0", "byteorder", @@ -7213,7 +7213,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "bindgen", "cc", @@ -7222,7 +7222,7 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "futures", "metrics", @@ -7233,7 +7233,7 @@ dependencies = [ [[package]] name = "reth-metrics-derive" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "metrics", "once_cell", @@ -7247,7 +7247,7 @@ dependencies = [ [[package]] name = "reth-net-common" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "tokio", @@ -7255,7 +7255,7 @@ dependencies = [ [[package]] name = "reth-net-nat" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "futures-util", "reqwest", @@ -7267,7 +7267,7 @@ dependencies = [ [[package]] name = "reth-network" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-node-bindings", "alloy-provider", @@ -7322,7 +7322,7 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "enr", @@ -7336,7 +7336,7 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "auto_impl", "futures", @@ -7354,7 +7354,7 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7370,7 +7370,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "anyhow", "bincode", @@ -7391,7 +7391,7 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-db-api", "reth-engine-primitives", @@ -7406,7 +7406,7 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "aquamarine", "backon", @@ -7455,7 +7455,7 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rpc-types-engine", "clap", @@ -7518,7 +7518,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "eyre", "futures", @@ -7549,7 +7549,7 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "futures", "humantime", @@ -7570,7 +7570,7 @@ dependencies = [ [[package]] name = "reth-node-optimism" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "async-trait", @@ -7611,7 +7611,7 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-consensus", "reth-consensus-common", @@ -7621,7 +7621,7 @@ dependencies = [ [[package]] name = "reth-optimism-payload-builder" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "reth-basic-payload-builder", @@ -7643,11 +7643,11 @@ dependencies = [ [[package]] name = "reth-optimism-primitives" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" [[package]] name = "reth-payload-builder" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "futures-util", "metrics", @@ -7670,7 +7670,7 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-errors", "reth-primitives", @@ -7683,7 +7683,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-primitives", "reth-rpc-types", @@ -7692,7 +7692,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-chains", "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", @@ -7743,7 +7743,7 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", @@ -7760,7 +7760,7 @@ dependencies = [ [[package]] name = "reth-provider" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "alloy-rpc-types-engine", @@ -7801,7 +7801,7 @@ dependencies = [ [[package]] name = "reth-prune" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "assert_matches", @@ -7828,7 +7828,7 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "arbitrary", @@ -7848,7 +7848,7 @@ dependencies = [ [[package]] name = "reth-revm" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "alloy-rlp", @@ -7865,7 +7865,7 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -7922,7 +7922,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "jsonrpsee", "reth-engine-primitives", @@ -7935,7 +7935,7 @@ dependencies = [ [[package]] name = "reth-rpc-api-testing-util" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "futures", "jsonrpsee", @@ -7949,7 +7949,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "clap", "http 1.1.0", @@ -7990,7 +7990,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "assert_matches", @@ -8022,7 +8022,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rpc-types-engine", "assert_matches", @@ -8039,7 +8039,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "serde", @@ -8048,7 +8048,7 @@ dependencies = [ [[package]] name = "reth-rpc-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", @@ -8069,7 +8069,7 @@ dependencies = [ [[package]] name = "reth-rpc-types-compat" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", @@ -8080,7 +8080,7 @@ dependencies = [ [[package]] name = "reth-stages" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "assert_matches", @@ -8123,7 +8123,7 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "aquamarine", "assert_matches", @@ -8150,7 +8150,7 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "arbitrary", @@ -8167,7 +8167,7 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "assert_matches", "parking_lot 0.12.3", @@ -8188,7 +8188,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "clap", @@ -8199,7 +8199,7 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "auto_impl", "reth-db-api", @@ -8214,7 +8214,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "reth-fs-util", "reth-primitives", @@ -8223,7 +8223,7 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "dyn-clone", "futures-util", @@ -8239,7 +8239,7 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", "rand 0.8.5", @@ -8249,7 +8249,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "tokio", "tokio-stream", @@ -8258,7 +8258,7 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "clap", "eyre", @@ -8272,7 +8272,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "aquamarine", @@ -8309,7 +8309,7 @@ dependencies = [ [[package]] name = "reth-trie" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "auto_impl", @@ -8339,7 +8339,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "criterion", @@ -8364,7 +8364,7 @@ dependencies = [ [[package]] name = "reth-trie-types" -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "alloy-rlp", diff --git a/Cargo.toml b/Cargo.toml index 410d2e123890..812548cbb692 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.2.0-beta.9" +version = "1.0.0-rc.1" edition = "2021" rust-version = "1.76" license = "MIT OR Apache-2.0" From 8398625d149968325072798015a02ed1b3b26e8b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 12 Jun 2024 20:47:28 +0200 Subject: [PATCH 015/405] fix: prometheus graceful shutdown (#8784) --- .../src/metrics/prometheus_exporter.rs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/node-core/src/metrics/prometheus_exporter.rs b/crates/node-core/src/metrics/prometheus_exporter.rs index 1ce85346fb0e..b7a3ba7015c3 100644 --- a/crates/node-core/src/metrics/prometheus_exporter.rs +++ b/crates/node-core/src/metrics/prometheus_exporter.rs @@ -2,6 +2,7 @@ use crate::metrics::version_metrics::register_version_metrics; use eyre::WrapErr; +use futures::{future::FusedFuture, FutureExt}; use http::Response; use metrics::describe_gauge; use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; @@ -65,17 +66,14 @@ async fn start_endpoint( tokio::net::TcpListener::bind(listen_addr).await.wrap_err("Could not bind to address")?; task_executor.spawn_with_graceful_shutdown_signal(|signal| async move { - let mut shutdown = signal.ignore_guard(); + let mut shutdown = signal.ignore_guard().fuse(); loop { - let io = tokio::select! { - res = listener.accept() => match res { - Ok((stream, _remote_addr)) => stream, - Err(err) => { - tracing::error!(%err, "failed to accept connection"); - continue; - } - }, - _ = &mut shutdown => break, + let io = match listener.accept().await { + Ok((stream, _remote_addr)) => stream, + Err(err) => { + tracing::error!(%err, "failed to accept connection"); + continue; + } }; let handle = handle.clone(); @@ -89,7 +87,11 @@ async fn start_endpoint( if let Err(error) = jsonrpsee::server::serve_with_graceful_shutdown(io, service, &mut shutdown).await { - tracing::error!(%error, "metrics endpoint crashed") + tracing::debug!(%error, "failed to serve request") + } + + if shutdown.is_terminated() { + break; } } }); From 7b5864329db6010dd774a9f234de7500b0647d4a Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:49:05 +0200 Subject: [PATCH 016/405] fix: add a 5 second timeout for `tokio_runtime` shutdown (#8771) Co-authored-by: Matthias Seitz --- crates/cli/runner/src/lib.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/cli/runner/src/lib.rs b/crates/cli/runner/src/lib.rs index 9d5fea6a1c5a..a848ad0b21d2 100644 --- a/crates/cli/runner/src/lib.rs +++ b/crates/cli/runner/src/lib.rs @@ -11,7 +11,7 @@ //! Entrypoint for running commands. use reth_tasks::{TaskExecutor, TaskManager}; -use std::{future::Future, pin::pin}; +use std::{future::Future, pin::pin, sync::mpsc, time::Duration}; use tracing::{debug, error, trace}; /// Executes CLI commands. @@ -52,17 +52,26 @@ impl CliRunner { // after the command has finished or exit signal was received we shutdown the task // manager which fires the shutdown signal to all tasks spawned via the task // executor and awaiting on tasks spawned with graceful shutdown - task_manager.graceful_shutdown_with_timeout(std::time::Duration::from_secs(10)); + task_manager.graceful_shutdown_with_timeout(Duration::from_secs(5)); } - // drop the tokio runtime on a separate thread because drop blocks until its pools - // (including blocking pool) are shutdown. In other words `drop(tokio_runtime)` would block - // the current thread but we want to exit right away. + // `drop(tokio_runtime)` would block the current thread until its pools + // (including blocking pool) are shutdown. Since we want to exit as soon as possible, drop + // it on a separate thread and wait for up to 5 seconds for this operation to + // complete. + let (tx, rx) = mpsc::channel(); std::thread::Builder::new() .name("tokio-runtime-shutdown".to_string()) - .spawn(move || drop(tokio_runtime)) + .spawn(move || { + drop(tokio_runtime); + let _ = tx.send(()); + }) .unwrap(); + let _ = rx.recv_timeout(Duration::from_secs(5)).inspect_err(|err| { + debug!(target: "reth::cli", %err, "tokio runtime shutdown timed out"); + }); + command_res } From 73adc91a0d4c70fa55ad0018541266aa115f9fcd Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 12 Jun 2024 20:51:24 +0200 Subject: [PATCH 017/405] fix: disable sysinfo multithreading (#8783) --- Cargo.lock | 1 - crates/storage/db/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e70c29b75992..8b4d9d628c95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9540,7 +9540,6 @@ dependencies = [ "libc", "ntapi", "once_cell", - "rayon", "windows 0.52.0", ] diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 8fb417fb3e3c..e144e81271b7 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -43,7 +43,7 @@ derive_more.workspace = true eyre.workspace = true paste.workspace = true rustc-hash.workspace = true -sysinfo = "0.30" +sysinfo = { version = "0.30", default-features = false } # arbitrary utils strum = { workspace = true, features = ["derive"] } From a80f0126dd76e0e25f500a37143b41077e1f8256 Mon Sep 17 00:00:00 2001 From: ibremseth Date: Wed, 12 Jun 2024 17:58:37 -0400 Subject: [PATCH 018/405] fix: separate Base Sepolia and OP Sepolia BASE_FEE_PARAMS (#8789) --- crates/primitives/src/basefee.rs | 39 ++++++++++++++++++++++++-- crates/primitives/src/chain/mod.rs | 4 ++- crates/primitives/src/chain/spec.rs | 8 +++--- crates/primitives/src/constants/mod.rs | 21 +++++++++++++- 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/crates/primitives/src/basefee.rs b/crates/primitives/src/basefee.rs index b886b41e9299..0bafb9c89773 100644 --- a/crates/primitives/src/basefee.rs +++ b/crates/primitives/src/basefee.rs @@ -9,7 +9,9 @@ mod tests { use super::*; #[cfg(feature = "optimism")] - use crate::chain::{OP_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS}; + use crate::chain::{ + BASE_SEPOLIA_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, + }; #[test] fn calculate_base_fee_success() { @@ -92,7 +94,7 @@ mod tests { 18000000, 18000000, ]; let next_base_fee = [ - 1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1, + 1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1, 2, 3, ]; @@ -108,4 +110,37 @@ mod tests { ); } } + + #[cfg(feature = "optimism")] + #[test] + fn calculate_base_sepolia_base_fee_success() { + let base_fee = [ + 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, + 1, 2, + ]; + let gas_used = [ + 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, + 10000000, + ]; + let gas_limit = [ + 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, + 18000000, 18000000, + ]; + let next_base_fee = [ + 1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1, + 2, 3, + ]; + + for i in 0..base_fee.len() { + assert_eq!( + next_base_fee[i], + calc_next_block_base_fee( + gas_used[i] as u128, + gas_limit[i] as u128, + base_fee[i] as u128, + BASE_SEPOLIA_BASE_FEE_PARAMS, + ) as u64 + ); + } + } } diff --git a/crates/primitives/src/chain/mod.rs b/crates/primitives/src/chain/mod.rs index 727abe038112..41de1c450210 100644 --- a/crates/primitives/src/chain/mod.rs +++ b/crates/primitives/src/chain/mod.rs @@ -10,7 +10,9 @@ pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; #[cfg(feature = "optimism")] #[cfg(test)] -pub(crate) use spec::{OP_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS}; +pub(crate) use spec::{ + BASE_SEPOLIA_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, +}; // The chain spec module. mod spec; diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 6d9cb9e56e98..5bdc5d9c22a3 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -24,8 +24,8 @@ pub use alloy_eips::eip1559::BaseFeeParams; #[cfg(feature = "optimism")] pub(crate) use crate::{ constants::{ - OP_BASE_FEE_PARAMS, OP_CANYON_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, - OP_SEPOLIA_CANYON_BASE_FEE_PARAMS, + BASE_SEPOLIA_BASE_FEE_PARAMS, BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS, + OP_CANYON_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, OP_SEPOLIA_CANYON_BASE_FEE_PARAMS, }, net::{base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes}, }; @@ -397,8 +397,8 @@ pub static BASE_SEPOLIA: Lazy> = Lazy::new(|| { ]), base_fee_params: BaseFeeParamsKind::Variable( vec![ - (Hardfork::London, OP_SEPOLIA_BASE_FEE_PARAMS), - (Hardfork::Canyon, OP_SEPOLIA_CANYON_BASE_FEE_PARAMS), + (Hardfork::London, BASE_SEPOLIA_BASE_FEE_PARAMS), + (Hardfork::Canyon, BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS), ] .into(), ), diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs index f4a1e24dcac3..be8ca1564b00 100644 --- a/crates/primitives/src/constants/mod.rs +++ b/crates/primitives/src/constants/mod.rs @@ -103,7 +103,26 @@ pub const OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON: u128 = 250; /// Base fee max change denominator for Optimism Sepolia as defined in the Optimism /// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. #[cfg(feature = "optimism")] -pub const OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 10; +pub const OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 6; + +/// Base fee max change denominator for Base Sepolia as defined in the Optimism +/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. +#[cfg(feature = "optimism")] +pub const BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 10; + +/// Get the base fee parameters for Base Sepolia. +#[cfg(feature = "optimism")] +pub const BASE_SEPOLIA_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, + elasticity_multiplier: BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +/// Get the base fee parameters for Base Sepolia (post Canyon). +#[cfg(feature = "optimism")] +pub const BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + elasticity_multiplier: BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; /// Get the base fee parameters for Optimism Sepolia. #[cfg(feature = "optimism")] From 686a40466cd867607d77f19f6cf408c03124167b Mon Sep 17 00:00:00 2001 From: 0xAtreides <103257861+JackG-eth@users.noreply.github.com> Date: Thu, 13 Jun 2024 00:19:41 +0100 Subject: [PATCH 019/405] feat: support `no_std` for `reth-consensus` (#8792) --- Cargo.lock | 2 +- crates/consensus/consensus/Cargo.toml | 4 +++- crates/consensus/consensus/src/lib.rs | 13 +++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b4d9d628c95..f8646ae64dad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6591,7 +6591,7 @@ version = "1.0.0-rc.1" dependencies = [ "auto_impl", "reth-primitives", - "thiserror", + "thiserror-no-std", ] [[package]] diff --git a/crates/consensus/consensus/Cargo.toml b/crates/consensus/consensus/Cargo.toml index 8ea4236bfa12..1d4d6d758c42 100644 --- a/crates/consensus/consensus/Cargo.toml +++ b/crates/consensus/consensus/Cargo.toml @@ -16,7 +16,9 @@ reth-primitives.workspace = true # misc auto_impl.workspace = true -thiserror.workspace = true +thiserror-no-std = {workspace = true, default-features = false } [features] +default = ["std"] +std = ["thiserror-no-std/std"] test-utils = [] diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index fd3c694c2fa9..5768fee46f27 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -7,14 +7,23 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] use reth_primitives::{ BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected, GotExpectedBoxed, Header, HeaderValidationError, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader, B256, U256, }; + +#[cfg(feature = "std")] use std::fmt::Debug; +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{fmt::Debug, vec::Vec}; + /// A consensus implementation that does nothing. pub mod noop; @@ -119,7 +128,7 @@ pub trait Consensus: Debug + Send + Sync { } /// Consensus Errors -#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] +#[derive(thiserror_no_std::Error, Debug, PartialEq, Eq, Clone)] pub enum ConsensusError { /// Error when the gas used in the header exceeds the gas limit. #[error("block used gas ({gas_used}) is greater than gas limit ({gas_limit})")] @@ -333,6 +342,6 @@ impl ConsensusError { } /// `HeaderConsensusError` combines a `ConsensusError` with the `SealedHeader` it relates to. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror_no_std::Error, Debug)] #[error("Consensus error: {0}, Invalid header: {1:?}")] pub struct HeaderConsensusError(ConsensusError, SealedHeader); From 46c8255fa887dcc9d9b65aa9c1c47eeeabc4764d Mon Sep 17 00:00:00 2001 From: 0xAtreides <103257861+JackG-eth@users.noreply.github.com> Date: Thu, 13 Jun 2024 00:29:36 +0100 Subject: [PATCH 020/405] feat: support `no_std` for `reth-storage-errors` (#8790) Co-authored-by: Matthias Seitz --- Cargo.lock | 2 +- crates/storage/errors/Cargo.toml | 6 ++++-- crates/storage/errors/src/db.rs | 23 +++++++++++++++++------ crates/storage/errors/src/lib.rs | 4 ++++ crates/storage/errors/src/lockfile.rs | 6 ++++-- crates/storage/errors/src/provider.rs | 16 ++++++++++++---- 6 files changed, 42 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8646ae64dad..07421f72b227 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8218,7 +8218,7 @@ version = "1.0.0-rc.1" dependencies = [ "reth-fs-util", "reth-primitives", - "thiserror", + "thiserror-no-std", ] [[package]] diff --git a/crates/storage/errors/Cargo.toml b/crates/storage/errors/Cargo.toml index 5fa806345269..d8e699f8df40 100644 --- a/crates/storage/errors/Cargo.toml +++ b/crates/storage/errors/Cargo.toml @@ -14,6 +14,8 @@ workspace = true reth-primitives.workspace = true reth-fs-util.workspace = true -thiserror.workspace = true +thiserror-no-std = { workspace = true, default-features = false } - +[features] +default = ["std"] +std = ["thiserror-no-std/std"] \ No newline at end of file diff --git a/crates/storage/errors/src/db.rs b/crates/storage/errors/src/db.rs index b731e4d240dc..8b4896d23236 100644 --- a/crates/storage/errors/src/db.rs +++ b/crates/storage/errors/src/db.rs @@ -1,8 +1,19 @@ -use std::{fmt::Display, str::FromStr}; -use thiserror::Error; +#[cfg(feature = "std")] +use std::{fmt::Display, str::FromStr, string::String}; + +#[cfg(not(feature = "std"))] +use alloc::{ + boxed::Box, + format, + string::{String, ToString}, + vec::Vec, +}; + +#[cfg(not(feature = "std"))] +use core::{fmt::Display, str::FromStr}; /// Database error type. -#[derive(Clone, Debug, PartialEq, Eq, Error)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror_no_std::Error)] pub enum DatabaseError { /// Failed to open the database. #[error("failed to open the database: {0}")] @@ -43,7 +54,7 @@ pub enum DatabaseError { } /// Common error struct to propagate implementation-specific error information. -#[derive(Debug, Error, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, thiserror_no_std::Error)] #[error("{message} ({code})")] pub struct DatabaseErrorInfo { /// Human-readable error message. @@ -70,7 +81,7 @@ impl From for DatabaseError { } /// Database write error. -#[derive(Clone, Debug, PartialEq, Eq, Error)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror_no_std::Error)] #[error( "write operation {operation:?} failed for key \"{key}\" in table {table_name:?}: {info}", key = reth_primitives::hex::encode(key), @@ -179,7 +190,7 @@ impl FromStr for LogLevel { "debug" => Ok(Self::Debug), "trace" => Ok(Self::Trace), "extra" => Ok(Self::Extra), - _ => Err(format!("Invalid log level: {}", s)), + _ => Err(format!("Invalid log level: {s}")), } } } diff --git a/crates/storage/errors/src/lib.rs b/crates/storage/errors/src/lib.rs index 8247c635270e..dc8d24a160d2 100644 --- a/crates/storage/errors/src/lib.rs +++ b/crates/storage/errors/src/lib.rs @@ -7,6 +7,10 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; /// Database error pub mod db; diff --git a/crates/storage/errors/src/lockfile.rs b/crates/storage/errors/src/lockfile.rs index 674485457ab8..db27cb6e2e45 100644 --- a/crates/storage/errors/src/lockfile.rs +++ b/crates/storage/errors/src/lockfile.rs @@ -1,8 +1,10 @@ use reth_fs_util::FsPathError; -use thiserror::Error; -#[derive(Error, Debug, Clone, PartialEq, Eq)] +#[cfg(not(feature = "std"))] +use alloc::string::{String, ToString}; + /// Storage lock error. +#[derive(Debug, Clone, PartialEq, Eq, thiserror_no_std::Error)] pub enum StorageLockError { /// Write lock taken #[error("storage directory is currently in use as read-write by another process: PID {0}")] diff --git a/crates/storage/errors/src/provider.rs b/crates/storage/errors/src/provider.rs index 7472d500cab2..52a010474f96 100644 --- a/crates/storage/errors/src/provider.rs +++ b/crates/storage/errors/src/provider.rs @@ -2,14 +2,21 @@ use reth_primitives::{ Address, BlockHash, BlockHashOrNumber, BlockNumber, GotExpected, StaticFileSegment, TxHashOrNumber, TxNumber, B256, U256, }; + +#[cfg(feature = "std")] use std::path::PathBuf; -use thiserror::Error; + +#[cfg(not(feature = "std"))] +use alloc::{ + boxed::Box, + string::{String, ToString}, +}; /// Provider result type. pub type ProviderResult = Result; /// Bundled errors variants thrown by various providers. -#[derive(Clone, Debug, Error, PartialEq, Eq)] +#[derive(Clone, Debug, thiserror_no_std::Error, PartialEq, Eq)] pub enum ProviderError { /// Database error. #[error(transparent)] @@ -108,6 +115,7 @@ pub enum ProviderError { #[error("this provider does not support this request")] UnsupportedProvider, /// Static File is not found at specified path. + #[cfg(feature = "std")] #[error("not able to find {0} static file at {1}")] MissingStaticFilePath(StaticFileSegment, PathBuf), /// Static File is not found for requested block. @@ -143,7 +151,7 @@ impl From for ProviderError { } /// A root mismatch error at a given block height. -#[derive(Clone, Debug, Error, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror_no_std::Error)] #[error("root mismatch at #{block_number} ({block_hash}): {root}")] pub struct RootMismatch { /// The target block root diff. @@ -155,7 +163,7 @@ pub struct RootMismatch { } /// Consistent database view error. -#[derive(Clone, Debug, Error, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror_no_std::Error)] pub enum ConsistentViewError { /// Error thrown on attempt to initialize provider while node is still syncing. #[error("node is syncing. best block: {best_block:?}")] From ce5c6e948a7ab90a34d0dc2a84b1269b65b2206d Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 13 Jun 2024 07:05:10 -0400 Subject: [PATCH 021/405] feat: log debug log directory on startup (#8785) --- bin/reth/src/cli/mod.rs | 2 ++ crates/node-core/src/args/log.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index 033abdd377d5..60792cb76b14 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -19,6 +19,7 @@ use reth_node_builder::{NodeBuilder, WithLaunchContext}; use reth_primitives::ChainSpec; use reth_tracing::FileWorkerGuard; use std::{ffi::OsString, fmt, future::Future, sync::Arc}; +use tracing::info; /// Re-export of the `reth_node_core` types specifically in the `cli` module. /// @@ -139,6 +140,7 @@ impl Cli { self.logs.log_file_directory.join(self.chain.chain.to_string()); let _guard = self.init_tracing()?; + info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory); let runner = CliRunner::default(); match self.command { diff --git a/crates/node-core/src/args/log.rs b/crates/node-core/src/args/log.rs index 9f77e555582b..aa2e0cf5f1a3 100644 --- a/crates/node-core/src/args/log.rs +++ b/crates/node-core/src/args/log.rs @@ -92,6 +92,8 @@ impl LogArgs { } /// Initializes tracing with the configured options from cli args. + /// + /// Returns the file worker guard, and the file name, if a file worker was configured. pub fn init_tracing(&self) -> eyre::Result> { let mut tracer = RethTracer::new(); From 9cfd588a72d7aa70fa3565e3f272fee389b37ae1 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Thu, 13 Jun 2024 14:10:43 +0200 Subject: [PATCH 022/405] fix: migration of ethereum-package to ethpandaops (#8797) --- .github/workflows/assertoor.yml | 2 +- book/run/private-testnet.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/assertoor.yml b/.github/workflows/assertoor.yml index becbf4a3a59b..02931c656348 100644 --- a/.github/workflows/assertoor.yml +++ b/.github/workflows/assertoor.yml @@ -33,7 +33,7 @@ jobs: cat etc/assertoor/assertoor-template.yaml | envsubst > etc/assertoor/assertoor.yaml - kurtosis run github.com/kurtosis-tech/ethereum-package --enclave assertoor-${{ github.run_id }} --args-file etc/assertoor/assertoor.yaml + kurtosis run github.com/ethpandaops/ethereum-package --enclave assertoor-${{ github.run_id }} --args-file etc/assertoor/assertoor.yaml enclave_dump=$(kurtosis enclave inspect assertoor-${{ github.run_id }}) diff --git a/book/run/private-testnet.md b/book/run/private-testnet.md index c85ff7d54786..3b24e94449dc 100644 --- a/book/run/private-testnet.md +++ b/book/run/private-testnet.md @@ -2,15 +2,15 @@ For those who need a private testnet to validate functionality or scale with Reth. ## Using Docker locally -This guide uses [Kurtosis' ethereum-package](https://github.com/kurtosis-tech/ethereum-package) and assumes you have Kurtosis and Docker installed and have Docker already running on your machine. +This guide uses [Kurtosis' ethereum-package](https://github.com/ethpandaops/ethereum-package) and assumes you have Kurtosis and Docker installed and have Docker already running on your machine. * Go [here](https://docs.kurtosis.com/install/) to install Kurtosis * Go [here](https://docs.docker.com/get-docker/) to install Docker -The [`ethereum-package`](https://github.com/kurtosis-tech/ethereum-package) is a [package](https://docs.kurtosis.com/advanced-concepts/packages) for a general purpose Ethereum testnet definition used for instantiating private testnets at any scale over Docker or Kubernetes, locally or in the cloud. This guide will go through how to spin up a local private testnet with Reth various CL clients locally. Specifically, you will instantiate a 2-node network over Docker with Reth/Lighthouse and Reth/Teku client combinations. +The [`ethereum-package`](https://github.com/ethpandaops/ethereum-package) is a [package](https://docs.kurtosis.com/advanced-concepts/packages) for a general purpose Ethereum testnet definition used for instantiating private testnets at any scale over Docker or Kubernetes, locally or in the cloud. This guide will go through how to spin up a local private testnet with Reth various CL clients locally. Specifically, you will instantiate a 2-node network over Docker with Reth/Lighthouse and Reth/Teku client combinations. -To see all possible configurations and flags you can use, including metrics and observability tools (e.g. Grafana, Prometheus, etc), go [here](https://github.com/kurtosis-tech/ethereum-package#configuration). +To see all possible configurations and flags you can use, including metrics and observability tools (e.g. Grafana, Prometheus, etc), go [here](https://github.com/ethpandaops/ethereum-package#configuration). -Genesis data will be generated using this [genesis-generator](https://github.com/ethpandaops/ethereum-genesis-generator) to be used to bootstrap the EL and CL clients for each node. The end result will be a private testnet with nodes deployed as Docker containers in an ephemeral, isolated environment on your machine called an [enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/). Read more about how the `ethereum-package` works by going [here](https://github.com/kurtosis-tech/ethereum-package/). +Genesis data will be generated using this [genesis-generator](https://github.com/ethpandaops/ethereum-genesis-generator) to be used to bootstrap the EL and CL clients for each node. The end result will be a private testnet with nodes deployed as Docker containers in an ephemeral, isolated environment on your machine called an [enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/). Read more about how the `ethereum-package` works by going [here](https://github.com/ethpandaops/ethereum-package/). ### Step 1: Define the parameters and shape of your private network First, in your home directory, create a file with the name `network_params.json` with the following contents: @@ -43,7 +43,7 @@ First, in your home directory, create a file with the name `network_params.json` Next, run the following command from your command line: ```bash -kurtosis run github.com/kurtosis-tech/ethereum-package --args-file ~/network_params.json +kurtosis run github.com/ethpandaops/ethereum-package --args-file ~/network_params.json ``` Kurtosis will spin up an [enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/) (i.e an ephemeral, isolated environment) and begin to configure and instantiate the nodes in your network. In the end, Kurtosis will print the services running in your enclave that form your private testnet alongside all the container ports and files that were generated & used to start up the private testnet. Here is a sample output: ```console @@ -94,13 +94,13 @@ b454497fbec8 el-1-reth-lighthouse engine-rpc: 8551/tcp - 46829c4bd8b0 prelaunch-data-generator-el-genesis-data RUNNING ``` -Great! You now have a private network with 2 full Ethereum nodes on your local machine over Docker - one that is a Reth/Lighthouse pair and another that is Reth/Teku. Check out the [Kurtosis docs](https://docs.kurtosis.com/cli) to learn about the various ways you can interact with and inspect your network. +Great! You now have a private network with 2 full Ethereum nodes on your local machine over Docker - one that is a Reth/Lighthouse pair and another that is Reth/Teku. Check out the [Kurtosis docs](https://docs.kurtosis.com/cli) to learn about the various ways you can interact with and inspect your network. ## Using Kurtosis on Kubernetes Kurtosis packages are portable and reproducible, meaning they will work the same way over Docker or Kubernetes, locally or on remote infrastructure. For use cases that require a larger scale, Kurtosis can be deployed on Kubernetes by following these docs [here](https://docs.kurtosis.com/k8s/). ## Running the network with additional services -The [`ethereum-package`](https://github.com/kurtosis-tech/ethereum-package) comes with many optional flags and arguments you can enable for your private network. Some include: +The [`ethereum-package`](https://github.com/ethpandaops/ethereum-package) comes with many optional flags and arguments you can enable for your private network. Some include: - A Grafana + Prometheus instance - A transaction spammer called [`tx-fuzz`](https://github.com/MariusVanDerWijden/tx-fuzz) - [A network metrics collector](https://github.com/dapplion/beacon-metrics-gazer) From f9493fd1f9ef712a3a221e047df822eb3e4303b0 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 13 Jun 2024 14:29:36 +0100 Subject: [PATCH 023/405] docs(book): start documenting ExExes (#8779) Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> Co-authored-by: Oliver --- book/SUMMARY.md | 3 + book/developers/developers.md | 2 +- book/developers/exex/exex.md | 19 ++++ book/developers/exex/hello-world.md | 162 +++++++++++++++++++++++++++ book/developers/exex/how-it-works.md | 26 +++++ 5 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 book/developers/exex/exex.md create mode 100644 book/developers/exex/hello-world.md create mode 100644 book/developers/exex/how-it-works.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 473a389dae6a..1a39a0d87591 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -73,4 +73,7 @@ - [`reth recover`](./cli/reth/recover.md) - [`reth recover storage-tries`](./cli/reth/recover/storage-tries.md) - [Developers](./developers/developers.md) + - [Execution Extensions](./developers/exex/exex.md) + - [How do ExExes work?](./developers/exex/how-it-works.md) + - [Hello World](./developers/exex/hello-world.md) - [Contribute](./developers/contribute.md) diff --git a/book/developers/developers.md b/book/developers/developers.md index e5bf7cde90de..9d8c5a9c6739 100644 --- a/book/developers/developers.md +++ b/book/developers/developers.md @@ -1,3 +1,3 @@ # Developers -Reth is composed of several crates that can be used in standalone projects. If you are interested in using one or more of the crates, you can get an overview of them in the [developer docs](https://github.com/paradigmxyz/reth/tree/main/docs), or take a look at the [crate docs](https://paradigmxyz.github.io/reth/docs). \ No newline at end of file +Reth is composed of several crates that can be used in standalone projects. If you are interested in using one or more of the crates, you can get an overview of them in the [developer docs](https://github.com/paradigmxyz/reth/tree/main/docs), or take a look at the [crate docs](https://paradigmxyz.github.io/reth/docs). diff --git a/book/developers/exex/exex.md b/book/developers/exex/exex.md new file mode 100644 index 000000000000..0c3199bc8745 --- /dev/null +++ b/book/developers/exex/exex.md @@ -0,0 +1,19 @@ +# Execution Extensions (ExEx) + +## What are Execution Extensions? + +Execution Extensions allow developers to build their own infrastructure that relies on Reth +as a base for driving the chain (be it [Ethereum](../../run/mainnet.md) or [OP Stack](../../run/optimism.md)) forward. + +An Execution Extension is a task that derives its state from changes in Reth's state. +Some examples of such state derivations are rollups, bridges, and indexers. + +Read more about things you can build with Execution Extensions in the [Paradigm blog](https://www.paradigm.xyz/2024/05/reth-exex). + +## How do I build an Execution Extension? + +Let's dive into how to build our own ExEx (short for Execution Extension) from scratch, add tests for it, +and run it on the Holesky testnet. + +1. [How do ExExes work?](./how-it-works.md) +1. [Hello World](./hello-world.md) diff --git a/book/developers/exex/hello-world.md b/book/developers/exex/hello-world.md new file mode 100644 index 000000000000..c3da13ac4cc8 --- /dev/null +++ b/book/developers/exex/hello-world.md @@ -0,0 +1,162 @@ +# Hello World + +Let's write a simple "Hello World" ExEx that emits a log every time a new chain of blocks is committed, reverted, or reorged. + +### Create a project + +First, let's create a new project for our ExEx + +```console +cargo new --bin my-exex +cd my-exex +``` + +And add Reth as a dependency in `Cargo.toml` + +```toml +[package] +name = "my-exex" +version = "0.1.0" +edition = "2021" + +[dependencies] +reth = { git = "https://github.com/paradigmxyz/reth.git" } # Reth +reth-exex = { git = "https://github.com/paradigmxyz/reth.git" } # Execution Extensions +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth.git" } # Ethereum Node implementation +reth-tracing = { git = "https://github.com/paradigmxyz/reth.git" } # Logging +eyre = "0.6" # Easy error handling +``` + +### Default Reth node + +Now, let's jump to our `main.rs` and start by initializing and launching a default Reth node + +```rust,norun,noplayground,ignore +use reth_node_ethereum::EthereumNode; + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let handle = builder.node(EthereumNode::default()).launch().await?; + + handle.wait_for_node_exit().await + }) +} +``` + +You can already test that it works by running the binary and initializing the Holesky node in a custom datadir +(to not interfere with any instances of Reth you already have on your machine): + +```console +$ cargo run -- init --chain holesky --datadir data + +2024-06-12T16:48:06.420296Z INFO reth init starting +2024-06-12T16:48:06.422380Z INFO Opening storage db_path="data/db" sf_path="data/static_files" +2024-06-12T16:48:06.432939Z INFO Verifying storage consistency. +2024-06-12T16:48:06.577673Z INFO Genesis block written hash=0xb5f7f912443c940f21fd611f12828d75b53 +4364ed9e95ca4e307729a4661bde4 +``` + +### Simplest ExEx + +The simplest ExEx is just an async function that never returns. We need to install it into our node + +```rust,norun,noplayground,ignore +use reth::api::FullNodeComponents; +use reth_exex::{ExExContext, ExExEvent, ExExNotification}; +use reth_node_ethereum::EthereumNode; +use reth_tracing::tracing::info; + +async fn my_exex(mut ctx: ExExContext) -> eyre::Result<()> { + loop {} +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let handle = builder + .node(EthereumNode::default()) + .install_exex("my-exex", |ctx| async move { Ok(my_exex(ctx)) }) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) +} +``` + +See that unused `_ctx`? That's the context that we'll use to listen to new notifications coming from the main node, +and send events back to it. It also contains all components that the node exposes to the ExEx. + +Currently, our ExEx does absolutely nothing by running an infinite loop in an async function that never returns. + +
+ +It's important that the future returned by the ExEx (`my_exex`) never resolves. + +If you try running a node with an ExEx that exits, the node will exit as well. + +
+ +### Hello World ExEx + +Now, let's extend our simplest ExEx and start actually listening to new notifications, log them, and send events back to the main node + +```rust,norun,noplayground,ignore +use reth::api::FullNodeComponents; +use reth_exex::{ExExContext, ExExEvent, ExExNotification}; +use reth_node_ethereum::EthereumNode; +use reth_tracing::tracing::info; + +async fn my_exex(mut ctx: ExExContext) -> eyre::Result<()> { + while let Some(notification) = ctx.notifications.recv().await { + match ¬ification { + ExExNotification::ChainCommitted { new } => { + info!(committed_chain = ?new.range(), "Received commit"); + } + ExExNotification::ChainReorged { old, new } => { + info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg"); + } + ExExNotification::ChainReverted { old } => { + info!(reverted_chain = ?old.range(), "Received revert"); + } + }; + + if let Some(committed_chain) = notification.committed_chain() { + ctx.events + .send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + } + + Ok(()) +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let handle = builder + .node(EthereumNode::default()) + .install_exex("my-exex", |ctx| async move { Ok(my_exex(ctx)) }) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) +} +``` + +Woah, there's a lot of new stuff here! Let's go through it step by step: + +- First, we've added a `while let Some(notification) = ctx.notifications.recv().await` loop that waits for new notifications to come in. + - The main node is responsible for sending notifications to the ExEx, so we're waiting for them to come in. +- Next, we've added a `match ¬ification { ... }` block that matches on the type of the notification. + - In each case, we're logging the notification and the corresponding block range, be it a chain commit, revert, or reorg. +- Finally, we're checking if the notification contains a committed chain, and if it does, we're sending a `ExExEvent::FinishedHeight` event back to the main node using the `ctx.events.send` method. + +
+ +Sending an `ExExEvent::FinishedHeight` event is a very important part of every ExEx. + +It's the only way to communicate to the main node that the ExEx has finished processing the specified height +and it's safe to prune the associated data. + +
+ +What we've arrived at is the [minimal ExEx example](https://github.com/paradigmxyz/reth/blob/b8cd7be6c92a71aea5341cdeba685f124c6de540/examples/exex/minimal/src/main.rs) that we provide in the Reth repository. diff --git a/book/developers/exex/how-it-works.md b/book/developers/exex/how-it-works.md new file mode 100644 index 000000000000..7fd179bf9155 --- /dev/null +++ b/book/developers/exex/how-it-works.md @@ -0,0 +1,26 @@ +# How do ExExes work? + +ExExes are just [Futures](https://doc.rust-lang.org/std/future/trait.Future.html) that run indefinitely alongside Reth +– as simple as that. + +An ExEx is usually driven by and acts on new notifications about chain commits, reverts, and reorgs, but it can span beyond that. + +They are installed into the node by using the [node builder](https://reth.rs/docs/reth/builder/struct.NodeBuilder.html). +Reth manages the lifecycle of all ExExes, including: +- Polling ExEx futures +- Sending [notifications](https://reth.rs/docs/reth_exex/enum.ExExNotification.html) about new chain, reverts, + and reorgs from historical and live sync +- Processing [events](https://reth.rs/docs/reth_exex/enum.ExExEvent.html) emitted by ExExes +- Pruning (in case of a full or pruned node) only the data that have been processed by all ExExes +- Shutting ExExes down when the node is shut down + +## Pruning + +Pruning deserves a special mention here. + +ExExes **SHOULD** emit an [`ExExEvent::FinishedHeight`](https://reth.rs/docs/reth_exex/enum.ExExEvent.html#variant.FinishedHeight) +event to signify what blocks have been processed. This event is used by Reth to determine what state can be pruned. + +An ExEx will only receive notifications for block numbers greater than the block in the most recently emitted `FinishedHeight` event. + +To clarify: if an ExEx emits `ExExEvent::FinishedHeight(0)` it will receive notifications for any `block_number > 0`. From 41933b76dea22b7e66abf72e790ed66b29399eb8 Mon Sep 17 00:00:00 2001 From: Tuan Tran Date: Thu, 13 Jun 2024 20:43:39 +0700 Subject: [PATCH 024/405] refactor: disable default features for alloy workspace deps (#8768) Co-authored-by: Matthias Seitz --- Cargo.toml | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 812548cbb692..8e94a845dc39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -353,28 +353,32 @@ alloy-dyn-abi = "0.7.2" alloy-sol-types = "0.7.2" alloy-rlp = "0.3.4" alloy-trie = "0.4" -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false, features = [ + "eth", +] } +alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false, features = [ "reqwest", ] } alloy-eips = { git = "https://github.com/alloy-rs/alloy", default-features = false, rev = "14ed25d" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", features = ["reqwest-rustls-tls"], default-features = false } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", features = [ + "reqwest-rustls-tls", +], default-features = false } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } # misc auto_impl = "1" From e5111f03396f661bdff69d04047d0c076bad75f3 Mon Sep 17 00:00:00 2001 From: Krishang <93703995+kamuik16@users.noreply.github.com> Date: Thu, 13 Jun 2024 19:38:57 +0530 Subject: [PATCH 025/405] feat: add append_receipts function (#8718) Co-authored-by: Alexey Shekhirin --- .../stages/stages/src/test_utils/test_db.rs | 4 +- .../static-file/src/segments/receipts.rs | 8 ++-- .../bundle_state_with_receipts.rs | 14 ++++--- .../src/providers/static_file/metrics.rs | 22 +++++++++++ .../src/providers/static_file/writer.rs | 38 +++++++++++++++++++ 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/crates/stages/stages/src/test_utils/test_db.rs b/crates/stages/stages/src/test_utils/test_db.rs index 37fdacd28f09..f2d653c0bb2f 100644 --- a/crates/stages/stages/src/test_utils/test_db.rs +++ b/crates/stages/stages/src/test_utils/test_db.rs @@ -349,9 +349,7 @@ impl TestStageDB { let mut writer = provider.latest_writer(StaticFileSegment::Receipts)?; let res = receipts.into_iter().try_for_each(|(block_num, receipts)| { writer.increment_block(StaticFileSegment::Receipts, block_num)?; - for (tx_num, receipt) in receipts { - writer.append_receipt(tx_num, receipt)?; - } + writer.append_receipts(receipts.into_iter().map(Ok))?; Ok(()) }); writer.commit_without_sync_all()?; diff --git a/crates/static-file/static-file/src/segments/receipts.rs b/crates/static-file/static-file/src/segments/receipts.rs index 06102a7d8a3f..e0ed58086722 100644 --- a/crates/static-file/static-file/src/segments/receipts.rs +++ b/crates/static-file/static-file/src/segments/receipts.rs @@ -42,11 +42,9 @@ impl Segment for Receipts { let mut receipts_cursor = provider.tx_ref().cursor_read::()?; let receipts_walker = receipts_cursor.walk_range(block_body_indices.tx_num_range())?; - for entry in receipts_walker { - let (tx_number, receipt) = entry?; - - static_file_writer.append_receipt(tx_number, receipt)?; - } + static_file_writer.append_receipts( + receipts_walker.map(|result| result.map_err(ProviderError::from)), + )?; } Ok(()) 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 81198481e26b..de5aff11ba24 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 @@ -42,12 +42,14 @@ impl StateWriter for ExecutionOutcome { if let Some(static_file_producer) = &mut static_file_producer { // Increment block on static file header. static_file_producer.increment_block(StaticFileSegment::Receipts, block_number)?; - - for (tx_idx, receipt) in receipts.into_iter().enumerate() { - let receipt = receipt - .expect("receipt should not be filtered when saving to static files."); - static_file_producer.append_receipt(first_tx_index + tx_idx as u64, receipt)?; - } + let receipts = receipts.into_iter().enumerate().map(|(tx_idx, receipt)| { + Ok(( + first_tx_index + tx_idx as u64, + receipt + .expect("receipt should not be filtered when saving to static files."), + )) + }); + static_file_producer.append_receipts(receipts)?; } else if !receipts.is_empty() { for (tx_idx, receipt) in receipts.into_iter().enumerate() { if let Some(receipt) = receipt { diff --git a/crates/storage/provider/src/providers/static_file/metrics.rs b/crates/storage/provider/src/providers/static_file/metrics.rs index f1a4204a7b58..72589ca69856 100644 --- a/crates/storage/provider/src/providers/static_file/metrics.rs +++ b/crates/storage/provider/src/providers/static_file/metrics.rs @@ -80,6 +80,28 @@ impl StaticFileProviderMetrics { .record(duration.as_secs_f64()); } } + + pub(crate) fn record_segment_operations( + &self, + segment: StaticFileSegment, + operation: StaticFileProviderOperation, + count: u64, + duration: Option, + ) { + self.segment_operations + .get(&(segment, operation)) + .expect("segment operation metrics should exist") + .calls_total + .increment(count); + + if let Some(duration) = duration { + self.segment_operations + .get(&(segment, operation)) + .expect("segment operation metrics should exist") + .write_duration_seconds + .record(duration.as_secs_f64() / count as f64); + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] diff --git a/crates/storage/provider/src/providers/static_file/writer.rs b/crates/storage/provider/src/providers/static_file/writer.rs index 147d812ce36c..304429a0150b 100644 --- a/crates/storage/provider/src/providers/static_file/writer.rs +++ b/crates/storage/provider/src/providers/static_file/writer.rs @@ -547,6 +547,44 @@ impl StaticFileProviderRW { Ok(result) } + /// Appends multiple receipts to the static file. + /// + /// Returns the current [`TxNumber`] as seen in the static file, if any. + pub fn append_receipts(&mut self, receipts: I) -> ProviderResult> + where + I: IntoIterator>, + { + let mut receipts_iter = receipts.into_iter().peekable(); + // If receipts are empty, we can simply return None + if receipts_iter.peek().is_none() { + return Ok(None); + } + + let start = Instant::now(); + self.ensure_no_queued_prune()?; + + // At this point receipts contains at least one receipt, so this would be overwritten. + let mut tx_number = 0; + let mut count: u64 = 0; + + for receipt_result in receipts_iter { + let (tx_num, receipt) = receipt_result?; + tx_number = self.append_with_tx_number(StaticFileSegment::Receipts, tx_num, receipt)?; + count += 1; + } + + if let Some(metrics) = &self.metrics { + metrics.record_segment_operations( + StaticFileSegment::Receipts, + StaticFileProviderOperation::Append, + count, + Some(start.elapsed()), + ); + } + + Ok(Some(tx_number)) + } + /// Adds an instruction to prune `to_delete`transactions during commit. /// /// Note: `last_block` refers to the block the unwinds ends at. From 763317d35697127abe6cd30775c5a32d631142c1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 13 Jun 2024 16:53:47 +0200 Subject: [PATCH 026/405] chore: bump inspectors and alloy (#8803) --- Cargo.lock | 149 ++++++++++++++++---------------- Cargo.toml | 42 ++++----- crates/rpc/rpc-types/src/lib.rs | 2 +- 3 files changed, 97 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07421f72b227..c964ab2855d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,12 +124,12 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "arbitrary", "c-kzg", "proptest", @@ -171,11 +171,11 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "arbitrary", "c-kzg", "derive_more", @@ -203,10 +203,10 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "serde", "serde_json", ] @@ -237,7 +237,7 @@ dependencies = [ [[package]] name = "alloy-json-rpc" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", "serde", @@ -249,13 +249,13 @@ dependencies = [ [[package]] name = "alloy-network" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-json-rpc", "alloy-primitives", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-signer", "alloy-sol-types", "async-trait", @@ -267,9 +267,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "k256", "serde_json", @@ -309,18 +309,18 @@ dependencies = [ [[package]] name = "alloy-provider" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-chains", - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-json-rpc", "alloy-network", "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types-engine", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-rpc-types-trace", "alloy-transport", "alloy-transport-http", @@ -344,7 +344,7 @@ dependencies = [ [[package]] name = "alloy-pubsub" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -384,7 +384,7 @@ dependencies = [ [[package]] name = "alloy-rpc-client" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -407,11 +407,12 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-rpc-types-engine", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types-trace", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", ] [[package]] @@ -426,19 +427,19 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "serde", ] [[package]] name = "alloy-rpc-types-beacon" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "alloy-rpc-types-engine", "serde", @@ -449,14 +450,14 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "jsonrpsee-types", "jsonwebtoken 9.3.0", "rand 0.8.5", @@ -467,14 +468,14 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-sol-types", "arbitrary", "itertools 0.13.0", @@ -507,11 +508,11 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "serde", "serde_json", ] @@ -519,7 +520,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", "serde", @@ -539,7 +540,7 @@ dependencies = [ [[package]] name = "alloy-signer" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", "async-trait", @@ -552,9 +553,9 @@ dependencies = [ [[package]] name = "alloy-signer-wallet" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-network", "alloy-primitives", "alloy-signer", @@ -641,7 +642,7 @@ dependencies = [ [[package]] name = "alloy-transport" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -658,7 +659,7 @@ dependencies = [ [[package]] name = "alloy-transport-http" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -672,7 +673,7 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -690,12 +691,13 @@ dependencies = [ [[package]] name = "alloy-transport-ws" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=14ed25d#14ed25d8ab485fc0d313fd1e055862c9d20ef273" +source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", "http 1.1.0", + "rustls 0.23.9", "serde_json", "tokio", "tokio-tungstenite", @@ -2970,7 +2972,7 @@ dependencies = [ name = "exex-rollup" version = "0.0.0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-rlp", "alloy-sol-types", "eyre", @@ -6454,8 +6456,8 @@ dependencies = [ name = "reth-bench" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-json-rpc", "alloy-provider", "alloy-pubsub", @@ -6546,9 +6548,9 @@ dependencies = [ name = "reth-codecs" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "arbitrary", "bytes", @@ -6609,8 +6611,8 @@ dependencies = [ name = "reth-consensus-debug-client" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-provider", "auto_impl", "eyre", @@ -6825,9 +6827,9 @@ dependencies = [ name = "reth-e2e-test-utils" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-network", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-signer", "alloy-signer-wallet", "eyre", @@ -7047,7 +7049,7 @@ dependencies = [ name = "reth-evm-ethereum" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-sol-types", "reth-ethereum-consensus", "reth-evm", @@ -7083,7 +7085,7 @@ dependencies = [ name = "reth-execution-errors" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "reth-consensus", "reth-prune-types", @@ -7096,7 +7098,7 @@ dependencies = [ name = "reth-execution-types" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "reth-execution-errors", "reth-primitives", @@ -7695,12 +7697,12 @@ name = "reth-primitives" version = "1.0.0-rc.1" dependencies = [ "alloy-chains", - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-trie", "arbitrary", "assert_matches", @@ -7745,8 +7747,8 @@ dependencies = [ name = "reth-primitives-traits" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", "arbitrary", "bytes", @@ -7850,7 +7852,7 @@ dependencies = [ name = "reth-revm" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-rlp", "reth-consensus-common", "reth-execution-errors", @@ -8051,7 +8053,7 @@ name = "reth-rpc-types" version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-rpc-types-anvil", "alloy-rpc-types-beacon", "alloy-rpc-types-engine", @@ -8072,7 +8074,7 @@ name = "reth-rpc-types-compat" version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "reth-primitives", "reth-rpc-types", "serde_json", @@ -8241,7 +8243,7 @@ dependencies = [ name = "reth-testing-utils" version = "1.0.0-rc.1" dependencies = [ - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", + "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "rand 0.8.5", "reth-primitives", "secp256k1 0.28.2", @@ -8400,11 +8402,10 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=5e3058a#5e3058a87caa24df748e090083ef76518d082c10" +source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=70e721d#70e721d9d4af486bfc7bd3115251cde78414b78d" dependencies = [ "alloy-primitives", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=14ed25d)", - "alloy-rpc-types-trace", + "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-sol-types", "anstyle", "boa_engine", diff --git a/Cargo.toml b/Cargo.toml index 8e94a845dc39..09ac3ac2bdd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,7 +344,7 @@ revm = { version = "9.0.0", features = [ revm-primitives = { version = "4.0.0", features = [ "std", ], default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "5e3058a" } +revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "70e721d" } # eth alloy-chains = "0.1.15" @@ -353,32 +353,32 @@ alloy-dyn-abi = "0.7.2" alloy-sol-types = "0.7.2" alloy-rlp = "0.3.4" alloy-trie = "0.4" -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false, features = [ +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false, features = [ "eth", ] } -alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false, features = [ +alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false, features = [ "reqwest", ] } -alloy-eips = { git = "https://github.com/alloy-rs/alloy", default-features = false, rev = "14ed25d" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", features = [ +alloy-eips = { git = "https://github.com/alloy-rs/alloy", default-features = false, rev = "00d81d7" } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", features = [ "reqwest-rustls-tls", ], default-features = false } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "14ed25d", default-features = false } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } # misc auto_impl = "1" diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index 442f00769ec8..a8d266648e60 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -9,7 +9,7 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] - +#[allow(hidden_glob_reexports)] mod eth; mod mev; mod net; From 76c8f4842cc5b49b9c14ca44a2a7fab979c48c63 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:36:18 +0200 Subject: [PATCH 027/405] chore: move proof types and root functions from `primitives/proofs` into `reth-trie-common` (#8724) Co-authored-by: Matthias Seitz --- Cargo.lock | 125 ++++++++------- Cargo.toml | 4 +- crates/blockchain-tree/src/blockchain_tree.rs | 4 +- crates/primitives/Cargo.toml | 18 +-- crates/primitives/src/chain/spec.rs | 11 +- .../src/{proofs/mod.rs => proofs.rs} | 151 +----------------- crates/primitives/src/proofs/traits.rs | 59 ------- crates/revm/src/test_utils.rs | 5 +- crates/rpc/rpc-types-compat/Cargo.toml | 1 + crates/rpc/rpc-types-compat/src/proof.rs | 6 +- crates/stages/types/Cargo.toml | 2 +- crates/stages/types/src/checkpoints.rs | 2 +- crates/storage/db-api/Cargo.toml | 2 +- crates/storage/db-api/src/models/mod.rs | 2 +- crates/storage/db/Cargo.toml | 2 +- crates/storage/db/src/tables/mod.rs | 2 +- .../src/providers/bundle_state_provider.rs | 4 +- .../src/providers/state/historical.rs | 6 +- .../provider/src/providers/state/latest.rs | 5 +- .../provider/src/providers/state/macros.rs | 2 +- .../storage/provider/src/test_utils/blocks.rs | 10 +- .../storage/provider/src/test_utils/mock.rs | 8 +- .../storage/provider/src/test_utils/noop.rs | 11 +- crates/storage/storage-api/src/state.rs | 5 +- crates/trie/{types => common}/Cargo.toml | 29 +++- crates/trie/common/src/account.rs | 73 +++++++++ .../{types => common}/src/hash_builder/mod.rs | 0 .../src/hash_builder/state.rs | 0 .../src/hash_builder/value.rs | 0 crates/trie/{types => common}/src/lib.rs | 7 + crates/trie/{types => common}/src/mask.rs | 0 crates/trie/{types => common}/src/nibbles.rs | 0 .../{types => common}/src/nodes/branch.rs | 0 .../trie/{types => common}/src/nodes/mod.rs | 0 .../types.rs => trie/common/src/proofs.rs} | 33 +++- crates/trie/common/src/root.rs | 117 ++++++++++++++ crates/trie/{types => common}/src/storage.rs | 0 crates/trie/{types => common}/src/subnode.rs | 0 crates/trie/parallel/src/async_root.rs | 6 +- crates/trie/parallel/src/parallel_root.rs | 6 +- crates/trie/trie/Cargo.toml | 10 +- .../trie}/benches/trie_root.rs | 6 +- crates/trie/trie/src/lib.rs | 2 +- crates/trie/trie/src/proof.rs | 11 +- crates/trie/trie/src/test_utils.rs | 10 +- crates/trie/trie/src/trie.rs | 16 +- crates/trie/types/src/account.rs | 22 --- 47 files changed, 401 insertions(+), 394 deletions(-) rename crates/primitives/src/{proofs/mod.rs => proofs.rs} (88%) delete mode 100644 crates/primitives/src/proofs/traits.rs rename crates/trie/{types => common}/Cargo.toml (52%) create mode 100644 crates/trie/common/src/account.rs rename crates/trie/{types => common}/src/hash_builder/mod.rs (100%) rename crates/trie/{types => common}/src/hash_builder/state.rs (100%) rename crates/trie/{types => common}/src/hash_builder/value.rs (100%) rename crates/trie/{types => common}/src/lib.rs (87%) rename crates/trie/{types => common}/src/mask.rs (100%) rename crates/trie/{types => common}/src/nibbles.rs (100%) rename crates/trie/{types => common}/src/nodes/branch.rs (100%) rename crates/trie/{types => common}/src/nodes/mod.rs (100%) rename crates/{primitives/src/proofs/types.rs => trie/common/src/proofs.rs} (80%) create mode 100644 crates/trie/common/src/root.rs rename crates/trie/{types => common}/src/storage.rs (100%) rename crates/trie/{types => common}/src/subnode.rs (100%) rename crates/{primitives => trie/trie}/benches/trie_root.rs (92%) delete mode 100644 crates/trie/types/src/account.rs diff --git a/Cargo.lock b/Cargo.lock index c964ab2855d9..8fae53681028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fd095a9d70f4b1c5c102c84a4c782867a5c6416dbf6dcd42a63e7c7a89d3c8" +checksum = "24ceb48af11349cd7fbd12aa739800be3c4b3965f640b7ae26666907f3bdf091" dependencies = [ "alloy-rlp", "arbitrary", @@ -140,7 +140,7 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" +source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-primitives", @@ -189,7 +189,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" +source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -214,7 +214,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" +source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" dependencies = [ "alloy-primitives", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -418,7 +418,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" +source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" dependencies = [ "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -490,7 +490,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" +source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" 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)", @@ -530,7 +530,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#7578618d61213ea832c40c7e613f1d644ce08f27" +source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" dependencies = [ "alloy-primitives", "serde", @@ -680,7 +680,7 @@ dependencies = [ "alloy-transport", "bytes", "futures", - "interprocess 2.1.1", + "interprocess 2.2.0", "pin-project", "serde_json", "tokio", @@ -697,7 +697,7 @@ dependencies = [ "alloy-transport", "futures", "http 1.1.0", - "rustls 0.23.9", + "rustls 0.23.10", "serde_json", "tokio", "tokio-tungstenite", @@ -1122,9 +1122,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.72" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -3647,9 +3647,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3935c160d00ac752e09787e6e6bfc26494c2183cc922f1bc678a60d4733bc2" +checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" [[package]] name = "httpdate" @@ -3728,7 +3728,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -4110,9 +4110,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f2533e1f1a70bec71ea7a85d1c0a4dab141c314035ce76e51a19a2f48be708" +checksum = "67bafc2f5dbdad79a6d925649758d5472647b416028099f0b829d1b67fdd47d3" dependencies = [ "doctest-file", "futures-core", @@ -4285,7 +4285,7 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core", "pin-project", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pki-types", "rustls-platform-verifier", "soketto", @@ -4340,7 +4340,7 @@ dependencies = [ "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-platform-verifier", "serde", "serde_json", @@ -4605,9 +4605,9 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ec70441b2fb35355076726a6bc466c932e9bdc66f6a11c6c0aa17c7ab9be0" +checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" dependencies = [ "asn1_der", "bs58", @@ -4835,9 +4835,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "6d0d8b92cd8358e8d229c11df9358decae64d137c5be540952c5ca7b25aea768" [[package]] name = "memmap2" @@ -5333,9 +5333,9 @@ dependencies = [ [[package]] name = "object" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -6656,7 +6656,7 @@ dependencies = [ "reth-stages-types", "reth-storage-errors", "reth-tracing", - "reth-trie-types", + "reth-trie-common", "rustc-hash", "serde", "serde_json", @@ -6690,7 +6690,7 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "reth-storage-errors", - "reth-trie-types", + "reth-trie-common", "serde", "serde_json", "test-fuzz", @@ -7711,12 +7711,9 @@ dependencies = [ "c-kzg", "criterion", "derive_more", - "hash-db", - "itertools 0.12.1", "modular-bitfield", "nybbles", "once_cell", - "plain_hasher", "pprof", "proptest", "proptest-derive", @@ -7727,7 +7724,7 @@ dependencies = [ "reth-network-peers", "reth-primitives-traits", "reth-static-file-types", - "reth-trie-types", + "reth-trie-common", "revm", "revm-primitives", "roaring", @@ -8077,6 +8074,7 @@ dependencies = [ "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "reth-primitives", "reth-rpc-types", + "reth-trie-common", "serde_json", ] @@ -8162,7 +8160,7 @@ dependencies = [ "proptest-derive", "rand 0.8.5", "reth-codecs", - "reth-trie-types", + "reth-trie-common", "serde", "test-fuzz", ] @@ -8329,7 +8327,7 @@ dependencies = [ "reth-provider", "reth-stages-types", "reth-storage-errors", - "reth-trie-types", + "reth-trie-common", "revm", "serde_json", "similar-asserts", @@ -8339,6 +8337,34 @@ dependencies = [ "triehash", ] +[[package]] +name = "reth-trie-common" +version = "1.0.0-rc.1" +dependencies = [ + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "arbitrary", + "assert_matches", + "bytes", + "derive_more", + "hash-db", + "itertools 0.12.1", + "nybbles", + "plain_hasher", + "proptest", + "proptest-derive", + "reth-codecs", + "reth-primitives-traits", + "revm-primitives", + "serde", + "serde_json", + "test-fuzz", + "toml", +] + [[package]] name = "reth-trie-parallel" version = "1.0.0-rc.1" @@ -8364,27 +8390,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "reth-trie-types" -version = "1.0.0-rc.1" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "alloy-trie", - "arbitrary", - "assert_matches", - "bytes", - "derive_more", - "nybbles", - "proptest", - "proptest-derive", - "reth-codecs", - "serde", - "serde_json", - "test-fuzz", - "toml", -] - [[package]] name = "revm" version = "9.0.0" @@ -8695,9 +8700,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.9" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a218f0f6d05669de4eabfb24f31ce802035c952429d037507b4a4a39f0e60c5b" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "log", "once_cell", @@ -8748,7 +8753,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", @@ -9855,7 +9860,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pki-types", "tokio", ] @@ -9880,7 +9885,7 @@ checksum = "becd34a233e7e31a3dbf7c7241b38320f57393dcae8e7324b0167d21b8e320b0" dependencies = [ "futures-util", "log", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -10221,7 +10226,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pki-types", "sha1", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 09ac3ac2bdd9..e55c73f43247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,9 +99,9 @@ members = [ "crates/tokio-util/", "crates/tracing/", "crates/transaction-pool/", + "crates/trie/common", "crates/trie/parallel/", "crates/trie/trie", - "crates/trie/types", "examples/beacon-api-sidecar-fetcher/", "examples/beacon-api-sse/", "examples/bsc-p2p", @@ -332,8 +332,8 @@ reth-tokio-util = { path = "crates/tokio-util" } reth-tracing = { path = "crates/tracing" } reth-transaction-pool = { path = "crates/transaction-pool" } reth-trie = { path = "crates/trie/trie" } +reth-trie-common = { path = "crates/trie/common" } reth-trie-parallel = { path = "crates/trie/parallel" } -reth-trie-types = { path = "crates/trie/types" } # revm revm = { version = "9.0.0", features = [ diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 5c8ee5623d22..5359212d1757 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1383,7 +1383,7 @@ mod tests { use reth_primitives::{ constants::{EIP1559_INITIAL_BASE_FEE, EMPTY_ROOT_HASH, ETHEREUM_BLOCK_GAS_LIMIT}, keccak256, - proofs::{calculate_transaction_root, state_root_unhashed}, + proofs::calculate_transaction_root, revm_primitives::AccountInfo, Account, Address, ChainSpecBuilder, Genesis, GenesisAccount, Header, Signature, Transaction, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, Withdrawals, B256, @@ -1394,7 +1394,7 @@ mod tests { ProviderFactory, }; use reth_stages_api::StageCheckpoint; - use reth_trie::StateRoot; + use reth_trie::{root::state_root_unhashed, StateRoot}; use std::collections::HashMap; fn setup_externals( diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 0add20276386..f1b68e4e5a98 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -18,7 +18,7 @@ reth-codecs.workspace = true reth-ethereum-forks.workspace = true reth-network-peers.workspace = true reth-static-file-types.workspace = true -reth-trie-types.workspace = true +reth-trie-common.workspace = true revm.workspace = true revm-primitives = { workspace = true, features = ["serde"] } @@ -27,7 +27,6 @@ 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"] } alloy-rpc-types = { workspace = true, optional = true } alloy-genesis.workspace = true alloy-eips = { workspace = true, features = ["serde"] } @@ -45,7 +44,6 @@ c-kzg = { workspace = true, features = ["serde"], optional = true } bytes.workspace = true byteorder = "1" derive_more.workspace = true -itertools.workspace = true modular-bitfield.workspace = true once_cell.workspace = true rayon.workspace = true @@ -56,10 +54,6 @@ thiserror.workspace = true zstd = { version = "0.13", features = ["experimental"], optional = true } roaring = "0.10.2" -# `test-utils` feature -hash-db = { version = "~0.15", optional = true } -plain_hasher = { version = "0.2", optional = true } - # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } @@ -82,9 +76,6 @@ test-fuzz.workspace = true toml.workspace = true triehash = "0.8" -hash-db = "~0.15" -plain_hasher = "0.2" - sucds = "0.8.1" criterion.workspace = true @@ -126,7 +117,7 @@ optimism = [ "revm/optimism", ] alloy-compat = ["alloy-rpc-types"] -test-utils = ["dep:plain_hasher", "dep:hash-db"] +test-utils = [] [[bench]] name = "recover_ecdsa_crit" @@ -137,11 +128,6 @@ name = "validate_blob_tx" required-features = ["arbitrary", "c-kzg"] harness = false -[[bench]] -name = "trie_root" -required-features = ["arbitrary", "test-utils"] -harness = false - [[bench]] name = "integer_list" harness = false diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 5bdc5d9c22a3..74c94b345fca 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -5,13 +5,13 @@ use crate::{ }, holesky_nodes, net::{goerli_nodes, mainnet_nodes, sepolia_nodes}, - proofs::state_root_ref_unhashed, revm_primitives::{address, b256}, Address, BlockNumber, Chain, ChainKind, ForkFilter, ForkFilterKey, ForkHash, ForkId, Genesis, Hardfork, Head, Header, NamedChain, NodeRecord, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, MAINNET_DEPOSIT_CONTRACT, U256, }; use once_cell::sync::Lazy; +use reth_trie_common::root::state_root_ref_unhashed; use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, @@ -1726,8 +1726,10 @@ impl OptimismGenesisInfo { #[cfg(test)] mod tests { + use reth_trie_common::TrieAccount; + use super::*; - use crate::{b256, hex, proofs::IntoTrieAccount, ChainConfig, GenesisAccount}; + use crate::{b256, hex, ChainConfig, GenesisAccount}; use std::{collections::HashMap, str::FromStr}; fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) { for (block, expected_id) in cases { @@ -2829,10 +2831,7 @@ Post-merge hard forks (timestamp based): for (key, expected_rlp) in key_rlp { let account = chainspec.genesis.alloc.get(&key).expect("account should exist"); - assert_eq!( - &alloy_rlp::encode(IntoTrieAccount::to_trie_account(account.clone())), - expected_rlp - ); + assert_eq!(&alloy_rlp::encode(TrieAccount::from(account.clone())), expected_rlp); } assert_eq!(chainspec.genesis_hash, None); diff --git a/crates/primitives/src/proofs/mod.rs b/crates/primitives/src/proofs.rs similarity index 88% rename from crates/primitives/src/proofs/mod.rs rename to crates/primitives/src/proofs.rs index dbce919496e5..b5a050434eaa 100644 --- a/crates/primitives/src/proofs/mod.rs +++ b/crates/primitives/src/proofs.rs @@ -1,58 +1,12 @@ //! Helper function for calculating Merkle proofs and hashes. use crate::{ - constants::EMPTY_OMMER_ROOT_HASH, keccak256, Address, Header, Receipt, ReceiptWithBloom, - ReceiptWithBloomRef, Request, TransactionSigned, Withdrawal, B256, U256, + constants::EMPTY_OMMER_ROOT_HASH, keccak256, Header, Receipt, ReceiptWithBloom, + ReceiptWithBloomRef, Request, TransactionSigned, Withdrawal, B256, }; -use reth_trie_types::{hash_builder::HashBuilder, Nibbles}; - -mod types; -pub use types::{AccountProof, StorageProof}; -mod traits; -pub use traits::IntoTrieAccount; +use reth_trie_common::root::{ordered_trie_root, ordered_trie_root_with_encoder}; use alloy_eips::eip7685::Encodable7685; -use alloy_rlp::Encodable; -use itertools::Itertools; - -/// Adjust the index of an item for rlp encoding. -pub const fn adjust_index_for_rlp(i: usize, len: usize) -> usize { - if i > 0x7f { - i - } else if i == 0x7f || i + 1 == len { - 0 - } else { - i + 1 - } -} - -/// Compute a trie root of the collection of rlp encodable items. -pub fn ordered_trie_root(items: &[T]) -> B256 { - ordered_trie_root_with_encoder(items, |item, buf| item.encode(buf)) -} - -/// Compute a trie root of the collection of items with a custom encoder. -pub fn ordered_trie_root_with_encoder(items: &[T], mut encode: F) -> B256 -where - F: FnMut(&T, &mut Vec), -{ - let mut value_buffer = Vec::new(); - - let mut hb = HashBuilder::default(); - let items_len = items.len(); - for i in 0..items_len { - let index = adjust_index_for_rlp(i, items_len); - - let index_buffer = alloy_rlp::encode_fixed_size(&index); - - value_buffer.clear(); - encode(&items[index], &mut value_buffer); - - hb.add_leaf(Nibbles::unpack(&index_buffer), &value_buffer); - } - - hb.root() -} /// Calculate a transaction root. /// @@ -175,109 +129,16 @@ pub fn calculate_ommers_root(ommers: &[Header]) -> B256 { keccak256(ommers_rlp) } -/// Hashes and sorts account keys, then proceeds to calculating the root hash of the state -/// represented as MPT. -/// See [`state_root_unsorted`] for more info. -pub fn state_root_ref_unhashed<'a, A: IntoTrieAccount + Clone + 'a>( - state: impl IntoIterator, -) -> B256 { - state_root_unsorted( - state.into_iter().map(|(address, account)| (keccak256(address), account.clone())), - ) -} - -/// Hashes and sorts account keys, then proceeds to calculating the root hash of the state -/// represented as MPT. -/// See [`state_root_unsorted`] for more info. -pub fn state_root_unhashed( - state: impl IntoIterator, -) -> B256 { - state_root_unsorted(state.into_iter().map(|(address, account)| (keccak256(address), account))) -} - -/// Sorts the hashed account keys and calculates the root hash of the state represented as MPT. -/// See [`state_root`] for more info. -pub fn state_root_unsorted(state: impl IntoIterator) -> B256 { - state_root(state.into_iter().sorted_by_key(|(key, _)| *key)) -} - -/// Calculates the root hash of the state represented as MPT. -/// Corresponds to [geth's `deriveHash`](https://github.com/ethereum/go-ethereum/blob/6c149fd4ad063f7c24d726a73bc0546badd1bc73/core/genesis.go#L119). -/// -/// # Panics -/// -/// If the items are not in sorted order. -pub fn state_root(state: impl IntoIterator) -> B256 { - let mut hb = HashBuilder::default(); - let mut account_rlp_buf = Vec::new(); - for (hashed_key, account) in state { - account_rlp_buf.clear(); - account.to_trie_account().encode(&mut account_rlp_buf); - hb.add_leaf(Nibbles::unpack(hashed_key), &account_rlp_buf); - } - hb.root() -} - -/// Hashes storage keys, sorts them and them calculates the root hash of the storage trie. -/// See [`storage_root_unsorted`] for more info. -pub fn storage_root_unhashed(storage: impl IntoIterator) -> B256 { - storage_root_unsorted(storage.into_iter().map(|(slot, value)| (keccak256(slot), value))) -} - -/// Sorts and calculates the root hash of account storage trie. -/// See [`storage_root`] for more info. -pub fn storage_root_unsorted(storage: impl IntoIterator) -> B256 { - storage_root(storage.into_iter().sorted_by_key(|(key, _)| *key)) -} - -/// Calculates the root hash of account storage trie. -/// -/// # Panics -/// -/// If the items are not in sorted order. -pub fn storage_root(storage: impl IntoIterator) -> B256 { - let mut hb = HashBuilder::default(); - for (hashed_slot, value) in storage { - hb.add_leaf(Nibbles::unpack(hashed_slot), alloy_rlp::encode_fixed_size(&value).as_ref()); - } - hb.root() -} - -/// Implementation of hasher using our keccak256 hashing function -/// for compatibility with `triehash` crate. -#[cfg(any(test, feature = "test-utils"))] -pub mod triehash { - use super::{keccak256, B256}; - use hash_db::Hasher; - use plain_hasher::PlainHasher; - - /// A [Hasher] that calculates a keccak256 hash of the given data. - #[derive(Default, Debug, Clone, PartialEq, Eq)] - #[non_exhaustive] - pub struct KeccakHasher; - - #[cfg(any(test, feature = "test-utils"))] - impl Hasher for KeccakHasher { - type Out = B256; - type StdHasher = PlainHasher; - - const LENGTH: usize = 32; - - fn hash(x: &[u8]) -> Self::Out { - keccak256(x) - } - } -} - #[cfg(test)] mod tests { use super::*; use crate::{ bloom, constants::EMPTY_ROOT_HASH, hex_literal::hex, Block, GenesisAccount, Log, TxType, - GOERLI, HOLESKY, MAINNET, SEPOLIA, + GOERLI, HOLESKY, MAINNET, SEPOLIA, U256, }; - use alloy_primitives::{b256, LogData}; + use alloy_primitives::{b256, Address, LogData}; use alloy_rlp::Decodable; + use reth_trie_common::root::{state_root_ref_unhashed, state_root_unhashed}; use std::collections::HashMap; #[test] diff --git a/crates/primitives/src/proofs/traits.rs b/crates/primitives/src/proofs/traits.rs deleted file mode 100644 index 7fef86944b7b..000000000000 --- a/crates/primitives/src/proofs/traits.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::Account; -use alloy_consensus::constants::{EMPTY_ROOT_HASH, KECCAK_EMPTY}; -use alloy_genesis::GenesisAccount; -use alloy_primitives::{keccak256, B256, U256}; -use reth_trie_types::TrieAccount; -use revm_primitives::AccountInfo; - -/// Converts a type into a [`TrieAccount`]. -pub trait IntoTrieAccount { - /// Converts to this type into a [`TrieAccount`]. - fn to_trie_account(self) -> TrieAccount; -} - -impl IntoTrieAccount for GenesisAccount { - fn to_trie_account(self) -> TrieAccount { - let storage_root = self - .storage - .map(|storage| { - super::storage_root_unhashed( - storage - .into_iter() - .filter(|(_, value)| *value != B256::ZERO) - .map(|(slot, value)| (slot, U256::from_be_bytes(*value))), - ) - }) - .unwrap_or(EMPTY_ROOT_HASH); - - TrieAccount { - nonce: self.nonce.unwrap_or_default(), - balance: self.balance, - storage_root, - code_hash: self.code.map_or(KECCAK_EMPTY, keccak256), - } - } -} - -impl IntoTrieAccount for (Account, B256) { - fn to_trie_account(self) -> TrieAccount { - let (account, storage_root) = self; - TrieAccount { - nonce: account.nonce, - balance: account.balance, - storage_root, - code_hash: account.bytecode_hash.unwrap_or(KECCAK_EMPTY), - } - } -} - -impl IntoTrieAccount for (AccountInfo, B256) { - fn to_trie_account(self) -> TrieAccount { - let (account, storage_root) = self; - TrieAccount { - nonce: account.nonce, - balance: account.balance, - storage_root, - code_hash: account.code_hash, - } - } -} diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index bfab663dafff..90ac4ea0466e 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -1,10 +1,9 @@ use reth_primitives::{ - keccak256, proofs::AccountProof, Account, Address, BlockNumber, Bytecode, Bytes, StorageKey, - B256, U256, + keccak256, Account, Address, BlockNumber, Bytecode, Bytes, StorageKey, B256, U256, }; use reth_storage_api::{AccountReader, BlockHashReader, StateProvider, StateRootProvider}; use reth_storage_errors::provider::ProviderResult; -use reth_trie::updates::TrieUpdates; +use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::db::BundleState; use std::collections::HashMap; diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-types-compat/Cargo.toml index fa21f15c5544..a589ef418c64 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-types-compat/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] reth-primitives.workspace = true reth-rpc-types.workspace = true +reth-trie-common.workspace = true alloy-rlp.workspace = true alloy-rpc-types.workspace = true diff --git a/crates/rpc/rpc-types-compat/src/proof.rs b/crates/rpc/rpc-types-compat/src/proof.rs index 17e5cc193633..9d2fec876b0c 100644 --- a/crates/rpc/rpc-types-compat/src/proof.rs +++ b/crates/rpc/rpc-types-compat/src/proof.rs @@ -1,12 +1,10 @@ //! Compatibility functions for rpc proof related types. -use reth_primitives::{ - proofs::{AccountProof, StorageProof}, - U64, -}; +use reth_primitives::U64; use reth_rpc_types::{ serde_helpers::JsonStorageKey, EIP1186AccountProofResponse, EIP1186StorageProof, }; +use reth_trie_common::{AccountProof, StorageProof}; /// Creates a new rpc storage proof from a primitive storage proof type. pub fn from_primitive_storage_proof(proof: StorageProof) -> EIP1186StorageProof { diff --git a/crates/stages/types/Cargo.toml b/crates/stages/types/Cargo.toml index ab64a89c9036..973cd572ce0b 100644 --- a/crates/stages/types/Cargo.toml +++ b/crates/stages/types/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] reth-codecs.workspace = true -reth-trie-types.workspace = true +reth-trie-common.workspace = true alloy-primitives.workspace = true modular-bitfield.workspace = true diff --git a/crates/stages/types/src/checkpoints.rs b/crates/stages/types/src/checkpoints.rs index b78751cc675b..bc424282b01c 100644 --- a/crates/stages/types/src/checkpoints.rs +++ b/crates/stages/types/src/checkpoints.rs @@ -1,7 +1,7 @@ use alloy_primitives::{Address, BlockNumber, B256}; use bytes::Buf; use reth_codecs::{main_codec, Compact}; -use reth_trie_types::{hash_builder::HashBuilderState, StoredSubNode}; +use reth_trie_common::{hash_builder::HashBuilderState, StoredSubNode}; use std::ops::RangeInclusive; use super::StageId; diff --git a/crates/storage/db-api/Cargo.toml b/crates/storage/db-api/Cargo.toml index fbbf0ca5d7be..a3ecc56f8534 100644 --- a/crates/storage/db-api/Cargo.toml +++ b/crates/storage/db-api/Cargo.toml @@ -18,7 +18,7 @@ reth-primitives.workspace = true reth-prune-types.workspace = true reth-storage-errors.workspace = true reth-stages-types.workspace = true -reth-trie-types.workspace = true +reth-trie-common.workspace = true # codecs modular-bitfield.workspace = true diff --git a/crates/storage/db-api/src/models/mod.rs b/crates/storage/db-api/src/models/mod.rs index 7438feedea51..f5ef4ea5fcce 100644 --- a/crates/storage/db-api/src/models/mod.rs +++ b/crates/storage/db-api/src/models/mod.rs @@ -8,7 +8,7 @@ use reth_codecs::{main_codec, Compact}; use reth_primitives::{Address, B256, *}; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::StageCheckpoint; -use reth_trie_types::{StoredNibbles, StoredNibblesSubKey, *}; +use reth_trie_common::{StoredNibbles, StoredNibblesSubKey, *}; pub mod accounts; pub mod blocks; diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index e144e81271b7..2bac4c107867 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -25,7 +25,7 @@ reth-nippy-jar.workspace = true reth-prune-types.workspace = true reth-stages-types.workspace = true reth-tracing.workspace = true -reth-trie-types.workspace = true +reth-trie-common.workspace = true # codecs serde = { workspace = true, default-features = false } diff --git a/crates/storage/db/src/tables/mod.rs b/crates/storage/db/src/tables/mod.rs index 5d3c685a526f..367a7dd5531e 100644 --- a/crates/storage/db/src/tables/mod.rs +++ b/crates/storage/db/src/tables/mod.rs @@ -37,7 +37,7 @@ use reth_primitives::{ }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::StageCheckpoint; -use reth_trie_types::{StorageTrieEntry, StoredBranchNode, StoredNibbles, StoredNibblesSubKey}; +use reth_trie_common::{StorageTrieEntry, StoredBranchNode, StoredNibbles, StoredNibblesSubKey}; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index aec1ce18284b..49fb196ffb18 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -1,9 +1,9 @@ use crate::{ AccountReader, BlockHashReader, ExecutionDataProvider, StateProvider, StateRootProvider, }; -use reth_primitives::{proofs::AccountProof, Account, Address, BlockNumber, Bytecode, B256}; +use reth_primitives::{Account, Address, BlockNumber, Bytecode, B256}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use reth_trie::updates::TrieUpdates; +use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::db::BundleState; /// A state provider that resolves to data from either a wrapped [`crate::ExecutionOutcome`] diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index d0304c1c4ac1..12545fe7838e 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -10,11 +10,11 @@ use reth_db_api::{ transaction::DbTx, }; use reth_primitives::{ - constants::EPOCH_SLOTS, proofs::AccountProof, Account, Address, BlockNumber, Bytecode, - StaticFileSegment, StorageKey, StorageValue, B256, + constants::EPOCH_SLOTS, Account, Address, BlockNumber, Bytecode, StaticFileSegment, StorageKey, + StorageValue, B256, }; use reth_storage_errors::provider::ProviderResult; -use reth_trie::{updates::TrieUpdates, HashedPostState}; +use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState}; use revm::db::BundleState; use std::fmt::Debug; diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index a8e554461de3..56b4ecc38b11 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -8,11 +8,10 @@ use reth_db_api::{ transaction::DbTx, }; use reth_primitives::{ - proofs::AccountProof, Account, Address, BlockNumber, Bytecode, StaticFileSegment, StorageKey, - StorageValue, B256, + Account, Address, BlockNumber, Bytecode, StaticFileSegment, StorageKey, StorageValue, B256, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use reth_trie::{proof::Proof, updates::TrieUpdates, HashedPostState}; +use reth_trie::{proof::Proof, updates::TrieUpdates, AccountProof, HashedPostState}; use revm::db::BundleState; /// State provider over latest state that takes tx reference. diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index 1d5a9597832c..a39cddfe39fc 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -43,7 +43,7 @@ macro_rules! delegate_provider_impls { } StateProvider $(where [$($generics)*])?{ fn storage(&self, account: reth_primitives::Address, storage_key: reth_primitives::StorageKey) -> reth_storage_errors::provider::ProviderResult>; - fn proof(&self, address: reth_primitives::Address, keys: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult; + fn proof(&self, address: reth_primitives::Address, keys: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult; fn bytecode_by_hash(&self, code_hash: reth_primitives::B256) -> reth_storage_errors::provider::ProviderResult>; } ); diff --git a/crates/storage/provider/src/test_utils/blocks.rs b/crates/storage/provider/src/test_utils/blocks.rs index be4420fc5199..5fb8beeb2435 100644 --- a/crates/storage/provider/src/test_utils/blocks.rs +++ b/crates/storage/provider/src/test_utils/blocks.rs @@ -5,13 +5,11 @@ use alloy_rlp::Decodable; use reth_db::tables; use reth_db_api::{database::Database, models::StoredBlockBodyIndices}; use reth_primitives::{ - alloy_primitives, b256, - hex_literal::hex, - proofs::{state_root_unhashed, storage_root_unhashed}, - revm::compat::into_reth_acc, - Address, BlockNumber, Bytes, Header, Receipt, Requests, SealedBlock, SealedBlockWithSenders, - TxType, Withdrawal, Withdrawals, B256, U256, + alloy_primitives, b256, hex_literal::hex, revm::compat::into_reth_acc, Address, BlockNumber, + Bytes, Header, Receipt, Requests, SealedBlock, SealedBlockWithSenders, TxType, Withdrawal, + Withdrawals, B256, U256, }; +use reth_trie::root::{state_root_unhashed, storage_root_unhashed}; use revm::{ db::BundleState, primitives::{AccountInfo, HashMap}, diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index a255eb28e1be..0788ddf1e816 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -9,14 +9,14 @@ use parking_lot::Mutex; use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices}; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ - keccak256, proofs::AccountProof, Account, Address, Block, BlockHash, BlockHashOrNumber, - BlockId, BlockNumber, BlockWithSenders, Bytecode, Bytes, ChainInfo, ChainSpec, Header, Receipt, - SealedBlock, SealedBlockWithSenders, SealedHeader, StorageKey, StorageValue, TransactionMeta, + keccak256, Account, Address, Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumber, + BlockWithSenders, Bytecode, Bytes, ChainInfo, ChainSpec, Header, Receipt, SealedBlock, + SealedBlockWithSenders, SealedHeader, StorageKey, StorageValue, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use reth_trie::updates::TrieUpdates; +use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::{ db::BundleState, primitives::{BlockEnv, CfgEnvWithHandlerCfg}, diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 8894a68f7ed6..b681586a1427 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -9,16 +9,15 @@ use crate::{ use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices}; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ - proofs::AccountProof, Account, Address, Block, BlockHash, BlockHashOrNumber, BlockId, - BlockNumber, BlockWithSenders, Bytecode, ChainInfo, ChainSpec, Header, Receipt, SealedBlock, - SealedBlockWithSenders, SealedHeader, StorageKey, StorageValue, TransactionMeta, - TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, - MAINNET, U256, + Account, Address, Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumber, BlockWithSenders, + Bytecode, ChainInfo, ChainSpec, Header, Receipt, SealedBlock, SealedBlockWithSenders, + SealedHeader, StorageKey, StorageValue, TransactionMeta, TransactionSigned, + TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, MAINNET, U256, }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_errors::provider::ProviderResult; -use reth_trie::updates::TrieUpdates; +use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::{ db::BundleState, primitives::{BlockEnv, CfgEnvWithHandlerCfg}, diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 9e258bfd1656..059909a463b0 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -2,10 +2,11 @@ use super::{AccountReader, BlockHashReader, BlockIdReader, StateRootProvider}; use auto_impl::auto_impl; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ - proofs::AccountProof, Address, BlockHash, BlockId, BlockNumHash, BlockNumber, BlockNumberOrTag, - Bytecode, StorageKey, StorageValue, B256, KECCAK_EMPTY, U256, + Address, BlockHash, BlockId, BlockNumHash, BlockNumber, BlockNumberOrTag, Bytecode, StorageKey, + StorageValue, B256, KECCAK_EMPTY, U256, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; +use reth_trie::AccountProof; /// Type alias of boxed [`StateProvider`]. pub type StateProviderBox = Box; diff --git a/crates/trie/types/Cargo.toml b/crates/trie/common/Cargo.toml similarity index 52% rename from crates/trie/types/Cargo.toml rename to crates/trie/common/Cargo.toml index 4dc2f15dc99f..241a3518f741 100644 --- a/crates/trie/types/Cargo.toml +++ b/crates/trie/common/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "reth-trie-types" +name = "reth-trie-common" version.workspace = true edition.workspace = true homepage.workspace = true @@ -12,17 +12,29 @@ description = "Commonly used types for trie usage in reth." workspace = true [dependencies] +reth-primitives-traits.workspace = true reth-codecs.workspace = true alloy-primitives.workspace = true alloy-rlp = { workspace = true, features = ["arrayvec"] } alloy-trie = { workspace = true, features = ["serde"] } +alloy-consensus.workspace = true +alloy-genesis.workspace = true +revm-primitives.workspace = true + bytes.workspace = true derive_more.workspace = true serde.workspace = true - +itertools.workspace = true nybbles = { workspace = true, features = ["serde", "rlp"] } +# `test-utils` feature +hash-db = { version = "~0.15", optional = true } +plain_hasher = { version = "0.2", optional = true } +arbitrary = { workspace = true, features = ["derive"], optional = true } +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } + [dev-dependencies] arbitrary = { workspace = true, features = ["derive"] } assert_matches.workspace = true @@ -30,4 +42,15 @@ proptest.workspace = true proptest-derive.workspace = true serde_json.workspace = true test-fuzz.workspace = true -toml.workspace = true \ No newline at end of file +toml.workspace = true +hash-db = "~0.15" +plain_hasher = "0.2" + +[features] +test-utils = ["dep:plain_hasher", "dep:hash-db", "arbitrary"] +arbitrary = [ + "alloy-trie/arbitrary", + "dep:arbitrary", + "dep:proptest", + "dep:proptest-derive", +] \ No newline at end of file diff --git a/crates/trie/common/src/account.rs b/crates/trie/common/src/account.rs new file mode 100644 index 000000000000..64860ab78b31 --- /dev/null +++ b/crates/trie/common/src/account.rs @@ -0,0 +1,73 @@ +use crate::root::storage_root_unhashed; +use alloy_consensus::constants::KECCAK_EMPTY; +use alloy_genesis::GenesisAccount; +use alloy_primitives::{keccak256, B256, U256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; +use alloy_trie::EMPTY_ROOT_HASH; +use reth_primitives_traits::Account; +use revm_primitives::AccountInfo; + +/// An Ethereum account as represented in the trie. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)] +pub struct TrieAccount { + /// Account nonce. + pub nonce: u64, + /// Account balance. + pub balance: U256, + /// Account's storage root. + pub storage_root: B256, + /// Hash of the account's bytecode. + pub code_hash: B256, +} + +impl TrieAccount { + /// Get account's storage root. + pub const fn storage_root(&self) -> B256 { + self.storage_root + } +} + +impl From for TrieAccount { + fn from(account: GenesisAccount) -> Self { + let storage_root = account + .storage + .map(|storage| { + storage_root_unhashed( + storage + .into_iter() + .filter(|(_, value)| *value != B256::ZERO) + .map(|(slot, value)| (slot, U256::from_be_bytes(*value))), + ) + }) + .unwrap_or(EMPTY_ROOT_HASH); + + Self { + nonce: account.nonce.unwrap_or_default(), + balance: account.balance, + storage_root, + code_hash: account.code.map_or(KECCAK_EMPTY, keccak256), + } + } +} + +impl From<(Account, B256)> for TrieAccount { + fn from((account, storage_root): (Account, B256)) -> Self { + Self { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.bytecode_hash.unwrap_or(KECCAK_EMPTY), + } + } +} + +impl From<(AccountInfo, B256)> for TrieAccount { + fn from((account, storage_root): (AccountInfo, B256)) -> Self { + Self { + nonce: account.nonce, + balance: account.balance, + storage_root, + code_hash: account.code_hash, + } + } +} diff --git a/crates/trie/types/src/hash_builder/mod.rs b/crates/trie/common/src/hash_builder/mod.rs similarity index 100% rename from crates/trie/types/src/hash_builder/mod.rs rename to crates/trie/common/src/hash_builder/mod.rs diff --git a/crates/trie/types/src/hash_builder/state.rs b/crates/trie/common/src/hash_builder/state.rs similarity index 100% rename from crates/trie/types/src/hash_builder/state.rs rename to crates/trie/common/src/hash_builder/state.rs diff --git a/crates/trie/types/src/hash_builder/value.rs b/crates/trie/common/src/hash_builder/value.rs similarity index 100% rename from crates/trie/types/src/hash_builder/value.rs rename to crates/trie/common/src/hash_builder/value.rs diff --git a/crates/trie/types/src/lib.rs b/crates/trie/common/src/lib.rs similarity index 87% rename from crates/trie/types/src/lib.rs rename to crates/trie/common/src/lib.rs index 0b4927b9c8ac..bc3749e6f936 100644 --- a/crates/trie/types/src/lib.rs +++ b/crates/trie/common/src/lib.rs @@ -31,4 +31,11 @@ pub use storage::StorageTrieEntry; mod subnode; pub use subnode::StoredSubNode; +mod proofs; +#[cfg(any(test, feature = "test-utils"))] +pub use proofs::triehash; +pub use proofs::{AccountProof, StorageProof}; + +pub mod root; + pub use alloy_trie::{proof, BranchNodeCompact, HashBuilder, TrieMask, EMPTY_ROOT_HASH}; diff --git a/crates/trie/types/src/mask.rs b/crates/trie/common/src/mask.rs similarity index 100% rename from crates/trie/types/src/mask.rs rename to crates/trie/common/src/mask.rs diff --git a/crates/trie/types/src/nibbles.rs b/crates/trie/common/src/nibbles.rs similarity index 100% rename from crates/trie/types/src/nibbles.rs rename to crates/trie/common/src/nibbles.rs diff --git a/crates/trie/types/src/nodes/branch.rs b/crates/trie/common/src/nodes/branch.rs similarity index 100% rename from crates/trie/types/src/nodes/branch.rs rename to crates/trie/common/src/nodes/branch.rs diff --git a/crates/trie/types/src/nodes/mod.rs b/crates/trie/common/src/nodes/mod.rs similarity index 100% rename from crates/trie/types/src/nodes/mod.rs rename to crates/trie/common/src/nodes/mod.rs diff --git a/crates/primitives/src/proofs/types.rs b/crates/trie/common/src/proofs.rs similarity index 80% rename from crates/primitives/src/proofs/types.rs rename to crates/trie/common/src/proofs.rs index f2225df7942e..36a0c2615f22 100644 --- a/crates/primitives/src/proofs/types.rs +++ b/crates/trie/common/src/proofs.rs @@ -1,12 +1,13 @@ //! Merkle trie proofs. -use super::{traits::IntoTrieAccount, Nibbles}; -use crate::{keccak256, Account, Address, Bytes, B256, U256}; +use crate::{Nibbles, TrieAccount}; +use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; use alloy_rlp::encode_fixed_size; use alloy_trie::{ proof::{verify_proof, ProofVerificationError}, EMPTY_ROOT_HASH, }; +use reth_primitives_traits::Account; /// The merkle proof with the relevant account info. #[derive(PartialEq, Eq, Debug)] @@ -64,7 +65,7 @@ impl AccountProof { let expected = if self.info.is_none() && self.storage_root == EMPTY_ROOT_HASH { None } else { - Some(alloy_rlp::encode(IntoTrieAccount::to_trie_account(( + Some(alloy_rlp::encode(TrieAccount::from(( self.info.unwrap_or_default(), self.storage_root, )))) @@ -122,3 +123,29 @@ impl StorageProof { verify_proof(root, self.nibbles.clone(), expected, &self.proof) } } + +/// Implementation of hasher using our keccak256 hashing function +/// for compatibility with `triehash` crate. +#[cfg(any(test, feature = "test-utils"))] +pub mod triehash { + use alloy_primitives::{keccak256, B256}; + use hash_db::Hasher; + use plain_hasher::PlainHasher; + + /// A [Hasher] that calculates a keccak256 hash of the given data. + #[derive(Default, Debug, Clone, PartialEq, Eq)] + #[non_exhaustive] + pub struct KeccakHasher; + + #[cfg(any(test, feature = "test-utils"))] + impl Hasher for KeccakHasher { + type Out = B256; + type StdHasher = PlainHasher; + + const LENGTH: usize = 32; + + fn hash(x: &[u8]) -> Self::Out { + keccak256(x) + } + } +} diff --git a/crates/trie/common/src/root.rs b/crates/trie/common/src/root.rs new file mode 100644 index 000000000000..434eea20b59c --- /dev/null +++ b/crates/trie/common/src/root.rs @@ -0,0 +1,117 @@ +//! Common root computation functions. + +use crate::TrieAccount; +use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_rlp::Encodable; +use alloy_trie::HashBuilder; +use itertools::Itertools; +use nybbles::Nibbles; + +/// Adjust the index of an item for rlp encoding. +pub const fn adjust_index_for_rlp(i: usize, len: usize) -> usize { + if i > 0x7f { + i + } else if i == 0x7f || i + 1 == len { + 0 + } else { + i + 1 + } +} + +/// Compute a trie root of the collection of rlp encodable items. +pub fn ordered_trie_root(items: &[T]) -> B256 { + ordered_trie_root_with_encoder(items, |item, buf| item.encode(buf)) +} + +/// Compute a trie root of the collection of items with a custom encoder. +pub fn ordered_trie_root_with_encoder(items: &[T], mut encode: F) -> B256 +where + F: FnMut(&T, &mut Vec), +{ + let mut value_buffer = Vec::new(); + + let mut hb = HashBuilder::default(); + let items_len = items.len(); + for i in 0..items_len { + let index = adjust_index_for_rlp(i, items_len); + + let index_buffer = alloy_rlp::encode_fixed_size(&index); + + value_buffer.clear(); + encode(&items[index], &mut value_buffer); + + hb.add_leaf(Nibbles::unpack(&index_buffer), &value_buffer); + } + + hb.root() +} + +/// Hashes and sorts account keys, then proceeds to calculating the root hash of the state +/// represented as MPT. +/// See [`state_root_unsorted`] for more info. +pub fn state_root_ref_unhashed<'a, A: Into + Clone + 'a>( + state: impl IntoIterator, +) -> B256 { + state_root_unsorted( + state.into_iter().map(|(address, account)| (keccak256(address), account.clone())), + ) +} + +/// Hashes and sorts account keys, then proceeds to calculating the root hash of the state +/// represented as MPT. +/// See [`state_root_unsorted`] for more info. +pub fn state_root_unhashed>( + state: impl IntoIterator, +) -> B256 { + state_root_unsorted(state.into_iter().map(|(address, account)| (keccak256(address), account))) +} + +/// Sorts the hashed account keys and calculates the root hash of the state represented as MPT. +/// See [`state_root`] for more info. +pub fn state_root_unsorted>( + state: impl IntoIterator, +) -> B256 { + state_root(state.into_iter().sorted_by_key(|(key, _)| *key)) +} + +/// Calculates the root hash of the state represented as MPT. +/// Corresponds to [geth's `deriveHash`](https://github.com/ethereum/go-ethereum/blob/6c149fd4ad063f7c24d726a73bc0546badd1bc73/core/genesis.go#L119). +/// +/// # Panics +/// +/// If the items are not in sorted order. +pub fn state_root>(state: impl IntoIterator) -> B256 { + let mut hb = HashBuilder::default(); + let mut account_rlp_buf = Vec::new(); + for (hashed_key, account) in state { + account_rlp_buf.clear(); + account.into().encode(&mut account_rlp_buf); + hb.add_leaf(Nibbles::unpack(hashed_key), &account_rlp_buf); + } + hb.root() +} + +/// Hashes storage keys, sorts them and them calculates the root hash of the storage trie. +/// See [`storage_root_unsorted`] for more info. +pub fn storage_root_unhashed(storage: impl IntoIterator) -> B256 { + storage_root_unsorted(storage.into_iter().map(|(slot, value)| (keccak256(slot), value))) +} + +/// Sorts and calculates the root hash of account storage trie. +/// See [`storage_root`] for more info. +pub fn storage_root_unsorted(storage: impl IntoIterator) -> B256 { + storage_root(storage.into_iter().sorted_by_key(|(key, _)| *key)) +} + +/// Calculates the root hash of account storage trie. +/// +/// # Panics +/// +/// If the items are not in sorted order. +pub fn storage_root(storage: impl IntoIterator) -> B256 { + let mut hb = HashBuilder::default(); + for (hashed_slot, value) in storage { + hb.add_leaf(Nibbles::unpack(hashed_slot), alloy_rlp::encode_fixed_size(&value).as_ref()); + } + hb.root() +} diff --git a/crates/trie/types/src/storage.rs b/crates/trie/common/src/storage.rs similarity index 100% rename from crates/trie/types/src/storage.rs rename to crates/trie/common/src/storage.rs diff --git a/crates/trie/types/src/subnode.rs b/crates/trie/common/src/subnode.rs similarity index 100% rename from crates/trie/types/src/subnode.rs rename to crates/trie/common/src/subnode.rs diff --git a/crates/trie/parallel/src/async_root.rs b/crates/trie/parallel/src/async_root.rs index 7441bd0a02f4..a36a01be5ecb 100644 --- a/crates/trie/parallel/src/async_root.rs +++ b/crates/trie/parallel/src/async_root.rs @@ -3,7 +3,7 @@ use alloy_rlp::{BufMut, Encodable}; use itertools::Itertools; use reth_db_api::database::Database; use reth_execution_errors::StorageRootError; -use reth_primitives::{proofs::IntoTrieAccount, B256}; +use reth_primitives::B256; use reth_provider::{providers::ConsistentDbView, DatabaseProviderFactory, ProviderError}; use reth_tasks::pool::BlockingTaskPool; use reth_trie::{ @@ -12,7 +12,7 @@ use reth_trie::{ trie_cursor::TrieCursorFactory, updates::TrieUpdates, walker::TrieWalker, - HashBuilder, HashedPostState, Nibbles, StorageRoot, + HashBuilder, HashedPostState, Nibbles, StorageRoot, TrieAccount, }; use std::{collections::HashMap, sync::Arc}; use thiserror::Error; @@ -170,7 +170,7 @@ where } account_rlp.clear(); - let account = IntoTrieAccount::to_trie_account((account, storage_root)); + let account = TrieAccount::from((account, storage_root)); account.encode(&mut account_rlp as &mut dyn BufMut); hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); } diff --git a/crates/trie/parallel/src/parallel_root.rs b/crates/trie/parallel/src/parallel_root.rs index f814e2ff88cf..edf552096fca 100644 --- a/crates/trie/parallel/src/parallel_root.rs +++ b/crates/trie/parallel/src/parallel_root.rs @@ -3,7 +3,7 @@ use alloy_rlp::{BufMut, Encodable}; use rayon::prelude::*; use reth_db_api::database::Database; use reth_execution_errors::StorageRootError; -use reth_primitives::{proofs::IntoTrieAccount, B256}; +use reth_primitives::B256; use reth_provider::{providers::ConsistentDbView, DatabaseProviderFactory, ProviderError}; use reth_trie::{ hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory}, @@ -11,7 +11,7 @@ use reth_trie::{ trie_cursor::TrieCursorFactory, updates::TrieUpdates, walker::TrieWalker, - HashBuilder, HashedPostState, Nibbles, StorageRoot, + HashBuilder, HashedPostState, Nibbles, StorageRoot, TrieAccount, }; use std::collections::HashMap; use thiserror::Error; @@ -152,7 +152,7 @@ where } account_rlp.clear(); - let account = IntoTrieAccount::to_trie_account((account, storage_root)); + let account = TrieAccount::from((account, storage_root)); account.encode(&mut account_rlp as &mut dyn BufMut); hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); } diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index 99beeff07004..d01914759a87 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -18,7 +18,7 @@ reth-execution-errors.workspace = true reth-db.workspace = true reth-db-api.workspace = true reth-stages-types.workspace = true -reth-trie-types.workspace = true +reth-trie-common.workspace = true revm.workspace = true @@ -46,6 +46,7 @@ reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } reth-db = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } reth-storage-errors.workspace = true +reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } # trie triehash = "0.8" @@ -65,7 +66,7 @@ criterion.workspace = true [features] metrics = ["reth-metrics", "dep:metrics"] -test-utils = ["triehash"] +test-utils = ["triehash", "reth-trie-common/test-utils"] [[bench]] name = "prefix_set" @@ -74,3 +75,8 @@ harness = false [[bench]] name = "hash_post_state" harness = false + +[[bench]] +name = "trie_root" +required-features = ["test-utils"] +harness = false diff --git a/crates/primitives/benches/trie_root.rs b/crates/trie/trie/benches/trie_root.rs similarity index 92% rename from crates/primitives/benches/trie_root.rs rename to crates/trie/trie/benches/trie_root.rs index b61e3aa8541d..8a149404abbb 100644 --- a/crates/primitives/benches/trie_root.rs +++ b/crates/trie/trie/benches/trie_root.rs @@ -1,7 +1,8 @@ #![allow(missing_docs, unreachable_pub)] use criterion::{black_box, criterion_group, criterion_main, Criterion}; use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner}; -use reth_primitives::{proofs::triehash::KeccakHasher, ReceiptWithBloom, B256}; +use reth_primitives::{ReceiptWithBloom, B256}; +use reth_trie::triehash::KeccakHasher; /// Benchmarks different implementations of the root calculation. pub fn trie_root_benchmark(c: &mut Criterion) { @@ -41,8 +42,7 @@ criterion_main!(benches); mod implementations { use super::*; use alloy_rlp::Encodable; - use reth_primitives::proofs::adjust_index_for_rlp; - use reth_trie_types::{HashBuilder, Nibbles}; + use reth_trie_common::{root::adjust_index_for_rlp, HashBuilder, Nibbles}; pub fn trie_hash_ordered_trie_root(receipts: &[ReceiptWithBloom]) -> B256 { triehash::ordered_trie_root::(receipts.iter().map(|receipt| { diff --git a/crates/trie/trie/src/lib.rs b/crates/trie/trie/src/lib.rs index 16251dc11903..eea65a7b34d8 100644 --- a/crates/trie/trie/src/lib.rs +++ b/crates/trie/trie/src/lib.rs @@ -51,7 +51,7 @@ pub use progress::{IntermediateStateRootState, StateRootProgress}; pub mod stats; // re-export for convenience -pub use reth_trie_types::*; +pub use reth_trie_common::*; /// Trie calculation metrics. #[cfg(feature = "metrics")] diff --git a/crates/trie/trie/src/proof.rs b/crates/trie/trie/src/proof.rs index d3438dfaa57a..19c8b7fabbc6 100644 --- a/crates/trie/trie/src/proof.rs +++ b/crates/trie/trie/src/proof.rs @@ -10,13 +10,8 @@ use alloy_rlp::{BufMut, Encodable}; use reth_db::tables; use reth_db_api::transaction::DbTx; use reth_execution_errors::{StateRootError, StorageRootError}; -use reth_primitives::{ - constants::EMPTY_ROOT_HASH, - keccak256, - proofs::{AccountProof, IntoTrieAccount, StorageProof}, - Address, B256, -}; -use reth_trie_types::proof::ProofRetainer; +use reth_primitives::{constants::EMPTY_ROOT_HASH, keccak256, Address, B256}; +use reth_trie_common::{proof::ProofRetainer, AccountProof, StorageProof, TrieAccount}; /// A struct for generating merkle proofs. /// /// Proof generator adds the target address and slots to the prefix set, enables the proof retainer @@ -83,7 +78,7 @@ where }; account_rlp.clear(); - let account = IntoTrieAccount::to_trie_account((account, storage_root)); + let account = TrieAccount::from((account, storage_root)); account.encode(&mut account_rlp as &mut dyn BufMut); hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); diff --git a/crates/trie/trie/src/test_utils.rs b/crates/trie/trie/src/test_utils.rs index bd0c1936fbf7..e2fc1f192c32 100644 --- a/crates/trie/trie/src/test_utils.rs +++ b/crates/trie/trie/src/test_utils.rs @@ -1,8 +1,6 @@ use alloy_rlp::encode_fixed_size; -use reth_primitives::{ - proofs::{triehash::KeccakHasher, IntoTrieAccount}, - Account, Address, B256, U256, -}; +use reth_primitives::{Account, Address, B256, U256}; +use reth_trie_common::{triehash::KeccakHasher, TrieAccount}; /// Re-export of [triehash]. pub use triehash; @@ -15,7 +13,7 @@ where { let encoded_accounts = accounts.into_iter().map(|(address, (account, storage))| { let storage_root = storage_root(storage); - let account = IntoTrieAccount::to_trie_account((account, storage_root)); + let account = TrieAccount::from((account, storage_root)); (address, alloy_rlp::encode(account)) }); triehash::sec_trie_root::(encoded_accounts) @@ -36,7 +34,7 @@ where { let encoded_accounts = accounts.into_iter().map(|(address, (account, storage))| { let storage_root = storage_root_prehashed(storage); - let account = IntoTrieAccount::to_trie_account((account, storage_root)); + let account = TrieAccount::from((account, storage_root)); (address, alloy_rlp::encode(account)) }); diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 91ba2e0463e0..3fe97a6f788c 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -7,14 +7,12 @@ use crate::{ trie_cursor::TrieCursorFactory, updates::{TrieKey, TrieOp, TrieUpdates}, walker::TrieWalker, - HashBuilder, Nibbles, + HashBuilder, Nibbles, TrieAccount, }; use alloy_rlp::{BufMut, Encodable}; use reth_db_api::transaction::DbTx; use reth_execution_errors::{StateRootError, StorageRootError}; -use reth_primitives::{ - constants::EMPTY_ROOT_HASH, keccak256, proofs::IntoTrieAccount, Address, BlockNumber, B256, -}; +use reth_primitives::{constants::EMPTY_ROOT_HASH, keccak256, Address, BlockNumber, B256}; use std::ops::RangeInclusive; use tracing::{debug, trace}; @@ -282,7 +280,7 @@ where }; account_rlp.clear(); - let account = IntoTrieAccount::to_trie_account((account, storage_root)); + let account = TrieAccount::from((account, storage_root)); account.encode(&mut account_rlp as &mut dyn BufMut); hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); @@ -558,10 +556,9 @@ mod tests { cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, transaction::DbTxMut, }; - use reth_primitives::{ - hex_literal::hex, proofs::triehash::KeccakHasher, Account, StorageEntry, U256, - }; + use reth_primitives::{hex_literal::hex, Account, StorageEntry, U256}; use reth_provider::{test_utils::create_test_provider_factory, DatabaseProviderRW}; + use reth_trie_common::triehash::KeccakHasher; use std::{ collections::{BTreeMap, HashMap}, ops::Mul, @@ -828,8 +825,7 @@ mod tests { } fn encode_account(account: Account, storage_root: Option) -> Vec { - let account = - IntoTrieAccount::to_trie_account((account, storage_root.unwrap_or(EMPTY_ROOT_HASH))); + let account = TrieAccount::from((account, storage_root.unwrap_or(EMPTY_ROOT_HASH))); let mut account_rlp = Vec::with_capacity(account.length()); account.encode(&mut account_rlp); account_rlp diff --git a/crates/trie/types/src/account.rs b/crates/trie/types/src/account.rs deleted file mode 100644 index 480a8c6a69e8..000000000000 --- a/crates/trie/types/src/account.rs +++ /dev/null @@ -1,22 +0,0 @@ -use alloy_primitives::{B256, U256}; -use alloy_rlp::{RlpDecodable, RlpEncodable}; - -/// An Ethereum account as represented in the trie. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)] -pub struct TrieAccount { - /// Account nonce. - pub nonce: u64, - /// Account balance. - pub balance: U256, - /// Account's storage root. - pub storage_root: B256, - /// Hash of the account's bytecode. - pub code_hash: B256, -} - -impl TrieAccount { - /// Get account's storage root. - pub const fn storage_root(&self) -> B256 { - self.storage_root - } -} From ff33c281c7a6ccee704510c640eb86be7dc015de Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 13 Jun 2024 20:33:45 +0200 Subject: [PATCH 028/405] fix(grafana): fix broken panel (#8806) --- etc/docker-compose.yml | 2 +- etc/grafana/dashboards/reth-mempool.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/etc/docker-compose.yml b/etc/docker-compose.yml index 8e69dc34ec33..618aa6f5ae63 100644 --- a/etc/docker-compose.yml +++ b/etc/docker-compose.yml @@ -46,7 +46,7 @@ services: grafana: restart: unless-stopped - image: grafana/grafana:10.3.3 + image: grafana/grafana:latest depends_on: - reth - prometheus diff --git a/etc/grafana/dashboards/reth-mempool.json b/etc/grafana/dashboards/reth-mempool.json index f93276e509a4..092faaccb878 100644 --- a/etc/grafana/dashboards/reth-mempool.json +++ b/etc/grafana/dashboards/reth-mempool.json @@ -1458,7 +1458,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_network_pool_transactions_messages_sent_total{instance=~\"$instance\"}[$__rate_interval])", + "expr": "rate(reth_network_pool_transactions_messages_sent{instance=~\"$instance\"}[$__rate_interval])", "hide": false, "instant": false, "legendFormat": "Tx", @@ -1471,7 +1471,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_network_pool_transactions_messages_received_total{instance=~\"$instance\"}[$__rate_interval])", + "expr": "rate(reth_network_pool_transactions_messages_received{instance=~\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Rx", "range": true, @@ -1483,7 +1483,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_network_pool_transactions_messages_sent_total{instance=~\"$instance\"} - reth_network_pool_transactions_messages_received_total{instance=~\"$instance\"}", + "expr": "reth_network_pool_transactions_messages_sent{instance=~\"$instance\"} - reth_network_pool_transactions_messages_received{instance=~\"$instance\"}", "hide": false, "legendFormat": "Messages in Channel", "range": true, From de46a46849ba9399cf9e179a0c0f62601ce626e8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 13 Jun 2024 22:15:08 +0200 Subject: [PATCH 029/405] fix: windows build (#8807) --- crates/storage/db/src/implementation/mdbx/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index 78b1ad6359ea..4191d7aae364 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -41,7 +41,6 @@ const DEFAULT_MAX_READERS: u64 = 32_000; /// Space that a read-only transaction can occupy until the warning is emitted. /// See [`reth_libmdbx::EnvironmentBuilder::set_handle_slow_readers`] for more information. -#[cfg(not(windows))] const MAX_SAFE_READER_SPACE: usize = 10 * GIGABYTE; /// Environment used when opening a MDBX environment. RO/RW. From d4bd1c8f5d81189abe5a45c42b86cc62163eb64b Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:32:49 -0400 Subject: [PATCH 030/405] fix: only check that base fee is not zero (#8808) Co-authored-by: Matthias Seitz --- crates/rpc/rpc-types-compat/src/engine/payload.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index 296fbf5d93a4..dacfab06418e 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -2,7 +2,7 @@ //! Ethereum's Engine use reth_primitives::{ - constants::{EMPTY_OMMER_ROOT_HASH, MAXIMUM_EXTRA_DATA_SIZE, MIN_PROTOCOL_BASE_FEE_U256}, + constants::{EMPTY_OMMER_ROOT_HASH, MAXIMUM_EXTRA_DATA_SIZE}, proofs::{self}, Block, Header, Request, SealedBlock, TransactionSigned, UintTryTo, Withdrawals, B256, U256, }; @@ -18,7 +18,7 @@ pub fn try_payload_v1_to_block(payload: ExecutionPayloadV1) -> Result Date: Thu, 13 Jun 2024 22:45:49 +0200 Subject: [PATCH 031/405] chore: remove educe dependency (#8810) --- Cargo.lock | 26 -------------------------- crates/net/ecies/Cargo.toml | 1 - crates/net/ecies/src/algorithm.rs | 28 +++++++++++++++++++++------- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fae53681028..6624aeac3b26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2712,18 +2712,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "educe" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "ef-tests" version = "1.0.0-rc.1" @@ -2811,19 +2799,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "enum-ordinalize" -version = "3.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "enumn" version = "0.1.13" @@ -6864,7 +6839,6 @@ dependencies = [ "concat-kdf", "ctr 0.9.2", "digest 0.10.7", - "educe", "futures", "generic-array", "hmac 0.12.1", diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index c4dc29df9eed..d7800c74dbba 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -23,7 +23,6 @@ tokio-stream.workspace = true tokio-util = { workspace = true, features = ["codec"] } pin-project.workspace = true -educe = "0.4.19" tracing.workspace = true # HeaderBytes diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs index d76502457d0c..5970e8405eac 100644 --- a/crates/net/ecies/src/algorithm.rs +++ b/crates/net/ecies/src/algorithm.rs @@ -11,7 +11,6 @@ use alloy_rlp::{Encodable, Rlp, RlpEncodable, RlpMaxEncodedLen}; use byteorder::{BigEndian, ByteOrder, ReadBytesExt}; use ctr::Ctr64BE; use digest::{crypto_common::KeyIvInit, Digest}; -use educe::Educe; use rand::{thread_rng, Rng}; use reth_network_peers::{id2pk, pk2id}; use reth_primitives::{ @@ -51,17 +50,13 @@ fn kdf(secret: B256, s1: &[u8], dest: &mut [u8]) { concat_kdf::derive_key_into::(secret.as_slice(), s1, dest).unwrap(); } -#[derive(Educe)] -#[educe(Debug)] pub struct ECIES { - #[educe(Debug(ignore))] secret_key: SecretKey, public_key: PublicKey, remote_public_key: Option, pub(crate) remote_id: Option, - #[educe(Debug(ignore))] ephemeral_secret_key: SecretKey, ephemeral_public_key: PublicKey, ephemeral_shared_secret: Option, @@ -70,9 +65,7 @@ pub struct ECIES { nonce: B256, remote_nonce: Option, - #[educe(Debug(ignore))] ingress_aes: Option>, - #[educe(Debug(ignore))] egress_aes: Option>, ingress_mac: Option, egress_mac: Option, @@ -83,6 +76,27 @@ pub struct ECIES { body_size: Option, } +impl core::fmt::Debug for ECIES { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ECIES") + .field("public_key", &self.public_key) + .field("remote_public_key", &self.remote_public_key) + .field("remote_id", &self.remote_id) + .field("ephemeral_public_key", &self.ephemeral_public_key) + .field("ephemeral_shared_secret", &self.ephemeral_shared_secret) + .field("remote_ephemeral_public_key", &self.remote_ephemeral_public_key) + .field("nonce", &self.nonce) + .field("remote_nonce", &self.remote_nonce) + .field("ingress_mac", &self.ingress_mac) + .field("egress_mac", &self.egress_mac) + .field("init_msg", &self.init_msg) + .field("remote_init_msg", &self.remote_init_msg) + .field("body_size", &self.body_size) + .finish() + } +} + fn split_at_mut(arr: &mut [T], idx: usize) -> Result<(&mut [T], &mut [T]), ECIESError> { if idx > arr.len() { return Err(ECIESErrorImpl::OutOfBounds { idx, len: arr.len() }.into()) From be12177a2fe8a5ebbd1186432749df9d90b750bf Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 13 Jun 2024 22:46:30 +0200 Subject: [PATCH 032/405] chore: rm HasRemoteAddr trait (#8809) --- Cargo.lock | 1 - crates/net/common/Cargo.toml | 2 +- crates/net/common/src/lib.rs | 3 --- crates/net/common/src/stream.rs | 13 ------------- crates/net/ecies/Cargo.toml | 1 - crates/net/ecies/src/stream.rs | 6 ++---- crates/net/network/src/session/mod.rs | 3 +-- 7 files changed, 4 insertions(+), 25 deletions(-) delete mode 100644 crates/net/common/src/stream.rs diff --git a/Cargo.lock b/Cargo.lock index 6624aeac3b26..c5280bf95297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6844,7 +6844,6 @@ dependencies = [ "hmac 0.12.1", "pin-project", "rand 0.8.5", - "reth-net-common", "reth-network-peers", "reth-primitives", "secp256k1 0.28.2", diff --git a/crates/net/common/Cargo.toml b/crates/net/common/Cargo.toml index c477725b559c..975c2bdecc74 100644 --- a/crates/net/common/Cargo.toml +++ b/crates/net/common/Cargo.toml @@ -16,4 +16,4 @@ workspace = true alloy-primitives.workspace = true # async -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = ["time"] } diff --git a/crates/net/common/src/lib.rs b/crates/net/common/src/lib.rs index 9706d36620b4..3020abf26d66 100644 --- a/crates/net/common/src/lib.rs +++ b/crates/net/common/src/lib.rs @@ -10,7 +10,4 @@ pub mod ban_list; -/// Traits related to tokio streams -pub mod stream; - pub mod ratelimit; diff --git a/crates/net/common/src/stream.rs b/crates/net/common/src/stream.rs deleted file mode 100644 index 4cf6f12bb1de..000000000000 --- a/crates/net/common/src/stream.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::net::SocketAddr; -use tokio::net::TcpStream; -/// This trait is for instrumenting a `TCPStream` with a socket addr -pub trait HasRemoteAddr { - /// Maybe returns a [`SocketAddr`] - fn remote_addr(&self) -> Option; -} - -impl HasRemoteAddr for TcpStream { - fn remote_addr(&self) -> Option { - self.peer_addr().ok() - } -} diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index d7800c74dbba..2123bf48ffd2 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -12,7 +12,6 @@ workspace = true [dependencies] reth-primitives.workspace = true -reth-net-common.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } alloy-rlp = { workspace = true, features = ["derive"] } diff --git a/crates/net/ecies/src/stream.rs b/crates/net/ecies/src/stream.rs index 02c834fe0dcf..ca43fd454081 100644 --- a/crates/net/ecies/src/stream.rs +++ b/crates/net/ecies/src/stream.rs @@ -4,7 +4,6 @@ use crate::{ codec::ECIESCodec, error::ECIESErrorImpl, ECIESError, EgressECIESValue, IngressECIESValue, }; use futures::{ready, Sink, SinkExt}; -use reth_net_common::stream::HasRemoteAddr; use reth_primitives::{ bytes::{Bytes, BytesMut}, B512 as PeerId, @@ -38,10 +37,10 @@ pub struct ECIESStream { impl ECIESStream where - Io: AsyncRead + AsyncWrite + Unpin + HasRemoteAddr, + Io: AsyncRead + AsyncWrite + Unpin, { /// Connect to an `ECIES` server - #[instrument(skip(transport, secret_key), fields(peer=&*format!("{:?}", transport.remote_addr())))] + #[instrument(skip(transport, secret_key))] pub async fn connect( transport: Io, secret_key: SecretKey, @@ -98,7 +97,6 @@ where } /// Listen on a just connected ECIES client - #[instrument(skip_all, fields(peer=&*format!("{:?}", transport.remote_addr())))] pub async fn incoming(transport: Io, secret_key: SecretKey) -> Result { let ecies = ECIESCodec::new_server(secret_key)?; diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index ab5a9634cf57..4f6be86317a0 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -15,7 +15,6 @@ use reth_eth_wire::{ UnauthedP2PStream, }; use reth_metrics::common::mpsc::MeteredPollSender; -use reth_net_common::stream::HasRemoteAddr; use reth_network_peers::PeerId; use reth_primitives::{ForkFilter, ForkId, ForkTransition, Head}; use reth_tasks::TaskSpawner; @@ -927,7 +926,7 @@ async fn authenticate( /// Returns an [`ECIESStream`] if it can be built. If not, send a /// [`PendingSessionEvent::EciesAuthError`] and returns `None` -async fn get_eciess_stream( +async fn get_eciess_stream( stream: Io, secret_key: SecretKey, direction: Direction, From 23fc18db251381b79a17c122e28f49d6a7a6436a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 13 Jun 2024 22:46:30 +0200 Subject: [PATCH 033/405] chore: use sha2 from workspace (#8811) --- crates/net/ecies/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index 2123bf48ffd2..6e8670d89457 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -35,7 +35,7 @@ ctr = "0.9.2" digest = "0.10.5" secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } concat-kdf = "0.1.0" -sha2 = "0.10.6" +sha2.workspace = true sha3 = "0.10.5" aes = "0.8.1" hmac = "0.12.1" From a568535b8508f17442bbc10c9d9d90d71a7caf2c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 13 Jun 2024 23:16:11 +0200 Subject: [PATCH 034/405] chore(deps): replace reth-primitives in discv4 (#8813) --- Cargo.lock | 2 ++ crates/net/discv4/Cargo.toml | 5 ++++- crates/net/discv4/src/config.rs | 3 ++- crates/net/discv4/src/lib.rs | 5 +++-- crates/net/discv4/src/node.rs | 4 ++-- crates/net/discv4/src/proto.rs | 11 ++++++----- crates/net/discv4/src/test_utils.rs | 5 +++-- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5280bf95297..9f1f7ef61111 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6695,6 +6695,7 @@ dependencies = [ name = "reth-discv4" version = "1.0.0-rc.1" dependencies = [ + "alloy-primitives", "alloy-rlp", "assert_matches", "discv5", @@ -6702,6 +6703,7 @@ dependencies = [ "generic-array", "parking_lot 0.12.3", "rand 0.8.5", + "reth-ethereum-forks", "reth-net-common", "reth-net-nat", "reth-network-peers", diff --git a/crates/net/discv4/Cargo.toml b/crates/net/discv4/Cargo.toml index fa6a396cbb04..39b19d542799 100644 --- a/crates/net/discv4/Cargo.toml +++ b/crates/net/discv4/Cargo.toml @@ -13,12 +13,13 @@ workspace = true [dependencies] # reth -reth-primitives.workspace = true reth-net-common.workspace = true +reth-ethereum-forks.workspace = true reth-net-nat.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } # ethereum +alloy-primitives.workspace = true alloy-rlp = { workspace = true, features = ["derive"] } discv5.workspace = true secp256k1 = { workspace = true, features = [ @@ -28,6 +29,7 @@ secp256k1 = { workspace = true, features = [ "serde", ] } enr.workspace = true + # async/futures tokio = { workspace = true, features = ["io-util", "net", "time"] } tokio-stream.workspace = true @@ -42,6 +44,7 @@ generic-array.workspace = true serde = { workspace = true, optional = true } [dev-dependencies] +reth-primitives.workspace = true assert_matches.workspace = true rand.workspace = true tokio = { workspace = true, features = ["macros"] } diff --git a/crates/net/discv4/src/config.rs b/crates/net/discv4/src/config.rs index 43030e70a35a..174514d98c67 100644 --- a/crates/net/discv4/src/config.rs +++ b/crates/net/discv4/src/config.rs @@ -3,10 +3,11 @@ //! This basis of this file has been taken from the discv5 codebase: //! +use alloy_primitives::bytes::Bytes; use alloy_rlp::Encodable; use reth_net_common::ban_list::BanList; use reth_net_nat::{NatResolver, ResolveNatInterval}; -use reth_primitives::{bytes::Bytes, NodeRecord}; +use reth_network_peers::NodeRecord; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::{ diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index f0f7a9b0a1aa..52e388a0ab95 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -28,6 +28,7 @@ use crate::{ error::{DecodePacketError, Discv4Error}, proto::{FindNode, Message, Neighbours, Packet, Ping, Pong}, }; +use alloy_primitives::{bytes::Bytes, hex, B256}; use discv5::{ kbucket, kbucket::{ @@ -39,8 +40,8 @@ use discv5::{ use enr::Enr; use parking_lot::Mutex; use proto::{EnrRequest, EnrResponse}; +use reth_ethereum_forks::ForkId; use reth_network_peers::{pk2id, PeerId}; -use reth_primitives::{bytes::Bytes, hex, ForkId, B256}; use secp256k1::SecretKey; use std::{ cell::RefCell, @@ -76,7 +77,7 @@ use node::{kad_key, NodeKey}; mod table; // reexport NodeRecord primitive -pub use reth_primitives::NodeRecord; +pub use reth_network_peers::NodeRecord; #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; diff --git a/crates/net/discv4/src/node.rs b/crates/net/discv4/src/node.rs index 0a8f436c3f0e..242c38832286 100644 --- a/crates/net/discv4/src/node.rs +++ b/crates/net/discv4/src/node.rs @@ -1,6 +1,6 @@ +use alloy_primitives::keccak256; use generic_array::GenericArray; -use reth_network_peers::PeerId; -use reth_primitives::{keccak256, NodeRecord}; +use reth_network_peers::{NodeRecord, PeerId}; /// The key type for the table. #[derive(Debug, Copy, Clone, Eq, PartialEq)] diff --git a/crates/net/discv4/src/proto.rs b/crates/net/discv4/src/proto.rs index c319b164e097..610628d56b4f 100644 --- a/crates/net/discv4/src/proto.rs +++ b/crates/net/discv4/src/proto.rs @@ -1,13 +1,14 @@ //! Discovery v4 protocol implementation. use crate::{error::DecodePacketError, MAX_PACKET_SIZE, MIN_PACKET_SIZE}; -use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header, RlpDecodable, RlpEncodable}; -use enr::Enr; -use reth_network_peers::{pk2id, PeerId}; -use reth_primitives::{ +use alloy_primitives::{ bytes::{Buf, BufMut, Bytes, BytesMut}, - keccak256, EnrForkIdEntry, ForkId, NodeRecord, B256, + keccak256, B256, }; +use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header, RlpDecodable, RlpEncodable}; +use enr::Enr; +use reth_ethereum_forks::{EnrForkIdEntry, ForkId}; +use reth_network_peers::{pk2id, NodeRecord, PeerId}; use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, SecretKey, SECP256K1, diff --git a/crates/net/discv4/src/test_utils.rs b/crates/net/discv4/src/test_utils.rs index 49521ec694ec..06133dba6790 100644 --- a/crates/net/discv4/src/test_utils.rs +++ b/crates/net/discv4/src/test_utils.rs @@ -5,9 +5,10 @@ use crate::{ receive_loop, send_loop, Discv4, Discv4Config, Discv4Service, EgressSender, IngressEvent, IngressReceiver, PeerId, SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS, }; +use alloy_primitives::{hex, B256}; use rand::{thread_rng, Rng, RngCore}; -use reth_network_peers::pk2id; -use reth_primitives::{hex, ForkHash, ForkId, NodeRecord, B256}; +use reth_ethereum_forks::{ForkHash, ForkId}; +use reth_network_peers::{pk2id, NodeRecord}; use secp256k1::{SecretKey, SECP256K1}; use std::{ collections::{HashMap, HashSet}, From a96884d6700635aa5f3999d09c817a08fc7face2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 13 Jun 2024 23:19:49 +0200 Subject: [PATCH 035/405] chore(ecies): replace reth-primitives with alloy-primitives (#8812) --- Cargo.lock | 2 +- crates/net/ecies/Cargo.toml | 7 ++++--- crates/net/ecies/src/algorithm.rs | 10 +++++----- crates/net/ecies/src/codec.rs | 2 +- crates/net/ecies/src/lib.rs | 2 +- crates/net/ecies/src/mac.rs | 2 +- crates/net/ecies/src/stream.rs | 4 ++-- crates/net/ecies/src/util.rs | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f1f7ef61111..39814f166922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6834,6 +6834,7 @@ name = "reth-ecies" version = "1.0.0-rc.1" dependencies = [ "aes 0.8.4", + "alloy-primitives", "alloy-rlp", "block-padding", "byteorder", @@ -6847,7 +6848,6 @@ dependencies = [ "pin-project", "rand 0.8.5", "reth-network-peers", - "reth-primitives", "secp256k1 0.28.2", "sha2 0.10.8", "sha3", diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index 6e8670d89457..eb2a0b023b3f 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -11,13 +11,14 @@ repository.workspace = true workspace = true [dependencies] -reth-primitives.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } -alloy-rlp = { workspace = true, features = ["derive"] } +alloy-primitives = { workspace = true, features = ["rand", "rlp"] } +alloy-rlp = { workspace = true, features = ["derive", "arrayvec"] } + futures.workspace = true thiserror.workspace = true -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = ["time"] } tokio-stream.workspace = true tokio-util = { workspace = true, features = ["codec"] } pin-project.workspace = true diff --git a/crates/net/ecies/src/algorithm.rs b/crates/net/ecies/src/algorithm.rs index 5970e8405eac..83dcc657bce9 100644 --- a/crates/net/ecies/src/algorithm.rs +++ b/crates/net/ecies/src/algorithm.rs @@ -7,16 +7,16 @@ use crate::{ ECIESError, }; use aes::{cipher::StreamCipher, Aes128, Aes256}; +use alloy_primitives::{ + bytes::{BufMut, Bytes, BytesMut}, + B128, B256, B512 as PeerId, +}; use alloy_rlp::{Encodable, Rlp, RlpEncodable, RlpMaxEncodedLen}; use byteorder::{BigEndian, ByteOrder, ReadBytesExt}; use ctr::Ctr64BE; use digest::{crypto_common::KeyIvInit, Digest}; use rand::{thread_rng, Rng}; use reth_network_peers::{id2pk, pk2id}; -use reth_primitives::{ - bytes::{BufMut, Bytes, BytesMut}, - B128, B256, B512 as PeerId, -}; use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, PublicKey, SecretKey, SECP256K1, @@ -735,7 +735,7 @@ impl ECIES { #[cfg(test)] mod tests { use super::*; - use reth_primitives::{b256, hex}; + use alloy_primitives::{b256, hex}; #[test] fn ecdh() { diff --git a/crates/net/ecies/src/codec.rs b/crates/net/ecies/src/codec.rs index 890113fd69f8..7ad30d38d0d9 100644 --- a/crates/net/ecies/src/codec.rs +++ b/crates/net/ecies/src/codec.rs @@ -1,5 +1,5 @@ use crate::{algorithm::ECIES, ECIESError, EgressECIESValue, IngressECIESValue}; -use reth_primitives::{BytesMut, B512 as PeerId}; +use alloy_primitives::{bytes::BytesMut, B512 as PeerId}; use secp256k1::SecretKey; use std::{fmt::Debug, io}; use tokio_util::codec::{Decoder, Encoder}; diff --git a/crates/net/ecies/src/lib.rs b/crates/net/ecies/src/lib.rs index 07fb044c5cde..378398d6ba30 100644 --- a/crates/net/ecies/src/lib.rs +++ b/crates/net/ecies/src/lib.rs @@ -18,7 +18,7 @@ pub use error::ECIESError; mod codec; -use reth_primitives::{ +use alloy_primitives::{ bytes::{Bytes, BytesMut}, B512 as PeerId, }; diff --git a/crates/net/ecies/src/mac.rs b/crates/net/ecies/src/mac.rs index 30baa298c965..03847d091eed 100644 --- a/crates/net/ecies/src/mac.rs +++ b/crates/net/ecies/src/mac.rs @@ -10,11 +10,11 @@ //! For more information, refer to the [Ethereum MAC specification](https://github.com/ethereum/devp2p/blob/master/rlpx.md#mac). use aes::Aes256Enc; +use alloy_primitives::{B128, B256}; use block_padding::NoPadding; use cipher::BlockEncrypt; use digest::KeyInit; use generic_array::GenericArray; -use reth_primitives::{B128, B256}; use sha3::{Digest, Keccak256}; use typenum::U16; diff --git a/crates/net/ecies/src/stream.rs b/crates/net/ecies/src/stream.rs index ca43fd454081..dbd7577a35a8 100644 --- a/crates/net/ecies/src/stream.rs +++ b/crates/net/ecies/src/stream.rs @@ -3,11 +3,11 @@ use crate::{ codec::ECIESCodec, error::ECIESErrorImpl, ECIESError, EgressECIESValue, IngressECIESValue, }; -use futures::{ready, Sink, SinkExt}; -use reth_primitives::{ +use alloy_primitives::{ bytes::{Bytes, BytesMut}, B512 as PeerId, }; +use futures::{ready, Sink, SinkExt}; use secp256k1::SecretKey; use std::{ fmt::Debug, diff --git a/crates/net/ecies/src/util.rs b/crates/net/ecies/src/util.rs index f6b30288a4ca..cffd1f19dede 100644 --- a/crates/net/ecies/src/util.rs +++ b/crates/net/ecies/src/util.rs @@ -1,7 +1,7 @@ //! Utility functions for hashing and encoding. +use alloy_primitives::B256; use hmac::{Hmac, Mac}; -use reth_primitives::B256; use sha2::{Digest, Sha256}; /// Hashes the input data with SHA256. From 560080ee1990a14194f4e6da969f5a7e1b95a236 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 13 Jun 2024 23:51:56 +0200 Subject: [PATCH 036/405] chore(deps): replace reth-primitives in dns (#8814) --- Cargo.lock | 3 +++ crates/net/dns/Cargo.toml | 5 ++++- crates/net/dns/src/lib.rs | 8 +++++--- crates/net/dns/src/tree.rs | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39814f166922..ffecc04e5bb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6746,12 +6746,15 @@ dependencies = [ name = "reth-dns-discovery" version = "1.0.0-rc.1" dependencies = [ + "alloy-chains", + "alloy-primitives", "alloy-rlp", "data-encoding", "enr", "linked_hash_set", "parking_lot 0.12.3", "rand 0.8.5", + "reth-ethereum-forks", "reth-net-common", "reth-network-peers", "reth-primitives", diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index 7859383014d0..aacba3f88189 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -13,11 +13,12 @@ workspace = true [dependencies] # reth -reth-primitives.workspace = true +reth-ethereum-forks.workspace = true reth-net-common.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } # ethereum +alloy-primitives.workspace = true secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery", "serde"] } enr.workspace = true @@ -39,7 +40,9 @@ serde = { workspace = true, optional = true } serde_with = { version = "3.3.0", optional = true } [dev-dependencies] +reth-primitives.workspace = true alloy-rlp.workspace = true +alloy-chains.workspace = true tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] } reth-tracing.workspace = true rand.workspace = true diff --git a/crates/net/dns/src/lib.rs b/crates/net/dns/src/lib.rs index 4edcdbeb3172..432cef45184e 100644 --- a/crates/net/dns/src/lib.rs +++ b/crates/net/dns/src/lib.rs @@ -22,8 +22,8 @@ use crate::{ pub use config::DnsDiscoveryConfig; use enr::Enr; use error::ParseDnsEntryError; -use reth_network_peers::pk2id; -use reth_primitives::{EnrForkIdEntry, ForkId, NodeRecord}; +use reth_ethereum_forks::{EnrForkIdEntry, ForkId}; +use reth_network_peers::{pk2id, NodeRecord}; use schnellru::{ByLength, LruMap}; use secp256k1::SecretKey; use std::{ @@ -411,9 +411,11 @@ fn convert_enr_node_record(enr: &Enr) -> Option mod tests { use super::*; use crate::tree::TreeRootEntry; + use alloy_chains::Chain; use alloy_rlp::{Decodable, Encodable}; use enr::EnrKey; - use reth_primitives::{Chain, ForkHash, Hardfork, MAINNET}; + use reth_ethereum_forks::{ForkHash, Hardfork}; + use reth_primitives::MAINNET; use secp256k1::rand::thread_rng; use std::{future::poll_fn, net::Ipv4Addr}; diff --git a/crates/net/dns/src/tree.rs b/crates/net/dns/src/tree.rs index 0491be423127..80182b694f80 100644 --- a/crates/net/dns/src/tree.rs +++ b/crates/net/dns/src/tree.rs @@ -21,9 +21,9 @@ use crate::error::{ ParseDnsEntryError::{FieldNotFound, UnknownEntry}, ParseEntryResult, }; +use alloy_primitives::{hex, Bytes}; use data_encoding::{BASE32_NOPAD, BASE64URL_NOPAD}; use enr::{Enr, EnrKey, EnrKeyUnambiguous, EnrPublicKey, Error as EnrError}; -use reth_primitives::{hex, Bytes}; use secp256k1::SecretKey; #[cfg(feature = "serde")] use serde_with::{DeserializeFromStr, SerializeDisplay}; From bc15e6c03a9e678d1a2320b4f550537011e42835 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 14 Jun 2024 01:32:16 +0200 Subject: [PATCH 037/405] fix: truncate request vectors when reverting exec outcome (#8815) Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> --- crates/evm/execution-types/src/bundle.rs | 52 ++++++++++++++++-------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/crates/evm/execution-types/src/bundle.rs b/crates/evm/execution-types/src/bundle.rs index 6f1e5995da48..d6338d762ce2 100644 --- a/crates/evm/execution-types/src/bundle.rs +++ b/crates/evm/execution-types/src/bundle.rs @@ -247,6 +247,8 @@ impl ExecutionOutcome { // remove receipts self.receipts.truncate(new_len); + // remove requests + self.requests.truncate(new_len); // Revert last n reverts. self.bundle.revert(rm_trx); @@ -595,37 +597,51 @@ mod tests { #[test] fn test_revert_to() { + // Create a random receipt object + let receipt = Receipt { + tx_type: TxType::Legacy, + cumulative_gas_used: 46913, + logs: vec![], + success: true, + #[cfg(feature = "optimism")] + deposit_nonce: Some(18), + #[cfg(feature = "optimism")] + deposit_receipt_version: Some(34), + }; + // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { - receipt_vec: vec![vec![Some(Receipt { - tx_type: TxType::Legacy, - cumulative_gas_used: 46913, - logs: vec![], - success: true, - #[cfg(feature = "optimism")] - deposit_nonce: Some(18), - #[cfg(feature = "optimism")] - deposit_receipt_version: Some(34), - })]], + receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]], }; // Define the first block number let first_block = 123; + // Create a DepositRequest object with specific attributes. + let request = Request::DepositRequest(DepositRequest { + pubkey: FixedBytes::<48>::from([1; 48]), + withdrawal_credentials: B256::from([0; 32]), + amount: 1111, + signature: FixedBytes::<96>::from([2; 96]), + index: 222, + }); + + // Create a vector of Requests containing the request. + let requests = vec![Requests(vec![request]), Requests(vec![request])]; + // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block - let mut exec_res = ExecutionOutcome { - bundle: Default::default(), - receipts: receipts.clone(), - requests: vec![], - first_block, - }; + let mut exec_res = + ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block }; // Assert that the revert_to method returns true when reverting to the initial block number. assert!(exec_res.revert_to(123)); - // Assert that the receipts remain unchanged after reverting to the initial block number. - assert_eq!(exec_res.receipts, receipts); + // Assert that the receipts are properly cut after reverting to the initial block number. + assert_eq!(exec_res.receipts, Receipts { receipt_vec: vec![vec![Some(receipt)]] }); + + // Assert that the requests are properly cut after reverting to the initial block number. + assert_eq!(exec_res.requests, vec![Requests(vec![request])]); // Assert that the revert_to method returns false when attempting to revert to a block // number greater than the initial block number. From fc770423b31a0ec6638c836d14c182af945efd92 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:07:39 +0200 Subject: [PATCH 038/405] fix(ci): add missing `const fn` (#8822) --- crates/blockchain-tree/src/canonical_chain.rs | 2 +- crates/blockchain-tree/src/noop.rs | 2 +- crates/consensus/auto-seal/src/lib.rs | 2 +- crates/consensus/beacon/src/engine/message.rs | 2 +- crates/consensus/beacon/src/engine/sync.rs | 2 +- crates/consensus/beacon/src/engine/test_utils.rs | 2 +- crates/ethereum/consensus/src/lib.rs | 2 +- crates/ethereum/engine-primitives/src/payload.rs | 2 +- crates/ethereum/evm/src/execute.rs | 4 ++-- crates/net/network/src/transactions/fetcher.rs | 2 +- crates/net/p2p/src/test_utils/headers.rs | 2 +- crates/node-core/src/args/network.rs | 2 +- crates/node-core/src/engine/engine_store.rs | 2 +- crates/node-core/src/exit.rs | 2 +- crates/node/builder/src/builder/states.rs | 2 +- crates/optimism/evm/src/execute.rs | 4 ++-- crates/optimism/payload/src/payload.rs | 2 +- crates/payload/basic/src/lib.rs | 4 ++-- crates/payload/validator/src/lib.rs | 2 +- crates/primitives/src/block.rs | 2 +- crates/primitives/src/withdrawal.rs | 2 +- crates/rpc/ipc/src/server/future.rs | 2 +- crates/rpc/rpc-types/src/mev.rs | 2 +- crates/rpc/rpc/src/admin.rs | 2 +- crates/stages/api/src/test_utils.rs | 2 +- crates/stages/stages/src/test_utils/set.rs | 2 +- crates/stages/types/src/checkpoints.rs | 2 +- crates/storage/db/src/implementation/mdbx/cursor.rs | 2 +- crates/storage/db/src/tables/raw.rs | 4 ++-- crates/storage/provider/src/providers/database/provider.rs | 4 ++-- crates/tasks/src/shutdown.rs | 2 +- crates/transaction-pool/src/test_utils/gen.rs | 4 ++-- crates/transaction-pool/src/test_utils/mock.rs | 2 +- crates/trie/common/src/proofs.rs | 2 +- 34 files changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/blockchain-tree/src/canonical_chain.rs b/crates/blockchain-tree/src/canonical_chain.rs index ab91ee5476a1..8a9893a1807f 100644 --- a/crates/blockchain-tree/src/canonical_chain.rs +++ b/crates/blockchain-tree/src/canonical_chain.rs @@ -12,7 +12,7 @@ pub(crate) struct CanonicalChain { } impl CanonicalChain { - pub(crate) fn new(chain: BTreeMap) -> Self { + pub(crate) const fn new(chain: BTreeMap) -> Self { Self { chain } } diff --git a/crates/blockchain-tree/src/noop.rs b/crates/blockchain-tree/src/noop.rs index 3ff14ca04f06..d92131dc8ac2 100644 --- a/crates/blockchain-tree/src/noop.rs +++ b/crates/blockchain-tree/src/noop.rs @@ -27,7 +27,7 @@ pub struct NoopBlockchainTree { impl NoopBlockchainTree { /// Create a new `NoopBlockchainTree` with a canon state notification sender. - pub fn with_canon_state_notifications( + pub const fn with_canon_state_notifications( canon_state_notification_sender: CanonStateNotificationSender, ) -> Self { Self { canon_state_notification_sender: Some(canon_state_notification_sender) } diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index ada2936ff1b2..d95d96770ac0 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -56,7 +56,7 @@ pub struct AutoSealConsensus { impl AutoSealConsensus { /// Create a new instance of [`AutoSealConsensus`] - pub fn new(chain_spec: Arc) -> Self { + pub const fn new(chain_spec: Arc) -> Self { Self { chain_spec } } } diff --git a/crates/consensus/beacon/src/engine/message.rs b/crates/consensus/beacon/src/engine/message.rs index 4dac9758372e..052b275c181d 100644 --- a/crates/consensus/beacon/src/engine/message.rs +++ b/crates/consensus/beacon/src/engine/message.rs @@ -86,7 +86,7 @@ impl OnForkChoiceUpdated { } /// If the forkchoice update was successful and no payload attributes were provided, this method - pub(crate) fn updated_with_pending_payload_id( + pub(crate) const fn updated_with_pending_payload_id( payload_status: PayloadStatus, pending_payload_id: oneshot::Receiver>, ) -> Self { diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index 5299a972f982..5ab3ab3dbd2f 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -438,7 +438,7 @@ mod tests { impl TestPipelineBuilder { /// Create a new [`TestPipelineBuilder`]. - fn new() -> Self { + const fn new() -> Self { Self { pipeline_exec_outputs: VecDeque::new(), executor_results: Vec::new(), diff --git a/crates/consensus/beacon/src/engine/test_utils.rs b/crates/consensus/beacon/src/engine/test_utils.rs index b040f87d4bcb..8158a35f31ab 100644 --- a/crates/consensus/beacon/src/engine/test_utils.rs +++ b/crates/consensus/beacon/src/engine/test_utils.rs @@ -57,7 +57,7 @@ pub struct TestEnv { } impl TestEnv { - fn new( + const fn new( db: DB, tip_rx: watch::Receiver, engine_handle: BeaconConsensusEngineHandle, diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 2772bd8f0f70..dc9821d4f6d9 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -32,7 +32,7 @@ pub struct EthBeaconConsensus { impl EthBeaconConsensus { /// Create a new instance of [`EthBeaconConsensus`] - pub fn new(chain_spec: Arc) -> Self { + pub const fn new(chain_spec: Arc) -> Self { Self { chain_spec } } } diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index a802622002ab..e68630f4c266 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -39,7 +39,7 @@ pub struct EthBuiltPayload { impl EthBuiltPayload { /// Initializes the payload with the given initial block. - pub fn new(id: PayloadId, block: SealedBlock, fees: U256) -> Self { + pub const fn new(id: PayloadId, block: SealedBlock, fees: U256) -> Self { Self { id, block, fees, sidecars: Vec::new() } } diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index e9b1243f78a4..0013a0c6638e 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -54,7 +54,7 @@ impl EthExecutorProvider { impl EthExecutorProvider { /// Creates a new executor provider. - pub fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { + pub const fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { Self { chain_spec, evm_config } } } @@ -236,7 +236,7 @@ pub struct EthBlockExecutor { impl EthBlockExecutor { /// Creates a new Ethereum block executor. - pub fn new(chain_spec: Arc, evm_config: EvmConfig, state: State) -> Self { + pub const fn new(chain_spec: Arc, evm_config: EvmConfig, state: State) -> Self { Self { executor: EthEvmExecutor { chain_spec, evm_config }, state } } diff --git a/crates/net/network/src/transactions/fetcher.rs b/crates/net/network/src/transactions/fetcher.rs index e1f89a6a4cdd..41081b191515 100644 --- a/crates/net/network/src/transactions/fetcher.rs +++ b/crates/net/network/src/transactions/fetcher.rs @@ -1152,7 +1152,7 @@ pub struct GetPooledTxRequestFut { impl GetPooledTxRequestFut { #[inline] - fn new( + const fn new( peer_id: PeerId, requested_hashes: RequestTxHashes, response: oneshot::Receiver>, diff --git a/crates/net/p2p/src/test_utils/headers.rs b/crates/net/p2p/src/test_utils/headers.rs index 706ceded9909..73dd04849d44 100644 --- a/crates/net/p2p/src/test_utils/headers.rs +++ b/crates/net/p2p/src/test_utils/headers.rs @@ -38,7 +38,7 @@ pub struct TestHeaderDownloader { impl TestHeaderDownloader { /// Instantiates the downloader with the mock responses - pub fn new( + pub const fn new( client: TestHeadersClient, consensus: Arc, limit: u64, diff --git a/crates/node-core/src/args/network.rs b/crates/node-core/src/args/network.rs index 93e4d8db1b12..c91e864048f4 100644 --- a/crates/node-core/src/args/network.rs +++ b/crates/node-core/src/args/network.rs @@ -205,7 +205,7 @@ impl NetworkArgs { /// Sets the p2p and discovery ports to zero, allowing the OD to assign a random unused port /// when network components bind to sockets. - pub fn with_unused_ports(mut self) -> Self { + pub const fn with_unused_ports(mut self) -> Self { self = self.with_unused_p2p_port(); self.discovery = self.discovery.with_unused_discovery_port(); self diff --git a/crates/node-core/src/engine/engine_store.rs b/crates/node-core/src/engine/engine_store.rs index 21e3c370a855..c6e2b65c5be0 100644 --- a/crates/node-core/src/engine/engine_store.rs +++ b/crates/node-core/src/engine/engine_store.rs @@ -129,7 +129,7 @@ pub struct EngineStoreStream { impl EngineStoreStream { /// Create new engine store stream wrapper. - pub fn new(stream: S, path: PathBuf) -> Self { + pub const fn new(stream: S, path: PathBuf) -> Self { Self { stream, store: EngineMessageStore::new(path) } } } diff --git a/crates/node-core/src/exit.rs b/crates/node-core/src/exit.rs index 85adf6cb6607..7957af1854fc 100644 --- a/crates/node-core/src/exit.rs +++ b/crates/node-core/src/exit.rs @@ -22,7 +22,7 @@ pub struct NodeExitFuture { impl NodeExitFuture { /// Create a new `NodeExitFuture`. - pub fn new( + pub const fn new( consensus_engine_rx: oneshot::Receiver>, terminate: bool, ) -> Self { diff --git a/crates/node/builder/src/builder/states.rs b/crates/node/builder/src/builder/states.rs index a20f7eaa71ed..8f09d5edd94b 100644 --- a/crates/node/builder/src/builder/states.rs +++ b/crates/node/builder/src/builder/states.rs @@ -31,7 +31,7 @@ pub struct NodeBuilderWithTypes { impl NodeBuilderWithTypes { /// Creates a new instance of the node builder with the given configuration and types. - pub fn new(config: NodeConfig, database: T::DB) -> Self { + pub const fn new(config: NodeConfig, database: T::DB) -> Self { Self { config, adapter: NodeTypesAdapter::new(database) } } diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 0f1ed60a17e4..208eddfd1914 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -44,7 +44,7 @@ impl OpExecutorProvider { impl OpExecutorProvider { /// Creates a new executor provider. - pub fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { + pub const fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { Self { chain_spec, evm_config } } } @@ -236,7 +236,7 @@ pub struct OpBlockExecutor { impl OpBlockExecutor { /// Creates a new Ethereum block executor. - pub fn new(chain_spec: Arc, evm_config: EvmConfig, state: State) -> Self { + pub const fn new(chain_spec: Arc, evm_config: EvmConfig, state: State) -> Self { Self { executor: OpEvmExecutor { chain_spec, evm_config }, state } } diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index 7f9bd1ce78ed..b969c14d5fb5 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -179,7 +179,7 @@ pub struct OptimismBuiltPayload { impl OptimismBuiltPayload { /// Initializes the payload with the given initial block. - pub fn new( + pub const fn new( id: PayloadId, block: SealedBlock, fees: U256, diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index d74bf1d77c65..c31a6f0ec7e7 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -636,7 +636,7 @@ pub struct PendingPayload

{ impl

PendingPayload

{ /// Constructs a `PendingPayload` future. - pub fn new( + pub const fn new( cancel: Cancelled, payload: oneshot::Receiver, PayloadBuilderError>>, ) -> Self { @@ -773,7 +773,7 @@ pub struct BuildArguments { impl BuildArguments { /// Create new build arguments. - pub fn new( + pub const fn new( client: Client, pool: Pool, cached_reads: CachedReads, diff --git a/crates/payload/validator/src/lib.rs b/crates/payload/validator/src/lib.rs index eced6a4814ac..d63d0f308564 100644 --- a/crates/payload/validator/src/lib.rs +++ b/crates/payload/validator/src/lib.rs @@ -22,7 +22,7 @@ pub struct ExecutionPayloadValidator { impl ExecutionPayloadValidator { /// Create a new validator. - pub fn new(chain_spec: Arc) -> Self { + pub const fn new(chain_spec: Arc) -> Self { Self { chain_spec } } diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index af0e2680ca56..6d0cf3e1e32c 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -633,7 +633,7 @@ impl From for BlockBody { /// This __does not, and should not guarantee__ that the header is valid with respect to __anything /// else__. #[cfg(any(test, feature = "arbitrary"))] -pub fn generate_valid_header( +pub const fn generate_valid_header( mut header: Header, eip_4844_active: bool, blob_gas_used: u64, diff --git a/crates/primitives/src/withdrawal.rs b/crates/primitives/src/withdrawal.rs index e4d1b37c0523..aff3bbc83c4e 100644 --- a/crates/primitives/src/withdrawal.rs +++ b/crates/primitives/src/withdrawal.rs @@ -15,7 +15,7 @@ pub struct Withdrawals(Vec); impl Withdrawals { /// Create a new Withdrawals instance. - pub fn new(withdrawals: Vec) -> Self { + pub const fn new(withdrawals: Vec) -> Self { Self(withdrawals) } diff --git a/crates/rpc/ipc/src/server/future.rs b/crates/rpc/ipc/src/server/future.rs index 85c69c2a64b0..d6aa675c98f9 100644 --- a/crates/rpc/ipc/src/server/future.rs +++ b/crates/rpc/ipc/src/server/future.rs @@ -33,7 +33,7 @@ use tokio::sync::watch; pub(crate) struct StopHandle(watch::Receiver<()>); impl StopHandle { - pub(crate) fn new(rx: watch::Receiver<()>) -> Self { + pub(crate) const fn new(rx: watch::Receiver<()>) -> Self { Self(rx) } diff --git a/crates/rpc/rpc-types/src/mev.rs b/crates/rpc/rpc-types/src/mev.rs index d95538d1cf03..6963cc09b720 100644 --- a/crates/rpc/rpc-types/src/mev.rs +++ b/crates/rpc/rpc-types/src/mev.rs @@ -400,7 +400,7 @@ pub struct SimBundleLogs { impl SendBundleRequest { /// Create a new bundle request. - pub fn new( + pub const fn new( block_num: u64, max_block: Option, protocol_version: ProtocolVersion, diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index b1b0ca70580c..6c84dc8a386b 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -24,7 +24,7 @@ pub struct AdminApi { impl AdminApi { /// Creates a new instance of `AdminApi`. - pub fn new(network: N, chain_spec: Arc) -> Self { + pub const fn new(network: N, chain_spec: Arc) -> Self { Self { network, chain_spec } } } diff --git a/crates/stages/api/src/test_utils.rs b/crates/stages/api/src/test_utils.rs index 1495c54b0601..8d76cee31bef 100644 --- a/crates/stages/api/src/test_utils.rs +++ b/crates/stages/api/src/test_utils.rs @@ -16,7 +16,7 @@ pub struct TestStage { } impl TestStage { - pub fn new(id: StageId) -> Self { + pub const fn new(id: StageId) -> Self { Self { id, exec_outputs: VecDeque::new(), unwind_outputs: VecDeque::new() } } diff --git a/crates/stages/stages/src/test_utils/set.rs b/crates/stages/stages/src/test_utils/set.rs index df167f5c80e5..d17695168e2c 100644 --- a/crates/stages/stages/src/test_utils/set.rs +++ b/crates/stages/stages/src/test_utils/set.rs @@ -11,7 +11,7 @@ pub struct TestStages { } impl TestStages { - pub fn new( + pub const fn new( exec_outputs: VecDeque>, unwind_outputs: VecDeque>, ) -> Self { diff --git a/crates/stages/types/src/checkpoints.rs b/crates/stages/types/src/checkpoints.rs index bc424282b01c..d7188de1be9f 100644 --- a/crates/stages/types/src/checkpoints.rs +++ b/crates/stages/types/src/checkpoints.rs @@ -21,7 +21,7 @@ pub struct MerkleCheckpoint { impl MerkleCheckpoint { /// Creates a new Merkle checkpoint. - pub fn new( + pub const fn new( target_block: BlockNumber, last_account_key: B256, walker_stack: Vec, diff --git a/crates/storage/db/src/implementation/mdbx/cursor.rs b/crates/storage/db/src/implementation/mdbx/cursor.rs index 956374072bca..e2afbe0c16c7 100644 --- a/crates/storage/db/src/implementation/mdbx/cursor.rs +++ b/crates/storage/db/src/implementation/mdbx/cursor.rs @@ -36,7 +36,7 @@ pub struct Cursor { } impl Cursor { - pub(crate) fn new_with_metrics( + pub(crate) const fn new_with_metrics( inner: reth_libmdbx::Cursor, metrics: Option>, ) -> Self { diff --git a/crates/storage/db/src/tables/raw.rs b/crates/storage/db/src/tables/raw.rs index 1b501b4c5642..1e8fa56b3603 100644 --- a/crates/storage/db/src/tables/raw.rs +++ b/crates/storage/db/src/tables/raw.rs @@ -53,7 +53,7 @@ impl RawKey { /// Creates a raw key from an existing `Vec`. Useful when we already have the encoded /// key. - pub fn from_vec(vec: Vec) -> Self { + pub const fn from_vec(vec: Vec) -> Self { Self { key: vec, _phantom: std::marker::PhantomData } } @@ -118,7 +118,7 @@ impl RawValue { /// Creates a raw value from an existing `Vec`. Useful when we already have the encoded /// value. - pub fn from_vec(vec: Vec) -> Self { + pub const fn from_vec(vec: Vec) -> Self { Self { value: vec, _phantom: std::marker::PhantomData } } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 01c481659905..a305d8ddc9d0 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -115,7 +115,7 @@ impl DatabaseProvider { impl DatabaseProvider { /// Creates a provider with an inner read-write transaction. - pub fn new_rw( + pub const fn new_rw( tx: TX, chain_spec: Arc, static_file_provider: StaticFileProvider, @@ -252,7 +252,7 @@ where impl DatabaseProvider { /// Creates a provider with an inner read-only transaction. - pub fn new( + pub const fn new( tx: TX, chain_spec: Arc, static_file_provider: StaticFileProvider, diff --git a/crates/tasks/src/shutdown.rs b/crates/tasks/src/shutdown.rs index 918a0cf36a57..bd9a50dc9aa3 100644 --- a/crates/tasks/src/shutdown.rs +++ b/crates/tasks/src/shutdown.rs @@ -20,7 +20,7 @@ pub struct GracefulShutdown { } impl GracefulShutdown { - pub(crate) fn new(shutdown: Shutdown, guard: GracefulShutdownGuard) -> Self { + pub(crate) const fn new(shutdown: Shutdown, guard: GracefulShutdownGuard) -> Self { Self { shutdown, guard: Some(guard) } } diff --git a/crates/transaction-pool/src/test_utils/gen.rs b/crates/transaction-pool/src/test_utils/gen.rs index 0b981ea155d6..87c2ba580d38 100644 --- a/crates/transaction-pool/src/test_utils/gen.rs +++ b/crates/transaction-pool/src/test_utils/gen.rs @@ -225,13 +225,13 @@ impl TransactionBuilder { } /// Increments the nonce value of the transaction builder by 1. - pub fn inc_nonce(mut self) -> Self { + pub const fn inc_nonce(mut self) -> Self { self.nonce += 1; self } /// Decrements the nonce value of the transaction builder by 1, avoiding underflow. - pub fn decr_nonce(mut self) -> Self { + pub const fn decr_nonce(mut self) -> Self { self.nonce = self.nonce.saturating_sub(1); self } diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 07afac215e12..9eb8d832ef22 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -1447,7 +1447,7 @@ pub struct MockTransactionSet { impl MockTransactionSet { /// Create a new [`MockTransactionSet`] from a list of transactions - fn new(transactions: Vec) -> Self { + const fn new(transactions: Vec) -> Self { Self { transactions } } diff --git a/crates/trie/common/src/proofs.rs b/crates/trie/common/src/proofs.rs index 36a0c2615f22..11953a48decf 100644 --- a/crates/trie/common/src/proofs.rs +++ b/crates/trie/common/src/proofs.rs @@ -27,7 +27,7 @@ pub struct AccountProof { impl AccountProof { /// Create new account proof entity. - pub fn new(address: Address) -> Self { + pub const fn new(address: Address) -> Self { Self { address, info: None, From f3bd255007a8cd3d73c0d5747affd2c7c6b7b9cc Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Fri, 14 Jun 2024 13:13:36 +0200 Subject: [PATCH 039/405] `reth-primitives`: Hide `alloy-consensus/arbitrary` behind `reth-primitives/arbitrary` (#8821) --- crates/primitives/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index f1b68e4e5a98..7dda61ddd7d1 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -24,7 +24,7 @@ revm-primitives = { workspace = true, features = ["serde"] } # ethereum alloy-chains = { workspace = true, features = ["serde", "rlp"] } -alloy-consensus = { workspace = true, features = ["arbitrary", "serde"] } +alloy-consensus = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["rand", "rlp"] } alloy-rlp = { workspace = true, features = ["arrayvec"] } alloy-rpc-types = { workspace = true, optional = true } @@ -96,6 +96,7 @@ arbitrary = [ "nybbles/arbitrary", "alloy-trie/arbitrary", "alloy-chains/arbitrary", + "alloy-consensus/arbitrary", "alloy-eips/arbitrary", "dep:arbitrary", "dep:proptest", From 68e902db2e22492b0042ba76f13acff32191f2ba Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:21:45 +0200 Subject: [PATCH 040/405] chore: move header validation from `reth-primitives` to consensus crates (#8802) --- crates/consensus/common/src/validation.rs | 138 +++++--- crates/consensus/consensus/src/lib.rs | 65 +++- crates/ethereum/consensus/src/lib.rs | 233 ++++++++++++- crates/optimism/consensus/src/lib.rs | 17 +- crates/primitives/src/header.rs | 392 +--------------------- crates/primitives/src/lib.rs | 2 +- 6 files changed, 390 insertions(+), 457 deletions(-) diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index e06cceec91b4..1b482fd5bdcb 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -9,56 +9,29 @@ use reth_primitives::{ ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader, }; -/// Validate header standalone -pub fn validate_header_standalone( - header: &SealedHeader, - chain_spec: &ChainSpec, -) -> Result<(), ConsensusError> { - // Gas used needs to be less than gas limit. Gas used is going to be checked after execution. +/// Gas used needs to be less than gas limit. Gas used is going to be checked after execution. +#[inline] +pub fn validate_header_gas(header: &SealedHeader) -> Result<(), ConsensusError> { if header.gas_used > header.gas_limit { return Err(ConsensusError::HeaderGasUsedExceedsGasLimit { gas_used: header.gas_used, gas_limit: header.gas_limit, }) } + Ok(()) +} - // Check if base fee is set. +/// Ensure the EIP-1559 base fee is set if the London hardfork is active. +#[inline] +pub fn validate_header_base_fee( + header: &SealedHeader, + chain_spec: &ChainSpec, +) -> Result<(), ConsensusError> { if chain_spec.fork(Hardfork::London).active_at_block(header.number) && header.base_fee_per_gas.is_none() { return Err(ConsensusError::BaseFeeMissing) } - - let wd_root_missing = header.withdrawals_root.is_none() && !chain_spec.is_optimism(); - - // EIP-4895: Beacon chain push withdrawals as operations - if chain_spec.is_shanghai_active_at_timestamp(header.timestamp) && wd_root_missing { - return Err(ConsensusError::WithdrawalsRootMissing) - } else if !chain_spec.is_shanghai_active_at_timestamp(header.timestamp) && - header.withdrawals_root.is_some() - { - return Err(ConsensusError::WithdrawalsRootUnexpected) - } - - // Ensures that EIP-4844 fields are valid once cancun is active. - if chain_spec.is_cancun_active_at_timestamp(header.timestamp) { - validate_4844_header_standalone(header)?; - } else if header.blob_gas_used.is_some() { - return Err(ConsensusError::BlobGasUsedUnexpected) - } else if header.excess_blob_gas.is_some() { - return Err(ConsensusError::ExcessBlobGasUnexpected) - } else if header.parent_beacon_block_root.is_some() { - 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(()) } @@ -175,6 +148,7 @@ pub fn validate_4844_header_standalone(header: &SealedHeader) -> Result<(), Cons /// /// From yellow paper: extraData: An arbitrary byte array containing data relevant to this block. /// This must be 32 bytes or fewer; formally Hx. +#[inline] pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError> { if header.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE { Err(ConsensusError::ExtraDataExceedsMax { len: header.extra_data.len() }) @@ -183,6 +157,78 @@ pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError> } } +/// Validates against the parent hash and number. +/// +/// This function ensures that the header block number is sequential and that the hash of the parent +/// header matches the parent hash in the header. +#[inline] +pub fn validate_against_parent_hash_number( + header: &SealedHeader, + parent: &SealedHeader, +) -> Result<(), ConsensusError> { + // Parent number is consistent. + if parent.number + 1 != header.number { + return Err(ConsensusError::ParentBlockNumberMismatch { + parent_block_number: parent.number, + block_number: header.number, + }) + } + + if parent.hash() != header.parent_hash { + return Err(ConsensusError::ParentHashMismatch( + GotExpected { got: header.parent_hash, expected: parent.hash() }.into(), + )) + } + + Ok(()) +} + +/// Validates the base fee against the parent and EIP-1559 rules. +#[inline] +pub fn validate_against_parent_eip1559_base_fee( + header: &SealedHeader, + parent: &SealedHeader, + chain_spec: &ChainSpec, +) -> Result<(), ConsensusError> { + if chain_spec.fork(Hardfork::London).active_at_block(header.number) { + let base_fee = header.base_fee_per_gas.ok_or(ConsensusError::BaseFeeMissing)?; + + let expected_base_fee = + if chain_spec.fork(Hardfork::London).transitions_at_block(header.number) { + reth_primitives::constants::EIP1559_INITIAL_BASE_FEE + } else { + // This BaseFeeMissing will not happen as previous blocks are checked to have + // them. + parent + .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(header.timestamp)) + .ok_or(ConsensusError::BaseFeeMissing)? + }; + if expected_base_fee != base_fee { + return Err(ConsensusError::BaseFeeDiff(GotExpected { + expected: expected_base_fee, + got: base_fee, + })) + } + } + + Ok(()) +} + +/// Validates the timestamp against the parent to make sure it is in the past. +#[inline] +pub fn validate_against_parent_timestamp( + header: &SealedHeader, + parent: &SealedHeader, +) -> Result<(), ConsensusError> { + if header.is_timestamp_in_past(parent.timestamp) { + return Err(ConsensusError::TimestampIsInPast { + parent_timestamp: parent.timestamp, + timestamp: header.timestamp, + }) + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -410,22 +456,6 @@ mod tests { .return_const(Ok(Some(Withdrawal { index: 2, ..Default::default() }))); } - #[test] - fn shanghai_block_zero_withdrawals() { - // ensures that if shanghai is activated, and we include a block with a withdrawals root, - // that the header is valid - let chain_spec = ChainSpecBuilder::mainnet().shanghai_activated().build(); - - let header = Header { - base_fee_per_gas: Some(1337u64), - withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])), - ..Default::default() - } - .seal_slow(); - - assert_eq!(validate_header_standalone(&header, &chain_spec), Ok(())); - } - #[test] fn cancun_block_incorrect_blob_gas_used() { let chain_spec = ChainSpecBuilder::mainnet().cancun_activated().build(); diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index 5768fee46f27..7aee9f15e706 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -10,8 +10,8 @@ #![cfg_attr(not(feature = "std"), no_std)] use reth_primitives::{ - BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected, GotExpectedBoxed, Header, - HeaderValidationError, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader, + constants::MINIMUM_GAS_LIMIT, BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected, + GotExpectedBoxed, Header, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader, B256, U256, }; @@ -206,6 +206,10 @@ pub enum ConsensusError { block_number: BlockNumber, }, + /// Error when the parent hash does not match the expected parent hash. + #[error("mismatched parent hash: {0}")] + ParentHashMismatch(GotExpectedBoxed), + /// Error when the block timestamp is in the future compared to our clock time. #[error("block timestamp {timestamp} is in the future compared to our clock time {present_timestamp}")] TimestampIsInFuture { @@ -329,9 +333,60 @@ pub enum ConsensusError { #[error(transparent)] InvalidTransaction(#[from] InvalidTransactionError), - /// Error type transparently wrapping `HeaderValidationError`. - #[error(transparent)] - HeaderValidationError(#[from] HeaderValidationError), + /// Error when the block's base fee is different from the expected base fee. + #[error("block base fee mismatch: {0}")] + BaseFeeDiff(GotExpected), + + /// Error when there is an invalid excess blob gas. + #[error( + "invalid excess blob gas: {diff}; \ + parent excess blob gas: {parent_excess_blob_gas}, \ + parent blob gas used: {parent_blob_gas_used}" + )] + ExcessBlobGasDiff { + /// The excess blob gas diff. + diff: GotExpected, + /// The parent excess blob gas. + parent_excess_blob_gas: u64, + /// The parent blob gas used. + parent_blob_gas_used: u64, + }, + + /// Error when the child gas limit exceeds the maximum allowed increase. + #[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")] + GasLimitInvalidIncrease { + /// The parent gas limit. + parent_gas_limit: u64, + /// The child gas limit. + child_gas_limit: u64, + }, + + /// Error indicating that the child gas limit is below the minimum allowed limit. + /// + /// This error occurs when the child gas limit is less than the specified minimum gas limit. + #[error("child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})")] + GasLimitInvalidMinimum { + /// The child gas limit. + child_gas_limit: u64, + }, + + /// Error when the child gas limit exceeds the maximum allowed decrease. + #[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")] + GasLimitInvalidDecrease { + /// The parent gas limit. + parent_gas_limit: u64, + /// The child gas limit. + child_gas_limit: u64, + }, + + /// Error when the block timestamp is in the past compared to the parent timestamp. + #[error("block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}")] + TimestampIsInPast { + /// The parent block's timestamp. + parent_timestamp: u64, + /// The block's timestamp. + timestamp: u64, + }, } impl ConsensusError { diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index dc9821d4f6d9..d37b8bbc33d5 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -10,11 +10,15 @@ use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ - validate_block_pre_execution, validate_header_extradata, validate_header_standalone, + validate_4844_header_standalone, validate_against_parent_eip1559_base_fee, + validate_against_parent_hash_number, validate_against_parent_timestamp, + validate_block_pre_execution, validate_header_base_fee, validate_header_extradata, + validate_header_gas, }; use reth_primitives::{ - BlockWithSenders, Chain, ChainSpec, Hardfork, Header, SealedBlock, SealedHeader, - EMPTY_OMMER_ROOT_HASH, U256, + constants::MINIMUM_GAS_LIMIT, eip4844::calculate_excess_blob_gas, BlockWithSenders, Chain, + ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, + U256, }; use std::{sync::Arc, time::SystemTime}; @@ -35,11 +39,122 @@ impl EthBeaconConsensus { pub const fn new(chain_spec: Arc) -> Self { Self { chain_spec } } + + /// Validates that the EIP-4844 header fields are correct with respect to the parent block. This + /// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and + /// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the + /// parent header fields. + pub fn validate_against_parent_4844( + header: &SealedHeader, + parent: &SealedHeader, + ) -> Result<(), ConsensusError> { + // From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension): + // + // > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas + // > are evaluated as 0. + // + // This means in the first post-fork block, calculate_excess_blob_gas will return 0. + let parent_blob_gas_used = parent.blob_gas_used.unwrap_or(0); + let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or(0); + + if header.blob_gas_used.is_none() { + return Err(ConsensusError::BlobGasUsedMissing) + } + let excess_blob_gas = header.excess_blob_gas.ok_or(ConsensusError::ExcessBlobGasMissing)?; + + let expected_excess_blob_gas = + calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used); + if expected_excess_blob_gas != excess_blob_gas { + return Err(ConsensusError::ExcessBlobGasDiff { + diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas }, + parent_excess_blob_gas, + parent_blob_gas_used, + }) + } + + Ok(()) + } + + /// Checks the gas limit for consistency between parent and self headers. + /// + /// The maximum allowable difference between self and parent gas limits is determined by the + /// parent's gas limit divided by the elasticity multiplier (1024). + fn validate_against_parent_gas_limit( + &self, + header: &SealedHeader, + parent: &SealedHeader, + ) -> Result<(), ConsensusError> { + // Determine the parent gas limit, considering elasticity multiplier on the London fork. + let parent_gas_limit = + if self.chain_spec.fork(Hardfork::London).transitions_at_block(header.number) { + parent.gas_limit * + self.chain_spec + .base_fee_params_at_timestamp(header.timestamp) + .elasticity_multiplier as u64 + } else { + parent.gas_limit + }; + + // Check for an increase in gas limit beyond the allowed threshold. + if header.gas_limit > parent_gas_limit { + if header.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 { + return Err(ConsensusError::GasLimitInvalidIncrease { + parent_gas_limit, + child_gas_limit: header.gas_limit, + }) + } + } + // Check for a decrease in gas limit beyond the allowed threshold. + else if parent_gas_limit - header.gas_limit >= parent_gas_limit / 1024 { + return Err(ConsensusError::GasLimitInvalidDecrease { + parent_gas_limit, + child_gas_limit: header.gas_limit, + }) + } + // Check if the self gas limit is below the minimum required limit. + else if header.gas_limit < MINIMUM_GAS_LIMIT { + return Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: header.gas_limit }) + } + + Ok(()) + } } impl Consensus for EthBeaconConsensus { fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> { - validate_header_standalone(header, &self.chain_spec)?; + validate_header_gas(header)?; + validate_header_base_fee(header, &self.chain_spec)?; + + // EIP-4895: Beacon chain push withdrawals as operations + if self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp) && + header.withdrawals_root.is_none() + { + return Err(ConsensusError::WithdrawalsRootMissing) + } else if !self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp) && + header.withdrawals_root.is_some() + { + return Err(ConsensusError::WithdrawalsRootUnexpected) + } + + // Ensures that EIP-4844 fields are valid once cancun is active. + if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) { + validate_4844_header_standalone(header)?; + } else if header.blob_gas_used.is_some() { + return Err(ConsensusError::BlobGasUsedUnexpected) + } else if header.excess_blob_gas.is_some() { + return Err(ConsensusError::ExcessBlobGasUnexpected) + } else if header.parent_beacon_block_root.is_some() { + return Err(ConsensusError::ParentBeaconBlockRootUnexpected) + } + + if self.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(()) } @@ -48,7 +163,21 @@ impl Consensus for EthBeaconConsensus { header: &SealedHeader, parent: &SealedHeader, ) -> Result<(), ConsensusError> { - header.validate_against_parent(parent, &self.chain_spec).map_err(ConsensusError::from)?; + validate_against_parent_hash_number(header, parent)?; + + validate_against_parent_timestamp(header, parent)?; + + // TODO Check difficulty increment between parent and self + // Ace age did increment it by some formula that we need to follow. + self.validate_against_parent_gas_limit(header, parent)?; + + validate_against_parent_eip1559_base_fee(header, parent, &self.chain_spec)?; + + // ensure that the blob gas fields for this block + if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) { + Self::validate_against_parent_4844(header, parent)?; + } + Ok(()) } @@ -127,3 +256,97 @@ impl Consensus for EthBeaconConsensus { validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests) } } + +#[cfg(test)] +mod tests { + use reth_primitives::{proofs, ChainSpecBuilder, B256}; + + use super::*; + + fn header_with_gas_limit(gas_limit: u64) -> SealedHeader { + let header = Header { gas_limit, ..Default::default() }; + header.seal(B256::ZERO) + } + + #[test] + fn test_valid_gas_limit_increase() { + let parent = header_with_gas_limit(1024 * 10); + let child = header_with_gas_limit(parent.gas_limit + 5); + + assert_eq!( + EthBeaconConsensus::new(Arc::new(ChainSpec::default())) + .validate_against_parent_gas_limit(&child, &parent), + Ok(()) + ); + } + + #[test] + fn test_gas_limit_below_minimum() { + let parent = header_with_gas_limit(MINIMUM_GAS_LIMIT); + let child = header_with_gas_limit(MINIMUM_GAS_LIMIT - 1); + + assert_eq!( + EthBeaconConsensus::new(Arc::new(ChainSpec::default())) + .validate_against_parent_gas_limit(&child, &parent), + Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: child.gas_limit }) + ); + } + + #[test] + fn test_invalid_gas_limit_increase_exceeding_limit() { + let parent = header_with_gas_limit(1024 * 10); + let child = header_with_gas_limit(parent.gas_limit + parent.gas_limit / 1024 + 1); + + assert_eq!( + EthBeaconConsensus::new(Arc::new(ChainSpec::default())) + .validate_against_parent_gas_limit(&child, &parent), + Err(ConsensusError::GasLimitInvalidIncrease { + parent_gas_limit: parent.gas_limit, + child_gas_limit: child.gas_limit, + }) + ); + } + + #[test] + fn test_valid_gas_limit_decrease_within_limit() { + let parent = header_with_gas_limit(1024 * 10); + let child = header_with_gas_limit(parent.gas_limit - 5); + + assert_eq!( + EthBeaconConsensus::new(Arc::new(ChainSpec::default())) + .validate_against_parent_gas_limit(&child, &parent), + Ok(()) + ); + } + + #[test] + fn test_invalid_gas_limit_decrease_exceeding_limit() { + let parent = header_with_gas_limit(1024 * 10); + let child = header_with_gas_limit(parent.gas_limit - parent.gas_limit / 1024 - 1); + + assert_eq!( + EthBeaconConsensus::new(Arc::new(ChainSpec::default())) + .validate_against_parent_gas_limit(&child, &parent), + Err(ConsensusError::GasLimitInvalidDecrease { + parent_gas_limit: parent.gas_limit, + child_gas_limit: child.gas_limit, + }) + ); + } + + #[test] + fn shanghai_block_zero_withdrawals() { + // ensures that if shanghai is activated, and we include a block with a withdrawals root, + // that the header is valid + let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build()); + + let header = Header { + base_fee_per_gas: Some(1337u64), + withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])), + ..Default::default() + } + .seal_slow(); + + assert_eq!(EthBeaconConsensus::new(chain_spec).validate_header(&header), Ok(())); + } +} diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index 5ac9cf924fd7..96ba25a8fcea 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -11,7 +11,9 @@ use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ - validate_block_pre_execution, validate_header_extradata, validate_header_standalone, + validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number, + validate_against_parent_timestamp, validate_block_pre_execution, validate_header_base_fee, + validate_header_extradata, validate_header_gas, }; use reth_primitives::{ BlockWithSenders, ChainSpec, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256, @@ -44,8 +46,8 @@ impl OptimismBeaconConsensus { impl Consensus for OptimismBeaconConsensus { fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> { - validate_header_standalone(header, &self.chain_spec)?; - Ok(()) + validate_header_gas(header)?; + validate_header_base_fee(header, &self.chain_spec) } fn validate_header_against_parent( @@ -53,7 +55,14 @@ impl Consensus for OptimismBeaconConsensus { header: &SealedHeader, parent: &SealedHeader, ) -> Result<(), ConsensusError> { - header.validate_against_parent(parent, &self.chain_spec).map_err(ConsensusError::from)?; + validate_against_parent_hash_number(header, parent)?; + + if self.chain_spec.is_bedrock_active_at_block(header.number) { + validate_against_parent_timestamp(header, parent)?; + } + + validate_against_parent_eip1559_base_fee(header, parent, &self.chain_spec)?; + Ok(()) } diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index abb5e1f34144..11aa79379b0d 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -2,14 +2,10 @@ use crate::block::{generate_valid_header, valid_header_strategy}; use crate::{ basefee::calc_next_block_base_fee, - constants, - constants::{ - ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH, - MINIMUM_GAS_LIMIT, - }, + constants::{ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH}, eip4844::{calc_blob_gasprice, calculate_excess_blob_gas}, - keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes, - ChainSpec, GotExpected, GotExpectedBoxed, Hardfork, B256, B64, U256, + keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes, B256, + B64, U256, }; use alloy_rlp::{length_of_length, Decodable, Encodable}; use bytes::BufMut; @@ -517,92 +513,6 @@ impl Decodable for Header { } } -/// Errors that can occur during header sanity checks. -#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] -pub enum HeaderValidationError { - /// Error when the block number does not match the parent block number. - #[error( - "block number {block_number} does not match parent block number {parent_block_number}" - )] - ParentBlockNumberMismatch { - /// The parent block number. - parent_block_number: BlockNumber, - /// The block number. - block_number: BlockNumber, - }, - - /// Error when the parent hash does not match the expected parent hash. - #[error("mismatched parent hash: {0}")] - ParentHashMismatch(GotExpectedBoxed), - - /// Error when the block timestamp is in the past compared to the parent timestamp. - #[error("block timestamp {timestamp} is in the past compared to the parent timestamp {parent_timestamp}")] - TimestampIsInPast { - /// The parent block's timestamp. - parent_timestamp: u64, - /// The block's timestamp. - timestamp: u64, - }, - - /// Error when the base fee is missing. - #[error("base fee missing")] - BaseFeeMissing, - - /// Error when the block's base fee is different from the expected base fee. - #[error("block base fee mismatch: {0}")] - BaseFeeDiff(GotExpected), - - /// Error when the child gas limit exceeds the maximum allowed decrease. - #[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")] - GasLimitInvalidDecrease { - /// The parent gas limit. - parent_gas_limit: u64, - /// The child gas limit. - child_gas_limit: u64, - }, - - /// Error when the child gas limit exceeds the maximum allowed increase. - #[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")] - GasLimitInvalidIncrease { - /// The parent gas limit. - parent_gas_limit: u64, - /// The child gas limit. - child_gas_limit: u64, - }, - - /// Error indicating that the child gas limit is below the minimum allowed limit. - /// - /// This error occurs when the child gas limit is less than the specified minimum gas limit. - #[error("child gas limit {child_gas_limit} is below the minimum allowed limit ({MINIMUM_GAS_LIMIT})")] - GasLimitInvalidMinimum { - /// The child gas limit. - child_gas_limit: u64, - }, - - /// Error when blob gas used is missing. - #[error("missing blob gas used")] - BlobGasUsedMissing, - - /// Error when excess blob gas is missing. - #[error("missing excess blob gas")] - ExcessBlobGasMissing, - - /// Error when there is an invalid excess blob gas. - #[error( - "invalid excess blob gas: {diff}; \ - parent excess blob gas: {parent_excess_blob_gas}, \ - parent blob gas used: {parent_blob_gas_used}" - )] - ExcessBlobGasDiff { - /// The excess blob gas diff. - diff: GotExpected, - /// The parent excess blob gas. - parent_excess_blob_gas: u64, - /// The parent blob gas used. - parent_blob_gas_used: u64, - }, -} - /// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want /// to modify header. #[main_codec(no_arbitrary)] @@ -670,198 +580,6 @@ impl SealedHeader { self.header.difficulty = difficulty; } - /// Checks the gas limit for consistency between parent and self headers. - /// - /// The maximum allowable difference between self and parent gas limits is determined by the - /// parent's gas limit divided by the elasticity multiplier (1024). - /// - /// This check is skipped if the Optimism flag is enabled in the chain spec, as gas limits on - /// Optimism can adjust instantly. - #[inline(always)] - fn validate_gas_limit( - &self, - parent: &Self, - chain_spec: &ChainSpec, - ) -> Result<(), HeaderValidationError> { - // Determine the parent gas limit, considering elasticity multiplier on the London fork. - let parent_gas_limit = - if chain_spec.fork(Hardfork::London).transitions_at_block(self.number) { - parent.gas_limit * - chain_spec.base_fee_params_at_timestamp(self.timestamp).elasticity_multiplier - as u64 - } else { - parent.gas_limit - }; - - // Check for an increase in gas limit beyond the allowed threshold. - if self.gas_limit > parent_gas_limit { - if self.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 { - return Err(HeaderValidationError::GasLimitInvalidIncrease { - parent_gas_limit, - child_gas_limit: self.gas_limit, - }) - } - } - // Check for a decrease in gas limit beyond the allowed threshold. - else if parent_gas_limit - self.gas_limit >= parent_gas_limit / 1024 { - return Err(HeaderValidationError::GasLimitInvalidDecrease { - parent_gas_limit, - child_gas_limit: self.gas_limit, - }) - } - // Check if the self gas limit is below the minimum required limit. - else if self.gas_limit < MINIMUM_GAS_LIMIT { - return Err(HeaderValidationError::GasLimitInvalidMinimum { - child_gas_limit: self.gas_limit, - }) - } - - Ok(()) - } - - /// Validates the integrity and consistency of a sealed block header in relation to its parent - /// header. - /// - /// This function checks various properties of the sealed header against its parent header and - /// the chain specification. It ensures that the block forms a valid and secure continuation - /// of the blockchain. - /// - /// ## Arguments - /// - /// * `parent` - The sealed header of the parent block. - /// * `chain_spec` - The chain specification providing configuration parameters for the - /// blockchain. - /// - /// ## Errors - /// - /// Returns a [`HeaderValidationError`] if any validation check fails, indicating specific - /// issues with the sealed header. The possible errors include mismatched block numbers, - /// parent hash mismatches, timestamp inconsistencies, gas limit violations, base fee - /// discrepancies (for EIP-1559), and errors related to the blob gas fields (EIP-4844). - /// - /// ## Note - /// - /// Some checks, such as gas limit validation, are conditionally skipped based on the presence - /// of certain features (e.g., Optimism feature) or the activation of specific hardforks. - pub fn validate_against_parent( - &self, - parent: &Self, - chain_spec: &ChainSpec, - ) -> Result<(), HeaderValidationError> { - // Parent number is consistent. - if parent.number + 1 != self.number { - return Err(HeaderValidationError::ParentBlockNumberMismatch { - parent_block_number: parent.number, - block_number: self.number, - }) - } - - if parent.hash != self.parent_hash { - return Err(HeaderValidationError::ParentHashMismatch( - GotExpected { got: self.parent_hash, expected: parent.hash }.into(), - )) - } - - // timestamp in past check - #[cfg(feature = "optimism")] - if chain_spec.is_bedrock_active_at_block(self.header.number) && - self.header.is_timestamp_in_past(parent.timestamp) - { - return Err(HeaderValidationError::TimestampIsInPast { - parent_timestamp: parent.timestamp, - timestamp: self.timestamp, - }) - } - - #[cfg(not(feature = "optimism"))] - if self.header.is_timestamp_in_past(parent.timestamp) { - return Err(HeaderValidationError::TimestampIsInPast { - parent_timestamp: parent.timestamp, - timestamp: self.timestamp, - }) - } - - // TODO Check difficulty increment between parent and self - // Ace age did increment it by some formula that we need to follow. - - if cfg!(feature = "optimism") { - // On Optimism, the gas limit can adjust instantly, so we skip this check - // if the optimism feature is enabled in the chain spec. - if !chain_spec.is_optimism() { - self.validate_gas_limit(parent, chain_spec)?; - } - } else { - self.validate_gas_limit(parent, chain_spec)?; - } - - // EIP-1559 check base fee - if chain_spec.fork(Hardfork::London).active_at_block(self.number) { - let base_fee = self.base_fee_per_gas.ok_or(HeaderValidationError::BaseFeeMissing)?; - - let expected_base_fee = if chain_spec - .fork(Hardfork::London) - .transitions_at_block(self.number) - { - constants::EIP1559_INITIAL_BASE_FEE - } else { - // This BaseFeeMissing will not happen as previous blocks are checked to have - // them. - parent - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(self.timestamp)) - .ok_or(HeaderValidationError::BaseFeeMissing)? - }; - if expected_base_fee != base_fee { - return Err(HeaderValidationError::BaseFeeDiff(GotExpected { - expected: expected_base_fee, - got: base_fee, - })) - } - } - - // ensure that the blob gas fields for this block - if chain_spec.is_cancun_active_at_timestamp(self.timestamp) { - self.validate_4844_header_against_parent(parent)?; - } - - Ok(()) - } - - /// Validates that the EIP-4844 header fields are correct with respect to the parent block. This - /// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and - /// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the - /// parent header fields. - pub fn validate_4844_header_against_parent( - &self, - parent: &Self, - ) -> Result<(), HeaderValidationError> { - // From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension): - // - // > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas - // > are evaluated as 0. - // - // This means in the first post-fork block, calculate_excess_blob_gas will return 0. - let parent_blob_gas_used = parent.blob_gas_used.unwrap_or(0); - let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or(0); - - if self.blob_gas_used.is_none() { - return Err(HeaderValidationError::BlobGasUsedMissing) - } - let excess_blob_gas = - self.excess_blob_gas.ok_or(HeaderValidationError::ExcessBlobGasMissing)?; - - let expected_excess_blob_gas = - calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used); - if expected_excess_blob_gas != excess_blob_gas { - return Err(HeaderValidationError::ExcessBlobGasDiff { - diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas }, - parent_excess_blob_gas, - parent_blob_gas_used, - }) - } - - Ok(()) - } - /// Extract raw header that can be modified. pub fn unseal(self) -> Header { self.header @@ -1035,10 +753,7 @@ impl From for bool { #[cfg(test)] mod tests { use super::{Bytes, Decodable, Encodable, Header, B256}; - use crate::{ - address, b256, bloom, bytes, header::MINIMUM_GAS_LIMIT, hex, Address, ChainSpec, - HeaderValidationError, HeadersDirection, SealedHeader, U256, - }; + use crate::{address, b256, bloom, bytes, hex, Address, HeadersDirection, U256}; use std::str::FromStr; // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 @@ -1301,103 +1016,4 @@ mod tests { Header::decode(&mut data.as_slice()) .expect_err("blob_gas_used size should make this header decoding fail"); } - - #[test] - fn test_valid_gas_limit_increase() { - let parent = SealedHeader { - header: Header { gas_limit: 1024 * 10, ..Default::default() }, - ..Default::default() - }; - let child = SealedHeader { - header: Header { gas_limit: parent.header.gas_limit + 5, ..Default::default() }, - ..Default::default() - }; - let chain_spec = ChainSpec::default(); - - assert_eq!(child.validate_gas_limit(&parent, &chain_spec), Ok(())); - } - - #[test] - fn test_gas_limit_below_minimum() { - let parent = SealedHeader { - header: Header { gas_limit: MINIMUM_GAS_LIMIT, ..Default::default() }, - ..Default::default() - }; - let child = SealedHeader { - header: Header { gas_limit: MINIMUM_GAS_LIMIT - 1, ..Default::default() }, - ..Default::default() - }; - let chain_spec = ChainSpec::default(); - - assert_eq!( - child.validate_gas_limit(&parent, &chain_spec), - Err(HeaderValidationError::GasLimitInvalidMinimum { child_gas_limit: child.gas_limit }) - ); - } - - #[test] - fn test_invalid_gas_limit_increase_exceeding_limit() { - let gas_limit = 1024 * 10; - let parent = SealedHeader { - header: Header { gas_limit, ..Default::default() }, - ..Default::default() - }; - let child = SealedHeader { - header: Header { - gas_limit: parent.header.gas_limit + parent.header.gas_limit / 1024 + 1, - ..Default::default() - }, - ..Default::default() - }; - let chain_spec = ChainSpec::default(); - - assert_eq!( - child.validate_gas_limit(&parent, &chain_spec), - Err(HeaderValidationError::GasLimitInvalidIncrease { - parent_gas_limit: parent.header.gas_limit, - child_gas_limit: child.header.gas_limit, - }) - ); - } - - #[test] - fn test_valid_gas_limit_decrease_within_limit() { - let gas_limit = 1024 * 10; - let parent = SealedHeader { - header: Header { gas_limit, ..Default::default() }, - ..Default::default() - }; - let child = SealedHeader { - header: Header { gas_limit: parent.header.gas_limit - 5, ..Default::default() }, - ..Default::default() - }; - let chain_spec = ChainSpec::default(); - - assert_eq!(child.validate_gas_limit(&parent, &chain_spec), Ok(())); - } - - #[test] - fn test_invalid_gas_limit_decrease_exceeding_limit() { - let gas_limit = 1024 * 10; - let parent = SealedHeader { - header: Header { gas_limit, ..Default::default() }, - ..Default::default() - }; - let child = SealedHeader { - header: Header { - gas_limit: parent.header.gas_limit - parent.header.gas_limit / 1024 - 1, - ..Default::default() - }, - ..Default::default() - }; - let chain_spec = ChainSpec::default(); - - assert_eq!( - child.validate_gas_limit(&parent, &chain_spec), - Err(HeaderValidationError::GasLimitInvalidDecrease { - parent_gas_limit: parent.header.gas_limit, - child_gas_limit: child.header.gas_limit, - }) - ); - } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index d9cb4d326e44..02e86c45987d 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -63,7 +63,7 @@ pub use constants::{ }; pub use error::{GotExpected, GotExpectedBoxed}; pub use genesis::{ChainConfig, Genesis, GenesisAccount}; -pub use header::{Header, HeaderValidationError, HeadersDirection, SealedHeader}; +pub use header::{Header, HeadersDirection, SealedHeader}; pub use integer_list::IntegerList; pub use log::{logs_bloom, Log}; pub use net::{ From b7ce1de00eaf2c793dc967817e5b969948e23800 Mon Sep 17 00:00:00 2001 From: bocalhky <2865836+bocalhky@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:27:02 +0300 Subject: [PATCH 041/405] chore: fix references (#8820) Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com> --- book/run/optimism.md | 2 +- docs/repo/layout.md | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/book/run/optimism.md b/book/run/optimism.md index a44d0b603fc2..b16a7e4b9f77 100644 --- a/book/run/optimism.md +++ b/book/run/optimism.md @@ -101,7 +101,7 @@ op-node \ [l1-el-spec]: https://github.com/ethereum/execution-specs [rollup-node-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/rollup-node.md [op-geth-forkdiff]: https://op-geth.optimism.io -[sequencer]: https://github.com/ethereum-optimism/specs/blob/main/specs/introduction.md#sequencers +[sequencer]: https://github.com/ethereum-optimism/specs/blob/main/specs/background.md#sequencers [op-stack-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs [l2-el-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine.md [deposit-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/deposits.md diff --git a/docs/repo/layout.md b/docs/repo/layout.md index c3c53321fac3..552da3196b19 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -137,11 +137,9 @@ Crates related to building and validating payloads (blocks). ### Primitives -These crates define primitive types or algorithms such as RLP. +These crates define primitive types or algorithms. - [`primitives`](../../crates/primitives): Commonly used types in Reth. -- [`rlp`](../../crates/rlp): An implementation of RLP, forked from an earlier Apache-licensed version of [`fastrlp`][fastrlp] -- [`rlp/rlp-derive`](../../crates/rlp/rlp-derive): Forked from an earlier Apache licenced version of the [`fastrlp-derive`][fastrlp-derive] crate, before it changed licence to GPL. - [`trie`](../../crates/trie): An implementation of a Merkle Patricia Trie used for various roots (e.g. the state root) in Ethereum. ### Misc @@ -154,8 +152,6 @@ Small utility crates. - [`metrics/metrics-derive`](../../crates/metrics/metrics-derive): A derive-style API for creating metrics - [`tracing`](../../crates/tracing): A small utility crate to install a uniform [`tracing`][tracing] subscriber -[fastrlp]: https://crates.io/crates/fastrlp -[fastrlp-derive]: https://crates.io/crates/fastrlp-derive [libmdbx-rs]: https://crates.io/crates/libmdbx [discv4]: https://github.com/ethereum/devp2p/blob/master/discv4.md [jsonrpsee]: https://github.com/paritytech/jsonrpsee/ From 7d80164fadd68489baab87f134192fedf7fdce0d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 13:55:23 +0200 Subject: [PATCH 042/405] chore(deps): use workspace deps (#8824) --- Cargo.toml | 1 + crates/blockchain-tree/Cargo.toml | 2 +- crates/net/dns/Cargo.toml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e55c73f43247..056a3e0d243e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -391,6 +391,7 @@ derive_more = "0.99.17" fdlimit = "0.3.0" eyre = "0.6" generic-array = "0.14" +linked_hash_set = "0.1" tracing = "0.1.0" tracing-appender = "0.2" thiserror = "1.0" diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index 64796939612d..16f84c6599e4 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -39,7 +39,7 @@ metrics.workspace = true # misc aquamarine.workspace = true -linked_hash_set = "0.1.4" +linked_hash_set.workspace = true [dev-dependencies] reth-db = { workspace = true, features = ["test-utils"] } diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index aacba3f88189..5b4b75c2f17a 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -31,13 +31,13 @@ trust-dns-resolver = "0.23" # misc data-encoding = "2" -linked_hash_set = "0.1" +linked_hash_set.workspace = true schnellru.workspace = true thiserror.workspace = true tracing.workspace = true parking_lot.workspace = true serde = { workspace = true, optional = true } -serde_with = { version = "3.3.0", optional = true } +serde_with = { workspace = true, optional = true } [dev-dependencies] reth-primitives.workspace = true From 697cbd8b96280994f843a7ad4578fbb5da5ab2dc Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:10:46 +0200 Subject: [PATCH 043/405] feat: implement `deref` and `derefMut` for Requests (#8816) --- crates/primitives/src/request.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/primitives/src/request.rs b/crates/primitives/src/request.rs index e2ccc9701826..63b861ba4b59 100644 --- a/crates/primitives/src/request.rs +++ b/crates/primitives/src/request.rs @@ -5,6 +5,7 @@ use alloy_eips::eip7685::{Decodable7685, Encodable7685}; use alloy_rlp::{Decodable, Encodable}; use reth_codecs::{main_codec, Compact}; use revm_primitives::Bytes; +use std::ops::{Deref, DerefMut}; /// A list of EIP-7685 requests. #[main_codec] @@ -53,3 +54,17 @@ impl Decodable for Requests { .map(Self)?) } } + +impl Deref for Requests { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Requests { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} From 579da56e9792a45b52b4a77929b43d3da995c9d9 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:41:44 +0200 Subject: [PATCH 044/405] fix: split requests in `split_at` execution outcome (#8823) --- crates/evm/execution-types/src/bundle.rs | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/crates/evm/execution-types/src/bundle.rs b/crates/evm/execution-types/src/bundle.rs index d6338d762ce2..06cb40b487d6 100644 --- a/crates/evm/execution-types/src/bundle.rs +++ b/crates/evm/execution-types/src/bundle.rs @@ -276,6 +276,11 @@ impl ExecutionOutcome { // Truncate higher state to [at..]. let at_idx = higher_state.block_number_to_index(at).unwrap(); higher_state.receipts = higher_state.receipts.split_off(at_idx).into(); + // Ensure that there are enough requests to truncate. + // Sometimes we just have receipts and no requests. + if at_idx < higher_state.requests.len() { + higher_state.requests = higher_state.requests.split_off(at_idx); + } higher_state.bundle.take_n_reverts(at_idx); higher_state.first_block = at; @@ -704,4 +709,77 @@ mod tests { } ); } + + #[test] + fn test_split_at_execution_outcome() { + // Create a random receipt object + let receipt = Receipt { + tx_type: TxType::Legacy, + cumulative_gas_used: 46913, + logs: vec![], + success: true, + #[cfg(feature = "optimism")] + deposit_nonce: Some(18), + #[cfg(feature = "optimism")] + deposit_receipt_version: Some(34), + }; + + // Create a Receipts object with a vector of receipt vectors + let receipts = Receipts { + receipt_vec: vec![ + vec![Some(receipt.clone())], + vec![Some(receipt.clone())], + vec![Some(receipt.clone())], + ], + }; + + // Define the first block number + let first_block = 123; + + // Create a DepositRequest object with specific attributes. + let request = Request::DepositRequest(DepositRequest { + pubkey: FixedBytes::<48>::from([1; 48]), + withdrawal_credentials: B256::from([0; 32]), + amount: 1111, + signature: FixedBytes::<96>::from([2; 96]), + index: 222, + }); + + // Create a vector of Requests containing the request. + let requests = + vec![Requests(vec![request]), Requests(vec![request]), Requests(vec![request])]; + + // Create a ExecutionOutcome object with the created bundle, receipts, requests, and + // first_block + let exec_res = + ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block }; + + // Split the ExecutionOutcome at block number 124 + let result = exec_res.clone().split_at(124); + + // Define the expected lower ExecutionOutcome after splitting + let lower_execution_outcome = ExecutionOutcome { + bundle: Default::default(), + receipts: Receipts { receipt_vec: vec![vec![Some(receipt.clone())]] }, + requests: vec![Requests(vec![request])], + first_block, + }; + + // Define the expected higher ExecutionOutcome after splitting + let higher_execution_outcome = ExecutionOutcome { + bundle: Default::default(), + receipts: Receipts { + receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt)]], + }, + requests: vec![Requests(vec![request]), Requests(vec![request])], + first_block: 124, + }; + + // Assert that the split result matches the expected lower and higher outcomes + assert_eq!(result.0, Some(lower_execution_outcome)); + assert_eq!(result.1, higher_execution_outcome); + + // Assert that splitting at the first block number returns None for the lower outcome + assert_eq!(exec_res.clone().split_at(123), (None, exec_res)); + } } From f218774950bf1562cbf60e20b162a70267c937d1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 14:52:23 +0200 Subject: [PATCH 045/405] chore: bump rust version 1.79 (#8829) --- .github/workflows/lint.yml | 4 ++-- Cargo.toml | 2 +- README.md | 2 +- clippy.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7011b9555bd5..ffcb7f239a57 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -72,7 +72,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: "1.76" # MSRV + toolchain: "1.79" # MSRV - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -115,7 +115,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: "1.76" # MSRV + toolchain: "1.79" # MSRV - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true diff --git a/Cargo.toml b/Cargo.toml index 056a3e0d243e..a6b79d2ce15c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace.package] version = "1.0.0-rc.1" edition = "2021" -rust-version = "1.76" +rust-version = "1.79" license = "MIT OR Apache-2.0" homepage = "https://paradigmxyz.github.io/reth" repository = "https://github.com/paradigmxyz/reth" diff --git a/README.md b/README.md index 7862ac03f403..00b575a798e2 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ When updating this, also update: - .github/workflows/lint.yml --> -The Minimum Supported Rust Version (MSRV) of this project is [1.76.0](https://blog.rust-lang.org/2024/02/08/Rust-1.76.0.html). +The Minimum Supported Rust Version (MSRV) of this project is [1.79.0](https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html). See the book for detailed instructions on how to [build from source](https://paradigmxyz.github.io/reth/installation/source.html). diff --git a/clippy.toml b/clippy.toml index 7e606c3f1f9f..865dfc7c95a5 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,2 @@ -msrv = "1.76" +msrv = "1.79" too-large-for-stack = 128 From 5c2e868945319d3cfc2fc8c2854b9913047e5d78 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 14 Jun 2024 14:26:25 +0100 Subject: [PATCH 046/405] chore(evm): rename `bundle.rs` to `execution_outcome.rs` (#8832) --- .../execution-types/src/{bundle.rs => execution_outcome.rs} | 6 +++--- crates/evm/execution-types/src/lib.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename crates/evm/execution-types/src/{bundle.rs => execution_outcome.rs} (99%) diff --git a/crates/evm/execution-types/src/bundle.rs b/crates/evm/execution-types/src/execution_outcome.rs similarity index 99% rename from crates/evm/execution-types/src/bundle.rs rename to crates/evm/execution-types/src/execution_outcome.rs index 06cb40b487d6..aebc8d45b5e8 100644 --- a/crates/evm/execution-types/src/bundle.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -216,17 +216,17 @@ impl ExecutionOutcome { &self.receipts[index] } - /// Is bundle state empty of blocks. + /// Is execution outcome empty. pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Number of blocks in bundle state. + /// Number of blocks in the execution outcome. pub fn len(&self) -> usize { self.receipts.len() } - /// Return first block of the bundle + /// Return first block of the execution outcome pub const fn first_block(&self) -> BlockNumber { self.first_block } diff --git a/crates/evm/execution-types/src/lib.rs b/crates/evm/execution-types/src/lib.rs index 7680e70852d3..0692fa57eb94 100644 --- a/crates/evm/execution-types/src/lib.rs +++ b/crates/evm/execution-types/src/lib.rs @@ -8,8 +8,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -mod bundle; -pub use bundle::*; +mod execution_outcome; +pub use execution_outcome::*; mod chain; pub use chain::*; From 69bf592ec10600f7e12ab94fecada935cdc14342 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:27:16 +0200 Subject: [PATCH 047/405] test: add unit tests for `Chain` execution type (#8826) --- crates/evm/execution-types/src/chain.rs | 82 ++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/crates/evm/execution-types/src/chain.rs b/crates/evm/execution-types/src/chain.rs index 800d34f1068b..97bd6fcf30ce 100644 --- a/crates/evm/execution-types/src/chain.rs +++ b/crates/evm/execution-types/src/chain.rs @@ -488,7 +488,7 @@ pub enum ChainSplit { #[cfg(test)] mod tests { use super::*; - use reth_primitives::B256; + use reth_primitives::{Receipt, Receipts, TxType, B256}; use revm::primitives::{AccountInfo, HashMap}; #[test] @@ -625,4 +625,84 @@ mod tests { ChainSplit::NoSplitPending(chain) ); } + + #[test] + fn receipts_by_block_hash() { + // Create a default SealedBlockWithSenders object + let block: SealedBlockWithSenders = SealedBlockWithSenders::default(); + + // Define block hashes for block1 and block2 + let block1_hash = B256::new([0x01; 32]); + let block2_hash = B256::new([0x02; 32]); + + // Clone the default block into block1 and block2 + let mut block1 = block.clone(); + let mut block2 = block; + + // Set the hashes of block1 and block2 + block1.block.header.set_hash(block1_hash); + block2.block.header.set_hash(block2_hash); + + // Create a random receipt object, receipt1 + let receipt1 = Receipt { + tx_type: TxType::Legacy, + cumulative_gas_used: 46913, + logs: vec![], + success: true, + #[cfg(feature = "optimism")] + deposit_nonce: Some(18), + #[cfg(feature = "optimism")] + deposit_receipt_version: Some(34), + }; + + // Create another random receipt object, receipt2 + let receipt2 = Receipt { + tx_type: TxType::Legacy, + cumulative_gas_used: 1325345, + logs: vec![], + success: true, + #[cfg(feature = "optimism")] + deposit_nonce: Some(18), + #[cfg(feature = "optimism")] + deposit_receipt_version: Some(34), + }; + + // Create a Receipts object with a vector of receipt vectors + let receipts = + Receipts { receipt_vec: vec![vec![Some(receipt1.clone())], vec![Some(receipt2)]] }; + + // Create an ExecutionOutcome object with the created bundle, receipts, an empty requests + // vector, and first_block set to 10 + let execution_outcome = ExecutionOutcome { + bundle: Default::default(), + receipts, + requests: vec![], + first_block: 10, + }; + + // Create a Chain object with a BTreeMap of blocks mapped to their block numbers, + // including block1_hash and block2_hash, and the execution_outcome + let chain = Chain { + blocks: BTreeMap::from([(10, block1), (11, block2)]), + execution_outcome: execution_outcome.clone(), + ..Default::default() + }; + + // Assert that the proper receipt vector is returned for block1_hash + assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1])); + + // Create an ExecutionOutcome object with a single receipt vector containing receipt1 + let execution_outcome1 = ExecutionOutcome { + bundle: Default::default(), + receipts: Receipts { receipt_vec: vec![vec![Some(receipt1)]] }, + requests: vec![], + first_block: 10, + }; + + // Assert that the execution outcome at the first block contains only the first receipt + assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1)); + + // Assert that the execution outcome at the tip block contains the whole execution outcome + assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome)); + } } From 3af5bd857e19a9f33f47bbcdb81f7040bbbe55c0 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 15:38:17 +0200 Subject: [PATCH 048/405] chore: add address constants with 0x prefix (#8833) --- crates/primitives/src/constants/mod.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs index be8ca1564b00..2d1cc6da2bea 100644 --- a/crates/primitives/src/constants/mod.rs +++ b/crates/primitives/src/constants/mod.rs @@ -68,7 +68,7 @@ pub const EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 2; /// Minimum gas limit allowed for transactions. pub const MINIMUM_GAS_LIMIT: u64 = 5000; -/// Deposit contract address +/// Deposit contract address: `0x00000000219ab540356cbb839cbe05303d7705fa` pub const MAINNET_DEPOSIT_CONTRACT: DepositContract = DepositContract::new( address!("00000000219ab540356cbb839cbe05303d7705fa"), 11052984, @@ -164,35 +164,36 @@ pub const ETH_TO_WEI: u128 = FINNEY_TO_WEI * 1000; /// Multiplier for converting mgas to gas. pub const MGAS_TO_GAS: u64 = 1_000_000u64; -/// The Ethereum mainnet genesis hash. +/// The Ethereum mainnet genesis hash: +/// `0x0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3` pub const MAINNET_GENESIS_HASH: B256 = b256!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"); -/// Goerli genesis hash. +/// Goerli genesis hash: `0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a` pub const GOERLI_GENESIS_HASH: B256 = b256!("bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"); -/// Sepolia genesis hash. +/// Sepolia genesis hash: `0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9` pub const SEPOLIA_GENESIS_HASH: B256 = b256!("25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9"); -/// Holesky genesis hash. +/// Holesky genesis hash: `0xff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d` pub const HOLESKY_GENESIS_HASH: B256 = b256!("ff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d"); -/// Testnet genesis hash. +/// Testnet genesis hash: `0x2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c` pub const DEV_GENESIS_HASH: B256 = b256!("2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c"); -/// Keccak256 over empty array. +/// Keccak256 over empty array: `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` pub const KECCAK_EMPTY: B256 = b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); -/// Ommer root of empty list. +/// Ommer root of empty list: `0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347` pub const EMPTY_OMMER_ROOT_HASH: B256 = b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); -/// Root hash of an empty trie. +/// Root hash of an empty trie: `0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421` pub const EMPTY_ROOT_HASH: B256 = b256!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); From ca574edbc8e07cc15bb48593cb6f86aa0d1c7594 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 14 Jun 2024 15:06:03 +0100 Subject: [PATCH 049/405] chore(primitives): use `derive_more` where possible (#8834) --- crates/primitives/benches/integer_list.rs | 11 +--- crates/primitives/src/account.rs | 12 +--- crates/primitives/src/block.rs | 76 +++++++---------------- crates/primitives/src/chain/spec.rs | 9 +-- crates/primitives/src/header.rs | 21 ++----- crates/primitives/src/integer_list.rs | 13 +--- crates/primitives/src/receipt.rs | 37 +---------- crates/primitives/src/request.rs | 33 +--------- crates/primitives/src/withdrawal.rs | 54 +++++----------- 9 files changed, 58 insertions(+), 208 deletions(-) diff --git a/crates/primitives/benches/integer_list.rs b/crates/primitives/benches/integer_list.rs index 6dcec9139747..3ea3543e0599 100644 --- a/crates/primitives/benches/integer_list.rs +++ b/crates/primitives/benches/integer_list.rs @@ -89,20 +89,13 @@ criterion_main!(benches); /// adapted to work with `sucds = "0.8.1"` #[allow(unused, unreachable_pub)] mod elias_fano { + use derive_more::Deref; use std::{fmt, ops::Deref}; use sucds::{mii_sequences::EliasFano, Serializable}; - #[derive(Clone, PartialEq, Eq, Default)] + #[derive(Clone, PartialEq, Eq, Default, Deref)] pub struct IntegerList(pub EliasFano); - impl Deref for IntegerList { - type Target = EliasFano; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl fmt::Debug for IntegerList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let vec: Vec = self.0.iter(0).collect(); diff --git a/crates/primitives/src/account.rs b/crates/primitives/src/account.rs index fead77789fa1..0fa55108e7c3 100644 --- a/crates/primitives/src/account.rs +++ b/crates/primitives/src/account.rs @@ -1,17 +1,17 @@ use crate::revm_primitives::{Bytecode as RevmBytecode, Bytes}; use byteorder::{BigEndian, ReadBytesExt}; use bytes::Buf; +use derive_more::Deref; use reth_codecs::Compact; use revm_primitives::JumpTable; use serde::{Deserialize, Serialize}; -use std::ops::Deref; pub use reth_primitives_traits::Account; /// Bytecode for an account. /// /// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Deref)] pub struct Bytecode(pub RevmBytecode); impl Bytecode { @@ -23,14 +23,6 @@ impl Bytecode { } } -impl Deref for Bytecode { - type Target = RevmBytecode; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - impl Compact for Bytecode { fn to_compact(self, buf: &mut B) -> usize where diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 6d0cf3e1e32c..77664ed1cc2f 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -3,11 +3,11 @@ use crate::{ TransactionSignedEcRecovered, Withdrawals, B256, }; use alloy_rlp::{RlpDecodable, RlpEncodable}; +use derive_more::{Deref, DerefMut}; #[cfg(any(test, feature = "arbitrary"))] use proptest::prelude::{any, prop_compose}; use reth_codecs::derive_arbitrary; use serde::{Deserialize, Serialize}; -use std::ops::Deref; pub use alloy_eips::eip1898::{ BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, @@ -28,12 +28,13 @@ prop_compose! { /// Withdrawals can be optionally included at the end of the RLP encoded message. #[derive_arbitrary(rlp, 25)] #[derive( - Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, RlpEncodable, RlpDecodable, + Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Deref, RlpEncodable, RlpDecodable, )] #[rlp(trailing)] pub struct Block { /// Block header. #[cfg_attr(any(test, feature = "arbitrary"), proptest(strategy = "valid_header_strategy()"))] + #[deref] pub header: Header, /// Transactions in this block. #[cfg_attr( @@ -181,17 +182,12 @@ impl Block { } } -impl Deref for Block { - type Target = Header; - fn deref(&self) -> &Self::Target { - &self.header - } -} - /// Sealed block with senders recovered from transactions. -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Deref, DerefMut)] pub struct BlockWithSenders { /// Block + #[deref] + #[deref_mut] pub block: Block, /// List of senders that match the transactions in the block pub senders: Vec

, @@ -253,30 +249,28 @@ impl BlockWithSenders { } } -impl Deref for BlockWithSenders { - type Target = Block; - fn deref(&self) -> &Self::Target { - &self.block - } -} - -#[cfg(any(test, feature = "test-utils"))] -impl std::ops::DerefMut for BlockWithSenders { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.block - } -} - /// Sealed Ethereum full block. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. #[derive_arbitrary(rlp)] #[derive( - Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, RlpEncodable, RlpDecodable, + Debug, + Clone, + PartialEq, + Eq, + Default, + Serialize, + Deserialize, + Deref, + DerefMut, + RlpEncodable, + RlpDecodable, )] #[rlp(trailing)] pub struct SealedBlock { /// Locked block header. + #[deref] + #[deref_mut] pub header: SealedHeader, /// Transactions with signatures. #[cfg_attr( @@ -450,24 +444,12 @@ impl From for Block { } } -impl Deref for SealedBlock { - type Target = SealedHeader; - fn deref(&self) -> &Self::Target { - &self.header - } -} - -#[cfg(any(test, feature = "test-utils"))] -impl std::ops::DerefMut for SealedBlock { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.header - } -} - /// Sealed block with senders recovered from transactions. -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Deref, DerefMut)] pub struct SealedBlockWithSenders { /// Sealed block + #[deref] + #[deref_mut] pub block: SealedBlock, /// List of senders that match transactions from block. pub senders: Vec
, @@ -521,20 +503,6 @@ impl SealedBlockWithSenders { } } -impl Deref for SealedBlockWithSenders { - type Target = SealedBlock; - fn deref(&self) -> &Self::Target { - &self.block - } -} - -#[cfg(any(test, feature = "test-utils"))] -impl std::ops::DerefMut for SealedBlockWithSenders { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.block - } -} - /// A response to `GetBlockBodies`, containing bodies if any bodies were found. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 74c94b345fca..96852805bfc3 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -10,6 +10,7 @@ use crate::{ Hardfork, Head, Header, NamedChain, NodeRecord, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, MAINNET_DEPOSIT_CONTRACT, U256, }; +use derive_more::From; use once_cell::sync::Lazy; use reth_trie_common::root::state_root_ref_unhashed; use serde::{Deserialize, Serialize}; @@ -484,15 +485,9 @@ impl From for BaseFeeParamsKind { /// A type alias to a vector of tuples of [Hardfork] and [`BaseFeeParams`], sorted by [Hardfork] /// activation order. This is used to specify dynamic EIP-1559 parameters for chains like Optimism. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, From)] pub struct ForkBaseFeeParams(Vec<(Hardfork, BaseFeeParams)>); -impl From> for ForkBaseFeeParams { - fn from(params: Vec<(Hardfork, BaseFeeParams)>) -> Self { - Self(params) - } -} - /// An Ethereum chain specification. /// /// A chain specification describes: diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index 11aa79379b0d..d0d8c3de4e9a 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -9,11 +9,12 @@ use crate::{ }; use alloy_rlp::{length_of_length, Decodable, Encodable}; use bytes::BufMut; +use derive_more::{AsRef, Deref}; #[cfg(any(test, feature = "arbitrary"))] use proptest::prelude::*; use reth_codecs::{add_arbitrary_tests, derive_arbitrary, main_codec, Compact}; use serde::{Deserialize, Serialize}; -use std::{mem, ops::Deref}; +use std::mem; /// Errors that can occur during header sanity checks. #[derive(Debug, PartialEq, Eq)] @@ -517,11 +518,13 @@ impl Decodable for Header { /// to modify header. #[main_codec(no_arbitrary)] #[add_arbitrary_tests(rlp, compact)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref)] pub struct SealedHeader { /// Locked Header hash. hash: BlockHash, /// Locked Header fields. + #[as_ref] + #[deref] header: Header, } @@ -658,20 +661,6 @@ impl Decodable for SealedHeader { } } -impl AsRef
for SealedHeader { - fn as_ref(&self) -> &Header { - &self.header - } -} - -impl Deref for SealedHeader { - type Target = Header; - - fn deref(&self) -> &Self::Target { - &self.header - } -} - /// Represents the direction for a headers request depending on the `reverse` field of the request. /// > The response must contain a number of block headers, of rising number when reverse is 0, /// > falling when 1 diff --git a/crates/primitives/src/integer_list.rs b/crates/primitives/src/integer_list.rs index 4dcb9a9bc9ae..f81afda933fa 100644 --- a/crates/primitives/src/integer_list.rs +++ b/crates/primitives/src/integer_list.rs @@ -1,25 +1,18 @@ use bytes::BufMut; +use derive_more::Deref; use roaring::RoaringTreemap; use serde::{ de::{SeqAccess, Unexpected, Visitor}, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer, }; -use std::{fmt, ops::Deref}; +use std::fmt; /// Uses Roaring Bitmaps to hold a list of integers. It provides really good compression with the /// capability to access its elements without decoding it. -#[derive(Clone, PartialEq, Default)] +#[derive(Clone, PartialEq, Default, Deref)] pub struct IntegerList(pub RoaringTreemap); -impl Deref for IntegerList { - type Target = RoaringTreemap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - impl fmt::Debug for IntegerList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let vec: Vec = self.0.iter().collect(); diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index fafbb6b5a907..21622f6e4012 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -4,15 +4,13 @@ use crate::{logs_bloom, Bloom, Bytes, TxType, B256}; use alloy_primitives::Log; use alloy_rlp::{length_of_length, Decodable, Encodable, RlpDecodable, RlpEncodable}; use bytes::{Buf, BufMut}; +use derive_more::{Deref, DerefMut, From, IntoIterator}; #[cfg(any(test, feature = "arbitrary"))] use proptest::strategy::Strategy; #[cfg(feature = "zstd-codec")] use reth_codecs::CompactZstd; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; -use std::{ - cmp::Ordering, - ops::{Deref, DerefMut}, -}; +use std::{cmp::Ordering, ops::Deref}; /// Receipt containing result of transaction execution. #[cfg_attr(feature = "zstd-codec", main_codec(no_arbitrary, zstd))] @@ -65,7 +63,7 @@ impl Receipt { } /// A collection of receipts organized as a two-dimensional vector. -#[derive(Clone, Debug, PartialEq, Eq, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Default, From, Deref, DerefMut, IntoIterator)] pub struct Receipts { /// A two-dimensional vector of optional `Receipt` instances. pub receipt_vec: Vec>>, @@ -110,41 +108,12 @@ impl Receipts { } } -impl From>>> for Receipts { - fn from(receipt_vec: Vec>>) -> Self { - Self { receipt_vec } - } -} - impl From> for Receipts { fn from(block_receipts: Vec) -> Self { Self { receipt_vec: vec![block_receipts.into_iter().map(Option::Some).collect()] } } } -impl Deref for Receipts { - type Target = Vec>>; - - fn deref(&self) -> &Self::Target { - &self.receipt_vec - } -} - -impl DerefMut for Receipts { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.receipt_vec - } -} - -impl IntoIterator for Receipts { - type Item = Vec>; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.receipt_vec.into_iter() - } -} - impl FromIterator>> for Receipts { fn from_iter>>>(iter: I) -> Self { iter.into_iter().collect::>().into() diff --git a/crates/primitives/src/request.rs b/crates/primitives/src/request.rs index 63b861ba4b59..937f71b00a9c 100644 --- a/crates/primitives/src/request.rs +++ b/crates/primitives/src/request.rs @@ -3,30 +3,15 @@ use crate::Request; use alloy_eips::eip7685::{Decodable7685, Encodable7685}; use alloy_rlp::{Decodable, Encodable}; +use derive_more::{Deref, DerefMut, From, IntoIterator}; use reth_codecs::{main_codec, Compact}; use revm_primitives::Bytes; -use std::ops::{Deref, DerefMut}; /// A list of EIP-7685 requests. #[main_codec] -#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Deref, DerefMut, From, IntoIterator)] 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 }; @@ -54,17 +39,3 @@ impl Decodable for Requests { .map(Self)?) } } - -impl Deref for Requests { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Requests { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/crates/primitives/src/withdrawal.rs b/crates/primitives/src/withdrawal.rs index aff3bbc83c4e..461908b266d6 100644 --- a/crates/primitives/src/withdrawal.rs +++ b/crates/primitives/src/withdrawal.rs @@ -1,8 +1,8 @@ //! [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895) Withdrawal types. use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; +use derive_more::{AsRef, Deref, DerefMut, From, IntoIterator}; use reth_codecs::{main_codec, Compact}; -use std::ops::{Deref, DerefMut}; /// Re-export from `alloy_eips`. #[doc(inline)] @@ -10,7 +10,22 @@ pub use alloy_eips::eip4895::Withdrawal; /// Represents a collection of Withdrawals. #[main_codec] -#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodableWrapper, RlpDecodableWrapper)] +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Default, + Hash, + From, + AsRef, + Deref, + DerefMut, + IntoIterator, + RlpEncodableWrapper, + RlpDecodableWrapper, +)] +#[as_ref(forward)] pub struct Withdrawals(Vec); impl Withdrawals { @@ -47,41 +62,6 @@ impl Withdrawals { } } -impl IntoIterator for Withdrawals { - type Item = Withdrawal; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl AsRef<[Withdrawal]> for Withdrawals { - fn as_ref(&self) -> &[Withdrawal] { - &self.0 - } -} - -impl Deref for Withdrawals { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Withdrawals { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From> for Withdrawals { - fn from(withdrawals: Vec) -> Self { - Self(withdrawals) - } -} - #[cfg(test)] mod tests { use super::*; From 217ff958ccd4b1178632d7c1bab1af4b352677f2 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:43:57 +0200 Subject: [PATCH 050/405] chore: move `Header` and `SealedHeader` to `reth-primitives-traits` (#8831) --- Cargo.lock | 6 + Cargo.toml | 5 +- crates/primitives-traits/Cargo.toml | 12 +- crates/primitives-traits/src/alloy_compat.rs | 48 ++ crates/primitives-traits/src/header/error.rs | 8 + crates/primitives-traits/src/header/mod.rs | 509 +++++++++++++ crates/primitives-traits/src/header/sealed.rs | 156 ++++ .../src/header/test_utils.rs | 66 ++ crates/primitives-traits/src/lib.rs | 9 + crates/primitives/Cargo.toml | 7 +- crates/primitives/src/alloy_compat.rs | 54 +- crates/primitives/src/block.rs | 74 +- crates/primitives/src/header.rs | 670 +----------------- crates/primitives/src/lib.rs | 2 +- 14 files changed, 839 insertions(+), 787 deletions(-) create mode 100644 crates/primitives-traits/src/alloy_compat.rs create mode 100644 crates/primitives-traits/src/header/error.rs create mode 100644 crates/primitives-traits/src/header/mod.rs create mode 100644 crates/primitives-traits/src/header/sealed.rs create mode 100644 crates/primitives-traits/src/header/test_utils.rs diff --git a/Cargo.lock b/Cargo.lock index ffecc04e5bb2..d00466a05c5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7723,14 +7723,20 @@ name = "reth-primitives-traits" version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "arbitrary", "bytes", + "derive_more", "modular-bitfield", "proptest", "proptest-derive", + "rand 0.8.5", "reth-codecs", + "revm-primitives", "serde", "test-fuzz", ] diff --git a/Cargo.toml b/Cargo.toml index a6b79d2ce15c..0b58652a637b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -357,9 +357,10 @@ alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", "eth", ] } alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false, features = [ diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index 50c5f3c24970..11f714779fbe 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -14,9 +14,15 @@ workspace = true [dependencies] reth-codecs.workspace = true +alloy-consensus.workspace = true +alloy-eips.workspace = true alloy-genesis.workspace = true alloy-primitives.workspace = true -alloy-consensus.workspace = true +alloy-rlp.workspace = true +alloy-rpc-types-eth = { workspace = true, optional = true } + +derive_more.workspace = true +revm-primitives.workspace = true # required by reth-codecs modular-bitfield.workspace = true @@ -33,11 +39,13 @@ arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true proptest-derive.workspace = true test-fuzz.workspace = true +rand.workspace = true [features] +test-utils = ["arbitrary"] arbitrary = [ "dep:arbitrary", "dep:proptest", "dep:proptest-derive" ] - +alloy-compat = ["alloy-rpc-types-eth"] \ No newline at end of file diff --git a/crates/primitives-traits/src/alloy_compat.rs b/crates/primitives-traits/src/alloy_compat.rs new file mode 100644 index 000000000000..4bf80e1f7c35 --- /dev/null +++ b/crates/primitives-traits/src/alloy_compat.rs @@ -0,0 +1,48 @@ +use super::Header; +use alloy_rpc_types_eth::{ConversionError, Header as RpcHeader}; + +impl TryFrom for Header { + type Error = ConversionError; + + fn try_from(header: RpcHeader) -> Result { + Ok(Self { + base_fee_per_gas: header + .base_fee_per_gas + .map(|base_fee_per_gas| { + base_fee_per_gas.try_into().map_err(ConversionError::BaseFeePerGasConversion) + }) + .transpose()?, + beneficiary: header.miner, + blob_gas_used: header + .blob_gas_used + .map(|blob_gas_used| { + blob_gas_used.try_into().map_err(ConversionError::BlobGasUsedConversion) + }) + .transpose()?, + difficulty: header.difficulty, + excess_blob_gas: header + .excess_blob_gas + .map(|excess_blob_gas| { + excess_blob_gas.try_into().map_err(ConversionError::ExcessBlobGasConversion) + }) + .transpose()?, + extra_data: header.extra_data, + gas_limit: header.gas_limit.try_into().map_err(ConversionError::GasLimitConversion)?, + gas_used: header.gas_used.try_into().map_err(ConversionError::GasUsedConversion)?, + logs_bloom: header.logs_bloom, + mix_hash: header.mix_hash.unwrap_or_default(), + nonce: u64::from_be_bytes(header.nonce.unwrap_or_default().0), + number: header.number.ok_or(ConversionError::MissingBlockNumber)?, + ommers_hash: header.uncles_hash, + parent_beacon_block_root: header.parent_beacon_block_root, + parent_hash: header.parent_hash, + receipts_root: header.receipts_root, + state_root: header.state_root, + 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-traits/src/header/error.rs b/crates/primitives-traits/src/header/error.rs new file mode 100644 index 000000000000..6161afc1a5cb --- /dev/null +++ b/crates/primitives-traits/src/header/error.rs @@ -0,0 +1,8 @@ +/// Errors that can occur during header sanity checks. +#[derive(Debug, PartialEq, Eq)] +pub enum HeaderError { + /// Represents an error when the block difficulty is too large. + LargeDifficulty, + /// Represents an error when the block extradata is too large. + LargeExtraData, +} diff --git a/crates/primitives-traits/src/header/mod.rs b/crates/primitives-traits/src/header/mod.rs new file mode 100644 index 000000000000..5ec41d41450c --- /dev/null +++ b/crates/primitives-traits/src/header/mod.rs @@ -0,0 +1,509 @@ +mod sealed; +pub use sealed::SealedHeader; + +mod error; +pub use error::HeaderError; + +#[cfg(any(test, feature = "test-utils", feature = "arbitrary"))] +pub mod test_utils; + +use alloy_consensus::constants::{EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH}; +use alloy_eips::{ + calc_next_block_base_fee, eip1559::BaseFeeParams, merge::ALLOWED_FUTURE_BLOCK_TIME_SECONDS, + BlockNumHash, +}; +use alloy_primitives::{keccak256, Address, BlockNumber, Bloom, Bytes, B256, B64, U256}; +use alloy_rlp::{length_of_length, Decodable, Encodable}; +use bytes::BufMut; +use reth_codecs::{main_codec, Compact}; +use revm_primitives::{calc_blob_gasprice, calc_excess_blob_gas}; +use std::mem; + +/// Block header +#[main_codec] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Header { + /// The Keccak 256-bit hash of the parent + /// block’s header, in its entirety; formally Hp. + pub parent_hash: B256, + /// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho. + pub ommers_hash: B256, + /// The 160-bit address to which all fees collected from the successful mining of this block + /// be transferred; formally Hc. + pub beneficiary: Address, + /// The Keccak 256-bit hash of the root node of the state trie, after all transactions are + /// executed and finalisations applied; formally Hr. + pub state_root: B256, + /// The Keccak 256-bit hash of the root node of the trie structure populated with each + /// transaction in the transactions list portion of the block; formally Ht. + pub transactions_root: B256, + /// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts + /// 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; + /// formally Hb. + pub logs_bloom: Bloom, + /// A scalar value corresponding to the difficulty level of this block. This can be calculated + /// from the previous block’s difficulty level and the timestamp; formally Hd. + pub difficulty: U256, + /// A scalar value equal to the number of ancestor blocks. The genesis block has a number of + /// zero; formally Hi. + pub number: BlockNumber, + /// A scalar value equal to the current limit of gas expenditure per block; formally Hl. + pub gas_limit: u64, + /// A scalar value equal to the total gas used in transactions in this block; formally Hg. + pub gas_used: u64, + /// A scalar value equal to the reasonable output of Unix’s time() at this block’s inception; + /// formally Hs. + pub timestamp: u64, + /// A 256-bit hash which, combined with the + /// nonce, proves that a sufficient amount of computation has been carried out on this block; + /// formally Hm. + pub mix_hash: B256, + /// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of + /// computation has been carried out on this block; formally Hn. + pub nonce: u64, + /// A scalar representing EIP1559 base fee which can move up or down each block according + /// to a formula which is a function of gas used in parent block and gas target + /// (block gas limit divided by elasticity multiplier) of parent block. + /// The algorithm results in the base fee per gas increasing when blocks are + /// above the gas target, and decreasing when blocks are below the gas target. The base fee per + /// gas is burned. + pub base_fee_per_gas: Option, + /// The total amount of blob gas consumed by the transactions within the block, added in + /// EIP-4844. + pub blob_gas_used: Option, + /// A running total of blob gas consumed in excess of the target, prior to the block. Blocks + /// with above-target blob gas consumption increase this value, blocks with below-target blob + /// gas consumption decrease it (bounded at 0). This was added in EIP-4844. + pub excess_blob_gas: Option, + /// The hash of the parent beacon block's root is included in execution blocks, as proposed by + /// EIP-4788. + /// + /// This enables trust-minimized access to consensus state, supporting staking pools, bridges, + /// and more. + /// + /// 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, +} + +impl AsRef for Header { + fn as_ref(&self) -> &Self { + self + } +} + +impl Default for Header { + fn default() -> Self { + Self { + parent_hash: Default::default(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: Default::default(), + state_root: EMPTY_ROOT_HASH, + transactions_root: EMPTY_ROOT_HASH, + receipts_root: EMPTY_ROOT_HASH, + logs_bloom: Default::default(), + difficulty: Default::default(), + number: 0, + gas_limit: 0, + gas_used: 0, + timestamp: 0, + extra_data: Default::default(), + mix_hash: Default::default(), + nonce: 0, + base_fee_per_gas: None, + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_root: None, + } + } +} + +impl Header { + /// Checks if the block's difficulty is set to zero, indicating a Proof-of-Stake header. + /// + /// This function is linked to EIP-3675, proposing the consensus upgrade to Proof-of-Stake: + /// [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#replacing-difficulty-with-0) + /// + /// Verifies whether, as per the EIP, the block's difficulty is updated to zero, + /// signifying the transition to a Proof-of-Stake mechanism. + /// + /// Returns `true` if the block's difficulty matches the constant zero set by the EIP. + pub fn is_zero_difficulty(&self) -> bool { + self.difficulty.is_zero() + } + + /// Performs a sanity check on the extradata field of the header. + /// + /// # Errors + /// + /// Returns an error if the extradata size is larger than 100 KB. + pub fn ensure_extradata_valid(&self) -> Result<(), HeaderError> { + if self.extra_data.len() > 100 * 1024 { + return Err(HeaderError::LargeExtraData) + } + Ok(()) + } + + /// Performs a sanity check on the block difficulty field of the header. + /// + /// # Errors + /// + /// Returns an error if the block difficulty exceeds 80 bits. + pub fn ensure_difficulty_valid(&self) -> Result<(), HeaderError> { + if self.difficulty.bit_len() > 80 { + return Err(HeaderError::LargeDifficulty) + } + Ok(()) + } + + /// Performs combined sanity checks on multiple header fields. + /// + /// This method combines checks for block difficulty and extradata sizes. + /// + /// # Errors + /// + /// Returns an error if either the block difficulty exceeds 80 bits + /// or if the extradata size is larger than 100 KB. + pub fn ensure_well_formed(&self) -> Result<(), HeaderError> { + self.ensure_difficulty_valid()?; + self.ensure_extradata_valid()?; + Ok(()) + } + + /// Checks if the block's timestamp is in the past compared to the parent block's timestamp. + /// + /// Note: This check is relevant only pre-merge. + pub const fn is_timestamp_in_past(&self, parent_timestamp: u64) -> bool { + self.timestamp <= parent_timestamp + } + + /// Checks if the block's timestamp is in the future based on the present timestamp. + /// + /// Clock can drift but this can be consensus issue. + /// + /// Note: This check is relevant only pre-merge. + pub const fn exceeds_allowed_future_timestamp(&self, present_timestamp: u64) -> bool { + self.timestamp > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS + } + + /// Returns the parent block's number and hash + pub const fn parent_num_hash(&self) -> BlockNumHash { + BlockNumHash { number: self.number.saturating_sub(1), hash: self.parent_hash } + } + + /// Heavy function that will calculate hash of data and will *not* save the change to metadata. + /// Use [`Header::seal`], [`SealedHeader`] and unlock if you need hash to be persistent. + pub fn hash_slow(&self) -> B256 { + keccak256(alloy_rlp::encode(self)) + } + + /// Checks if the header is empty - has no transactions and no ommers + pub fn is_empty(&self) -> bool { + self.transaction_root_is_empty() && + self.ommers_hash_is_empty() && + self.withdrawals_root.map_or(true, |root| root == EMPTY_ROOT_HASH) + } + + /// Check if the ommers hash equals to empty hash list. + pub fn ommers_hash_is_empty(&self) -> bool { + self.ommers_hash == EMPTY_OMMER_ROOT_HASH + } + + /// Check if the transaction root equals to empty root. + pub fn transaction_root_is_empty(&self) -> bool { + self.transactions_root == EMPTY_ROOT_HASH + } + + /// Returns the blob fee for _this_ block according to the EIP-4844 spec. + /// + /// Returns `None` if `excess_blob_gas` is None + pub fn blob_fee(&self) -> Option { + self.excess_blob_gas.map(calc_blob_gasprice) + } + + /// Returns the blob fee for the next block according to the EIP-4844 spec. + /// + /// Returns `None` if `excess_blob_gas` is None. + /// + /// See also [`Self::next_block_excess_blob_gas`] + pub fn next_block_blob_fee(&self) -> Option { + self.next_block_excess_blob_gas().map(calc_blob_gasprice) + } + + /// Calculate base fee for next block according to the EIP-1559 spec. + /// + /// Returns a `None` if no base fee is set, no EIP-1559 support + pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option { + Some(calc_next_block_base_fee( + self.gas_used as u128, + self.gas_limit as u128, + self.base_fee_per_gas? as u128, + base_fee_params, + ) as u64) + } + + /// Calculate excess blob gas for the next block according to the EIP-4844 spec. + /// + /// Returns a `None` if no excess blob gas is set, no EIP-4844 support + pub fn next_block_excess_blob_gas(&self) -> Option { + Some(calc_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?)) + } + + /// Seal the header with a known hash. + /// + /// WARNING: This method does not perform validation whether the hash is correct. + #[inline] + pub const fn seal(self, hash: B256) -> SealedHeader { + SealedHeader::new(self, hash) + } + + /// Calculate hash and seal the Header so that it can't be changed. + #[inline] + pub fn seal_slow(self) -> SealedHeader { + let hash = self.hash_slow(); + self.seal(hash) + } + + /// Calculate a heuristic for the in-memory size of the [Header]. + #[inline] + pub fn size(&self) -> usize { + mem::size_of::() + // parent hash + mem::size_of::() + // ommers hash + mem::size_of::
() + // beneficiary + mem::size_of::() + // state root + mem::size_of::() + // transactions root + mem::size_of::() + // receipts root + mem::size_of::>() + // withdrawals root + mem::size_of::() + // logs bloom + mem::size_of::() + // difficulty + mem::size_of::() + // number + mem::size_of::() + // gas limit + mem::size_of::() + // gas used + mem::size_of::() + // timestamp + mem::size_of::() + // mix hash + mem::size_of::() + // nonce + mem::size_of::>() + // base fee per gas + mem::size_of::>() + // blob gas used + mem::size_of::>() + // excess blob gas + mem::size_of::>() + // parent beacon block root + self.extra_data.len() // extra data + } + + fn header_payload_length(&self) -> usize { + let mut length = 0; + length += self.parent_hash.length(); // Hash of the previous block. + length += self.ommers_hash.length(); // Hash of uncle blocks. + length += self.beneficiary.length(); // Address that receives rewards. + length += self.state_root.length(); // Root hash of the state object. + length += self.transactions_root.length(); // Root hash of transactions in the block. + length += self.receipts_root.length(); // Hash of transaction receipts. + length += self.logs_bloom.length(); // Data structure containing event logs. + length += self.difficulty.length(); // Difficulty value of the block. + length += U256::from(self.number).length(); // Block number. + length += U256::from(self.gas_limit).length(); // Maximum gas allowed. + length += U256::from(self.gas_used).length(); // Actual gas used. + length += self.timestamp.length(); // Block timestamp. + length += self.extra_data.length(); // Additional arbitrary data. + length += self.mix_hash.length(); // Hash used for mining. + length += B64::new(self.nonce.to_be_bytes()).length(); // Nonce for mining. + + if let Some(base_fee) = self.base_fee_per_gas { + // Adding base fee length if it exists. + length += U256::from(base_fee).length(); + } + + if let Some(root) = self.withdrawals_root { + // Adding withdrawals_root length if it exists. + length += root.length(); + } + + if let Some(blob_gas_used) = self.blob_gas_used { + // Adding blob_gas_used length if it exists. + length += U256::from(blob_gas_used).length(); + } + + if let Some(excess_blob_gas) = self.excess_blob_gas { + // Adding excess_blob_gas length if it exists. + length += U256::from(excess_blob_gas).length(); + } + + if let Some(parent_beacon_block_root) = self.parent_beacon_block_root { + length += parent_beacon_block_root.length(); + } + + if let Some(requests_root) = self.requests_root { + length += requests_root.length(); + } + + length + } +} + +impl Encodable for Header { + fn encode(&self, out: &mut dyn BufMut) { + // Create a header indicating the encoded content is a list with the payload length computed + // from the header's payload calculation function. + let list_header = + alloy_rlp::Header { list: true, payload_length: self.header_payload_length() }; + list_header.encode(out); + + // Encode each header field sequentially + self.parent_hash.encode(out); // Encode parent hash. + self.ommers_hash.encode(out); // Encode ommer's hash. + self.beneficiary.encode(out); // Encode beneficiary. + self.state_root.encode(out); // Encode state root. + self.transactions_root.encode(out); // Encode transactions root. + self.receipts_root.encode(out); // Encode receipts root. + self.logs_bloom.encode(out); // Encode logs bloom. + self.difficulty.encode(out); // Encode difficulty. + U256::from(self.number).encode(out); // Encode block number. + U256::from(self.gas_limit).encode(out); // Encode gas limit. + U256::from(self.gas_used).encode(out); // Encode gas used. + self.timestamp.encode(out); // Encode timestamp. + self.extra_data.encode(out); // Encode extra data. + self.mix_hash.encode(out); // Encode mix hash. + B64::new(self.nonce.to_be_bytes()).encode(out); // Encode nonce. + + // Encode base fee. Put empty list if base fee is missing, + // but withdrawals root is present. + if let Some(ref base_fee) = self.base_fee_per_gas { + U256::from(*base_fee).encode(out); + } + + // Encode withdrawals root. Put empty string if withdrawals root is missing, + // but blob gas used is present. + if let Some(ref root) = self.withdrawals_root { + root.encode(out); + } + + // Encode blob gas used. Put empty list if blob gas used is missing, + // but excess blob gas is present. + if let Some(ref blob_gas_used) = self.blob_gas_used { + U256::from(*blob_gas_used).encode(out); + } + + // Encode excess blob gas. Put empty list if excess blob gas is missing, + // but parent beacon block root is present. + if let Some(ref excess_blob_gas) = self.excess_blob_gas { + U256::from(*excess_blob_gas).encode(out); + } + + // 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 requests_root) = self.requests_root { + requests_root.encode(out); + } + } + + fn length(&self) -> usize { + let mut length = 0; + length += self.header_payload_length(); + length += length_of_length(length); + length + } +} + +impl Decodable for Header { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let rlp_head = alloy_rlp::Header::decode(buf)?; + if !rlp_head.list { + return Err(alloy_rlp::Error::UnexpectedString) + } + let started_len = buf.len(); + let mut this = Self { + parent_hash: Decodable::decode(buf)?, + ommers_hash: Decodable::decode(buf)?, + beneficiary: Decodable::decode(buf)?, + state_root: Decodable::decode(buf)?, + transactions_root: Decodable::decode(buf)?, + receipts_root: Decodable::decode(buf)?, + logs_bloom: Decodable::decode(buf)?, + difficulty: Decodable::decode(buf)?, + number: u64::decode(buf)?, + gas_limit: u64::decode(buf)?, + gas_used: u64::decode(buf)?, + timestamp: Decodable::decode(buf)?, + extra_data: Decodable::decode(buf)?, + mix_hash: Decodable::decode(buf)?, + nonce: u64::from_be_bytes(B64::decode(buf)?.0), + base_fee_per_gas: None, + withdrawals_root: None, + 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)?); + } + + // Withdrawals root for post-shanghai headers + if started_len - buf.len() < rlp_head.payload_length { + this.withdrawals_root = Some(Decodable::decode(buf)?); + } + + // Blob gas used and excess blob gas for post-cancun headers + if started_len - buf.len() < rlp_head.payload_length { + this.blob_gas_used = Some(u64::decode(buf)?); + } + + if started_len - buf.len() < rlp_head.payload_length { + this.excess_blob_gas = Some(u64::decode(buf)?); + } + + // 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: + // * 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 started_len - buf.len() < rlp_head.payload_length { + this.requests_root = Some(B256::decode(buf)?); + } + + let consumed = started_len - buf.len(); + if consumed != rlp_head.payload_length { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: rlp_head.payload_length, + got: consumed, + }) + } + Ok(this) + } +} diff --git a/crates/primitives-traits/src/header/sealed.rs b/crates/primitives-traits/src/header/sealed.rs new file mode 100644 index 000000000000..91918b6877dc --- /dev/null +++ b/crates/primitives-traits/src/header/sealed.rs @@ -0,0 +1,156 @@ +use super::Header; +use alloy_eips::BlockNumHash; +use alloy_primitives::{keccak256, BlockHash}; +#[cfg(any(test, feature = "test-utils"))] +use alloy_primitives::{BlockNumber, B256, U256}; +use alloy_rlp::{Decodable, Encodable}; +use bytes::BufMut; +use derive_more::{AsRef, Deref}; +#[cfg(any(test, feature = "arbitrary"))] +use proptest::prelude::*; +use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; +use std::mem; + +/// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want +/// to modify header. +#[main_codec(no_arbitrary)] +#[add_arbitrary_tests(rlp, compact)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref)] +pub struct SealedHeader { + /// Locked Header hash. + hash: BlockHash, + /// Locked Header fields. + #[as_ref] + #[deref] + header: Header, +} + +impl SealedHeader { + /// Creates the sealed header with the corresponding block hash. + #[inline] + pub const fn new(header: Header, hash: BlockHash) -> Self { + Self { header, hash } + } + + /// Returns the sealed Header fields. + #[inline] + pub const fn header(&self) -> &Header { + &self.header + } + + /// Returns header/block hash. + #[inline] + pub const fn hash(&self) -> BlockHash { + self.hash + } + + /// Extract raw header that can be modified. + pub fn unseal(self) -> Header { + self.header + } + + /// This is the inverse of [`Header::seal_slow`] which returns the raw header and hash. + pub fn split(self) -> (Header, BlockHash) { + (self.header, self.hash) + } + + /// Return the number hash tuple. + pub fn num_hash(&self) -> BlockNumHash { + BlockNumHash::new(self.number, self.hash) + } + + /// Calculates a heuristic for the in-memory size of the [`SealedHeader`]. + #[inline] + pub fn size(&self) -> usize { + self.header.size() + mem::size_of::() + } +} + +impl Default for SealedHeader { + fn default() -> Self { + Header::default().seal_slow() + } +} + +impl Encodable for SealedHeader { + fn encode(&self, out: &mut dyn BufMut) { + self.header.encode(out); + } +} + +impl Decodable for SealedHeader { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let b = &mut &**buf; + let started_len = buf.len(); + + // decode the header from temp buffer + let header = Header::decode(b)?; + + // hash the consumed bytes, the rlp encoded header + let consumed = started_len - b.len(); + let hash = keccak256(&buf[..consumed]); + + // update original buffer + *buf = *b; + + Ok(Self { header, hash }) + } +} + +#[cfg(any(test, feature = "test-utils"))] +impl SealedHeader { + /// Updates the block header. + pub fn set_header(&mut self, header: Header) { + self.header = header + } + + /// Updates the block hash. + pub fn set_hash(&mut self, hash: BlockHash) { + self.hash = hash + } + + /// Updates the parent block hash. + pub fn set_parent_hash(&mut self, hash: BlockHash) { + self.header.parent_hash = hash + } + + /// Updates the block number. + pub fn set_block_number(&mut self, number: BlockNumber) { + self.header.number = number; + } + + /// Updates the block state root. + pub fn set_state_root(&mut self, state_root: B256) { + self.header.state_root = state_root; + } + + /// Updates the block difficulty. + pub fn set_difficulty(&mut self, difficulty: U256) { + self.header.difficulty = difficulty; + } +} + +#[cfg(any(test, feature = "arbitrary"))] +impl proptest::arbitrary::Arbitrary for SealedHeader { + type Parameters = (); + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + // map valid header strategy by sealing + crate::test_utils::valid_header_strategy().prop_map(|header| header.seal_slow()).boxed() + } + type Strategy = proptest::strategy::BoxedStrategy; +} + +#[cfg(any(test, feature = "arbitrary"))] +impl<'a> arbitrary::Arbitrary<'a> for SealedHeader { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let sealed_header = crate::test_utils::generate_valid_header( + u.arbitrary()?, + u.arbitrary()?, + u.arbitrary()?, + u.arbitrary()?, + u.arbitrary()?, + ) + .seal_slow(); + Ok(sealed_header) + } +} diff --git a/crates/primitives-traits/src/header/test_utils.rs b/crates/primitives-traits/src/header/test_utils.rs new file mode 100644 index 000000000000..07d00f03bba2 --- /dev/null +++ b/crates/primitives-traits/src/header/test_utils.rs @@ -0,0 +1,66 @@ +//! Test utilities to generate random valid headers. + +use crate::Header; +use alloy_primitives::B256; +use proptest::{arbitrary::any, prop_compose}; + +/// Generates a header which is valid __with respect to past and future forks__. This means, for +/// example, that if the withdrawals root is present, the base fee per gas is also present. +/// +/// If blob gas used were present, then the excess blob gas and parent beacon block root are also +/// present. In this example, the withdrawals root would also be present. +/// +/// This __does not, and should not guarantee__ that the header is valid with respect to __anything +/// else__. +pub const fn generate_valid_header( + mut header: Header, + eip_4844_active: bool, + blob_gas_used: u64, + excess_blob_gas: u64, + parent_beacon_block_root: B256, +) -> Header { + // EIP-1559 logic + if header.base_fee_per_gas.is_none() { + // If EIP-1559 is not active, clear related fields + header.withdrawals_root = None; + header.blob_gas_used = None; + header.excess_blob_gas = None; + header.parent_beacon_block_root = None; + } else if header.withdrawals_root.is_none() { + // If EIP-4895 is not active, clear related fields + header.blob_gas_used = None; + header.excess_blob_gas = None; + header.parent_beacon_block_root = None; + } else if eip_4844_active { + // Set fields based on EIP-4844 being active + header.blob_gas_used = Some(blob_gas_used); + header.excess_blob_gas = Some(excess_blob_gas); + header.parent_beacon_block_root = Some(parent_beacon_block_root); + } else { + // If EIP-4844 is not active, clear related fields + header.blob_gas_used = None; + header.excess_blob_gas = None; + header.parent_beacon_block_root = None; + } + + // todo(onbjerg): adjust this for eip-7589 + header.requests_root = None; + + header +} + +prop_compose! { + /// Generates a proptest strategy for constructing an instance of a header which is valid __with + /// respect to past and future forks__. + /// + /// See docs for [generate_valid_header] for more information. + pub fn valid_header_strategy()( + header in any::
(), + eip_4844_active in any::(), + blob_gas_used in any::(), + excess_blob_gas in any::(), + parent_beacon_block_root in any::() + ) -> Header { + generate_valid_header(header, eip_4844_active, blob_gas_used, excess_blob_gas, parent_beacon_block_root) + } +} diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index af8918de1977..146cf86eaa08 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -10,6 +10,15 @@ #![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#[cfg(feature = "alloy-compat")] +mod alloy_compat; + /// Minimal account pub mod account; pub use account::Account; + +/// Common header types +pub mod header; +#[cfg(any(test, feature = "arbitrary", feature = "test-utils"))] +pub use header::test_utils; +pub use header::{Header, HeaderError, SealedHeader}; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 7dda61ddd7d1..5d3cc821ea61 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -117,8 +117,11 @@ optimism = [ "reth-ethereum-forks/optimism", "revm/optimism", ] -alloy-compat = ["alloy-rpc-types"] -test-utils = [] +alloy-compat = [ + "reth-primitives-traits/alloy-compat", + "alloy-rpc-types", +] +test-utils = ["reth-primitives-traits/test-utils"] [[bench]] name = "recover_ecdsa_crit" diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index 492bb1533748..60618f587191 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -1,9 +1,9 @@ //! Common conversions from alloy types. use crate::{ - constants::EMPTY_TRANSACTIONS, transaction::extract_chain_id, Block, Header, Signature, - Transaction, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, - TxLegacy, TxType, + constants::EMPTY_TRANSACTIONS, transaction::extract_chain_id, Block, Signature, Transaction, + TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, + TxType, }; use alloy_primitives::TxKind; use alloy_rlp::Error as RlpError; @@ -61,54 +61,6 @@ impl TryFrom for Block { } } -impl TryFrom for Header { - type Error = alloy_rpc_types::ConversionError; - - fn try_from(header: alloy_rpc_types::Header) -> Result { - use alloy_rpc_types::ConversionError; - - Ok(Self { - base_fee_per_gas: header - .base_fee_per_gas - .map(|base_fee_per_gas| { - base_fee_per_gas.try_into().map_err(ConversionError::BaseFeePerGasConversion) - }) - .transpose()?, - beneficiary: header.miner, - blob_gas_used: header - .blob_gas_used - .map(|blob_gas_used| { - blob_gas_used.try_into().map_err(ConversionError::BlobGasUsedConversion) - }) - .transpose()?, - difficulty: header.difficulty, - excess_blob_gas: header - .excess_blob_gas - .map(|excess_blob_gas| { - excess_blob_gas.try_into().map_err(ConversionError::ExcessBlobGasConversion) - }) - .transpose()?, - extra_data: header.extra_data, - gas_limit: header.gas_limit.try_into().map_err(ConversionError::GasLimitConversion)?, - gas_used: header.gas_used.try_into().map_err(ConversionError::GasUsedConversion)?, - logs_bloom: header.logs_bloom, - mix_hash: header.mix_hash.unwrap_or_default(), - nonce: u64::from_be_bytes(header.nonce.unwrap_or_default().0), - number: header.number.ok_or(ConversionError::MissingBlockNumber)?, - ommers_hash: header.uncles_hash, - parent_beacon_block_root: header.parent_beacon_block_root, - parent_hash: header.parent_hash, - receipts_root: header.receipts_root, - state_root: header.state_root, - timestamp: header.timestamp, - transactions_root: header.transactions_root, - withdrawals_root: header.withdrawals_root, - // TODO: requests_root: header.requests_root, - requests_root: None, - }) - } -} - impl TryFrom for Transaction { type Error = alloy_rpc_types::ConversionError; diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 77664ed1cc2f..68665277800c 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -2,17 +2,18 @@ use crate::{ Address, Bytes, GotExpected, Header, Requests, SealedHeader, TransactionSigned, TransactionSignedEcRecovered, Withdrawals, B256, }; +pub use alloy_eips::eip1898::{ + BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, +}; use alloy_rlp::{RlpDecodable, RlpEncodable}; use derive_more::{Deref, DerefMut}; #[cfg(any(test, feature = "arbitrary"))] -use proptest::prelude::{any, prop_compose}; +use proptest::prelude::prop_compose; use reth_codecs::derive_arbitrary; +#[cfg(any(test, feature = "arbitrary"))] +pub use reth_primitives_traits::test_utils::{generate_valid_header, valid_header_strategy}; use serde::{Deserialize, Serialize}; -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 @@ -592,69 +593,6 @@ impl From for BlockBody { } } -/// Generates a header which is valid __with respect to past and future forks__. This means, for -/// example, that if the withdrawals root is present, the base fee per gas is also present. -/// -/// If blob gas used were present, then the excess blob gas and parent beacon block root are also -/// present. In this example, the withdrawals root would also be present. -/// -/// This __does not, and should not guarantee__ that the header is valid with respect to __anything -/// else__. -#[cfg(any(test, feature = "arbitrary"))] -pub const fn generate_valid_header( - mut header: Header, - eip_4844_active: bool, - blob_gas_used: u64, - excess_blob_gas: u64, - parent_beacon_block_root: B256, -) -> Header { - // EIP-1559 logic - if header.base_fee_per_gas.is_none() { - // If EIP-1559 is not active, clear related fields - header.withdrawals_root = None; - header.blob_gas_used = None; - header.excess_blob_gas = None; - header.parent_beacon_block_root = None; - } else if header.withdrawals_root.is_none() { - // If EIP-4895 is not active, clear related fields - header.blob_gas_used = None; - header.excess_blob_gas = None; - header.parent_beacon_block_root = None; - } else if eip_4844_active { - // Set fields based on EIP-4844 being active - header.blob_gas_used = Some(blob_gas_used); - header.excess_blob_gas = Some(excess_blob_gas); - header.parent_beacon_block_root = Some(parent_beacon_block_root); - } else { - // If EIP-4844 is not active, clear related fields - header.blob_gas_used = None; - header.excess_blob_gas = None; - header.parent_beacon_block_root = None; - } - - // todo(onbjerg): adjust this for eip-7589 - header.requests_root = None; - - header -} - -#[cfg(any(test, feature = "arbitrary"))] -prop_compose! { - /// Generates a proptest strategy for constructing an instance of a header which is valid __with - /// respect to past and future forks__. - /// - /// See docs for [generate_valid_header] for more information. - pub fn valid_header_strategy()( - header in any::
(), - eip_4844_active in any::(), - blob_gas_used in any::(), - excess_blob_gas in any::(), - parent_beacon_block_root in any::() - ) -> Header { - generate_valid_header(header, eip_4844_active, blob_gas_used, excess_blob_gas, parent_beacon_block_root) - } -} - #[cfg(test)] mod tests { use super::{BlockNumberOrTag::*, *}; diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index d0d8c3de4e9a..ea80328e7528 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -1,665 +1,11 @@ -#[cfg(any(test, feature = "arbitrary"))] -use crate::block::{generate_valid_header, valid_header_strategy}; -use crate::{ - basefee::calc_next_block_base_fee, - constants::{ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH}, - eip4844::{calc_blob_gasprice, calculate_excess_blob_gas}, - keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes, B256, - B64, U256, -}; -use alloy_rlp::{length_of_length, Decodable, Encodable}; +//! Header types. + +use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; -use derive_more::{AsRef, Deref}; -#[cfg(any(test, feature = "arbitrary"))] -use proptest::prelude::*; -use reth_codecs::{add_arbitrary_tests, derive_arbitrary, main_codec, Compact}; +use reth_codecs::derive_arbitrary; use serde::{Deserialize, Serialize}; -use std::mem; - -/// Errors that can occur during header sanity checks. -#[derive(Debug, PartialEq, Eq)] -pub enum HeaderError { - /// Represents an error when the block difficulty is too large. - LargeDifficulty, - /// Represents an error when the block extradata is too large. - LargeExtraData, -} - -/// Block header -#[main_codec] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Header { - /// The Keccak 256-bit hash of the parent - /// block’s header, in its entirety; formally Hp. - pub parent_hash: B256, - /// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho. - pub ommers_hash: B256, - /// The 160-bit address to which all fees collected from the successful mining of this block - /// be transferred; formally Hc. - pub beneficiary: Address, - /// The Keccak 256-bit hash of the root node of the state trie, after all transactions are - /// executed and finalisations applied; formally Hr. - pub state_root: B256, - /// The Keccak 256-bit hash of the root node of the trie structure populated with each - /// transaction in the transactions list portion of the block; formally Ht. - pub transactions_root: B256, - /// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts - /// 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; - /// formally Hb. - pub logs_bloom: Bloom, - /// A scalar value corresponding to the difficulty level of this block. This can be calculated - /// from the previous block’s difficulty level and the timestamp; formally Hd. - pub difficulty: U256, - /// A scalar value equal to the number of ancestor blocks. The genesis block has a number of - /// zero; formally Hi. - pub number: BlockNumber, - /// A scalar value equal to the current limit of gas expenditure per block; formally Hl. - pub gas_limit: u64, - /// A scalar value equal to the total gas used in transactions in this block; formally Hg. - pub gas_used: u64, - /// A scalar value equal to the reasonable output of Unix’s time() at this block’s inception; - /// formally Hs. - pub timestamp: u64, - /// A 256-bit hash which, combined with the - /// nonce, proves that a sufficient amount of computation has been carried out on this block; - /// formally Hm. - pub mix_hash: B256, - /// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of - /// computation has been carried out on this block; formally Hn. - pub nonce: u64, - /// A scalar representing EIP1559 base fee which can move up or down each block according - /// to a formula which is a function of gas used in parent block and gas target - /// (block gas limit divided by elasticity multiplier) of parent block. - /// The algorithm results in the base fee per gas increasing when blocks are - /// above the gas target, and decreasing when blocks are below the gas target. The base fee per - /// gas is burned. - pub base_fee_per_gas: Option, - /// The total amount of blob gas consumed by the transactions within the block, added in - /// EIP-4844. - pub blob_gas_used: Option, - /// A running total of blob gas consumed in excess of the target, prior to the block. Blocks - /// with above-target blob gas consumption increase this value, blocks with below-target blob - /// gas consumption decrease it (bounded at 0). This was added in EIP-4844. - pub excess_blob_gas: Option, - /// The hash of the parent beacon block's root is included in execution blocks, as proposed by - /// EIP-4788. - /// - /// This enables trust-minimized access to consensus state, supporting staking pools, bridges, - /// and more. - /// - /// 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, -} - -impl AsRef for Header { - fn as_ref(&self) -> &Self { - self - } -} - -impl Default for Header { - fn default() -> Self { - Self { - parent_hash: Default::default(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: Default::default(), - state_root: EMPTY_ROOT_HASH, - transactions_root: EMPTY_ROOT_HASH, - receipts_root: EMPTY_ROOT_HASH, - logs_bloom: Default::default(), - difficulty: Default::default(), - number: 0, - gas_limit: 0, - gas_used: 0, - timestamp: 0, - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: 0, - base_fee_per_gas: None, - withdrawals_root: None, - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - requests_root: None, - } - } -} - -impl Header { - /// Checks if the block's difficulty is set to zero, indicating a Proof-of-Stake header. - /// - /// This function is linked to EIP-3675, proposing the consensus upgrade to Proof-of-Stake: - /// [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#replacing-difficulty-with-0) - /// - /// Verifies whether, as per the EIP, the block's difficulty is updated to zero, - /// signifying the transition to a Proof-of-Stake mechanism. - /// - /// Returns `true` if the block's difficulty matches the constant zero set by the EIP. - pub fn is_zero_difficulty(&self) -> bool { - self.difficulty.is_zero() - } - - /// Performs a sanity check on the extradata field of the header. - /// - /// # Errors - /// - /// Returns an error if the extradata size is larger than 100 KB. - pub fn ensure_extradata_valid(&self) -> Result<(), HeaderError> { - if self.extra_data.len() > 100 * 1024 { - return Err(HeaderError::LargeExtraData) - } - Ok(()) - } - - /// Performs a sanity check on the block difficulty field of the header. - /// - /// # Errors - /// - /// Returns an error if the block difficulty exceeds 80 bits. - pub fn ensure_difficulty_valid(&self) -> Result<(), HeaderError> { - if self.difficulty.bit_len() > 80 { - return Err(HeaderError::LargeDifficulty) - } - Ok(()) - } - - /// Performs combined sanity checks on multiple header fields. - /// - /// This method combines checks for block difficulty and extradata sizes. - /// - /// # Errors - /// - /// Returns an error if either the block difficulty exceeds 80 bits - /// or if the extradata size is larger than 100 KB. - pub fn ensure_well_formed(&self) -> Result<(), HeaderError> { - self.ensure_difficulty_valid()?; - self.ensure_extradata_valid()?; - Ok(()) - } - - /// Checks if the block's timestamp is in the past compared to the parent block's timestamp. - /// - /// Note: This check is relevant only pre-merge. - pub const fn is_timestamp_in_past(&self, parent_timestamp: u64) -> bool { - self.timestamp <= parent_timestamp - } - - /// Checks if the block's timestamp is in the future based on the present timestamp. - /// - /// Clock can drift but this can be consensus issue. - /// - /// Note: This check is relevant only pre-merge. - pub const fn exceeds_allowed_future_timestamp(&self, present_timestamp: u64) -> bool { - self.timestamp > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS - } - - /// Returns the parent block's number and hash - pub const fn parent_num_hash(&self) -> BlockNumHash { - BlockNumHash { number: self.number.saturating_sub(1), hash: self.parent_hash } - } - - /// Heavy function that will calculate hash of data and will *not* save the change to metadata. - /// Use [`Header::seal`], [`SealedHeader`] and unlock if you need hash to be persistent. - pub fn hash_slow(&self) -> B256 { - keccak256(alloy_rlp::encode(self)) - } - - /// Checks if the header is empty - has no transactions and no ommers - pub fn is_empty(&self) -> bool { - self.transaction_root_is_empty() && - self.ommers_hash_is_empty() && - self.withdrawals_root.map_or(true, |root| root == EMPTY_ROOT_HASH) - } - - /// Check if the ommers hash equals to empty hash list. - pub fn ommers_hash_is_empty(&self) -> bool { - self.ommers_hash == EMPTY_OMMER_ROOT_HASH - } - - /// Check if the transaction root equals to empty root. - pub fn transaction_root_is_empty(&self) -> bool { - self.transactions_root == EMPTY_ROOT_HASH - } - - /// Returns the blob fee for _this_ block according to the EIP-4844 spec. - /// - /// Returns `None` if `excess_blob_gas` is None - pub fn blob_fee(&self) -> Option { - self.excess_blob_gas.map(calc_blob_gasprice) - } - - /// Returns the blob fee for the next block according to the EIP-4844 spec. - /// - /// Returns `None` if `excess_blob_gas` is None. - /// - /// See also [`Self::next_block_excess_blob_gas`] - pub fn next_block_blob_fee(&self) -> Option { - self.next_block_excess_blob_gas().map(calc_blob_gasprice) - } - - /// Calculate base fee for next block according to the EIP-1559 spec. - /// - /// Returns a `None` if no base fee is set, no EIP-1559 support - pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option { - Some(calc_next_block_base_fee( - self.gas_used as u128, - self.gas_limit as u128, - self.base_fee_per_gas? as u128, - base_fee_params, - ) as u64) - } - - /// Calculate excess blob gas for the next block according to the EIP-4844 spec. - /// - /// Returns a `None` if no excess blob gas is set, no EIP-4844 support - pub fn next_block_excess_blob_gas(&self) -> Option { - Some(calculate_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?)) - } - - /// Seal the header with a known hash. - /// - /// WARNING: This method does not perform validation whether the hash is correct. - #[inline] - pub const fn seal(self, hash: B256) -> SealedHeader { - SealedHeader { header: self, hash } - } - - /// Calculate hash and seal the Header so that it can't be changed. - #[inline] - pub fn seal_slow(self) -> SealedHeader { - let hash = self.hash_slow(); - self.seal(hash) - } - - /// Calculate a heuristic for the in-memory size of the [Header]. - #[inline] - pub fn size(&self) -> usize { - mem::size_of::() + // parent hash - mem::size_of::() + // ommers hash - mem::size_of::
() + // beneficiary - mem::size_of::() + // state root - mem::size_of::() + // transactions root - mem::size_of::() + // receipts root - mem::size_of::>() + // withdrawals root - mem::size_of::() + // logs bloom - mem::size_of::() + // difficulty - mem::size_of::() + // number - mem::size_of::() + // gas limit - mem::size_of::() + // gas used - mem::size_of::() + // timestamp - mem::size_of::() + // mix hash - mem::size_of::() + // nonce - mem::size_of::>() + // base fee per gas - mem::size_of::>() + // blob gas used - mem::size_of::>() + // excess blob gas - mem::size_of::>() + // parent beacon block root - self.extra_data.len() // extra data - } - - fn header_payload_length(&self) -> usize { - let mut length = 0; - length += self.parent_hash.length(); // Hash of the previous block. - length += self.ommers_hash.length(); // Hash of uncle blocks. - length += self.beneficiary.length(); // Address that receives rewards. - length += self.state_root.length(); // Root hash of the state object. - length += self.transactions_root.length(); // Root hash of transactions in the block. - length += self.receipts_root.length(); // Hash of transaction receipts. - length += self.logs_bloom.length(); // Data structure containing event logs. - length += self.difficulty.length(); // Difficulty value of the block. - length += U256::from(self.number).length(); // Block number. - length += U256::from(self.gas_limit).length(); // Maximum gas allowed. - length += U256::from(self.gas_used).length(); // Actual gas used. - length += self.timestamp.length(); // Block timestamp. - length += self.extra_data.length(); // Additional arbitrary data. - length += self.mix_hash.length(); // Hash used for mining. - length += B64::new(self.nonce.to_be_bytes()).length(); // Nonce for mining. - - if let Some(base_fee) = self.base_fee_per_gas { - // Adding base fee length if it exists. - length += U256::from(base_fee).length(); - } - - if let Some(root) = self.withdrawals_root { - // Adding withdrawals_root length if it exists. - length += root.length(); - } - - if let Some(blob_gas_used) = self.blob_gas_used { - // Adding blob_gas_used length if it exists. - length += U256::from(blob_gas_used).length(); - } - - if let Some(excess_blob_gas) = self.excess_blob_gas { - // Adding excess_blob_gas length if it exists. - length += U256::from(excess_blob_gas).length(); - } - - if let Some(parent_beacon_block_root) = self.parent_beacon_block_root { - length += parent_beacon_block_root.length(); - } - - if let Some(requests_root) = self.requests_root { - length += requests_root.length(); - } - - length - } -} - -impl Encodable for Header { - fn encode(&self, out: &mut dyn BufMut) { - // Create a header indicating the encoded content is a list with the payload length computed - // from the header's payload calculation function. - let list_header = - alloy_rlp::Header { list: true, payload_length: self.header_payload_length() }; - list_header.encode(out); - - // Encode each header field sequentially - self.parent_hash.encode(out); // Encode parent hash. - self.ommers_hash.encode(out); // Encode ommer's hash. - self.beneficiary.encode(out); // Encode beneficiary. - self.state_root.encode(out); // Encode state root. - self.transactions_root.encode(out); // Encode transactions root. - self.receipts_root.encode(out); // Encode receipts root. - self.logs_bloom.encode(out); // Encode logs bloom. - self.difficulty.encode(out); // Encode difficulty. - U256::from(self.number).encode(out); // Encode block number. - U256::from(self.gas_limit).encode(out); // Encode gas limit. - U256::from(self.gas_used).encode(out); // Encode gas used. - self.timestamp.encode(out); // Encode timestamp. - self.extra_data.encode(out); // Encode extra data. - self.mix_hash.encode(out); // Encode mix hash. - B64::new(self.nonce.to_be_bytes()).encode(out); // Encode nonce. - - // Encode base fee. Put empty list if base fee is missing, - // but withdrawals root is present. - if let Some(ref base_fee) = self.base_fee_per_gas { - U256::from(*base_fee).encode(out); - } - - // Encode withdrawals root. Put empty string if withdrawals root is missing, - // but blob gas used is present. - if let Some(ref root) = self.withdrawals_root { - root.encode(out); - } - - // Encode blob gas used. Put empty list if blob gas used is missing, - // but excess blob gas is present. - if let Some(ref blob_gas_used) = self.blob_gas_used { - U256::from(*blob_gas_used).encode(out); - } - - // Encode excess blob gas. Put empty list if excess blob gas is missing, - // but parent beacon block root is present. - if let Some(ref excess_blob_gas) = self.excess_blob_gas { - U256::from(*excess_blob_gas).encode(out); - } - - // 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 requests_root) = self.requests_root { - requests_root.encode(out); - } - } - - fn length(&self) -> usize { - let mut length = 0; - length += self.header_payload_length(); - length += length_of_length(length); - length - } -} - -impl Decodable for Header { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let rlp_head = alloy_rlp::Header::decode(buf)?; - if !rlp_head.list { - return Err(alloy_rlp::Error::UnexpectedString) - } - let started_len = buf.len(); - let mut this = Self { - parent_hash: Decodable::decode(buf)?, - ommers_hash: Decodable::decode(buf)?, - beneficiary: Decodable::decode(buf)?, - state_root: Decodable::decode(buf)?, - transactions_root: Decodable::decode(buf)?, - receipts_root: Decodable::decode(buf)?, - logs_bloom: Decodable::decode(buf)?, - difficulty: Decodable::decode(buf)?, - number: u64::decode(buf)?, - gas_limit: u64::decode(buf)?, - gas_used: u64::decode(buf)?, - timestamp: Decodable::decode(buf)?, - extra_data: Decodable::decode(buf)?, - mix_hash: Decodable::decode(buf)?, - nonce: u64::from_be_bytes(B64::decode(buf)?.0), - base_fee_per_gas: None, - withdrawals_root: None, - 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)?); - } - - // Withdrawals root for post-shanghai headers - if started_len - buf.len() < rlp_head.payload_length { - this.withdrawals_root = Some(Decodable::decode(buf)?); - } - - // Blob gas used and excess blob gas for post-cancun headers - if started_len - buf.len() < rlp_head.payload_length { - this.blob_gas_used = Some(u64::decode(buf)?); - } - - if started_len - buf.len() < rlp_head.payload_length { - this.excess_blob_gas = Some(u64::decode(buf)?); - } - - // 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: - // * 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 started_len - buf.len() < rlp_head.payload_length { - this.requests_root = Some(B256::decode(buf)?); - } - - let consumed = started_len - buf.len(); - if consumed != rlp_head.payload_length { - return Err(alloy_rlp::Error::ListLengthMismatch { - expected: rlp_head.payload_length, - got: consumed, - }) - } - Ok(this) - } -} - -/// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want -/// to modify header. -#[main_codec(no_arbitrary)] -#[add_arbitrary_tests(rlp, compact)] -#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref)] -pub struct SealedHeader { - /// Locked Header hash. - hash: BlockHash, - /// Locked Header fields. - #[as_ref] - #[deref] - header: Header, -} -impl SealedHeader { - /// Creates the sealed header with the corresponding block hash. - #[inline] - pub const fn new(header: Header, hash: BlockHash) -> Self { - Self { header, hash } - } - - /// Returns the sealed Header fields. - #[inline] - pub const fn header(&self) -> &Header { - &self.header - } - - /// Returns header/block hash. - #[inline] - pub const fn hash(&self) -> BlockHash { - self.hash - } - - /// Updates the block header. - #[cfg(any(test, feature = "test-utils"))] - pub fn set_header(&mut self, header: Header) { - self.header = header - } - - /// Updates the block hash. - #[cfg(any(test, feature = "test-utils"))] - pub fn set_hash(&mut self, hash: BlockHash) { - self.hash = hash - } - - /// Updates the parent block hash. - #[cfg(any(test, feature = "test-utils"))] - pub fn set_parent_hash(&mut self, hash: BlockHash) { - self.header.parent_hash = hash - } - - /// Updates the block number. - #[cfg(any(test, feature = "test-utils"))] - pub fn set_block_number(&mut self, number: BlockNumber) { - self.header.number = number; - } - - /// Updates the block state root. - #[cfg(any(test, feature = "test-utils"))] - pub fn set_state_root(&mut self, state_root: B256) { - self.header.state_root = state_root; - } - - /// Updates the block difficulty. - #[cfg(any(test, feature = "test-utils"))] - pub fn set_difficulty(&mut self, difficulty: U256) { - self.header.difficulty = difficulty; - } - - /// Extract raw header that can be modified. - pub fn unseal(self) -> Header { - self.header - } - - /// This is the inverse of [`Header::seal_slow`] which returns the raw header and hash. - pub fn split(self) -> (Header, BlockHash) { - (self.header, self.hash) - } - - /// Return the number hash tuple. - pub fn num_hash(&self) -> BlockNumHash { - BlockNumHash::new(self.number, self.hash) - } - - /// Calculates a heuristic for the in-memory size of the [`SealedHeader`]. - #[inline] - pub fn size(&self) -> usize { - self.header.size() + mem::size_of::() - } -} - -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for SealedHeader { - type Parameters = (); - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - // map valid header strategy by sealing - valid_header_strategy().prop_map(|header| header.seal_slow()).boxed() - } - type Strategy = proptest::strategy::BoxedStrategy; -} - -#[cfg(any(test, feature = "arbitrary"))] -impl<'a> arbitrary::Arbitrary<'a> for SealedHeader { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let sealed_header = generate_valid_header( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - ) - .seal_slow(); - Ok(sealed_header) - } -} - -impl Default for SealedHeader { - fn default() -> Self { - Header::default().seal_slow() - } -} - -impl Encodable for SealedHeader { - fn encode(&self, out: &mut dyn BufMut) { - self.header.encode(out); - } -} - -impl Decodable for SealedHeader { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let b = &mut &**buf; - let started_len = buf.len(); - - // decode the header from temp buffer - let header = Header::decode(b)?; - - // hash the consumed bytes, the rlp encoded header - let consumed = started_len - b.len(); - let hash = keccak256(&buf[..consumed]); - - // update original buffer - *buf = *b; - - Ok(Self { header, hash }) - } -} +pub use reth_primitives_traits::{Header, HeaderError, SealedHeader}; /// Represents the direction for a headers request depending on the `reverse` field of the request. /// > The response must contain a number of block headers, of rising number when reverse is 0, @@ -741,8 +87,10 @@ impl From for bool { #[cfg(test)] mod tests { - use super::{Bytes, Decodable, Encodable, Header, B256}; - use crate::{address, b256, bloom, bytes, hex, Address, HeadersDirection, U256}; + use crate::{ + address, b256, bloom, bytes, hex, Address, Bytes, Header, HeadersDirection, B256, U256, + }; + use alloy_rlp::{Decodable, Encodable}; use std::str::FromStr; // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 02e86c45987d..e2f695935ab3 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -30,7 +30,7 @@ pub mod constants; pub mod eip4844; mod error; pub mod genesis; -mod header; +pub mod header; mod integer_list; mod log; mod net; From 8719490be7973d4d6c7d2114f70140c8cec26d27 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:40:36 +0200 Subject: [PATCH 051/405] chore(deps): bump jsonwebtoken (#8840) --- Cargo.lock | 62 ++++++--------------------------------- Cargo.toml | 1 + crates/rpc/rpc/Cargo.toml | 2 +- 3 files changed, 11 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d00466a05c5c..6e8b0aa72059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -459,7 +459,7 @@ dependencies = [ "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "jsonrpsee-types", - "jsonwebtoken 9.3.0", + "jsonwebtoken", "rand 0.8.5", "serde", "thiserror", @@ -4404,20 +4404,6 @@ dependencies = [ "url", ] -[[package]] -name = "jsonwebtoken" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" -dependencies = [ - "base64 0.21.7", - "pem 1.1.1", - "ring 0.16.20", - "serde", - "serde_json", - "simple_asn1", -] - [[package]] name = "jsonwebtoken" version = "9.3.0" @@ -4426,8 +4412,8 @@ checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ "base64 0.21.7", "js-sys", - "pem 3.0.4", - "ring 0.17.8", + "pem", + "ring", "serde", "serde_json", "simple_asn1", @@ -5479,15 +5465,6 @@ dependencies = [ "hmac 0.12.1", ] -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - [[package]] name = "pem" version = "3.0.4" @@ -7863,7 +7840,7 @@ dependencies = [ "http-body", "hyper", "jsonrpsee", - "jsonwebtoken 8.3.0", + "jsonwebtoken", "metrics", "parking_lot 0.12.3", "pin-project", @@ -8471,21 +8448,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -8497,7 +8459,7 @@ dependencies = [ "getrandom 0.2.15", "libc", "spin 0.9.8", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -8675,7 +8637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -8690,7 +8652,7 @@ checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "log", "once_cell", - "ring 0.17.8", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -8759,9 +8721,9 @@ version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ - "ring 0.17.8", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -10332,12 +10294,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 0b58652a637b..af5c0192e393 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -460,6 +460,7 @@ jsonrpsee-http-client = "0.23" # http http = "1.0" http-body = "1.0" +jsonwebtoken = "9" # crypto secp256k1 = { version = "0.28", default-features = false, features = [ diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 700099b41e3f..85ef532d22f4 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -49,7 +49,7 @@ jsonrpsee.workspace = true http.workspace = true http-body.workspace = true hyper.workspace = true -jsonwebtoken = "8" +jsonwebtoken.workspace = true # async async-trait.workspace = true From b88065d6bdf0e317cfd490ff7779254778b00f30 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 14 Jun 2024 16:48:43 +0100 Subject: [PATCH 052/405] docs(book): clarify what ExExes are not (#8805) Co-authored-by: Oliver Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> --- book/SUMMARY.md | 1 + book/developers/exex/exex.md | 14 ++++++++++++-- book/developers/exex/remote.md | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 book/developers/exex/remote.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 1a39a0d87591..e7fa1ea68048 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -76,4 +76,5 @@ - [Execution Extensions](./developers/exex/exex.md) - [How do ExExes work?](./developers/exex/how-it-works.md) - [Hello World](./developers/exex/hello-world.md) + - [Remote](./developers/exex/remote.md) - [Contribute](./developers/contribute.md) diff --git a/book/developers/exex/exex.md b/book/developers/exex/exex.md index 0c3199bc8745..f0cd08d4a586 100644 --- a/book/developers/exex/exex.md +++ b/book/developers/exex/exex.md @@ -2,17 +2,27 @@ ## What are Execution Extensions? -Execution Extensions allow developers to build their own infrastructure that relies on Reth +Execution Extensions (or ExExes, for short) allow developers to build their own infrastructure that relies on Reth as a base for driving the chain (be it [Ethereum](../../run/mainnet.md) or [OP Stack](../../run/optimism.md)) forward. An Execution Extension is a task that derives its state from changes in Reth's state. Some examples of such state derivations are rollups, bridges, and indexers. +They are called Execution Extensions because the main trigger for them is the execution of new blocks (or reorgs of old blocks) +initiated by Reth. + Read more about things you can build with Execution Extensions in the [Paradigm blog](https://www.paradigm.xyz/2024/05/reth-exex). +## What Execution Extensions are not + +Execution Extensions are not separate processes that connect to the main Reth node process. +Instead, ExExes are compiled into the same binary as Reth, and run alongside it, using shared memory for communication. + +If you want to build an Execution Extension that sends data into a separate process, check out the [Remote](./remote.md) chapter. + ## How do I build an Execution Extension? -Let's dive into how to build our own ExEx (short for Execution Extension) from scratch, add tests for it, +Let's dive into how to build our own ExEx from scratch, add tests for it, and run it on the Holesky testnet. 1. [How do ExExes work?](./how-it-works.md) diff --git a/book/developers/exex/remote.md b/book/developers/exex/remote.md new file mode 100644 index 000000000000..a3ac9ff2e867 --- /dev/null +++ b/book/developers/exex/remote.md @@ -0,0 +1,3 @@ +# Remote Execution Extensions + +WIP From 05c67a8709d83482600b8b76e016893c3c5e8dce Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 14 Jun 2024 18:02:56 +0200 Subject: [PATCH 053/405] chore(mbdx): use std::ffi, remove unused files (#8839) --- Cargo.lock | 2 - crates/storage/libmdbx-rs/Cargo.lock | 1012 ----------------- crates/storage/libmdbx-rs/Cargo.toml | 10 +- crates/storage/libmdbx-rs/LICENSE | 202 ---- .../storage/libmdbx-rs/benches/transaction.rs | 17 +- crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml | 8 +- crates/storage/libmdbx-rs/mdbx-sys/build.rs | 170 +-- crates/storage/libmdbx-rs/src/cursor.rs | 3 +- crates/storage/libmdbx-rs/src/environment.rs | 4 +- crates/storage/libmdbx-rs/src/error.rs | 3 +- crates/storage/libmdbx-rs/src/transaction.rs | 2 +- 11 files changed, 103 insertions(+), 1330 deletions(-) delete mode 100644 crates/storage/libmdbx-rs/Cargo.lock delete mode 100644 crates/storage/libmdbx-rs/LICENSE diff --git a/Cargo.lock b/Cargo.lock index 6e8b0aa72059..652d49fac07e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7157,7 +7157,6 @@ dependencies = [ "dashmap", "derive_more", "indexmap 2.2.6", - "libc", "parking_lot 0.12.3", "pprof", "rand 0.8.5", @@ -7174,7 +7173,6 @@ version = "1.0.0-rc.1" dependencies = [ "bindgen", "cc", - "libc", ] [[package]] diff --git a/crates/storage/libmdbx-rs/Cargo.lock b/crates/storage/libmdbx-rs/Cargo.lock deleted file mode 100644 index 18c3cf63dc98..000000000000 --- a/crates/storage/libmdbx-rs/Cargo.lock +++ /dev/null @@ -1,1012 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bindgen" -version = "0.60.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clang-sys" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags", - "textwrap", - "unicode-width", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "criterion" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" -dependencies = [ - "atty", - "cast", - "clap", - "criterion-plot", - "csv", - "itertools", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" -dependencies = [ - "cast", - "itertools", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197" - -[[package]] -name = "libloading" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "lifetimed-bytes" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c970c8ea4c7b023a41cfa4af4c785a16694604c2f2a3b0d1f20a9bcb73fa550" -dependencies = [ - "bytes", -] - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "mdbx-sys" -version = "0.11.8-0" -dependencies = [ - "bindgen", - "cc", - "libc", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "nom" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" - -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "plotters" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" - -[[package]] -name = "plotters-svg" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rayon" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "reth-libmdbx" -version = "0.1.6" -dependencies = [ - "bitflags", - "byteorder", - "criterion", - "derive_more", - "indexmap", - "libc", - "lifetimed-bytes", - "mdbx-sys", - "parking_lot", - "rand", - "rand_xorshift", - "tempfile", - "thiserror", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "semver" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" - -[[package]] -name = "serde" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" -dependencies = [ - "itoa 1.0.4", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "syn" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "thiserror" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "web-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/crates/storage/libmdbx-rs/Cargo.toml b/crates/storage/libmdbx-rs/Cargo.toml index 68576e0d066e..8056b68557b8 100644 --- a/crates/storage/libmdbx-rs/Cargo.toml +++ b/crates/storage/libmdbx-rs/Cargo.toml @@ -1,32 +1,28 @@ [package] name = "reth-libmdbx" +description = "Idiomatic and safe MDBX wrapper" version.workspace = true edition.workspace = true rust-version.workspace = true license = "Apache-2.0" -description = "Idiomatic and safe MDBX wrapper with good licence" homepage.workspace = true repository.workspace = true [lints] workspace = true -[lib] -name = "reth_libmdbx" - [dependencies] +reth-mdbx-sys.workspace = true + bitflags.workspace = true byteorder = "1" derive_more.workspace = true indexmap = "2" -libc = "0.2" parking_lot.workspace = true thiserror.workspace = true dashmap = { workspace = true, features = ["inline"], optional = true } tracing.workspace = true -reth-mdbx-sys.workspace = true - [features] default = [] return-borrowed = [] diff --git a/crates/storage/libmdbx-rs/LICENSE b/crates/storage/libmdbx-rs/LICENSE deleted file mode 100644 index fec6b4387638..000000000000 --- a/crates/storage/libmdbx-rs/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2014 Dan Burkert - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/crates/storage/libmdbx-rs/benches/transaction.rs b/crates/storage/libmdbx-rs/benches/transaction.rs index 8cc84b01f3c4..33d25cdaa68a 100644 --- a/crates/storage/libmdbx-rs/benches/transaction.rs +++ b/crates/storage/libmdbx-rs/benches/transaction.rs @@ -2,7 +2,6 @@ mod utils; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use libc::size_t; use rand::{prelude::SliceRandom, SeedableRng}; use rand_xorshift::XorShiftRng; use reth_libmdbx::{ffi::*, ObjectLength, WriteFlags}; @@ -46,10 +45,10 @@ fn bench_get_rand_raw(c: &mut Criterion) { c.bench_function("bench_get_rand_raw", |b| { b.iter(|| unsafe { txn.txn_execute(|txn| { - let mut i: size_t = 0; + let mut i = 0; for key in &keys { - key_val.iov_len = key.len() as size_t; - key_val.iov_base = key.as_bytes().as_ptr() as *mut _; + key_val.iov_len = key.len(); + key_val.iov_base = key.as_bytes().as_ptr().cast_mut().cast(); mdbx_get(txn, dbi, &key_val, &mut data_val); @@ -102,12 +101,12 @@ fn bench_put_rand_raw(c: &mut Criterion) { env.with_raw_env_ptr(|env| { mdbx_txn_begin_ex(env, ptr::null_mut(), 0, &mut txn, ptr::null_mut()); - let mut i: ::libc::c_int = 0; + let mut i = 0; for (key, data) in &items { - key_val.iov_len = key.len() as size_t; - key_val.iov_base = key.as_bytes().as_ptr() as *mut _; - data_val.iov_len = data.len() as size_t; - data_val.iov_base = data.as_bytes().as_ptr() as *mut _; + key_val.iov_len = key.len(); + key_val.iov_base = key.as_bytes().as_ptr().cast_mut().cast(); + data_val.iov_len = data.len(); + data_val.iov_base = data.as_bytes().as_ptr().cast_mut().cast(); i += mdbx_put(txn, dbi, &key_val, &mut data_val, 0); } diff --git a/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml b/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml index fbdad4c51072..8cd56d1f2791 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml +++ b/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml @@ -1,19 +1,13 @@ [package] name = "reth-mdbx-sys" +description = "Raw bindings for libmdbx" version.workspace = true edition.workspace = true rust-version.workspace = true license = "Apache-2.0" -description = "Rust bindings for libmdbx with good licence." homepage.workspace = true repository.workspace = true -[lib] -name = "reth_mdbx_sys" - -[dependencies] -libc = "0.2" - [build-dependencies] cc = "1.0" bindgen = { version = "0.69", default-features = false, features = ["runtime"] } diff --git a/crates/storage/libmdbx-rs/mdbx-sys/build.rs b/crates/storage/libmdbx-rs/mdbx-sys/build.rs index 5f82d02b4c54..c265d02e2336 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/build.rs +++ b/crates/storage/libmdbx-rs/mdbx-sys/build.rs @@ -1,110 +1,112 @@ -use bindgen::{ - callbacks::{IntKind, ParseCallbacks}, - Formatter, +use std::{ + env, + path::{Path, PathBuf}, }; -use std::{env, path::PathBuf}; - -#[derive(Debug)] -struct Callbacks; - -impl ParseCallbacks for Callbacks { - fn int_macro(&self, name: &str, _value: i64) -> Option { - match name { - "MDBX_SUCCESS" | - "MDBX_KEYEXIST" | - "MDBX_NOTFOUND" | - "MDBX_PAGE_NOTFOUND" | - "MDBX_CORRUPTED" | - "MDBX_PANIC" | - "MDBX_VERSION_MISMATCH" | - "MDBX_INVALID" | - "MDBX_MAP_FULL" | - "MDBX_DBS_FULL" | - "MDBX_READERS_FULL" | - "MDBX_TLS_FULL" | - "MDBX_TXN_FULL" | - "MDBX_CURSOR_FULL" | - "MDBX_PAGE_FULL" | - "MDBX_MAP_RESIZED" | - "MDBX_INCOMPATIBLE" | - "MDBX_BAD_RSLOT" | - "MDBX_BAD_TXN" | - "MDBX_BAD_VALSIZE" | - "MDBX_BAD_DBI" | - "MDBX_LOG_DONTCHANGE" | - "MDBX_DBG_DONTCHANGE" | - "MDBX_RESULT_TRUE" | - "MDBX_UNABLE_EXTEND_MAPSIZE" | - "MDBX_PROBLEM" | - "MDBX_LAST_LMDB_ERRCODE" | - "MDBX_BUSY" | - "MDBX_EMULTIVAL" | - "MDBX_EBADSIGN" | - "MDBX_WANNA_RECOVERY" | - "MDBX_EKEYMISMATCH" | - "MDBX_TOO_LARGE" | - "MDBX_THREAD_MISMATCH" | - "MDBX_TXN_OVERLAPPING" | - "MDBX_LAST_ERRCODE" => Some(IntKind::Int), - _ => Some(IntKind::UInt), - } - } -} fn main() { - let mut mdbx = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap()); - mdbx.push("libmdbx"); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let mdbx = manifest_dir.join("libmdbx"); println!("cargo:rerun-if-changed={}", mdbx.display()); - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - - let bindings = bindgen::Builder::default() - .header(mdbx.join("mdbx.h").to_string_lossy()) - .allowlist_var("^(MDBX|mdbx)_.*") - .allowlist_type("^(MDBX|mdbx)_.*") - .allowlist_function("^(MDBX|mdbx)_.*") - .size_t_is_usize(true) - .ctypes_prefix("::libc") - .parse_callbacks(Box::new(Callbacks)) - .layout_tests(false) - .prepend_enum_name(false) - .generate_comments(false) - .disable_header_comment() - .formatter(Formatter::Rustfmt) - .generate() - .expect("Unable to generate bindings"); - - bindings.write_to_file(out_path.join("bindings.rs")).expect("Couldn't write bindings!"); - - let mut mdbx = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap()); - mdbx.push("libmdbx"); + let bindings = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("bindings.rs"); + generate_bindings(&mdbx, &bindings); - let mut cc_builder = cc::Build::new(); - cc_builder.flag_if_supported("-Wno-unused-parameter").flag_if_supported("-Wuninitialized"); + let mut cc = cc::Build::new(); + cc.flag_if_supported("-Wno-unused-parameter").flag_if_supported("-Wuninitialized"); if env::var("CARGO_CFG_TARGET_OS").unwrap() != "linux" { - cc_builder.flag_if_supported("-Wbad-function-cast"); + cc.flag_if_supported("-Wbad-function-cast"); } - let flags = format!("{:?}", cc_builder.get_compiler().cflags_env()); - cc_builder.define("MDBX_BUILD_FLAGS", flags.as_str()).define("MDBX_TXN_CHECKOWNER", "0"); + let flags = format!("{:?}", cc.get_compiler().cflags_env()); + cc.define("MDBX_BUILD_FLAGS", flags.as_str()).define("MDBX_TXN_CHECKOWNER", "0"); // Enable debugging on debug builds #[cfg(debug_assertions)] - cc_builder.define("MDBX_DEBUG", "1").define("MDBX_ENABLE_PROFGC", "1"); + cc.define("MDBX_DEBUG", "1").define("MDBX_ENABLE_PROFGC", "1"); // Disables debug logging on optimized builds #[cfg(not(debug_assertions))] - cc_builder.define("MDBX_DEBUG", "0").define("NDEBUG", None); + cc.define("MDBX_DEBUG", "0").define("NDEBUG", None); // Propagate `-C target-cpu=native` let rustflags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(); if rustflags.contains("target-cpu=native") && env::var("CARGO_CFG_TARGET_ENV").unwrap() != "msvc" { - cc_builder.flag("-march=native"); + cc.flag("-march=native"); } - cc_builder.file(mdbx.join("mdbx.c")).compile("libmdbx.a"); + cc.file(mdbx.join("mdbx.c")).compile("libmdbx.a"); +} + +fn generate_bindings(mdbx: &Path, out_file: &Path) { + use bindgen::{ + callbacks::{IntKind, ParseCallbacks}, + Formatter, + }; + + #[derive(Debug)] + struct Callbacks; + + impl ParseCallbacks for Callbacks { + fn int_macro(&self, name: &str, _value: i64) -> Option { + match name { + "MDBX_SUCCESS" | + "MDBX_KEYEXIST" | + "MDBX_NOTFOUND" | + "MDBX_PAGE_NOTFOUND" | + "MDBX_CORRUPTED" | + "MDBX_PANIC" | + "MDBX_VERSION_MISMATCH" | + "MDBX_INVALID" | + "MDBX_MAP_FULL" | + "MDBX_DBS_FULL" | + "MDBX_READERS_FULL" | + "MDBX_TLS_FULL" | + "MDBX_TXN_FULL" | + "MDBX_CURSOR_FULL" | + "MDBX_PAGE_FULL" | + "MDBX_MAP_RESIZED" | + "MDBX_INCOMPATIBLE" | + "MDBX_BAD_RSLOT" | + "MDBX_BAD_TXN" | + "MDBX_BAD_VALSIZE" | + "MDBX_BAD_DBI" | + "MDBX_LOG_DONTCHANGE" | + "MDBX_DBG_DONTCHANGE" | + "MDBX_RESULT_TRUE" | + "MDBX_UNABLE_EXTEND_MAPSIZE" | + "MDBX_PROBLEM" | + "MDBX_LAST_LMDB_ERRCODE" | + "MDBX_BUSY" | + "MDBX_EMULTIVAL" | + "MDBX_EBADSIGN" | + "MDBX_WANNA_RECOVERY" | + "MDBX_EKEYMISMATCH" | + "MDBX_TOO_LARGE" | + "MDBX_THREAD_MISMATCH" | + "MDBX_TXN_OVERLAPPING" | + "MDBX_LAST_ERRCODE" => Some(IntKind::Int), + _ => Some(IntKind::UInt), + } + } + } + + let bindings = bindgen::Builder::default() + .header(mdbx.join("mdbx.h").to_string_lossy()) + .allowlist_var("^(MDBX|mdbx)_.*") + .allowlist_type("^(MDBX|mdbx)_.*") + .allowlist_function("^(MDBX|mdbx)_.*") + .size_t_is_usize(true) + .merge_extern_blocks(true) + .parse_callbacks(Box::new(Callbacks)) + .layout_tests(false) + .prepend_enum_name(false) + .generate_comments(false) + .formatter(Formatter::Rustfmt) + .generate() + .expect("Unable to generate bindings"); + bindings.write_to_file(out_file).expect("Couldn't write bindings!"); } diff --git a/crates/storage/libmdbx-rs/src/cursor.rs b/crates/storage/libmdbx-rs/src/cursor.rs index 6cc9b05f2f64..36da31caa633 100644 --- a/crates/storage/libmdbx-rs/src/cursor.rs +++ b/crates/storage/libmdbx-rs/src/cursor.rs @@ -11,8 +11,7 @@ use ffi::{ MDBX_NEXT_MULTIPLE, MDBX_NEXT_NODUP, MDBX_PREV, MDBX_PREV_DUP, MDBX_PREV_MULTIPLE, MDBX_PREV_NODUP, MDBX_SET, MDBX_SET_KEY, MDBX_SET_LOWERBOUND, MDBX_SET_RANGE, }; -use libc::c_void; -use std::{borrow::Cow, fmt, marker::PhantomData, mem, ptr}; +use std::{borrow::Cow, ffi::c_void, fmt, marker::PhantomData, mem, ptr}; /// A cursor for navigating the items within a database. pub struct Cursor diff --git a/crates/storage/libmdbx-rs/src/environment.rs b/crates/storage/libmdbx-rs/src/environment.rs index f1a2cbe14a50..1549d42e1866 100644 --- a/crates/storage/libmdbx-rs/src/environment.rs +++ b/crates/storage/libmdbx-rs/src/environment.rs @@ -201,8 +201,8 @@ impl Environment { /// Note: /// /// * MDBX stores all the freelists in the designated database 0 in each environment, and the - /// freelist count is stored at the beginning of the value as `libc::uint32_t` in the native - /// byte order. + /// freelist count is stored at the beginning of the value as `uint32_t` in the native byte + /// order. /// /// * It will create a read transaction to traverse the freelist database. pub fn freelist(&self) -> Result { diff --git a/crates/storage/libmdbx-rs/src/error.rs b/crates/storage/libmdbx-rs/src/error.rs index 20a101153899..1df5a397b2de 100644 --- a/crates/storage/libmdbx-rs/src/error.rs +++ b/crates/storage/libmdbx-rs/src/error.rs @@ -1,5 +1,4 @@ -use libc::c_int; -use std::result; +use std::{ffi::c_int, result}; /// An MDBX result. pub type Result = result::Result; diff --git a/crates/storage/libmdbx-rs/src/transaction.rs b/crates/storage/libmdbx-rs/src/transaction.rs index 5e8049bb22c9..37af501c1838 100644 --- a/crates/storage/libmdbx-rs/src/transaction.rs +++ b/crates/storage/libmdbx-rs/src/transaction.rs @@ -8,9 +8,9 @@ use crate::{ }; use ffi::{mdbx_txn_renew, MDBX_txn_flags_t, MDBX_TXN_RDONLY, MDBX_TXN_READWRITE}; use indexmap::IndexSet; -use libc::{c_uint, c_void}; use parking_lot::{Mutex, MutexGuard}; use std::{ + ffi::{c_uint, c_void}, fmt::{self, Debug}, mem::size_of, ptr, slice, From e3317751264be567a0f7294d0847a1fdd10d4885 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 14 Jun 2024 18:07:35 +0200 Subject: [PATCH 054/405] chore: move `primitives/constants` to `reth-primitives-traits` (#8842) --- crates/primitives-traits/src/constants.rs | 170 ++++++++++++++++++++++ crates/primitives-traits/src/lib.rs | 3 + crates/primitives/src/constants/mod.rs | 167 +-------------------- 3 files changed, 175 insertions(+), 165 deletions(-) create mode 100644 crates/primitives-traits/src/constants.rs diff --git a/crates/primitives-traits/src/constants.rs b/crates/primitives-traits/src/constants.rs new file mode 100644 index 000000000000..bda693976305 --- /dev/null +++ b/crates/primitives-traits/src/constants.rs @@ -0,0 +1,170 @@ +//! Ethereum protocol-related constants + +use alloy_primitives::{b256, B256, U256}; +use std::time::Duration; + +/// The client version: `reth/v{major}.{minor}.{patch}` +pub const RETH_CLIENT_VERSION: &str = concat!("reth/v", env!("CARGO_PKG_VERSION")); + +/// The first four bytes of the call data for a function call specifies the function to be called. +pub const SELECTOR_LEN: usize = 4; + +/// Maximum extra data size in a block after genesis +pub const MAXIMUM_EXTRA_DATA_SIZE: usize = 32; + +/// An EPOCH is a series of 32 slots. +pub const EPOCH_SLOTS: u64 = 32; + +/// The duration of a slot in seconds. +/// +/// This is the time period of 12 seconds in which a randomly chosen validator has time to propose a +/// block. +pub const SLOT_DURATION: Duration = Duration::from_secs(12); + +/// An EPOCH is a series of 32 slots (~6.4min). +pub const EPOCH_DURATION: Duration = Duration::from_secs(12 * EPOCH_SLOTS); + +/// The default block nonce in the beacon consensus +pub const BEACON_NONCE: u64 = 0u64; + +/// The default Ethereum block gas limit. +// TODO: This should be a chain spec parameter. +/// See . +pub const ETHEREUM_BLOCK_GAS_LIMIT: u64 = 30_000_000; + +/// The minimum tx fee below which the txpool will reject the transaction. +/// +/// Configured to `7` WEI which is the lowest possible value of base fee under mainnet EIP-1559 +/// parameters. `BASE_FEE_MAX_CHANGE_DENOMINATOR` +/// is `8`, or 12.5%. Once the base fee has dropped to `7` WEI it cannot decrease further because +/// 12.5% of 7 is less than 1. +/// +/// Note that min base fee under different 1559 parameterizations may differ, but there's no +/// significant harm in leaving this setting as is. +pub const MIN_PROTOCOL_BASE_FEE: u64 = 7; + +/// Same as [`MIN_PROTOCOL_BASE_FEE`] but as a U256. +pub const MIN_PROTOCOL_BASE_FEE_U256: U256 = U256::from_limbs([7u64, 0, 0, 0]); + +/// Initial base fee as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) +pub const EIP1559_INITIAL_BASE_FEE: u64 = 1_000_000_000; + +/// Base fee max change denominator as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) +pub const EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8; + +/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) +pub const EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 2; + +/// Minimum gas limit allowed for transactions. +pub const MINIMUM_GAS_LIMIT: u64 = 5000; + +/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism +/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. +pub const OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u128 = 50; + +/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism Canyon +/// hardfork. +pub const OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON: u128 = 250; + +/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism +/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. +pub const OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 6; + +/// Base fee max change denominator for Optimism Sepolia as defined in the Optimism +/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. +pub const OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u128 = 50; + +/// Base fee max change denominator for Optimism Sepolia as defined in the Optimism Canyon +/// hardfork. +pub const OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON: u128 = 250; + +/// Base fee max change denominator for Optimism Sepolia as defined in the Optimism +/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. +pub const OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 6; + +/// Base fee max change denominator for Base Sepolia as defined in the Optimism +/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. +pub const BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 10; + +/// Multiplier for converting gwei to wei. +pub const GWEI_TO_WEI: u64 = 1_000_000_000; + +/// Multiplier for converting finney (milliether) to wei. +pub const FINNEY_TO_WEI: u128 = (GWEI_TO_WEI as u128) * 1_000_000; + +/// Multiplier for converting ether to wei. +pub const ETH_TO_WEI: u128 = FINNEY_TO_WEI * 1000; + +/// Multiplier for converting mgas to gas. +pub const MGAS_TO_GAS: u64 = 1_000_000u64; + +/// The Ethereum mainnet genesis hash: +/// `0x0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3` +pub const MAINNET_GENESIS_HASH: B256 = + b256!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"); + +/// Goerli genesis hash: `0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a` +pub const GOERLI_GENESIS_HASH: B256 = + b256!("bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"); + +/// Sepolia genesis hash: `0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9` +pub const SEPOLIA_GENESIS_HASH: B256 = + b256!("25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9"); + +/// Holesky genesis hash: `0xff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d` +pub const HOLESKY_GENESIS_HASH: B256 = + b256!("ff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d"); + +/// Testnet genesis hash: `0x2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c` +pub const DEV_GENESIS_HASH: B256 = + b256!("2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c"); + +/// Keccak256 over empty array: `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` +pub const KECCAK_EMPTY: B256 = + b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + +/// Ommer root of empty list: `0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347` +pub const EMPTY_OMMER_ROOT_HASH: B256 = + b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); + +/// Root hash of an empty trie: `0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421` +pub const EMPTY_ROOT_HASH: B256 = + b256!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); + +/// Transactions root of empty receipts set. +pub const EMPTY_RECEIPTS: B256 = EMPTY_ROOT_HASH; + +/// Transactions root of empty transactions set. +pub const EMPTY_TRANSACTIONS: B256 = EMPTY_ROOT_HASH; + +/// Withdrawals root of empty withdrawals set. +pub const EMPTY_WITHDRAWALS: B256 = EMPTY_ROOT_HASH; + +/// The number of blocks to unwind during a reorg that already became a part of canonical chain. +/// +/// In reality, the node can end up in this particular situation very rarely. It would happen only +/// if the node process is abruptly terminated during ongoing reorg and doesn't boot back up for +/// long period of time. +/// +/// Unwind depth of `3` blocks significantly reduces the chance that the reorged block is kept in +/// the database. +pub const BEACON_CONSENSUS_REORG_UNWIND_DEPTH: u64 = 3; + +/// Max seconds from current time allowed for blocks, before they're considered future blocks. +/// +/// This is only used when checking whether or not the timestamp for pre-merge blocks is in the +/// future. +/// +/// See: +/// +pub const ALLOWED_FUTURE_BLOCK_TIME_SECONDS: u64 = 15; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn min_protocol_sanity() { + assert_eq!(MIN_PROTOCOL_BASE_FEE_U256.to::(), MIN_PROTOCOL_BASE_FEE); + } +} diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 146cf86eaa08..68161709cd3b 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -13,6 +13,9 @@ #[cfg(feature = "alloy-compat")] mod alloy_compat; +/// Common constants. +pub mod constants; + /// Minimal account pub mod account; pub use account::Account; diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs index 2d1cc6da2bea..191e6e1b26d6 100644 --- a/crates/primitives/src/constants/mod.rs +++ b/crates/primitives/src/constants/mod.rs @@ -3,9 +3,9 @@ use crate::{ chain::DepositContract, revm_primitives::{address, b256}, - B256, U256, }; -use std::time::Duration; + +pub use reth_primitives_traits::constants::*; #[cfg(feature = "optimism")] use crate::chain::BaseFeeParams; @@ -13,61 +13,6 @@ use crate::chain::BaseFeeParams; /// [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#parameters) constants. pub mod eip4844; -/// The client version: `reth/v{major}.{minor}.{patch}` -pub const RETH_CLIENT_VERSION: &str = concat!("reth/v", env!("CARGO_PKG_VERSION")); - -/// The first four bytes of the call data for a function call specifies the function to be called. -pub const SELECTOR_LEN: usize = 4; - -/// Maximum extra data size in a block after genesis -pub const MAXIMUM_EXTRA_DATA_SIZE: usize = 32; - -/// An EPOCH is a series of 32 slots. -pub const EPOCH_SLOTS: u64 = 32; - -/// The duration of a slot in seconds. -/// -/// This is the time period of 12 seconds in which a randomly chosen validator has time to propose a -/// block. -pub const SLOT_DURATION: Duration = Duration::from_secs(12); - -/// An EPOCH is a series of 32 slots (~6.4min). -pub const EPOCH_DURATION: Duration = Duration::from_secs(12 * EPOCH_SLOTS); - -/// The default block nonce in the beacon consensus -pub const BEACON_NONCE: u64 = 0u64; - -/// The default Ethereum block gas limit. -// TODO: This should be a chain spec parameter. -/// See . -pub const ETHEREUM_BLOCK_GAS_LIMIT: u64 = 30_000_000; - -/// The minimum tx fee below which the txpool will reject the transaction. -/// -/// Configured to `7` WEI which is the lowest possible value of base fee under mainnet EIP-1559 -/// parameters. `BASE_FEE_MAX_CHANGE_DENOMINATOR` -/// is `8`, or 12.5%. Once the base fee has dropped to `7` WEI it cannot decrease further because -/// 12.5% of 7 is less than 1. -/// -/// Note that min base fee under different 1559 parameterizations may differ, but there's no -/// significant harm in leaving this setting as is. -pub const MIN_PROTOCOL_BASE_FEE: u64 = 7; - -/// Same as [`MIN_PROTOCOL_BASE_FEE`] but as a U256. -pub const MIN_PROTOCOL_BASE_FEE_U256: U256 = U256::from_limbs([7u64, 0, 0, 0]); - -/// Initial base fee as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) -pub const EIP1559_INITIAL_BASE_FEE: u64 = 1_000_000_000; - -/// Base fee max change denominator as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) -pub const EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8; - -/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) -pub const EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 2; - -/// Minimum gas limit allowed for transactions. -pub const MINIMUM_GAS_LIMIT: u64 = 5000; - /// Deposit contract address: `0x00000000219ab540356cbb839cbe05303d7705fa` pub const MAINNET_DEPOSIT_CONTRACT: DepositContract = DepositContract::new( address!("00000000219ab540356cbb839cbe05303d7705fa"), @@ -75,41 +20,6 @@ pub const MAINNET_DEPOSIT_CONTRACT: DepositContract = DepositContract::new( b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), ); -/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism -/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. -#[cfg(feature = "optimism")] -pub const OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u128 = 50; - -/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism Canyon -/// hardfork. -#[cfg(feature = "optimism")] -pub const OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON: u128 = 250; - -/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism -/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. -#[cfg(feature = "optimism")] -pub const OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 6; - -/// Base fee max change denominator for Optimism Sepolia as defined in the Optimism -/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. -#[cfg(feature = "optimism")] -pub const OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u128 = 50; - -/// Base fee max change denominator for Optimism Sepolia as defined in the Optimism Canyon -/// hardfork. -#[cfg(feature = "optimism")] -pub const OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON: u128 = 250; - -/// Base fee max change denominator for Optimism Sepolia as defined in the Optimism -/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. -#[cfg(feature = "optimism")] -pub const OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 6; - -/// Base fee max change denominator for Base Sepolia as defined in the Optimism -/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. -#[cfg(feature = "optimism")] -pub const BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u128 = 10; - /// Get the base fee parameters for Base Sepolia. #[cfg(feature = "optimism")] pub const BASE_SEPOLIA_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { @@ -152,79 +62,6 @@ pub const OP_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { elasticity_multiplier: OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, }; -/// Multiplier for converting gwei to wei. -pub const GWEI_TO_WEI: u64 = 1_000_000_000; - -/// Multiplier for converting finney (milliether) to wei. -pub const FINNEY_TO_WEI: u128 = (GWEI_TO_WEI as u128) * 1_000_000; - -/// Multiplier for converting ether to wei. -pub const ETH_TO_WEI: u128 = FINNEY_TO_WEI * 1000; - -/// Multiplier for converting mgas to gas. -pub const MGAS_TO_GAS: u64 = 1_000_000u64; - -/// The Ethereum mainnet genesis hash: -/// `0x0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3` -pub const MAINNET_GENESIS_HASH: B256 = - b256!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"); - -/// Goerli genesis hash: `0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a` -pub const GOERLI_GENESIS_HASH: B256 = - b256!("bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"); - -/// Sepolia genesis hash: `0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9` -pub const SEPOLIA_GENESIS_HASH: B256 = - b256!("25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9"); - -/// Holesky genesis hash: `0xff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d` -pub const HOLESKY_GENESIS_HASH: B256 = - b256!("ff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d"); - -/// Testnet genesis hash: `0x2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c` -pub const DEV_GENESIS_HASH: B256 = - b256!("2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c"); - -/// Keccak256 over empty array: `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` -pub const KECCAK_EMPTY: B256 = - b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); - -/// Ommer root of empty list: `0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347` -pub const EMPTY_OMMER_ROOT_HASH: B256 = - b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); - -/// Root hash of an empty trie: `0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421` -pub const EMPTY_ROOT_HASH: B256 = - b256!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); - -/// Transactions root of empty receipts set. -pub const EMPTY_RECEIPTS: B256 = EMPTY_ROOT_HASH; - -/// Transactions root of empty transactions set. -pub const EMPTY_TRANSACTIONS: B256 = EMPTY_ROOT_HASH; - -/// Withdrawals root of empty withdrawals set. -pub const EMPTY_WITHDRAWALS: B256 = EMPTY_ROOT_HASH; - -/// The number of blocks to unwind during a reorg that already became a part of canonical chain. -/// -/// In reality, the node can end up in this particular situation very rarely. It would happen only -/// if the node process is abruptly terminated during ongoing reorg and doesn't boot back up for -/// long period of time. -/// -/// Unwind depth of `3` blocks significantly reduces the chance that the reorged block is kept in -/// the database. -pub const BEACON_CONSENSUS_REORG_UNWIND_DEPTH: u64 = 3; - -/// Max seconds from current time allowed for blocks, before they're considered future blocks. -/// -/// This is only used when checking whether or not the timestamp for pre-merge blocks is in the -/// future. -/// -/// See: -/// -pub const ALLOWED_FUTURE_BLOCK_TIME_SECONDS: u64 = 15; - #[cfg(test)] mod tests { use super::*; From cc46db3aa22fd14728dc68f3465a2df6e4b54d73 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 18:11:26 +0200 Subject: [PATCH 055/405] chore: re-export payload types from engine types (#8841) --- crates/engine-primitives/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/engine-primitives/src/lib.rs b/crates/engine-primitives/src/lib.rs index fd64030fc1ec..819584fc3744 100644 --- a/crates/engine-primitives/src/lib.rs +++ b/crates/engine-primitives/src/lib.rs @@ -9,14 +9,17 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use core::fmt; -use reth_payload_primitives::{ +pub use reth_payload_primitives::{ BuiltPayload, EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes, PayloadTypes, }; use reth_primitives::ChainSpec; use serde::{de::DeserializeOwned, ser::Serialize}; -/// The types that are used by the engine API. +/// This type defines the versioned types of the engine API. +/// +/// This includes the execution payload types and payload attributes that are used to trigger a +/// payload job. Hence this trait is also [`PayloadTypes`]. pub trait EngineTypes: PayloadTypes + DeserializeOwned + Serialize + fmt::Debug + Unpin + Send + Sync + Clone { From e6017c3cd9796cd7eb8f6ab5594ee70171d4ce6c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 18:18:40 +0200 Subject: [PATCH 056/405] chore: rm unused clap feature (#8843) --- Cargo.lock | 1 + bin/reth-bench/Cargo.toml | 2 +- bin/reth/Cargo.toml | 5 +++-- crates/primitives/Cargo.toml | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 652d49fac07e..8c3608a533d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6296,6 +6296,7 @@ dependencies = [ "reth-rpc-types-compat", "reth-stages", "reth-static-file", + "reth-static-file-types", "reth-tasks", "reth-tracing", "reth-transaction-pool", diff --git a/bin/reth-bench/Cargo.toml b/bin/reth-bench/Cargo.toml index bca2cf4f8d24..a0bf299f19c2 100644 --- a/bin/reth-bench/Cargo.toml +++ b/bin/reth-bench/Cargo.toml @@ -21,7 +21,7 @@ reth-node-core.workspace = true reth-node-api.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true -reth-primitives = { workspace = true, features = ["clap", "alloy-compat"] } +reth-primitives = { workspace = true, features = ["alloy-compat"] } reth-tracing.workspace = true # alloy diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 38410737ecfb..eebc415e742d 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] # reth reth-config.workspace = true -reth-primitives = { workspace = true, features = ["arbitrary", "clap"] } +reth-primitives = { workspace = true, features = ["arbitrary"] } reth-fs-util.workspace = true reth-db = { workspace = true, features = ["mdbx"] } reth-db-api.workspace = true @@ -49,7 +49,8 @@ reth-payload-validator.workspace = true reth-basic-payload-builder.workspace = true reth-discv4.workspace = true reth-discv5.workspace = true -reth-static-file = { workspace = true } +reth-static-file.workspace = true +reth-static-file-types = { workspace = true, features = ["clap"] } reth-trie = { workspace = true, features = ["metrics"] } reth-nippy-jar.workspace = true reth-node-api.workspace = true diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 5d3cc821ea61..dc3efc7d7b08 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -111,7 +111,6 @@ c-kzg = [ "alloy-eips/kzg", ] zstd-codec = ["dep:zstd"] -clap = ["reth-static-file-types/clap"] optimism = [ "reth-codecs/optimism", "reth-ethereum-forks/optimism", From 40d588470f579f26effc77aa288274b2a73783e1 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 14 Jun 2024 18:46:37 +0200 Subject: [PATCH 057/405] chore(rpc): use `TypedData` type in `eth_signTypedData` signature (#8845) --- Cargo.lock | 1 + crates/rpc/rpc-api/Cargo.toml | 5 ++++- crates/rpc/rpc-api/src/eth.rs | 3 ++- crates/rpc/rpc-builder/tests/it/http.rs | 15 ++++++++++--- crates/rpc/rpc-testing-util/Cargo.toml | 2 -- crates/rpc/rpc/src/eth/api/server.rs | 28 ++++++++++++------------- crates/rpc/rpc/src/eth/api/sign.rs | 16 ++++---------- crates/rpc/rpc/src/eth/signer.rs | 9 +++----- 8 files changed, 39 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c3608a533d4..6ed2e9e60373 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7883,6 +7883,7 @@ dependencies = [ name = "reth-rpc-api" version = "1.0.0-rc.1" dependencies = [ + "alloy-dyn-abi", "jsonrpsee", "reth-engine-primitives", "reth-network-peers", diff --git a/crates/rpc/rpc-api/Cargo.toml b/crates/rpc/rpc-api/Cargo.toml index 00581a1a16f4..5374c46e4898 100644 --- a/crates/rpc/rpc-api/Cargo.toml +++ b/crates/rpc/rpc-api/Cargo.toml @@ -19,9 +19,12 @@ reth-engine-primitives.workspace = true reth-network-peers.workspace = true # misc +alloy-dyn-abi = { workspace = true, features = ["eip712"] } jsonrpsee = { workspace = true, features = ["server", "macros"] } -serde_json.workspace = true serde = { workspace = true, features = ["derive"] } +[dev-dependencies] +serde_json.workspace = true + [features] client = ["jsonrpsee/client", "jsonrpsee/async-client"] diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs index 44b5df58a0a6..eb11fde824cc 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-api/src/eth.rs @@ -1,3 +1,4 @@ +use alloy_dyn_abi::TypedData; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; use reth_rpc_types::{ @@ -296,7 +297,7 @@ pub trait EthApi { /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). #[method(name = "signTypedData")] - async fn sign_typed_data(&self, address: Address, data: serde_json::Value) -> RpcResult; + async fn sign_typed_data(&self, address: Address, data: TypedData) -> RpcResult; /// Returns the account and storage values of the specified account including the Merkle-proof. /// This call can be used to verify that the data you are pulling from is not tampered with. diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 17bd638d0747..34ac352eee22 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -159,6 +159,17 @@ where let transaction_request = TransactionRequest::default(); let bytes = Bytes::default(); let tx = Bytes::from(hex!("02f871018303579880850555633d1b82520894eee27662c2b8eba3cd936a23f039f3189633e4c887ad591c62bdaeb180c080a07ea72c68abfb8fca1bd964f0f99132ed9280261bdca3e549546c0205e800f7d0a05b4ef3039e9c9b9babc179a1878fb825b5aaf5aed2fa8744854150157b08d6f3")); + let typed_data = serde_json::from_str( + r#"{ + "types": { + "EIP712Domain": [] + }, + "primaryType": "EIP712Domain", + "domain": {}, + "message": {} + }"#, + ) + .unwrap(); // Implemented EthApiClient::protocol_version(client).await.unwrap(); @@ -180,9 +191,7 @@ where EthApiClient::uncle_by_block_hash_and_index(client, hash, index).await.unwrap(); EthApiClient::uncle_by_block_number_and_index(client, block_number, index).await.unwrap(); EthApiClient::sign(client, address, bytes.clone()).await.unwrap_err(); - EthApiClient::sign_typed_data(client, address, jsonrpsee::core::JsonValue::Null) - .await - .unwrap_err(); + EthApiClient::sign_typed_data(client, address, typed_data).await.unwrap_err(); EthApiClient::transaction_by_hash(client, tx_hash).await.unwrap(); EthApiClient::transaction_by_block_hash_and_index(client, hash, index).await.unwrap(); EthApiClient::transaction_by_block_number_and_index(client, block_number, index).await.unwrap(); diff --git a/crates/rpc/rpc-testing-util/Cargo.toml b/crates/rpc/rpc-testing-util/Cargo.toml index b14451969cec..898fec038f70 100644 --- a/crates/rpc/rpc-testing-util/Cargo.toml +++ b/crates/rpc/rpc-testing-util/Cargo.toml @@ -24,10 +24,8 @@ futures.workspace = true jsonrpsee = { workspace = true, features = ["client", "async-client"] } serde_json.workspace = true - # assertions similar-asserts.workspace = true - [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros", "rt"] } diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 02a108f5fd07..3d3ceeb6c968 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -1,6 +1,16 @@ //! Implementation of the [`jsonrpsee`] generated [`reth_rpc_api::EthApiServer`] trait //! Handles RPC requests for the `eth_` namespace. +use super::EthApiSpec; +use crate::{ + eth::{ + api::{EthApi, EthTransactions}, + error::EthApiError, + revm_utils::EvmOverrides, + }, + result::{internal_rpc_err, ToRpcResult}, +}; +use alloy_dyn_abi::TypedData; use jsonrpsee::core::RpcResult as Result; use reth_evm::ConfigureEvm; use reth_network_api::NetworkInfo; @@ -16,20 +26,8 @@ use reth_rpc_types::{ FeeHistory, Header, Index, RichBlock, StateContext, SyncStatus, TransactionRequest, Work, }; use reth_transaction_pool::TransactionPool; -use serde_json::Value; use tracing::trace; -use crate::{ - eth::{ - api::{EthApi, EthTransactions}, - error::EthApiError, - revm_utils::EvmOverrides, - }, - result::{internal_rpc_err, ToRpcResult}, -}; - -use super::EthApiSpec; - #[async_trait::async_trait] impl EthApiServer for EthApi where @@ -391,7 +389,7 @@ where /// Handler for: `eth_sign` async fn sign(&self, address: Address, message: Bytes) -> Result { trace!(target: "rpc::eth", ?address, ?message, "Serving eth_sign"); - Ok(Self::sign(self, address, message).await?) + Ok(Self::sign(self, address, &message).await?) } /// Handler for: `eth_signTransaction` @@ -400,9 +398,9 @@ where } /// Handler for: `eth_signTypedData` - async fn sign_typed_data(&self, address: Address, data: Value) -> Result { + async fn sign_typed_data(&self, address: Address, data: TypedData) -> Result { trace!(target: "rpc::eth", ?address, ?data, "Serving eth_signTypedData"); - Ok(Self::sign_typed_data(self, data, address)?) + Ok(Self::sign_typed_data(self, &data, address)?) } /// Handler for: `eth_getProof` diff --git a/crates/rpc/rpc/src/eth/api/sign.rs b/crates/rpc/rpc/src/eth/api/sign.rs index 5cbdefa41c9e..5256de4a4dae 100644 --- a/crates/rpc/rpc/src/eth/api/sign.rs +++ b/crates/rpc/rpc/src/eth/api/sign.rs @@ -9,22 +9,14 @@ use crate::{ }; use alloy_dyn_abi::TypedData; use reth_primitives::{Address, Bytes}; -use serde_json::Value; impl EthApi { - pub(crate) async fn sign(&self, account: Address, message: Bytes) -> EthResult { - Ok(self.find_signer(&account)?.sign(account, &message).await?.to_hex_bytes()) + pub(crate) async fn sign(&self, account: Address, message: &[u8]) -> EthResult { + Ok(self.find_signer(&account)?.sign(account, message).await?.to_hex_bytes()) } - pub(crate) fn sign_typed_data(&self, data: Value, account: Address) -> EthResult { - Ok(self - .find_signer(&account)? - .sign_typed_data( - account, - &serde_json::from_value::(data) - .map_err(|_| SignError::InvalidTypedData)?, - )? - .to_hex_bytes()) + pub(crate) fn sign_typed_data(&self, data: &TypedData, account: Address) -> EthResult { + Ok(self.find_signer(&account)?.sign_typed_data(account, data)?.to_hex_bytes()) } pub(crate) fn find_signer( diff --git a/crates/rpc/rpc/src/eth/signer.rs b/crates/rpc/rpc/src/eth/signer.rs index cffaa01f2f44..50c06159c4f0 100644 --- a/crates/rpc/rpc/src/eth/signer.rs +++ b/crates/rpc/rpc/src/eth/signer.rs @@ -115,7 +115,6 @@ impl EthSigner for DevSigner { fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result { let encoded = payload.eip712_signing_hash().map_err(|_| SignError::InvalidTypedData)?; - // let b256 = encoded; self.sign_hash(encoded, address) } } @@ -136,8 +135,7 @@ mod tests { #[tokio::test] async fn test_sign_type_data() { - let eip_712_example = serde_json::json!( - r#"{ + let eip_712_example = r#"{ "types": { "EIP712Domain": [ { @@ -200,9 +198,8 @@ mod tests { }, "contents": "Hello, Bob!" } - }"# - ); - let data: TypedData = serde_json::from_value(eip_712_example).unwrap(); + }"#; + let data: TypedData = serde_json::from_str(eip_712_example).unwrap(); let signer = build_signer(); let sig = signer.sign_typed_data(Address::default(), &data).unwrap(); let expected = Signature { From d7af166d85fc9d56b5f9fcc631578786816fd1ac Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 18:50:59 +0200 Subject: [PATCH 058/405] chore(deps): rm unused arb feature (#8846) --- bin/reth/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index eebc415e742d..f6ec9bd7d440 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] # reth reth-config.workspace = true -reth-primitives = { workspace = true, features = ["arbitrary"] } +reth-primitives.workspace = true reth-fs-util.workspace = true reth-db = { workspace = true, features = ["mdbx"] } reth-db-api.workspace = true From f9165fb9af9c9184a52cbcc08ba689e2954c7dbc Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 14 Jun 2024 21:24:20 +0200 Subject: [PATCH 059/405] feat: add some utils to `EvmOverrides` (#8848) --- crates/rpc/rpc/src/eth/revm_utils.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 6943c6e28792..2f132fd4dd55 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -51,10 +51,32 @@ impl EvmOverrides { Self { state, block: None } } + /// Creates a new instance with the given block overrides. + pub const fn block(block: Option>) -> Self { + Self { state: None, block } + } + /// Returns `true` if the overrides contain state overrides. pub const fn has_state(&self) -> bool { self.state.is_some() } + + /// Returns `true` if the overrides contain block overrides. + pub const fn has_block(&self) -> bool { + self.block.is_some() + } + + /// Adds state overrides to an existing instance. + pub fn with_state(mut self, state: StateOverride) -> Self { + self.state = Some(state); + self + } + + /// Adds block overrides to an existing instance. + pub fn with_block(mut self, block: Box) -> Self { + self.block = Some(block); + self + } } impl From> for EvmOverrides { From 3b27da59dc9eaa920bad1683fca6ed92d34d2ee2 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:28:08 -0400 Subject: [PATCH 060/405] chore: move gas units to primitives constants (#8849) --- bin/reth-bench/src/bench/output.rs | 12 +----------- crates/primitives/src/constants/gas_units.rs | 8 ++++++++ crates/primitives/src/constants/mod.rs | 3 +++ crates/stages/stages/src/stages/execution.rs | 15 +++++++++------ 4 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 crates/primitives/src/constants/gas_units.rs diff --git a/bin/reth-bench/src/bench/output.rs b/bin/reth-bench/src/bench/output.rs index f976b5d28496..83103418d929 100644 --- a/bin/reth-bench/src/bench/output.rs +++ b/bin/reth-bench/src/bench/output.rs @@ -1,20 +1,10 @@ //! Contains various benchmark output formats, either for logging or for //! serialization to / from files. -//! -//! This also contains common constants for units, for example [GIGAGAS]. +use reth_primitives::constants::gas_units::GIGAGAS; use serde::{ser::SerializeStruct, Serialize}; use std::time::Duration; -/// Represents one Kilogas, or `1_000` gas. -const KILOGAS: u64 = 1_000; - -/// Represents one Megagas, or `1_000_000` gas. -const MEGAGAS: u64 = KILOGAS * 1_000; - -/// Represents one Gigagas, or `1_000_000_000` gas. -const GIGAGAS: u64 = MEGAGAS * 1_000; - /// This is the suffix for gas output csv files. pub(crate) const GAS_OUTPUT_SUFFIX: &str = "total_gas.csv"; diff --git a/crates/primitives/src/constants/gas_units.rs b/crates/primitives/src/constants/gas_units.rs new file mode 100644 index 000000000000..9916de864a63 --- /dev/null +++ b/crates/primitives/src/constants/gas_units.rs @@ -0,0 +1,8 @@ +/// Represents one Kilogas, or `1_000` gas. +pub const KILOGAS: u64 = 1_000; + +/// Represents one Megagas, or `1_000_000` gas. +pub const MEGAGAS: u64 = KILOGAS * 1_000; + +/// Represents one Gigagas, or `1_000_000_000` gas. +pub const GIGAGAS: u64 = MEGAGAS * 1_000; diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs index 191e6e1b26d6..41a26d609e08 100644 --- a/crates/primitives/src/constants/mod.rs +++ b/crates/primitives/src/constants/mod.rs @@ -10,6 +10,9 @@ pub use reth_primitives_traits::constants::*; #[cfg(feature = "optimism")] use crate::chain::BaseFeeParams; +/// Gas units, for example [`GIGAGAS`](gas_units::GIGAGAS). +pub mod gas_units; + /// [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#parameters) constants. pub mod eip4844; diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 0f2e5db6510c..b5b66cca7738 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -5,7 +5,10 @@ use reth_db::{static_file::HeaderMask, tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; use reth_exex::{ExExManagerHandle, ExExNotification}; -use reth_primitives::{BlockNumber, Header, StaticFileSegment}; +use reth_primitives::{ + constants::gas_units::{GIGAGAS, KILOGAS, MEGAGAS}, + BlockNumber, Header, StaticFileSegment, +}; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, BlockReader, Chain, DatabaseProviderRW, ExecutionOutcome, HeaderProvider, @@ -611,12 +614,12 @@ impl From for ExecutionStageThresholds { /// Depending on the magnitude of the gas throughput. pub fn format_gas_throughput(gas: u64, execution_duration: Duration) -> String { let gas_per_second = gas as f64 / execution_duration.as_secs_f64(); - if gas_per_second < 1_000_000.0 { - format!("{:.} Kgas/second", gas_per_second / 1_000.0) - } else if gas_per_second < 1_000_000_000.0 { - format!("{:.} Mgas/second", gas_per_second / 1_000_000.0) + if gas_per_second < MEGAGAS as f64 { + format!("{:.} Kgas/second", gas_per_second / KILOGAS as f64) + } else if gas_per_second < GIGAGAS as f64 { + format!("{:.} Mgas/second", gas_per_second / MEGAGAS as f64) } else { - format!("{:.} Ggas/second", gas_per_second / 1_000_000_000.0) + format!("{:.} Ggas/second", gas_per_second / GIGAGAS as f64) } } From 352d57655b240643a11888fd21c32c530200ad0a Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:59:14 -0400 Subject: [PATCH 061/405] chore: clean up reth-bench payload status checks (#8852) --- bin/reth-bench/src/valid_payload.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/bin/reth-bench/src/valid_payload.rs b/bin/reth-bench/src/valid_payload.rs index 67bd1bc1e360..4c96c43b18cf 100644 --- a/bin/reth-bench/src/valid_payload.rs +++ b/bin/reth-bench/src/valid_payload.rs @@ -5,7 +5,6 @@ use alloy_provider::{ext::EngineApi, Network}; use alloy_rpc_types_engine::{ ExecutionPayloadInputV2, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, - PayloadStatusEnum, }; use alloy_transport::{Transport, TransportResult}; use reth_node_api::EngineApiMessageVersion; @@ -76,8 +75,8 @@ where payload: ExecutionPayloadV1, ) -> TransportResult { let mut status = self.new_payload_v1(payload.clone()).await?; - while status.status != PayloadStatusEnum::Valid { - if status.status.is_invalid() { + while !status.is_valid() { + if status.is_invalid() { error!(?status, ?payload, "Invalid newPayloadV1",); panic!("Invalid newPayloadV1: {status:?}"); } @@ -91,8 +90,8 @@ where payload: ExecutionPayloadInputV2, ) -> TransportResult { let mut status = self.new_payload_v2(payload.clone()).await?; - while status.status != PayloadStatusEnum::Valid { - if status.status.is_invalid() { + while !status.is_valid() { + if status.is_invalid() { error!(?status, ?payload, "Invalid newPayloadV2",); panic!("Invalid newPayloadV2: {status:?}"); } @@ -110,8 +109,8 @@ where let mut status = self .new_payload_v3(payload.clone(), versioned_hashes.clone(), parent_beacon_block_root) .await?; - while status.status != PayloadStatusEnum::Valid { - if status.status.is_invalid() { + while !status.is_valid() { + if status.is_invalid() { error!( ?status, ?payload, @@ -136,8 +135,8 @@ where let mut status = self.fork_choice_updated_v1(fork_choice_state, payload_attributes.clone()).await?; - while status.payload_status.status != PayloadStatusEnum::Valid { - if status.payload_status.status.is_invalid() { + while !status.is_valid() { + if status.is_invalid() { error!( ?status, ?fork_choice_state, @@ -161,8 +160,8 @@ where let mut status = self.fork_choice_updated_v2(fork_choice_state, payload_attributes.clone()).await?; - while status.payload_status.status != PayloadStatusEnum::Valid { - if status.payload_status.status.is_invalid() { + while !status.is_valid() { + if status.is_invalid() { error!( ?status, ?fork_choice_state, @@ -186,8 +185,8 @@ where let mut status = self.fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()).await?; - while status.payload_status.status != PayloadStatusEnum::Valid { - if status.payload_status.status.is_invalid() { + while !status.is_valid() { + if status.is_invalid() { error!( ?status, ?fork_choice_state, From 6768243ee03e6f783d43b39d13fbca708f5c6cac Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:15:22 -0400 Subject: [PATCH 062/405] chore: remove reth-rpc-types dep in node-events (#8854) --- Cargo.lock | 2 +- crates/node/events/Cargo.toml | 4 +++- crates/node/events/src/node.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ed2e9e60373..ae37ba76c0d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7507,6 +7507,7 @@ dependencies = [ name = "reth-node-events" version = "1.0.0-rc.1" dependencies = [ + "alloy-rpc-types-engine", "futures", "humantime", "pin-project", @@ -7517,7 +7518,6 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-prune", - "reth-rpc-types", "reth-stages", "reth-static-file", "tokio", diff --git a/crates/node/events/Cargo.toml b/crates/node/events/Cargo.toml index 4ae2a6f7683f..1b14c74de5f3 100644 --- a/crates/node/events/Cargo.toml +++ b/crates/node/events/Cargo.toml @@ -20,7 +20,9 @@ reth-prune.workspace = true reth-static-file.workspace = true reth-db-api.workspace = true reth-primitives.workspace = true -reth-rpc-types.workspace = true + +# alloy +alloy-rpc-types-engine.workspace = true # async tokio.workspace = true diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index 1fc62a9b61e2..8a25c370037b 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -1,6 +1,7 @@ //! Support for handling events emitted by node components. use crate::cl::ConsensusLayerHealthEvent; +use alloy_rpc_types_engine::ForkchoiceState; use futures::Stream; use reth_beacon_consensus::{ BeaconConsensusEngineEvent, ConsensusEngineLiveSyncProgress, ForkchoiceStatus, @@ -10,7 +11,6 @@ use reth_network::{NetworkEvent, NetworkHandle}; use reth_network_api::PeersInfo; use reth_primitives::{constants, BlockNumber, B256}; use reth_prune::PrunerEvent; -use reth_rpc_types::engine::ForkchoiceState; use reth_stages::{EntitiesCheckpoint, ExecOutput, PipelineEvent, StageCheckpoint, StageId}; use reth_static_file::StaticFileProducerEvent; use std::{ From 343ff13fb50cc77d125d6d00961b36f5e4636a30 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 23:34:26 +0200 Subject: [PATCH 063/405] chore: use new quantity mod (#8855) --- crates/rpc/rpc-types/src/mev.rs | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/rpc/rpc-types/src/mev.rs b/crates/rpc/rpc-types/src/mev.rs index 6963cc09b720..20c92f1a6cd8 100644 --- a/crates/rpc/rpc-types/src/mev.rs +++ b/crates/rpc/rpc-types/src/mev.rs @@ -34,12 +34,12 @@ pub struct SendBundleRequest { #[serde(rename_all = "camelCase")] pub struct Inclusion { /// The first block the bundle is valid for. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub block: u64, /// The last block the bundle is valid for. #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub max_block: Option, @@ -104,10 +104,10 @@ pub struct Validity { #[serde(rename_all = "camelCase")] pub struct Refund { /// The index of the transaction in the bundle. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub body_idx: u64, /// The minimum percent of the bundle's earnings to redistribute. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub percent: u64, } @@ -119,7 +119,7 @@ pub struct RefundConfig { /// The address to refund. pub address: Address, /// The minimum percent of the bundle's earnings to redistribute. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub percent: u64, } @@ -322,7 +322,7 @@ pub struct SimBundleOverrides { #[serde(default, skip_serializing_if = "Option::is_none")] pub parent_block: Option, /// Block number used for simulation, defaults to parentBlock.number + 1 - #[serde(default, with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint")] + #[serde(default, with = "alloy_rpc_types::serde_helpers::quantity::opt")] pub block_number: Option, /// Coinbase used for simulation, defaults to parentBlock.coinbase #[serde(default, skip_serializing_if = "Option::is_none")] @@ -330,28 +330,28 @@ pub struct SimBundleOverrides { /// Timestamp used for simulation, defaults to parentBlock.timestamp + 12 #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub timestamp: Option, /// Gas limit used for simulation, defaults to parentBlock.gasLimit #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub gas_limit: Option, /// Base fee used for simulation, defaults to parentBlock.baseFeePerGas #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub base_fee: Option, /// Timeout in seconds, defaults to 5 #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub timeout: Option, @@ -367,19 +367,19 @@ pub struct SimBundleResponse { #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, /// The block number of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub state_block: u64, /// The gas price of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub mev_gas_price: u64, /// The profit of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub profit: u64, /// The refundable value of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub refundable_value: u64, /// The gas used by the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub gas_used: u64, /// Logs returned by `mev_simBundle`. #[serde(default, skip_serializing_if = "Option::is_none")] @@ -434,7 +434,7 @@ pub struct PrivateTransactionRequest { /// be included. #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub max_block_number: Option, @@ -625,19 +625,19 @@ pub struct EthSendBundle { /// A list of hex-encoded signed transactions pub txs: Vec, /// hex-encoded block number for which this bundle is valid - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub block_number: u64, /// unix timestamp when this bundle becomes active #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub min_timestamp: Option, /// unix timestamp how long this bundle stays valid #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub max_timestamp: Option, @@ -666,14 +666,14 @@ pub struct EthCallBundle { /// A list of hex-encoded signed transactions pub txs: Vec, /// hex encoded block number for which this bundle is valid on - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub block_number: u64, /// Either a hex encoded number or a block tag for which state to base this simulation on pub state_block_number: BlockNumberOrTag, /// the timestamp to use for this bundle simulation, in seconds since the unix epoch #[serde( default, - with = "alloy_rpc_types::serde_helpers::num::u64_opt_via_ruint", + with = "alloy_rpc_types::serde_helpers::quantity::opt", skip_serializing_if = "Option::is_none" )] pub timestamp: Option, @@ -700,10 +700,10 @@ pub struct EthCallBundleResponse { /// Results of individual transactions within the bundle pub results: Vec, /// The block number used as a base for this simulation - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub state_block_number: u64, /// The total gas used by all transactions in the bundle - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub total_gas_used: u64, } @@ -726,7 +726,7 @@ pub struct EthCallBundleTransactionResult { #[serde(with = "u256_numeric_string")] pub gas_price: U256, /// The amount of gas used by the transaction - #[serde(with = "alloy_rpc_types::serde_helpers::num::u64_via_ruint")] + #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] pub gas_used: u64, /// The address to which the transaction is sent (optional) pub to_address: Option
, From 22e6afe3f1c758c012d54586f1efb8750ebce165 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 14 Jun 2024 23:38:19 +0200 Subject: [PATCH 064/405] fix(net): use real tcp port for local node record (#8853) --- crates/net/network/src/discovery.rs | 21 +++++++++--- crates/net/network/src/listener.rs | 1 - crates/net/network/src/manager.rs | 9 ++++-- crates/net/network/tests/it/startup.rs | 44 ++++++++++++++++++++++++-- crates/net/peers/src/node_record.rs | 17 ++++++++-- 5 files changed, 78 insertions(+), 14 deletions(-) diff --git a/crates/net/network/src/discovery.rs b/crates/net/network/src/discovery.rs index 38346ec1d0da..07d24859ed0c 100644 --- a/crates/net/network/src/discovery.rs +++ b/crates/net/network/src/discovery.rs @@ -71,14 +71,17 @@ impl Discovery { /// This will spawn the [`reth_discv4::Discv4Service`] onto a new task and establish a listener /// channel to receive all discovered nodes. pub async fn new( + tcp_addr: SocketAddr, discovery_v4_addr: SocketAddr, sk: SecretKey, discv4_config: Option, discv5_config: Option, // contains discv5 listen address dns_discovery_config: Option, ) -> Result { - // setup discv4 - let local_enr = NodeRecord::from_secret_key(discovery_v4_addr, &sk); + // setup discv4 with the discovery address and tcp port + let local_enr = + NodeRecord::from_secret_key(discovery_v4_addr, &sk).with_tcp_port(tcp_addr.port()); + let discv4_future = async { let Some(disc_config) = discv4_config else { return Ok((None, None, None)) }; let (discv4, mut discv4_service) = @@ -342,6 +345,7 @@ mod tests { let (secret_key, _) = SECP256K1.generate_keypair(&mut rng); let discovery_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)); let _discovery = Discovery::new( + discovery_addr, discovery_addr, secret_key, Default::default(), @@ -370,9 +374,16 @@ mod tests { .discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build()) .build(); - Discovery::new(discv4_addr, secret_key, Some(discv4_config), Some(discv5_config), None) - .await - .expect("should build discv5 with discv4 downgrade") + Discovery::new( + discv4_addr, + discv4_addr, + secret_key, + Some(discv4_config), + Some(discv5_config), + None, + ) + .await + .expect("should build discv5 with discv4 downgrade") } #[tokio::test(flavor = "multi_thread")] diff --git a/crates/net/network/src/listener.rs b/crates/net/network/src/listener.rs index d0654042abec..9fcc15a104b5 100644 --- a/crates/net/network/src/listener.rs +++ b/crates/net/network/src/listener.rs @@ -1,7 +1,6 @@ //! Contains connection-oriented interfaces. use futures::{ready, Stream}; - use std::{ io, net::SocketAddr, diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index a3fdefffab5c..7a4092d4a4a9 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -197,7 +197,9 @@ where let incoming = ConnectionListener::bind(listener_addr).await.map_err(|err| { NetworkError::from_io_error(err, ServiceKind::Listener(listener_addr)) })?; - let listener_address = Arc::new(Mutex::new(incoming.local_address())); + + // retrieve the tcp address of the socket + let listener_addr = incoming.local_address(); // resolve boot nodes let mut resolved_boot_nodes = vec![]; @@ -214,6 +216,7 @@ where }); let discovery = Discovery::new( + listener_addr, discovery_v4_addr, secret_key, discovery_v4_config, @@ -248,7 +251,7 @@ where let handle = NetworkHandle::new( Arc::clone(&num_active_peers), - listener_address, + Arc::new(Mutex::new(listener_addr)), to_manager_tx, secret_key, local_peer_id, @@ -314,7 +317,7 @@ where NetworkBuilder { network: self, transactions: (), request_handler: () } } - /// Returns the [`SocketAddr`] that listens for incoming connections. + /// Returns the [`SocketAddr`] that listens for incoming tcp connections. pub const fn local_addr(&self) -> SocketAddr { self.swarm.listener().local_address() } diff --git a/crates/net/network/tests/it/startup.rs b/crates/net/network/tests/it/startup.rs index 0bef94e08c30..e924d322d172 100644 --- a/crates/net/network/tests/it/startup.rs +++ b/crates/net/network/tests/it/startup.rs @@ -3,7 +3,7 @@ use reth_network::{ error::{NetworkError, ServiceKind}, Discovery, NetworkConfigBuilder, NetworkManager, }; -use reth_network_api::NetworkInfo; +use reth_network_api::{NetworkInfo, PeersInfo}; use reth_provider::test_utils::NoopProvider; use secp256k1::SecretKey; use std::{ @@ -59,8 +59,46 @@ async fn test_discovery_addr_in_use() { let any_port_listener = TcpListener::bind(addr).await.unwrap(); let port = any_port_listener.local_addr().unwrap().port(); let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)); - let _discovery = Discovery::new(addr, secret_key, Some(disc_config), None, None).await.unwrap(); + let _discovery = + Discovery::new(addr, addr, secret_key, Some(disc_config), None, None).await.unwrap(); let disc_config = Discv4Config::default(); - let result = Discovery::new(addr, secret_key, Some(disc_config), None, None).await; + let result = Discovery::new(addr, addr, secret_key, Some(disc_config), None, None).await; assert!(is_addr_in_use_kind(&result.err().unwrap(), ServiceKind::Discovery(addr))); } + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_tcp_port_node_record_no_discovery() { + let secret_key = SecretKey::new(&mut rand::thread_rng()); + let config = NetworkConfigBuilder::new(secret_key) + .listener_port(0) + .disable_discovery() + .build_with_noop_provider(); + let network = NetworkManager::new(config).await.unwrap(); + + let local_addr = network.local_addr(); + // ensure we retrieved the port the OS chose + assert_ne!(local_addr.port(), 0); + + let record = network.handle().local_node_record(); + assert_eq!(record.tcp_port, local_addr.port()); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_tcp_port_node_record_discovery() { + let secret_key = SecretKey::new(&mut rand::thread_rng()); + let config = NetworkConfigBuilder::new(secret_key) + .listener_port(0) + .discovery_port(0) + .build_with_noop_provider(); + let network = NetworkManager::new(config).await.unwrap(); + + let local_addr = network.local_addr(); + // ensure we retrieved the port the OS chose + assert_ne!(local_addr.port(), 0); + + let record = network.handle().local_node_record(); + assert_eq!(record.tcp_port, local_addr.port()); + assert_ne!(record.udp_port, 0); +} diff --git a/crates/net/peers/src/node_record.rs b/crates/net/peers/src/node_record.rs index 99ca74b56939..3b6c38170d56 100644 --- a/crates/net/peers/src/node_record.rs +++ b/crates/net/peers/src/node_record.rs @@ -42,8 +42,10 @@ pub struct NodeRecord { } impl NodeRecord { + /// Derive the [`NodeRecord`] from the secret key and addr. + /// + /// Note: this will set both the TCP and UDP ports to the port of the addr. #[cfg(feature = "secp256k1")] - /// Derive the [`NodeRecord`] from the secret key and addr pub fn from_secret_key(addr: SocketAddr, sk: &secp256k1::SecretKey) -> Self { let pk = secp256k1::PublicKey::from_secret_key(secp256k1::SECP256K1, sk); let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); @@ -73,8 +75,19 @@ impl NodeRecord { self } + /// Sets the tcp port + pub const fn with_tcp_port(mut self, port: u16) -> Self { + self.tcp_port = port; + self + } + + /// Sets the udp port + pub const fn with_udp_port(mut self, port: u16) -> Self { + self.udp_port = port; + self + } + /// Creates a new record from a socket addr and peer id. - #[allow(dead_code)] pub const fn new(addr: SocketAddr, id: PeerId) -> Self { Self { address: addr.ip(), tcp_port: addr.port(), udp_port: addr.port(), id } } From c694433675c3fed05fbe72c50476f4a195acafc6 Mon Sep 17 00:00:00 2001 From: Philip Glazman <8378656+philipglazman@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:54:00 -0400 Subject: [PATCH 065/405] feat(primitives): gas limit setter (#8850) Co-authored-by: Matthias Seitz Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> --- crates/primitives/src/transaction/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 12d6eedc57cb..f517c5b62412 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -478,6 +478,18 @@ impl Transaction { } } + /// This sets the transaction's gas limit. + pub fn set_gas_limit(&mut self, gas_limit: u64) { + match self { + Self::Legacy(tx) => tx.gas_limit = gas_limit, + Self::Eip2930(tx) => tx.gas_limit = gas_limit, + Self::Eip1559(tx) => tx.gas_limit = gas_limit, + Self::Eip4844(tx) => tx.gas_limit = gas_limit, + #[cfg(feature = "optimism")] + Self::Deposit(tx) => tx.gas_limit = gas_limit, + } + } + /// This sets the transaction's nonce. pub fn set_nonce(&mut self, nonce: u64) { match self { From 925d40618890b1ec8bb5c2902ee306c7fbf7ee9e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 15 Jun 2024 13:19:01 +0200 Subject: [PATCH 066/405] chore(deps): move futures-core to workspace (#8857) --- Cargo.toml | 5 +++-- crates/payload/basic/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af5c0192e393..859820bcc6fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -438,9 +438,10 @@ tokio-util = { version = "0.7.4", features = ["codec"] } # async async-stream = "0.3" async-trait = "0.1.68" -futures = "0.3.26" +futures = "0.3" +futures-util = "0.3" +futures-core = "0.3" pin-project = "1.0.12" -futures-util = "0.3.25" hyper = "1.3" hyper-util = "0.1.5" reqwest = { version = "0.12", default-features = false } diff --git a/crates/payload/basic/Cargo.toml b/crates/payload/basic/Cargo.toml index 68464d7f459a..af64966ee413 100644 --- a/crates/payload/basic/Cargo.toml +++ b/crates/payload/basic/Cargo.toml @@ -27,7 +27,7 @@ revm.workspace = true # async tokio = { workspace = true, features = ["sync", "time"] } -futures-core = "0.3" +futures-core.workspace = true futures-util.workspace = true # metrics From 22d7f3369fa2fd338e2df1d8b2d99afc033f2c4f Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Sat, 15 Jun 2024 19:10:09 +0200 Subject: [PATCH 067/405] fix: re-add `validate_against_parent_4844` to optimism header validation (#8835) --- crates/consensus/common/src/validation.rs | 36 ++++++++++++++++ crates/ethereum/consensus/src/lib.rs | 50 ++++------------------- crates/optimism/consensus/src/lib.rs | 12 ++++-- 3 files changed, 52 insertions(+), 46 deletions(-) diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 1b482fd5bdcb..9cac6b2f9f1f 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -6,6 +6,7 @@ use reth_primitives::{ eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}, MAXIMUM_EXTRA_DATA_SIZE, }, + eip4844::calculate_excess_blob_gas, ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader, }; @@ -229,6 +230,41 @@ pub fn validate_against_parent_timestamp( Ok(()) } +/// Validates that the EIP-4844 header fields are correct with respect to the parent block. This +/// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and +/// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the +/// parent header fields. +pub fn validate_against_parent_4844( + header: &SealedHeader, + parent: &SealedHeader, +) -> Result<(), ConsensusError> { + // From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension): + // + // > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas + // > are evaluated as 0. + // + // This means in the first post-fork block, calculate_excess_blob_gas will return 0. + let parent_blob_gas_used = parent.blob_gas_used.unwrap_or(0); + let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or(0); + + if header.blob_gas_used.is_none() { + return Err(ConsensusError::BlobGasUsedMissing) + } + let excess_blob_gas = header.excess_blob_gas.ok_or(ConsensusError::ExcessBlobGasMissing)?; + + let expected_excess_blob_gas = + calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used); + if expected_excess_blob_gas != excess_blob_gas { + return Err(ConsensusError::ExcessBlobGasDiff { + diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas }, + parent_excess_blob_gas, + parent_blob_gas_used, + }) + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index d37b8bbc33d5..33f2b3b194e0 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -10,15 +10,14 @@ use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ - validate_4844_header_standalone, validate_against_parent_eip1559_base_fee, - validate_against_parent_hash_number, validate_against_parent_timestamp, - validate_block_pre_execution, validate_header_base_fee, validate_header_extradata, - validate_header_gas, + validate_4844_header_standalone, validate_against_parent_4844, + validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number, + validate_against_parent_timestamp, validate_block_pre_execution, validate_header_base_fee, + validate_header_extradata, validate_header_gas, }; use reth_primitives::{ - constants::MINIMUM_GAS_LIMIT, eip4844::calculate_excess_blob_gas, BlockWithSenders, Chain, - ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, - U256, + constants::MINIMUM_GAS_LIMIT, BlockWithSenders, Chain, ChainSpec, Hardfork, Header, + SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256, }; use std::{sync::Arc, time::SystemTime}; @@ -40,41 +39,6 @@ impl EthBeaconConsensus { Self { chain_spec } } - /// Validates that the EIP-4844 header fields are correct with respect to the parent block. This - /// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and - /// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the - /// parent header fields. - pub fn validate_against_parent_4844( - header: &SealedHeader, - parent: &SealedHeader, - ) -> Result<(), ConsensusError> { - // From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension): - // - // > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas - // > are evaluated as 0. - // - // This means in the first post-fork block, calculate_excess_blob_gas will return 0. - let parent_blob_gas_used = parent.blob_gas_used.unwrap_or(0); - let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or(0); - - if header.blob_gas_used.is_none() { - return Err(ConsensusError::BlobGasUsedMissing) - } - let excess_blob_gas = header.excess_blob_gas.ok_or(ConsensusError::ExcessBlobGasMissing)?; - - let expected_excess_blob_gas = - calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used); - if expected_excess_blob_gas != excess_blob_gas { - return Err(ConsensusError::ExcessBlobGasDiff { - diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas }, - parent_excess_blob_gas, - parent_blob_gas_used, - }) - } - - Ok(()) - } - /// Checks the gas limit for consistency between parent and self headers. /// /// The maximum allowable difference between self and parent gas limits is determined by the @@ -175,7 +139,7 @@ impl Consensus for EthBeaconConsensus { // ensure that the blob gas fields for this block if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) { - Self::validate_against_parent_4844(header, parent)?; + validate_against_parent_4844(header, parent)?; } Ok(()) diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index 96ba25a8fcea..691ab58c77b7 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -11,9 +11,10 @@ use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ - validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number, - validate_against_parent_timestamp, validate_block_pre_execution, validate_header_base_fee, - validate_header_extradata, validate_header_gas, + validate_against_parent_4844, validate_against_parent_eip1559_base_fee, + validate_against_parent_hash_number, validate_against_parent_timestamp, + validate_block_pre_execution, validate_header_base_fee, validate_header_extradata, + validate_header_gas, }; use reth_primitives::{ BlockWithSenders, ChainSpec, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256, @@ -63,6 +64,11 @@ impl Consensus for OptimismBeaconConsensus { validate_against_parent_eip1559_base_fee(header, parent, &self.chain_spec)?; + // ensure that the blob gas fields for this block + if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp) { + validate_against_parent_4844(header, parent)?; + } + Ok(()) } From e92dbe949c415f7c1ebbad812f3b905cf902cf14 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 11:07:48 +0000 Subject: [PATCH 068/405] chore(deps): weekly `cargo update` (#8863) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- Cargo.lock | 56 +++++++++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae37ba76c0d1..e99e9c60f08e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ceb48af11349cd7fbd12aa739800be3c4b3965f640b7ae26666907f3bdf091" +checksum = "d2feb5f466b3a786d5a622d8926418bc6a0d38bf632909f6ee9298a4a1d8c6e0" dependencies = [ "alloy-rlp", "arbitrary", @@ -140,7 +140,7 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" +source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-primitives", @@ -189,7 +189,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" +source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -211,17 +211,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "alloy-genesis" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" -dependencies = [ - "alloy-primitives", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", - "serde", - "serde_json", -] - [[package]] name = "alloy-json-abi" version = "0.7.6" @@ -269,7 +258,7 @@ name = "alloy-node-bindings" version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis", "alloy-primitives", "k256", "serde_json", @@ -418,7 +407,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" +source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" dependencies = [ "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -472,7 +461,7 @@ source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", @@ -490,11 +479,10 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" +source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" 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)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-primitives", "alloy-rlp", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -530,7 +518,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#b000e16f06161c15b42a4c59f27cb3feb23b3c14" +source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" dependencies = [ "alloy-primitives", "serde", @@ -2518,15 +2506,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -4796,9 +4784,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0d8b92cd8358e8d229c11df9358decae64d137c5be540952c5ca7b25aea768" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -5444,7 +5432,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.2", "smallvec", "windows-targets 0.52.5", ] @@ -6099,9 +6087,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.5.0", ] @@ -6503,7 +6491,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis", "alloy-primitives", "arbitrary", "bytes", @@ -7653,7 +7641,7 @@ dependencies = [ "alloy-chains", "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", @@ -7700,7 +7688,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", @@ -8202,7 +8190,7 @@ dependencies = [ name = "reth-testing-utils" version = "1.0.0-rc.1" dependencies = [ - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis", "rand 0.8.5", "reth-primitives", "secp256k1 0.28.2", @@ -8303,7 +8291,7 @@ name = "reth-trie-common" version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-trie", From 038d48fd18a02a198c653b220bbb01b3270e3a1c Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:49:09 +0200 Subject: [PATCH 069/405] rpc: make revm utils pub (#8860) --- crates/rpc/rpc/src/eth/revm_utils.rs | 29 ++++++++++------------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 2f132fd4dd55..8041087f2846 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -137,7 +137,7 @@ impl FillableTransaction for TransactionSigned { /// Returns the addresses of the precompiles corresponding to the `SpecId`. #[inline] -pub(crate) fn get_precompiles(spec_id: SpecId) -> impl IntoIterator { +pub fn get_precompiles(spec_id: SpecId) -> impl IntoIterator { let spec = PrecompileSpecId::from_spec_id(spec_id); Precompiles::new(spec).addresses().copied().map(Address::from) } @@ -151,7 +151,7 @@ pub(crate) fn get_precompiles(spec_id: SpecId) -> impl IntoIterator( +pub fn prepare_call_env( mut cfg: CfgEnvWithHandlerCfg, mut block: BlockEnv, request: TransactionRequest, @@ -221,7 +221,7 @@ where /// `eth_call`. /// /// Note: this does _not_ access the Database to check the sender. -pub(crate) fn build_call_evm_env( +pub fn build_call_evm_env( cfg: CfgEnvWithHandlerCfg, block: BlockEnv, request: TransactionRequest, @@ -234,10 +234,7 @@ pub(crate) fn build_call_evm_env( /// /// All [`TxEnv`] fields are derived from the given [`TransactionRequest`], if fields are `None`, /// they fall back to the [`BlockEnv`]'s settings. -pub(crate) fn create_txn_env( - block_env: &BlockEnv, - request: TransactionRequest, -) -> EthResult { +pub fn create_txn_env(block_env: &BlockEnv, request: TransactionRequest) -> EthResult { // Ensure that if versioned hashes are set, they're not empty if request.blob_versioned_hashes.as_ref().map_or(false, |hashes| hashes.is_empty()) { return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into()) @@ -300,10 +297,7 @@ pub(crate) fn create_txn_env( } /// Caps the configured [`TxEnv`] `gas_limit` with the allowance of the caller. -pub(crate) fn cap_tx_gas_limit_with_caller_allowance( - db: &mut DB, - env: &mut TxEnv, -) -> EthResult<()> +pub fn cap_tx_gas_limit_with_caller_allowance(db: &mut DB, env: &mut TxEnv) -> EthResult<()> where DB: Database, EthApiError: From<::Error>, @@ -321,7 +315,7 @@ where /// /// Returns an error if the caller has insufficient funds. /// Caution: This assumes non-zero `env.gas_price`. Otherwise, zero allowance will be returned. -pub(crate) fn caller_gas_allowance(db: &mut DB, env: &TxEnv) -> EthResult +pub fn caller_gas_allowance(db: &mut DB, env: &TxEnv) -> EthResult where DB: Database, EthApiError: From<::Error>, @@ -343,7 +337,8 @@ where } /// Helper type for representing the fees of a [`TransactionRequest`] -pub(crate) struct CallFees { +#[derive(Debug)] +pub struct CallFees { /// EIP-1559 priority fee max_priority_fee_per_gas: Option, /// Unified gas price setting @@ -506,10 +501,7 @@ fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { } /// Applies the given state overrides (a set of [`AccountOverride`]) to the [`CacheDB`]. -pub(crate) fn apply_state_overrides( - overrides: StateOverride, - db: &mut CacheDB, -) -> EthResult<()> +pub fn apply_state_overrides(overrides: StateOverride, db: &mut CacheDB) -> EthResult<()> where DB: DatabaseRef, EthApiError: From<::Error>, @@ -581,9 +573,8 @@ where #[cfg(test)] mod tests { - use reth_primitives::constants::GWEI_TO_WEI; - use super::*; + use reth_primitives::constants::GWEI_TO_WEI; #[test] fn test_ensure_0_fallback() { From 35f7e0b0002af815b8cbc495c455a46c83669bb5 Mon Sep 17 00:00:00 2001 From: Quentin V <35984401+quentinv72@users.noreply.github.com> Date: Sun, 16 Jun 2024 18:58:07 +0200 Subject: [PATCH 070/405] chore: Upgrade revm (#8870) Co-authored-by: Matthias Seitz --- Cargo.lock | 69 +++++++------------ Cargo.toml | 10 +-- crates/rpc/rpc/src/eth/error.rs | 5 ++ .../bundle_state_with_receipts.rs | 7 +- examples/custom-evm/src/main.rs | 4 +- 5 files changed, 43 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e99e9c60f08e..bc8ebdd6a57d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1526,7 +1526,7 @@ dependencies = [ "reth-network-api", "reth-primitives", "reth-tracing", - "secp256k1 0.28.2", + "secp256k1", "serde_json", "tokio", "tokio-stream", @@ -2769,7 +2769,7 @@ dependencies = [ "k256", "log", "rand 0.8.5", - "secp256k1 0.28.2", + "secp256k1", "serde", "sha3", "zeroize", @@ -2952,7 +2952,7 @@ dependencies = [ "reth-testing-utils", "reth-tracing", "rusqlite", - "secp256k1 0.28.2", + "secp256k1", "serde_json", "tokio", ] @@ -4763,7 +4763,7 @@ dependencies = [ "reth-network", "reth-network-peers", "reth-primitives", - "secp256k1 0.28.2", + "secp256k1", "tokio", ] @@ -5662,7 +5662,7 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-tracing", - "secp256k1 0.28.2", + "secp256k1", "serde_json", "tokio", "tokio-stream", @@ -6676,7 +6676,7 @@ dependencies = [ "reth-primitives", "reth-tracing", "schnellru", - "secp256k1 0.28.2", + "secp256k1", "serde", "thiserror", "tokio", @@ -6702,7 +6702,7 @@ dependencies = [ "reth-network-peers", "reth-primitives", "reth-tracing", - "secp256k1 0.28.2", + "secp256k1", "thiserror", "tokio", "tracing", @@ -6726,7 +6726,7 @@ dependencies = [ "reth-primitives", "reth-tracing", "schnellru", - "secp256k1 0.28.2", + "secp256k1", "serde", "serde_with", "thiserror", @@ -6817,7 +6817,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reth-network-peers", - "secp256k1 0.28.2", + "secp256k1", "sha2 0.10.8", "sha3", "thiserror", @@ -6872,7 +6872,7 @@ dependencies = [ "reth-network-peers", "reth-primitives", "reth-tracing", - "secp256k1 0.28.2", + "secp256k1", "serde", "snap", "test-fuzz", @@ -7003,7 +7003,7 @@ dependencies = [ "reth-revm", "reth-testing-utils", "revm-primitives", - "secp256k1 0.28.2", + "secp256k1", "serde_json", ] @@ -7251,7 +7251,7 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "schnellru", - "secp256k1 0.28.2", + "secp256k1", "serde", "serde_json", "serial_test", @@ -7304,7 +7304,7 @@ dependencies = [ "alloy-rlp", "enr", "rand 0.8.5", - "secp256k1 0.28.2", + "secp256k1", "serde_json", "serde_with", "thiserror", @@ -7391,7 +7391,7 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-transaction-pool", - "secp256k1 0.28.2", + "secp256k1", "tempfile", "tokio", "tokio-stream", @@ -7447,7 +7447,7 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-transaction-pool", - "secp256k1 0.28.2", + "secp256k1", "serde", "serde_json", "shellexpand", @@ -7670,7 +7670,7 @@ dependencies = [ "revm", "revm-primitives", "roaring", - "secp256k1 0.28.2", + "secp256k1", "serde", "serde_json", "sucds", @@ -7855,7 +7855,7 @@ dependencies = [ "revm-inspectors", "revm-primitives", "schnellru", - "secp256k1 0.28.2", + "secp256k1", "serde", "serde_json", "tempfile", @@ -8193,7 +8193,7 @@ dependencies = [ "alloy-genesis", "rand 0.8.5", "reth-primitives", - "secp256k1 0.28.2", + "secp256k1", ] [[package]] @@ -8342,7 +8342,7 @@ dependencies = [ [[package]] name = "revm" version = "9.0.0" -source = "git+https://github.com/bluealloy/revm?rev=a28a543#a28a5439b9cfb7494cbd670da10cbedcfe6c5854" +source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" dependencies = [ "auto_impl", "cfg-if", @@ -8356,7 +8356,7 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=70e721d#70e721d9d4af486bfc7bd3115251cde78414b78d" +source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=52f632e#52f632e66404b9d6bfd474eeef3ff65d58167f2e" dependencies = [ "alloy-primitives", "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", @@ -8373,7 +8373,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "5.0.0" -source = "git+https://github.com/bluealloy/revm?rev=a28a543#a28a5439b9cfb7494cbd670da10cbedcfe6c5854" +source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" dependencies = [ "revm-primitives", "serde", @@ -8382,7 +8382,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "7.0.0" -source = "git+https://github.com/bluealloy/revm?rev=a28a543#a28a5439b9cfb7494cbd670da10cbedcfe6c5854" +source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" dependencies = [ "aurora-engine-modexp", "blst", @@ -8392,7 +8392,7 @@ dependencies = [ "p256", "revm-primitives", "ripemd", - "secp256k1 0.29.0", + "secp256k1", "sha2 0.10.8", "substrate-bn", ] @@ -8400,7 +8400,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "4.0.0" -source = "git+https://github.com/bluealloy/revm?rev=a28a543#a28a5439b9cfb7494cbd670da10cbedcfe6c5854" +source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" dependencies = [ "alloy-primitives", "auto_impl", @@ -8826,20 +8826,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ "rand 0.8.5", - "secp256k1-sys 0.9.2", + "secp256k1-sys", "serde", ] -[[package]] -name = "secp256k1" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" -dependencies = [ - "rand 0.8.5", - "secp256k1-sys 0.10.0", -] - [[package]] name = "secp256k1-sys" version = "0.9.2" @@ -8849,15 +8839,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.11.0" diff --git a/Cargo.toml b/Cargo.toml index 859820bcc6fb..2c2a2a82ce85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,7 +344,7 @@ revm = { version = "9.0.0", features = [ revm-primitives = { version = "4.0.0", features = [ "std", ], default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "70e721d" } +revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "52f632e" } # eth alloy-chains = "0.1.15" @@ -494,7 +494,7 @@ similar-asserts = "1.5.0" test-fuzz = "5" [patch.crates-io] -revm = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } -revm-interpreter = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } -revm-precompile = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } -revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } +revm = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } +revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } +revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } +revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } \ No newline at end of file diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index 4a9b6d043989..84c1504015a3 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -113,6 +113,9 @@ pub enum EthApiError { /// Evm generic purpose error. #[error("Revm error: {0}")] EvmCustom(String), + /// Evm precompile error + #[error("Revm precompile error: {0}")] + EvmPrecompile(String), /// Error encountered when converting a transaction type #[error("Transaction conversion error")] TransactionConversionError, @@ -151,6 +154,7 @@ impl From for ErrorObject<'static> { EthApiError::Internal(_) | EthApiError::TransactionNotFound | EthApiError::EvmCustom(_) | + EthApiError::EvmPrecompile(_) | EthApiError::InvalidRewardPercentiles => internal_rpc_err(error.to_string()), EthApiError::UnknownBlockNumber | EthApiError::UnknownBlockOrTxIndex => { rpc_error_with_code(EthRpcErrorCode::ResourceNotFound.code(), error.to_string()) @@ -221,6 +225,7 @@ where EVMError::Header(InvalidHeader::ExcessBlobGasNotSet) => Self::ExcessBlobGasNotSet, EVMError::Database(err) => err.into(), EVMError::Custom(err) => Self::EvmCustom(err), + EVMError::Precompile(err) => Self::EvmPrecompile(err), } } } 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 de5aff11ba24..a790d92fcfcb 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 @@ -282,6 +282,7 @@ mod tests { EvmStorageSlot { present_value: U256::from(2), original_value: U256::from(1), + ..Default::default() }, )]), }, @@ -472,7 +473,11 @@ mod tests { // 0x00 => 1 => 2 storage: HashMap::from([( U256::ZERO, - EvmStorageSlot { original_value: U256::from(1), present_value: U256::from(2) }, + EvmStorageSlot { + original_value: U256::from(1), + present_value: U256::from(2), + ..Default::default() + }, )]), }, )])); diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index f44903f1a127..786e6241d70d 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -12,7 +12,7 @@ use reth::{ revm::{ handler::register::EvmHandler, inspector_handle_register, - precompile::{Precompile, PrecompileSpecId, Precompiles}, + precompile::{Precompile, PrecompileOutput, PrecompileSpecId, Precompiles}, Database, Evm, EvmBuilder, GetInspector, }, tasks::TaskManager, @@ -56,7 +56,7 @@ impl MyEvmConfig { /// A custom precompile that does nothing fn my_precompile(_data: &Bytes, _gas: u64, _env: &Env) -> PrecompileResult { - Ok((0, Bytes::new())) + Ok(PrecompileOutput::new(0, Bytes::new())) } } From 0d55e5b8b20ab4a5976dc8e47f190f6392b41da7 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 17 Jun 2024 01:07:59 +0800 Subject: [PATCH 071/405] feat(rpc/block): simplify the block_receipts rpc (#8866) Signed-off-by: jsvisa --- crates/rpc/rpc/src/eth/api/block.rs | 111 ++++++++++++++-------------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index cfc3fe058cb9..18a547faf9e6 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -63,65 +63,68 @@ where &self, block_id: BlockId, ) -> EthResult>> { - let mut block_and_receipts = None; - - if block_id.is_pending() { - block_and_receipts = self - .provider() + // Fetch block and receipts based on block_id + let block_and_receipts = if block_id.is_pending() { + self.provider() .pending_block_and_receipts()? - .map(|(sb, receipts)| (sb, Arc::new(receipts))); + .map(|(sb, receipts)| (sb, Arc::new(receipts))) } else if let Some(block_hash) = self.provider().block_hash_for_id(block_id)? { - block_and_receipts = self.cache().get_block_and_receipts(block_hash).await?; - } + self.cache().get_block_and_receipts(block_hash).await? + } else { + None + }; - if let Some((block, receipts)) = block_and_receipts { - let block_number = block.number; - let base_fee = block.base_fee_per_gas; - let block_hash = block.hash(); - let excess_blob_gas = block.excess_blob_gas; - let timestamp = block.timestamp; - let block = block.unseal(); - - #[cfg(feature = "optimism")] - let (block_timestamp, l1_block_info) = { - let body = reth_evm_optimism::extract_l1_info(&block); - (block.timestamp, body.ok()) - }; - - let receipts = block - .body - .into_iter() - .zip(receipts.iter()) - .enumerate() - .map(|(idx, (tx, receipt))| { - let meta = TransactionMeta { - tx_hash: tx.hash, - index: idx as u64, - block_hash, - block_number, - base_fee, - excess_blob_gas, - timestamp, - }; + // If no block and receipts found, return None + let Some((block, receipts)) = block_and_receipts else { + return Ok(None); + }; - #[cfg(feature = "optimism")] - let op_tx_meta = - self.build_op_tx_meta(&tx, l1_block_info.clone(), block_timestamp)?; - - build_transaction_receipt_with_block_receipts( - tx, - meta, - receipt.clone(), - &receipts, - #[cfg(feature = "optimism")] - op_tx_meta, - ) - }) - .collect::>>(); - return receipts.map(Some) - } + // Extract block details + let block_number = block.number; + let base_fee = block.base_fee_per_gas; + let block_hash = block.hash(); + let excess_blob_gas = block.excess_blob_gas; + let timestamp = block.timestamp; + let block = block.unseal(); + + #[cfg(feature = "optimism")] + let (block_timestamp, l1_block_info) = { + let body = reth_evm_optimism::extract_l1_info(&block); + (block.timestamp, body.ok()) + }; - Ok(None) + // Build transaction receipts + block + .body + .into_iter() + .zip(receipts.iter()) + .enumerate() + .map(|(idx, (tx, receipt))| { + let meta = TransactionMeta { + tx_hash: tx.hash, + index: idx as u64, + block_hash, + block_number, + base_fee, + excess_blob_gas, + timestamp, + }; + + #[cfg(feature = "optimism")] + let op_tx_meta = + self.build_op_tx_meta(&tx, l1_block_info.clone(), block_timestamp)?; + + build_transaction_receipt_with_block_receipts( + tx, + meta, + receipt.clone(), + &receipts, + #[cfg(feature = "optimism")] + op_tx_meta, + ) + }) + .collect::>>() + .map(Some) } /// Returns the number transactions in the given block. From 9b50226f560cd2e8de341ef2891faceeaede4d8c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 16 Jun 2024 19:49:26 +0200 Subject: [PATCH 072/405] feat: add is paris active at block fn (#8872) --- crates/primitives/src/chain/spec.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 96852805bfc3..308a988d75b9 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -842,6 +842,12 @@ impl ChainSpec { self.fork(Hardfork::Homestead).active_at_block(block_number) } + /// The Paris hardfork (merge) is activated via ttd, if we know the block is known then this + /// returns true if the block number is greater than or equal to the Paris (merge) block. + pub fn is_paris_active_at_block(&self, block_number: u64) -> Option { + self.paris_block_and_final_difficulty.map(|(paris_block, _)| block_number >= paris_block) + } + /// Convenience method to check if [`Hardfork::Bedrock`] is active at a given block number. #[cfg(feature = "optimism")] #[inline] From ce0559d6c73438e4e2683819d629465cfd3c8457 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 16 Jun 2024 20:10:02 +0200 Subject: [PATCH 073/405] fix: manually override certain chainconfig fields for adminNodeInfo (#8874) --- crates/rpc/rpc/src/admin.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 6c84dc8a386b..2ea412bb207f 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_network_api::{NetworkInfo, PeerKind, Peers}; use reth_network_peers::AnyNode; -use reth_primitives::{ChainSpec, NodeRecord}; +use reth_primitives::{ChainConfig, ChainSpec, NodeRecord}; use reth_rpc_api::AdminApiServer; use reth_rpc_types::{ admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo}, @@ -94,7 +94,14 @@ where async fn node_info(&self) -> RpcResult { let enode = self.network.local_node_record(); let status = self.network.network_status().await.to_rpc_result()?; - let config = self.chain_spec.genesis().config.clone(); + let config = ChainConfig { + chain_id: self.chain_spec.chain.id(), + terminal_total_difficulty_passed: self + .chain_spec + .get_final_paris_total_difficulty() + .is_some(), + ..self.chain_spec.genesis().config.clone() + }; let node_info = NodeInfo { id: B256::from_slice(&enode.id.as_slice()[..32]), From 8d54f2f8d3ea9fb3050369129e2c947dfa665bf6 Mon Sep 17 00:00:00 2001 From: 0xAtreides <103257861+JackG-eth@users.noreply.github.com> Date: Mon, 17 Jun 2024 07:57:46 +0100 Subject: [PATCH 074/405] feat: support `no_std` for `reth-primitives` (#8817) Co-authored-by: Matthias Seitz --- Cargo.lock | 2 +- crates/primitives/Cargo.toml | 5 +++-- crates/primitives/benches/integer_list.rs | 2 +- crates/primitives/src/alloy_compat.rs | 3 +++ crates/primitives/src/block.rs | 21 +++++++++++-------- crates/primitives/src/chain/spec.rs | 20 +++++++++++++----- crates/primitives/src/compression/mod.rs | 3 +++ crates/primitives/src/constants/eip4844.rs | 2 +- crates/primitives/src/error.rs | 7 ++++++- crates/primitives/src/integer_list.rs | 9 +++++--- crates/primitives/src/lib.rs | 4 ++++ crates/primitives/src/net.rs | 3 +++ crates/primitives/src/proofs.rs | 3 +++ crates/primitives/src/receipt.rs | 5 ++++- crates/primitives/src/request.rs | 3 +++ crates/primitives/src/revm/compat.rs | 3 +++ crates/primitives/src/revm/env.rs | 5 ++++- crates/primitives/src/transaction/eip1559.rs | 2 +- crates/primitives/src/transaction/eip2930.rs | 2 +- crates/primitives/src/transaction/eip4844.rs | 5 ++++- crates/primitives/src/transaction/error.rs | 6 +++--- crates/primitives/src/transaction/legacy.rs | 2 +- crates/primitives/src/transaction/mod.rs | 5 ++++- crates/primitives/src/transaction/pooled.rs | 3 +++ crates/primitives/src/transaction/sidecar.rs | 3 +++ .../primitives/src/transaction/signature.rs | 2 +- crates/primitives/src/transaction/variant.rs | 2 +- crates/primitives/src/withdrawal.rs | 11 ++++++---- 28 files changed, 104 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc8ebdd6a57d..d1719df9140d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7676,7 +7676,7 @@ dependencies = [ "sucds", "tempfile", "test-fuzz", - "thiserror", + "thiserror-no-std", "toml", "triehash", "zstd", diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index dc3efc7d7b08..90fdb0e3eb3f 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -50,7 +50,7 @@ rayon.workspace = true serde.workspace = true serde_json.workspace = true tempfile = { workspace = true, optional = true } -thiserror.workspace = true +thiserror-no-std = { workspace = true , default-features = false } zstd = { version = "0.13", features = ["experimental"], optional = true } roaring = "0.10.2" @@ -87,7 +87,7 @@ pprof = { workspace = true, features = [ secp256k1.workspace = true [features] -default = ["c-kzg", "zstd-codec", "alloy-compat"] +default = ["c-kzg", "zstd-codec", "alloy-compat", "std"] asm-keccak = ["alloy-primitives/asm-keccak"] arbitrary = [ "reth-primitives-traits/arbitrary", @@ -120,6 +120,7 @@ alloy-compat = [ "reth-primitives-traits/alloy-compat", "alloy-rpc-types", ] +std = ["thiserror-no-std/std"] test-utils = ["reth-primitives-traits/test-utils"] [[bench]] diff --git a/crates/primitives/benches/integer_list.rs b/crates/primitives/benches/integer_list.rs index 3ea3543e0599..097280748e2a 100644 --- a/crates/primitives/benches/integer_list.rs +++ b/crates/primitives/benches/integer_list.rs @@ -232,7 +232,7 @@ mod elias_fano { } /// Primitives error type. - #[derive(Debug, thiserror::Error)] + #[derive(Debug, thiserror_no_std::Error)] pub enum EliasFanoError { /// The provided input is invalid. #[error("{0}")] diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index 60618f587191..d193b787fd52 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -8,6 +8,9 @@ use crate::{ use alloy_primitives::TxKind; use alloy_rlp::Error as RlpError; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + impl TryFrom for Block { type Error = alloy_rpc_types::ConversionError; diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 68665277800c..1e14392bffb6 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -14,6 +14,9 @@ use reth_codecs::derive_arbitrary; pub use reth_primitives_traits::test_utils::{generate_valid_header, valid_header_strategy}; use serde::{Deserialize, Serialize}; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + // 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 @@ -177,9 +180,9 @@ impl Block { pub fn size(&self) -> usize { self.header.size() + // take into account capacity - self.body.iter().map(TransactionSigned::size).sum::() + self.body.capacity() * std::mem::size_of::() + - self.ommers.iter().map(Header::size).sum::() + self.ommers.capacity() * std::mem::size_of::
() + - self.withdrawals.as_ref().map_or(std::mem::size_of::>(), Withdrawals::total_size) + self.body.iter().map(TransactionSigned::size).sum::() + self.body.capacity() * core::mem::size_of::() + + self.ommers.iter().map(Header::size).sum::() + self.ommers.capacity() * core::mem::size_of::
() + + self.withdrawals.as_ref().map_or(core::mem::size_of::>(), Withdrawals::total_size) } } @@ -392,9 +395,9 @@ impl SealedBlock { pub fn size(&self) -> usize { self.header.size() + // take into account capacity - self.body.iter().map(TransactionSigned::size).sum::() + self.body.capacity() * std::mem::size_of::() + - self.ommers.iter().map(Header::size).sum::() + self.ommers.capacity() * std::mem::size_of::
() + - self.withdrawals.as_ref().map_or(std::mem::size_of::>(), Withdrawals::total_size) + self.body.iter().map(TransactionSigned::size).sum::() + self.body.capacity() * core::mem::size_of::() + + self.ommers.iter().map(Header::size).sum::() + self.ommers.capacity() * core::mem::size_of::
() + + self.withdrawals.as_ref().map_or(core::mem::size_of::>(), Withdrawals::total_size) } /// Calculates the total gas used by blob transactions in the sealed block. @@ -573,12 +576,12 @@ impl BlockBody { #[inline] pub fn size(&self) -> usize { self.transactions.iter().map(TransactionSigned::size).sum::() + - self.transactions.capacity() * std::mem::size_of::() + + self.transactions.capacity() * core::mem::size_of::() + self.ommers.iter().map(Header::size).sum::() + - self.ommers.capacity() * std::mem::size_of::
() + + self.ommers.capacity() * core::mem::size_of::
() + self.withdrawals .as_ref() - .map_or(std::mem::size_of::>(), Withdrawals::total_size) + .map_or(core::mem::size_of::>(), Withdrawals::total_size) } } diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 308a988d75b9..788acf7b1974 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -10,15 +10,25 @@ use crate::{ Hardfork, Head, Header, NamedChain, NodeRecord, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, MAINNET_DEPOSIT_CONTRACT, U256, }; +use core::{ + fmt, + fmt::{Display, Formatter}, +}; use derive_more::From; use once_cell::sync::Lazy; use reth_trie_common::root::state_root_ref_unhashed; use serde::{Deserialize, Serialize}; -use std::{ + +#[cfg(not(feature = "std"))] +use alloc::{ collections::BTreeMap, - fmt::{Display, Formatter}, + format, + string::{String, ToString}, sync::Arc, + vec::Vec, }; +#[cfg(feature = "std")] +use std::{collections::BTreeMap, sync::Arc}; pub use alloy_eips::eip1559::BaseFeeParams; @@ -1477,7 +1487,7 @@ struct DisplayFork { } impl Display for DisplayFork { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let name_with_eip = if let Some(eip) = &self.eip { format!("{} ({})", self.name, eip) } else { @@ -1551,13 +1561,13 @@ pub struct DisplayHardforks { } impl Display for DisplayHardforks { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn format( header: &str, forks: &[DisplayFork], next_is_empty: bool, f: &mut Formatter<'_>, - ) -> std::fmt::Result { + ) -> fmt::Result { writeln!(f, "{header}:")?; let mut iter = forks.iter().peekable(); while let Some(fork) = iter.next() { diff --git a/crates/primitives/src/compression/mod.rs b/crates/primitives/src/compression/mod.rs index 91bb544feafd..f7af0acbe4de 100644 --- a/crates/primitives/src/compression/mod.rs +++ b/crates/primitives/src/compression/mod.rs @@ -1,6 +1,9 @@ use std::{cell::RefCell, thread_local}; use zstd::bulk::{Compressor, Decompressor}; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// Compression/Decompression dictionary for `Receipt`. pub static RECEIPT_DICTIONARY: &[u8] = include_bytes!("./receipt_dictionary.bin"); /// Compression/Decompression dictionary for `Transaction`. diff --git a/crates/primitives/src/constants/eip4844.rs b/crates/primitives/src/constants/eip4844.rs index 3379a9e48ed8..c1748edf74b6 100644 --- a/crates/primitives/src/constants/eip4844.rs +++ b/crates/primitives/src/constants/eip4844.rs @@ -38,7 +38,7 @@ mod trusted_setup { } /// Error type for loading the trusted setup. - #[derive(Debug, thiserror::Error)] + #[derive(Debug, thiserror_no_std::Error)] pub enum LoadKzgSettingsError { /// Failed to create temp file to store bytes for loading [`KzgSettings`] via /// [`KzgSettings::load_trusted_setup_file`]. diff --git a/crates/primitives/src/error.rs b/crates/primitives/src/error.rs index 42257cc7ba65..8ae946c24208 100644 --- a/crates/primitives/src/error.rs +++ b/crates/primitives/src/error.rs @@ -1,8 +1,11 @@ -use std::{ +use core::{ fmt, ops::{Deref, DerefMut}, }; +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; + /// A pair of values, one of which is expected and one of which is actual. #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct GotExpected { @@ -18,6 +21,7 @@ impl fmt::Display for GotExpected { } } +#[cfg(feature = "std")] impl std::error::Error for GotExpected {} impl From<(T, T)> for GotExpected { @@ -55,6 +59,7 @@ impl fmt::Display for GotExpectedBoxed { } } +#[cfg(feature = "std")] impl std::error::Error for GotExpectedBoxed {} impl Deref for GotExpectedBoxed { diff --git a/crates/primitives/src/integer_list.rs b/crates/primitives/src/integer_list.rs index f81afda933fa..8e258fd8b060 100644 --- a/crates/primitives/src/integer_list.rs +++ b/crates/primitives/src/integer_list.rs @@ -1,4 +1,5 @@ use bytes::BufMut; +use core::fmt; use derive_more::Deref; use roaring::RoaringTreemap; use serde::{ @@ -6,7 +7,9 @@ use serde::{ ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer, }; -use std::fmt; + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; /// Uses Roaring Bitmaps to hold a list of integers. It provides really good compression with the /// capability to access its elements without decoding it. @@ -98,7 +101,7 @@ struct IntegerListVisitor; impl<'de> Visitor<'de> for IntegerListVisitor { type Value = IntegerList; - fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("a usize array") } @@ -137,7 +140,7 @@ impl<'a> Arbitrary<'a> for IntegerList { } /// Primitives error type. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror_no_std::Error)] pub enum RoaringBitmapError { /// The provided input is invalid. #[error("the provided input is invalid")] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e2f695935ab3..e73670826324 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -17,6 +17,10 @@ // TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged #![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; mod account; #[cfg(feature = "alloy-compat")] diff --git a/crates/primitives/src/net.rs b/crates/primitives/src/net.rs index 41fc6dfe691a..922a7df574ca 100644 --- a/crates/primitives/src/net.rs +++ b/crates/primitives/src/net.rs @@ -1,5 +1,8 @@ pub use reth_network_peers::{NodeRecord, NodeRecordParseError, TrustedPeer}; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + // Ethereum bootnodes come from // OP bootnodes come from diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index b5a050434eaa..96ebf49ece80 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -8,6 +8,9 @@ use reth_trie_common::root::{ordered_trie_root, ordered_trie_root_with_encoder}; use alloy_eips::eip7685::Encodable7685; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// Calculate a transaction root. /// /// `(rlp(index), encoded(tx))` pairs. diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 21622f6e4012..56be2d0a1690 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -4,13 +4,16 @@ use crate::{logs_bloom, Bloom, Bytes, TxType, B256}; use alloy_primitives::Log; use alloy_rlp::{length_of_length, Decodable, Encodable, RlpDecodable, RlpEncodable}; use bytes::{Buf, BufMut}; +use core::{cmp::Ordering, ops::Deref}; use derive_more::{Deref, DerefMut, From, IntoIterator}; #[cfg(any(test, feature = "arbitrary"))] use proptest::strategy::Strategy; #[cfg(feature = "zstd-codec")] use reth_codecs::CompactZstd; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; -use std::{cmp::Ordering, ops::Deref}; + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; /// Receipt containing result of transaction execution. #[cfg_attr(feature = "zstd-codec", main_codec(no_arbitrary, zstd))] diff --git a/crates/primitives/src/request.rs b/crates/primitives/src/request.rs index 937f71b00a9c..e3b5f220beb3 100644 --- a/crates/primitives/src/request.rs +++ b/crates/primitives/src/request.rs @@ -7,6 +7,9 @@ use derive_more::{Deref, DerefMut, From, IntoIterator}; use reth_codecs::{main_codec, Compact}; use revm_primitives::Bytes; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// A list of EIP-7685 requests. #[main_codec] #[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Deref, DerefMut, From, IntoIterator)] diff --git a/crates/primitives/src/revm/compat.rs b/crates/primitives/src/revm/compat.rs index 705fc188065c..cb492e6aa1b7 100644 --- a/crates/primitives/src/revm/compat.rs +++ b/crates/primitives/src/revm/compat.rs @@ -1,6 +1,9 @@ use crate::{revm_primitives::AccountInfo, Account, Address, TxKind, KECCAK_EMPTY, U256}; use revm::{interpreter::gas::validate_initial_tx_gas, primitives::SpecId}; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// Converts a Revm [`AccountInfo`] into a Reth [`Account`]. /// /// Sets `bytecode_hash` to `None` if `code_hash` is [`KECCAK_EMPTY`]. diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index db38bb533a03..0b12be6ae016 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -9,6 +9,9 @@ use alloy_eips::{eip4788::BEACON_ROOTS_ADDRESS, eip7002::WITHDRAWAL_REQUEST_PRED #[cfg(feature = "optimism")] use revm_primitives::OptimismFields; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// Fill block environment from Block. pub fn fill_block_env( block_env: &mut BlockEnv, @@ -73,7 +76,7 @@ pub fn block_coinbase(chain_spec: &ChainSpec, header: &Header, after_merge: bool } /// Error type for recovering Clique signer from a header. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror_no_std::Error)] pub enum CliqueSignerRecoveryError { /// Header extradata is too short. #[error("Invalid extra data length")] diff --git a/crates/primitives/src/transaction/eip1559.rs b/crates/primitives/src/transaction/eip1559.rs index 92f75db6ab11..878efaa4f954 100644 --- a/crates/primitives/src/transaction/eip1559.rs +++ b/crates/primitives/src/transaction/eip1559.rs @@ -2,8 +2,8 @@ use super::access_list::AccessList; use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256}; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; use bytes::BytesMut; +use core::mem; use reth_codecs::{main_codec, Compact}; -use std::mem; /// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)). #[main_codec] diff --git a/crates/primitives/src/transaction/eip2930.rs b/crates/primitives/src/transaction/eip2930.rs index 9dc46188675d..45bc5e67a28e 100644 --- a/crates/primitives/src/transaction/eip2930.rs +++ b/crates/primitives/src/transaction/eip2930.rs @@ -2,8 +2,8 @@ use super::access_list::AccessList; use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256}; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; use bytes::BytesMut; +use core::mem; use reth_codecs::{main_codec, Compact}; -use std::mem; /// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)). #[main_codec] diff --git a/crates/primitives/src/transaction/eip4844.rs b/crates/primitives/src/transaction/eip4844.rs index 214e2a5e1e52..f4a2be8e2a84 100644 --- a/crates/primitives/src/transaction/eip4844.rs +++ b/crates/primitives/src/transaction/eip4844.rs @@ -4,12 +4,15 @@ use crate::{ B256, U256, }; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; +use core::mem; use reth_codecs::{main_codec, Compact, CompactPlaceholder}; -use std::mem; #[cfg(feature = "c-kzg")] use crate::kzg::KzgSettings; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction) /// /// A transaction with blob hashes and max blob fee diff --git a/crates/primitives/src/transaction/error.rs b/crates/primitives/src/transaction/error.rs index 2b17fa7181a8..c5199dda5d9a 100644 --- a/crates/primitives/src/transaction/error.rs +++ b/crates/primitives/src/transaction/error.rs @@ -2,7 +2,7 @@ use crate::{GotExpectedBoxed, U256}; /// Represents error variants that can happen when trying to validate a /// [Transaction](crate::Transaction) -#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] +#[derive(Debug, Clone, Eq, PartialEq, thiserror_no_std::Error)] pub enum InvalidTransactionError { /// The sender does not have enough funds to cover the transaction fees #[error( @@ -55,7 +55,7 @@ pub enum InvalidTransactionError { /// Represents error variants that can happen when trying to convert a transaction to /// [`PooledTransactionsElement`](crate::PooledTransactionsElement) -#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] +#[derive(Debug, Clone, Eq, PartialEq, thiserror_no_std::Error)] pub enum TransactionConversionError { /// This error variant is used when a transaction cannot be converted into a /// [`PooledTransactionsElement`](crate::PooledTransactionsElement) because it is not supported @@ -66,7 +66,7 @@ pub enum TransactionConversionError { /// Represents error variants than can happen when trying to convert a /// [`TransactionSignedEcRecovered`](crate::TransactionSignedEcRecovered) transaction. -#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] +#[derive(Debug, Clone, Eq, PartialEq, thiserror_no_std::Error)] pub enum TryFromRecoveredTransactionError { /// Thrown if the transaction type is unsupported. #[error("Unsupported transaction type: {0}")] diff --git a/crates/primitives/src/transaction/legacy.rs b/crates/primitives/src/transaction/legacy.rs index d6cb4ae2ab1a..ebbe29a78c1e 100644 --- a/crates/primitives/src/transaction/legacy.rs +++ b/crates/primitives/src/transaction/legacy.rs @@ -1,8 +1,8 @@ use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256}; use alloy_rlp::{length_of_length, Encodable, Header}; use bytes::BytesMut; +use core::mem; use reth_codecs::{main_codec, Compact}; -use std::mem; /// Legacy transaction. #[main_codec] diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index f517c5b62412..42e420a5e9f7 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -8,12 +8,12 @@ use alloy_rlp::{ Decodable, Encodable, Error as RlpError, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE, }; use bytes::Buf; +use core::mem; use derive_more::{AsRef, Deref}; use once_cell::sync::Lazy; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use reth_codecs::{add_arbitrary_tests, derive_arbitrary, Compact}; use serde::{Deserialize, Serialize}; -use std::mem; pub use access_list::{AccessList, AccessListItem}; pub use eip1559::TxEip1559; @@ -60,6 +60,9 @@ pub use optimism::TxDeposit; #[cfg(feature = "optimism")] pub use tx_type::DEPOSIT_TX_TYPE_ID; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// Either a transaction hash or number. pub type TxHashOrNumber = BlockHashOrNumber; diff --git a/crates/primitives/src/transaction/pooled.rs b/crates/primitives/src/transaction/pooled.rs index 23e2ad3c1c5c..2ca58b179748 100644 --- a/crates/primitives/src/transaction/pooled.rs +++ b/crates/primitives/src/transaction/pooled.rs @@ -13,6 +13,9 @@ use derive_more::{AsRef, Deref}; use reth_codecs::add_arbitrary_tests; use serde::{Deserialize, Serialize}; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// A response to `GetPooledTransactions`. This can include either a blob transaction, or a /// non-4844 signed transaction. #[add_arbitrary_tests] diff --git a/crates/primitives/src/transaction/sidecar.rs b/crates/primitives/src/transaction/sidecar.rs index da273db36fba..c45683ce7980 100644 --- a/crates/primitives/src/transaction/sidecar.rs +++ b/crates/primitives/src/transaction/sidecar.rs @@ -12,6 +12,9 @@ pub use alloy_eips::eip4844::BlobTransactionSidecar; #[cfg(feature = "c-kzg")] pub use alloy_eips::eip4844::BlobTransactionValidationError; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// A response to `GetPooledTransactions` that includes blob data, their commitments, and their /// corresponding proofs. /// diff --git a/crates/primitives/src/transaction/signature.rs b/crates/primitives/src/transaction/signature.rs index d564c58ab9f5..077858a3c579 100644 --- a/crates/primitives/src/transaction/signature.rs +++ b/crates/primitives/src/transaction/signature.rs @@ -193,7 +193,7 @@ impl Signature { /// Calculates a heuristic for the in-memory size of the [Signature]. #[inline] pub const fn size(&self) -> usize { - std::mem::size_of::() + core::mem::size_of::() } } diff --git a/crates/primitives/src/transaction/variant.rs b/crates/primitives/src/transaction/variant.rs index b3f7a00be922..5bff5215d7ac 100644 --- a/crates/primitives/src/transaction/variant.rs +++ b/crates/primitives/src/transaction/variant.rs @@ -5,7 +5,7 @@ use crate::{ Address, Transaction, TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, B256, }; -use std::ops::Deref; +use core::ops::Deref; /// Represents various different transaction formats used in reth. /// diff --git a/crates/primitives/src/withdrawal.rs b/crates/primitives/src/withdrawal.rs index 461908b266d6..cfd0de2268c3 100644 --- a/crates/primitives/src/withdrawal.rs +++ b/crates/primitives/src/withdrawal.rs @@ -4,6 +4,9 @@ use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use derive_more::{AsRef, Deref, DerefMut, From, IntoIterator}; use reth_codecs::{main_codec, Compact}; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// Re-export from `alloy_eips`. #[doc(inline)] pub use alloy_eips::eip4895::Withdrawal; @@ -37,22 +40,22 @@ impl Withdrawals { /// Calculate the total size, including capacity, of the Withdrawals. #[inline] pub fn total_size(&self) -> usize { - self.capacity() * std::mem::size_of::() + self.capacity() * core::mem::size_of::() } /// Calculate a heuristic for the in-memory size of the [Withdrawals]. #[inline] pub fn size(&self) -> usize { - self.len() * std::mem::size_of::() + self.len() * core::mem::size_of::() } /// Get an iterator over the Withdrawals. - pub fn iter(&self) -> std::slice::Iter<'_, Withdrawal> { + pub fn iter(&self) -> core::slice::Iter<'_, Withdrawal> { self.0.iter() } /// Get a mutable iterator over the Withdrawals. - pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Withdrawal> { + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Withdrawal> { self.0.iter_mut() } From 4b22b83e7186aebda8fbf985f2c2061e063b6ad0 Mon Sep 17 00:00:00 2001 From: HAPPY Date: Mon, 17 Jun 2024 15:34:09 +0800 Subject: [PATCH 075/405] fix: division by zero when reading from nippy jar archive (#8878) Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --- crates/storage/nippy-jar/src/error.rs | 5 +++++ crates/storage/nippy-jar/src/lib.rs | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/storage/nippy-jar/src/error.rs b/crates/storage/nippy-jar/src/error.rs index 58e27a76b4c5..a440a9cb3e30 100644 --- a/crates/storage/nippy-jar/src/error.rs +++ b/crates/storage/nippy-jar/src/error.rs @@ -42,6 +42,11 @@ pub enum NippyJarError { /// The read offset size in number of bytes. offset_size: u8, }, + #[error("the size of an offset must be at least 1 byte, got {offset_size}")] + OffsetSizeTooSmall { + /// The read offset size in number of bytes. + offset_size: u8, + }, #[error("attempted to read an out of bounds offset: {index}")] OffsetOutOfBounds { /// The index of the offset that was being read. diff --git a/crates/storage/nippy-jar/src/lib.rs b/crates/storage/nippy-jar/src/lib.rs index c36dbb5c7f46..8247599d7aeb 100644 --- a/crates/storage/nippy-jar/src/lib.rs +++ b/crates/storage/nippy-jar/src/lib.rs @@ -514,6 +514,8 @@ impl DataReader { // Ensure that the size of an offset is at most 8 bytes. if offset_size > 8 { return Err(NippyJarError::OffsetSizeTooBig { offset_size }) + } else if offset_size == 0 { + return Err(NippyJarError::OffsetSizeTooSmall { offset_size }) } Ok(Self { data_file, data_mmap, offset_file, offset_size, offset_mmap }) @@ -551,7 +553,7 @@ impl DataReader { fn offset_at(&self, index: usize) -> Result { let mut buffer: [u8; 8] = [0; 8]; - let offset_end = index + self.offset_size as usize; + let offset_end = index.saturating_add(self.offset_size as usize); if offset_end > self.offset_mmap.len() { return Err(NippyJarError::OffsetOutOfBounds { index }) } From 859b96388a2d7dc2219979ce2b88cc38c8dfd01e Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 17 Jun 2024 11:13:36 +0200 Subject: [PATCH 076/405] chore(dep): bump secp256k1 (#8880) --- Cargo.lock | 152 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1719df9140d..3d13a0132902 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,10 +126,10 @@ name = "alloy-consensus" version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0", "arbitrary", "c-kzg", "proptest", @@ -139,13 +139,13 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" +version = "0.1.1" +source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", + "alloy-eips 0.1.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", + "alloy-serde 0.1.1", "c-kzg", "serde", ] @@ -175,7 +175,7 @@ source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d dependencies = [ "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0", "arbitrary", "c-kzg", "derive_more", @@ -188,12 +188,12 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" +version = "0.1.1" +source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" dependencies = [ "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", + "alloy-serde 0.1.1", "c-kzg", "once_cell", "serde", @@ -206,7 +206,7 @@ version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0", "serde", "serde_json", ] @@ -240,11 +240,11 @@ name = "alloy-network" version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-json-rpc", "alloy-primitives", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types-eth 0.1.0", "alloy-signer", "alloy-sol-types", "async-trait", @@ -301,15 +301,15 @@ version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-chains", - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-json-rpc", "alloy-network", "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types-engine", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types-eth 0.1.0", "alloy-rpc-types-trace", "alloy-transport", "alloy-transport-http", @@ -399,18 +399,18 @@ version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-rpc-types-engine", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types-eth 0.1.0", "alloy-rpc-types-trace", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0", ] [[package]] name = "alloy-rpc-types" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" +version = "0.1.1" +source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" dependencies = [ - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy)", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", + "alloy-rpc-types-eth 0.1.1", + "alloy-serde 0.1.1", ] [[package]] @@ -419,7 +419,7 @@ version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0", "serde", ] @@ -428,7 +428,7 @@ name = "alloy-rpc-types-beacon" version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0", "alloy-primitives", "alloy-rpc-types-engine", "serde", @@ -441,12 +441,12 @@ name = "alloy-rpc-types-engine" version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types-eth 0.1.0", + "alloy-serde 0.1.0", "jsonrpsee-types", "jsonwebtoken", "rand 0.8.5", @@ -459,12 +459,12 @@ name = "alloy-rpc-types-eth" version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-serde 0.1.0", "alloy-sol-types", "arbitrary", "itertools 0.13.0", @@ -478,14 +478,14 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" +version = "0.1.1" +source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" 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)", + "alloy-consensus 0.1.1", + "alloy-eips 0.1.1", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", + "alloy-serde 0.1.1", "alloy-sol-types", "itertools 0.13.0", "serde", @@ -499,8 +499,8 @@ version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types-eth 0.1.0", + "alloy-serde 0.1.0", "serde", "serde_json", ] @@ -517,8 +517,8 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#1c702463125ee90f5f9566713b21c039f9952228" +version = "0.1.1" +source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" dependencies = [ "alloy-primitives", "serde", @@ -543,7 +543,7 @@ name = "alloy-signer-wallet" version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", "alloy-network", "alloy-primitives", "alloy-signer", @@ -2757,9 +2757,9 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enr" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab656b89cdd15051d92d0931888103508de14ef9e51177c86d478dfa551ce0f" +checksum = "972070166c68827e64bd1ebc8159dd8e32d9bc2da7ebe8f20b61308f7974ad30" dependencies = [ "alloy-rlp", "base64 0.21.7", @@ -2935,7 +2935,7 @@ dependencies = [ name = "exex-rollup" version = "0.0.0" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", "alloy-rlp", "alloy-sol-types", "eyre", @@ -3093,10 +3093,10 @@ version = "0.1.0" source = "git+https://github.com/foundry-rs/block-explorers#af29524f4fc7dc25f59e8ae38652022f47ebee9b" dependencies = [ "alloy-chains", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", + "alloy-eips 0.1.1", "alloy-primitives", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy)", - "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", + "alloy-rpc-types 0.1.1", + "alloy-serde 0.1.1", "chrono", "reqwest", "serde", @@ -6397,8 +6397,8 @@ dependencies = [ name = "reth-bench" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-json-rpc", "alloy-provider", "alloy-pubsub", @@ -6489,8 +6489,8 @@ dependencies = [ name = "reth-codecs" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-genesis", "alloy-primitives", "arbitrary", @@ -6552,8 +6552,8 @@ dependencies = [ name = "reth-consensus-debug-client" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-provider", "auto_impl", "eyre", @@ -6773,9 +6773,9 @@ dependencies = [ name = "reth-e2e-test-utils" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", "alloy-network", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types 0.1.0", "alloy-signer", "alloy-signer-wallet", "eyre", @@ -6993,7 +6993,7 @@ dependencies = [ name = "reth-evm-ethereum" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0", "alloy-sol-types", "reth-ethereum-consensus", "reth-evm", @@ -7029,7 +7029,7 @@ dependencies = [ name = "reth-execution-errors" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0", "alloy-primitives", "reth-consensus", "reth-prune-types", @@ -7042,7 +7042,7 @@ dependencies = [ name = "reth-execution-types" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0", "alloy-primitives", "reth-execution-errors", "reth-primitives", @@ -7639,12 +7639,12 @@ name = "reth-primitives" version = "1.0.0-rc.1" dependencies = [ "alloy-chains", - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types 0.1.0", "alloy-trie", "arbitrary", "assert_matches", @@ -7686,12 +7686,12 @@ dependencies = [ name = "reth-primitives-traits" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", + "alloy-eips 0.1.0", "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types-eth 0.1.0", "arbitrary", "bytes", "derive_more", @@ -7797,7 +7797,7 @@ dependencies = [ name = "reth-revm" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-eips 0.1.0", "alloy-rlp", "reth-consensus-common", "reth-execution-errors", @@ -7999,7 +7999,7 @@ name = "reth-rpc-types" version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types 0.1.0", "alloy-rpc-types-anvil", "alloy-rpc-types-beacon", "alloy-rpc-types-engine", @@ -8020,7 +8020,7 @@ name = "reth-rpc-types-compat" version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types 0.1.0", "reth-primitives", "reth-rpc-types", "reth-trie-common", @@ -8290,7 +8290,7 @@ dependencies = [ name = "reth-trie-common" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-consensus 0.1.0", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -8359,7 +8359,7 @@ version = "0.1.0" source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=52f632e#52f632e66404b9d6bfd474eeef3ff65d58167f2e" dependencies = [ "alloy-primitives", - "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", + "alloy-rpc-types 0.1.0", "alloy-sol-types", "anstyle", "boa_engine", @@ -8821,9 +8821,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" dependencies = [ "rand 0.8.5", "secp256k1-sys", @@ -8832,9 +8832,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" dependencies = [ "cc", ] @@ -9794,9 +9794,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "becd34a233e7e31a3dbf7c7241b38320f57393dcae8e7324b0167d21b8e320b0" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", "log", diff --git a/Cargo.toml b/Cargo.toml index 2c2a2a82ce85..61d08cf9edb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -464,7 +464,7 @@ http-body = "1.0" jsonwebtoken = "9" # crypto -secp256k1 = { version = "0.28", default-features = false, features = [ +secp256k1 = { version = "0.29", default-features = false, features = [ "global-context", "recovery", ] } From 333a86db10249d71163f62e6b172f9bfd4daf92e Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:46:42 +0200 Subject: [PATCH 077/405] chore: bump alloy and rm `EvmOverrides` (#8875) --- Cargo.lock | 83 +++++++++++++------ Cargo.toml | 47 ++++++----- crates/e2e-test-utils/Cargo.toml | 2 +- crates/e2e-test-utils/src/transaction.rs | 26 +++--- crates/e2e-test-utils/src/wallet.rs | 6 +- crates/ethereum/evm/src/execute.rs | 2 +- .../execution-types/src/execution_outcome.rs | 2 +- crates/net/network/Cargo.toml | 2 +- crates/revm/src/state_change.rs | 6 +- crates/rpc/rpc-types/Cargo.toml | 3 + crates/rpc/rpc-types/src/lib.rs | 9 ++ crates/rpc/rpc-types/src/net.rs | 2 +- crates/rpc/rpc/src/debug.rs | 3 +- crates/rpc/rpc/src/eth/api/call.rs | 5 +- crates/rpc/rpc/src/eth/api/server.rs | 9 +- crates/rpc/rpc/src/eth/api/transactions.rs | 3 +- crates/rpc/rpc/src/eth/revm_utils.rs | 60 +------------- crates/rpc/rpc/src/trace.rs | 4 +- examples/custom-inspector/Cargo.toml | 1 + examples/custom-inspector/src/main.rs | 6 +- 20 files changed, 137 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d13a0132902..3ab202d1c2a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,7 +124,7 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-eips 0.1.0", "alloy-primitives", @@ -171,7 +171,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -203,7 +203,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-primitives", "alloy-serde 0.1.0", @@ -226,7 +226,7 @@ dependencies = [ [[package]] name = "alloy-json-rpc" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-primitives", "serde", @@ -238,13 +238,14 @@ dependencies = [ [[package]] name = "alloy-network" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-consensus 0.1.0", "alloy-eips 0.1.0", "alloy-json-rpc", "alloy-primitives", "alloy-rpc-types-eth 0.1.0", + "alloy-serde 0.1.0", "alloy-signer", "alloy-sol-types", "async-trait", @@ -256,7 +257,7 @@ dependencies = [ [[package]] name = "alloy-node-bindings" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -298,7 +299,7 @@ dependencies = [ [[package]] name = "alloy-provider" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-chains", "alloy-consensus 0.1.0", @@ -308,9 +309,9 @@ dependencies = [ "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-admin", "alloy-rpc-types-engine", "alloy-rpc-types-eth 0.1.0", - "alloy-rpc-types-trace", "alloy-transport", "alloy-transport-http", "alloy-transport-ws", @@ -333,7 +334,7 @@ dependencies = [ [[package]] name = "alloy-pubsub" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -373,7 +374,7 @@ dependencies = [ [[package]] name = "alloy-rpc-client" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -396,7 +397,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth 0.1.0", @@ -413,10 +414,21 @@ dependencies = [ "alloy-serde 0.1.1", ] +[[package]] +name = "alloy-rpc-types-admin" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +dependencies = [ + "alloy-genesis", + "alloy-primitives", + "serde", + "serde_json", +] + [[package]] name = "alloy-rpc-types-anvil" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-primitives", "alloy-serde 0.1.0", @@ -426,7 +438,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-eips 0.1.0", "alloy-primitives", @@ -439,7 +451,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-consensus 0.1.0", "alloy-eips 0.1.0", @@ -457,11 +469,10 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-consensus 0.1.0", "alloy-eips 0.1.0", - "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-serde 0.1.0", @@ -496,7 +507,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth 0.1.0", @@ -505,12 +516,26 @@ dependencies = [ "serde_json", ] +[[package]] +name = "alloy-rpc-types-txpool" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth 0.1.0", + "alloy-serde 0.1.0", + "serde", +] + [[package]] name = "alloy-serde" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-primitives", + "arbitrary", + "proptest", + "proptest-derive", "serde", "serde_json", ] @@ -528,7 +553,7 @@ dependencies = [ [[package]] name = "alloy-signer" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-primitives", "async-trait", @@ -539,9 +564,9 @@ dependencies = [ ] [[package]] -name = "alloy-signer-wallet" +name = "alloy-signer-local" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-consensus 0.1.0", "alloy-network", @@ -630,7 +655,7 @@ dependencies = [ [[package]] name = "alloy-transport" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -647,7 +672,7 @@ dependencies = [ [[package]] name = "alloy-transport-http" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -661,7 +686,7 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -679,7 +704,7 @@ dependencies = [ [[package]] name = "alloy-transport-ws" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=00d81d7#00d81d7882a0bee4720d6d6a1db4c8f164ebb9d0" +source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -2323,6 +2348,7 @@ dependencies = [ "futures-util", "reth", "reth-node-ethereum", + "reth-rpc-types", ] [[package]] @@ -6777,7 +6803,7 @@ dependencies = [ "alloy-network", "alloy-rpc-types 0.1.0", "alloy-signer", - "alloy-signer-wallet", + "alloy-signer-local", "eyre", "futures-util", "jsonrpsee", @@ -8000,10 +8026,13 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", "alloy-rpc-types 0.1.0", + "alloy-rpc-types-admin", "alloy-rpc-types-anvil", "alloy-rpc-types-beacon", "alloy-rpc-types-engine", "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde 0.1.0", "arbitrary", "bytes", "jsonrpsee-types", @@ -8356,7 +8385,7 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=52f632e#52f632e66404b9d6bfd474eeef3ff65d58167f2e" +source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=6bbf303#6bbf3033ba0119531de69cbddad1f4b89dfa2c4a" dependencies = [ "alloy-primitives", "alloy-rpc-types 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index 61d08cf9edb0..01122427cc15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,7 +344,7 @@ revm = { version = "9.0.0", features = [ revm-primitives = { version = "4.0.0", features = [ "std", ], default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "52f632e" } +revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "6bbf303" } # eth alloy-chains = "0.1.15" @@ -353,33 +353,36 @@ alloy-dyn-abi = "0.7.2" alloy-sol-types = "0.7.2" alloy-rlp = "0.3.4" alloy-trie = "0.4" -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false, features = [ +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false, features = [ "eth", ] } -alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false, features = [ +alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false, features = [ "reqwest", ] } -alloy-eips = { git = "https://github.com/alloy-rs/alloy", default-features = false, rev = "00d81d7" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", features = [ +alloy-eips = { git = "https://github.com/alloy-rs/alloy", default-features = false, rev = "6cb3713" } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", features = [ "reqwest-rustls-tls", ], default-features = false } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } # misc auto_impl = "1" diff --git a/crates/e2e-test-utils/Cargo.toml b/crates/e2e-test-utils/Cargo.toml index 77a5676ab284..c062bec4b2aa 100644 --- a/crates/e2e-test-utils/Cargo.toml +++ b/crates/e2e-test-utils/Cargo.toml @@ -29,7 +29,7 @@ tokio.workspace = true tokio-stream.workspace = true serde_json.workspace = true alloy-signer.workspace = true -alloy-signer-wallet = { workspace = true, features = ["mnemonic"] } +alloy-signer-local = { workspace = true, features = ["mnemonic"] } alloy-rpc-types.workspace = true alloy-network.workspace = true alloy-consensus = { workspace = true, features = ["kzg"] } diff --git a/crates/e2e-test-utils/src/transaction.rs b/crates/e2e-test-utils/src/transaction.rs index 8fe7efd0e77b..b278c96365a5 100644 --- a/crates/e2e-test-utils/src/transaction.rs +++ b/crates/e2e-test-utils/src/transaction.rs @@ -1,9 +1,9 @@ use alloy_consensus::{ BlobTransactionSidecar, SidecarBuilder, SimpleCoder, TxEip4844Variant, TxEnvelope, }; -use alloy_network::{eip2718::Encodable2718, EthereumSigner, TransactionBuilder}; +use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; use alloy_rpc_types::{TransactionInput, TransactionRequest}; -use alloy_signer_wallet::LocalWallet; +use alloy_signer_local::PrivateKeySigner; use eyre::Ok; use reth_primitives::{hex, Address, Bytes, U256}; @@ -13,19 +13,22 @@ pub struct TransactionTestContext; impl TransactionTestContext { /// Creates a static transfer and signs it, returning bytes - pub async fn transfer_tx(chain_id: u64, wallet: LocalWallet) -> TxEnvelope { + pub async fn transfer_tx(chain_id: u64, wallet: PrivateKeySigner) -> TxEnvelope { let tx = tx(chain_id, None, 0); Self::sign_tx(wallet, tx).await } /// Creates a static transfer and signs it, returning bytes - pub async fn transfer_tx_bytes(chain_id: u64, wallet: LocalWallet) -> Bytes { + pub async fn transfer_tx_bytes(chain_id: u64, wallet: PrivateKeySigner) -> Bytes { let signed = Self::transfer_tx(chain_id, wallet).await; signed.encoded_2718().into() } /// Creates a tx with blob sidecar and sign it - pub async fn tx_with_blobs(chain_id: u64, wallet: LocalWallet) -> eyre::Result { + pub async fn tx_with_blobs( + chain_id: u64, + wallet: PrivateKeySigner, + ) -> eyre::Result { let mut tx = tx(chain_id, None, 0); let mut builder = SidecarBuilder::::new(); @@ -40,13 +43,16 @@ impl TransactionTestContext { } /// Signs an arbitrary TransactionRequest using the provided wallet - pub async fn sign_tx(wallet: LocalWallet, tx: TransactionRequest) -> TxEnvelope { - let signer = EthereumSigner::from(wallet); + pub async fn sign_tx(wallet: PrivateKeySigner, tx: TransactionRequest) -> TxEnvelope { + let signer = EthereumWallet::from(wallet); tx.build(&signer).await.unwrap() } /// Creates a tx with blob sidecar and sign it, returning bytes - pub async fn tx_with_blobs_bytes(chain_id: u64, wallet: LocalWallet) -> eyre::Result { + pub async fn tx_with_blobs_bytes( + chain_id: u64, + wallet: PrivateKeySigner, + ) -> eyre::Result { let signed = Self::tx_with_blobs(chain_id, wallet).await?; Ok(signed.encoded_2718().into()) @@ -54,12 +60,12 @@ impl TransactionTestContext { pub async fn optimism_l1_block_info_tx( chain_id: u64, - wallet: LocalWallet, + wallet: PrivateKeySigner, nonce: u64, ) -> Bytes { let l1_block_info = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240")); let tx = tx(chain_id, Some(l1_block_info), nonce); - let signer = EthereumSigner::from(wallet); + let signer = EthereumWallet::from(wallet); tx.build(&signer).await.unwrap().encoded_2718().into() } diff --git a/crates/e2e-test-utils/src/wallet.rs b/crates/e2e-test-utils/src/wallet.rs index e841e7cd786c..f8a4230ee940 100644 --- a/crates/e2e-test-utils/src/wallet.rs +++ b/crates/e2e-test-utils/src/wallet.rs @@ -1,9 +1,9 @@ use alloy_signer::Signer; -use alloy_signer_wallet::{coins_bip39::English, LocalWallet, MnemonicBuilder}; +use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner}; /// One of the accounts of the genesis allocations. pub struct Wallet { - pub inner: LocalWallet, + pub inner: PrivateKeySigner, pub inner_nonce: u64, pub chain_id: u64, amount: usize, @@ -27,7 +27,7 @@ impl Wallet { self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/") } - pub fn gen(&self) -> Vec { + pub fn gen(&self) -> Vec { let builder = MnemonicBuilder::::default().phrase(TEST_MNEMONIC); // use the derivation path diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 0013a0c6638e..42b1b0c133e4 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -1312,7 +1312,7 @@ mod tests { let request = requests.first().unwrap(); let withdrawal_request = request.as_withdrawal_request().unwrap(); assert_eq!(withdrawal_request.source_address, sender_address); - assert_eq!(withdrawal_request.validator_public_key, validator_public_key); + assert_eq!(withdrawal_request.validator_pubkey, validator_public_key); assert_eq!(withdrawal_request.amount, u64::from_be_bytes(withdrawal_amount.into())); } diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs index aebc8d45b5e8..be9fd67b14ce 100644 --- a/crates/evm/execution-types/src/execution_outcome.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -377,7 +377,7 @@ mod tests { }), Request::WithdrawalRequest(WithdrawalRequest { source_address: Address::from([1; 20]), - validator_public_key: FixedBytes::<48>::from([10; 48]), + validator_pubkey: FixedBytes::<48>::from([10; 48]), amount: 72, }), ])]; diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index b41d3db54867..ee8d4acdd52c 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -82,7 +82,7 @@ reth-transaction-pool = { workspace = true, features = ["test-utils"] } # alloy deps for testing against nodes alloy-node-bindings.workspace = true -alloy-provider.workspace = true +alloy-provider= { workspace = true, features = ["admin-api"] } # misc serial_test.workspace = true diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index d181cf47411b..7305a0c0636f 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -319,14 +319,14 @@ where let mut source_address = Address::ZERO; data.copy_to_slice(source_address.as_mut_slice()); - let mut validator_public_key = FixedBytes::<48>::ZERO; - data.copy_to_slice(validator_public_key.as_mut_slice()); + let mut validator_pubkey = FixedBytes::<48>::ZERO; + data.copy_to_slice(validator_pubkey.as_mut_slice()); let amount = data.get_u64(); withdrawal_requests.push(Request::WithdrawalRequest(WithdrawalRequest { source_address, - validator_public_key, + validator_pubkey, amount, })); } diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index 7ef01886850d..648b8b24f2ca 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -19,6 +19,9 @@ alloy-rpc-types = { workspace = true, features = ["jsonrpsee-types"] } alloy-rpc-types-anvil.workspace = true alloy-rpc-types-trace.workspace = true alloy-rpc-types-beacon.workspace = true +alloy-rpc-types-admin.workspace = true +alloy-rpc-types-txpool.workspace = true +alloy-serde.workspace = true alloy-rpc-types-engine = { workspace = true, features = ["jsonrpsee-types"] } # misc diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index a8d266648e60..3d239d1f945b 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -22,6 +22,9 @@ pub use alloy_rpc_types::serde_helpers; // Ethereum specific rpc types coming from alloy. pub use alloy_rpc_types::*; +// Ethereum specific serde types coming from alloy. +pub use alloy_serde::*; + pub mod trace { //! RPC types for trace endpoints and inspectors. pub use alloy_rpc_types_trace::*; @@ -33,6 +36,12 @@ pub use alloy_rpc_types_anvil as anvil; // re-export beacon pub use alloy_rpc_types_beacon as beacon; +// re-export admin +pub use alloy_rpc_types_admin as admin; + +// re-export txpool +pub use alloy_rpc_types_txpool as txpool; + // Ethereum specific rpc types related to typed transaction requests and the engine API. pub use eth::{ engine, diff --git a/crates/rpc/rpc-types/src/net.rs b/crates/rpc/rpc-types/src/net.rs index b434bcbf8493..eb77ac7922d4 100644 --- a/crates/rpc/rpc-types/src/net.rs +++ b/crates/rpc/rpc-types/src/net.rs @@ -1,4 +1,4 @@ -use alloy_rpc_types::admin::EthProtocolInfo; +use alloy_rpc_types_admin::EthProtocolInfo; use serde::{Deserialize, Serialize}; /// The status of the network being ran by the local node. diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index ac1c563224cf..b2d524a6325b 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,7 +1,7 @@ use crate::{ eth::{ error::{EthApiError, EthResult}, - revm_utils::{prepare_call_env, EvmOverrides}, + revm_utils::prepare_call_env, EthTransactions, }, result::{internal_rpc_err, ToRpcResult}, @@ -20,6 +20,7 @@ use reth_provider::{ use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; use reth_rpc_types::{ + state::EvmOverrides, trace::geth::{ BlockTraceResult, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, TraceResult, diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index a58559131cff..d0fb52b25246 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -6,7 +6,6 @@ use crate::{ revm_utils::{ apply_state_overrides, build_call_evm_env, caller_gas_allowance, cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env, - EvmOverrides, }, EthTransactions, }, @@ -20,8 +19,8 @@ use reth_provider::{ }; use reth_revm::database::StateProviderDatabase; use reth_rpc_types::{ - state::StateOverride, AccessListWithGasUsed, Bundle, EthCallResponse, StateContext, - TransactionRequest, + state::{EvmOverrides, StateOverride}, + AccessListWithGasUsed, Bundle, EthCallResponse, StateContext, TransactionRequest, }; use reth_transaction_pool::TransactionPool; use revm::{ diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 3d3ceeb6c968..41c68da48300 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -6,7 +6,6 @@ use crate::{ eth::{ api::{EthApi, EthTransactions}, error::EthApiError, - revm_utils::EvmOverrides, }, result::{internal_rpc_err, ToRpcResult}, }; @@ -21,9 +20,11 @@ use reth_provider::{ }; use reth_rpc_api::EthApiServer; use reth_rpc_types::{ - serde_helpers::JsonStorageKey, state::StateOverride, AccessListWithGasUsed, - AnyTransactionReceipt, BlockOverrides, Bundle, EIP1186AccountProofResponse, EthCallResponse, - FeeHistory, Header, Index, RichBlock, StateContext, SyncStatus, TransactionRequest, Work, + serde_helpers::JsonStorageKey, + state::{EvmOverrides, StateOverride}, + AccessListWithGasUsed, AnyTransactionReceipt, BlockOverrides, Bundle, + EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, Index, RichBlock, + StateContext, SyncStatus, TransactionRequest, Work, }; use reth_transaction_pool::TransactionPool; use tracing::trace; diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 52efa4c34456..8829a0434e49 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -3,7 +3,7 @@ use crate::{ eth::{ api::pending_block::PendingBlockEnv, error::{EthApiError, EthResult, RpcInvalidTransactionError, SignError}, - revm_utils::{prepare_call_env, EvmOverrides}, + revm_utils::prepare_call_env, utils::recover_raw_transaction, }, EthApi, EthApiSpec, @@ -26,6 +26,7 @@ use reth_provider::{ }; use reth_revm::database::StateProviderDatabase; use reth_rpc_types::{ + state::EvmOverrides, transaction::{ EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest, LegacyTransactionRequest, diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 8041087f2846..560c3eeb63d6 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -10,7 +10,7 @@ use reth_primitives::{ TransactionSignedEcRecovered, TxHash, TxKind, B256, U256, }; use reth_rpc_types::{ - state::{AccountOverride, StateOverride}, + state::{AccountOverride, EvmOverrides, StateOverride}, BlockOverrides, TransactionRequest, }; #[cfg(feature = "optimism")] @@ -27,64 +27,6 @@ use revm::{ use std::cmp::min; use tracing::trace; -/// Helper type that bundles various overrides for EVM Execution. -/// -/// By `Default`, no overrides are included. -#[derive(Debug, Clone, Default)] -pub struct EvmOverrides { - /// Applies overrides to the state before execution. - pub state: Option, - /// Applies overrides to the block before execution. - /// - /// This is a `Box` because less common and only available in debug trace endpoints. - pub block: Option>, -} - -impl EvmOverrides { - /// Creates a new instance with the given overrides - pub const fn new(state: Option, block: Option>) -> Self { - Self { state, block } - } - - /// Creates a new instance with the given state overrides. - pub const fn state(state: Option) -> Self { - Self { state, block: None } - } - - /// Creates a new instance with the given block overrides. - pub const fn block(block: Option>) -> Self { - Self { state: None, block } - } - - /// Returns `true` if the overrides contain state overrides. - pub const fn has_state(&self) -> bool { - self.state.is_some() - } - - /// Returns `true` if the overrides contain block overrides. - pub const fn has_block(&self) -> bool { - self.block.is_some() - } - - /// Adds state overrides to an existing instance. - pub fn with_state(mut self, state: StateOverride) -> Self { - self.state = Some(state); - self - } - - /// Adds block overrides to an existing instance. - pub fn with_block(mut self, block: Box) -> Self { - self.block = Some(block); - self - } -} - -impl From> for EvmOverrides { - fn from(state: Option) -> Self { - Self::state(state) - } -} - /// Helper type to work with different transaction types when configuring the EVM env. /// /// This makes it easier to handle errors. diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 589a3037208d..fdbf7163bd9d 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -1,6 +1,6 @@ use crate::eth::{ error::{EthApiError, EthResult}, - revm_utils::{prepare_call_env, EvmOverrides}, + revm_utils::prepare_call_env, utils::recover_raw_transaction, EthTransactions, }; @@ -12,7 +12,7 @@ use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProvide use reth_revm::database::StateProviderDatabase; use reth_rpc_api::TraceApiServer; use reth_rpc_types::{ - state::StateOverride, + state::{EvmOverrides, StateOverride}, trace::{ filter::TraceFilter, opcode::{BlockOpcodeGas, TransactionOpcodeGas}, diff --git a/examples/custom-inspector/Cargo.toml b/examples/custom-inspector/Cargo.toml index d2dc4ba14ad1..0e71af297d6f 100644 --- a/examples/custom-inspector/Cargo.toml +++ b/examples/custom-inspector/Cargo.toml @@ -8,5 +8,6 @@ license.workspace = true [dependencies] reth.workspace = true reth-node-ethereum.workspace = true +reth-rpc-types.workspace = true clap = { workspace = true, features = ["derive"] } futures-util.workspace = true \ No newline at end of file diff --git a/examples/custom-inspector/src/main.rs b/examples/custom-inspector/src/main.rs index b0fe4fbb8c2e..c1aae0227fc0 100644 --- a/examples/custom-inspector/src/main.rs +++ b/examples/custom-inspector/src/main.rs @@ -21,13 +21,11 @@ use reth::{ interpreter::{Interpreter, OpCode}, Database, Evm, EvmContext, Inspector, }, - rpc::{ - compat::transaction::transaction_to_call_request, - eth::{revm_utils::EvmOverrides, EthTransactions}, - }, + rpc::{compat::transaction::transaction_to_call_request, eth::EthTransactions}, transaction_pool::TransactionPool, }; use reth_node_ethereum::node::EthereumNode; +use reth_rpc_types::state::EvmOverrides; fn main() { Cli::::parse() From ff4a114e43af2437288a145e12fc14a7aa8f1f74 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 17 Jun 2024 11:42:27 +0100 Subject: [PATCH 078/405] docs(book): tracking state in ExExes (#8804) Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> Co-authored-by: Oliver --- book/SUMMARY.md | 1 + book/developers/exex/exex.md | 1 + book/developers/exex/hello-world.md | 4 + book/developers/exex/tracking-state.md | 193 +++++++++++++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 book/developers/exex/tracking-state.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index e7fa1ea68048..499b6dd97f9a 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -76,5 +76,6 @@ - [Execution Extensions](./developers/exex/exex.md) - [How do ExExes work?](./developers/exex/how-it-works.md) - [Hello World](./developers/exex/hello-world.md) + - [Tracking State](./developers/exex/tracking-state.md) - [Remote](./developers/exex/remote.md) - [Contribute](./developers/contribute.md) diff --git a/book/developers/exex/exex.md b/book/developers/exex/exex.md index f0cd08d4a586..b65d3173677b 100644 --- a/book/developers/exex/exex.md +++ b/book/developers/exex/exex.md @@ -27,3 +27,4 @@ and run it on the Holesky testnet. 1. [How do ExExes work?](./how-it-works.md) 1. [Hello World](./hello-world.md) +1. [Tracking State](./tracking-state.md) diff --git a/book/developers/exex/hello-world.md b/book/developers/exex/hello-world.md index c3da13ac4cc8..0f50cacbb9a6 100644 --- a/book/developers/exex/hello-world.md +++ b/book/developers/exex/hello-world.md @@ -160,3 +160,7 @@ and it's safe to prune the associated data. What we've arrived at is the [minimal ExEx example](https://github.com/paradigmxyz/reth/blob/b8cd7be6c92a71aea5341cdeba685f124c6de540/examples/exex/minimal/src/main.rs) that we provide in the Reth repository. + +## What's next? + +Let's do something a bit more interesting, and see how you can [keep track of some state](./tracking-state.md) inside your ExEx. diff --git a/book/developers/exex/tracking-state.md b/book/developers/exex/tracking-state.md new file mode 100644 index 000000000000..5fe8b1c9ef83 --- /dev/null +++ b/book/developers/exex/tracking-state.md @@ -0,0 +1,193 @@ +# Tracking State + +In this chapter, we'll learn how to keep track of some state inside our ExEx. + +Let's continue with our Hello World example from the [previous chapter](./hello-world.md). + +### Turning ExEx into a struct + +First, we need to turn our ExEx into a stateful struct. + +Before, we had just an async function, but now we'll need to implement +the [`Future`](https://doc.rust-lang.org/std/future/trait.Future.html) trait manually. + +
+ +Having a stateful async function is also possible, but it makes testing harder, +because you can't access variables inside the function to assert the state of your ExEx. + +
+ +```rust,norun,noplayground,ignore +use std::{ + future::Future, + pin::Pin, + task::{ready, Context, Poll}, +}; + +use reth::api::FullNodeComponents; +use reth_exex::{ExExContext, ExExEvent, ExExNotification}; +use reth_node_ethereum::EthereumNode; +use reth_tracing::tracing::info; + +struct MyExEx { + ctx: ExExContext, +} + +impl Future for MyExEx { + type Output = eyre::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + while let Some(notification) = ready!(this.ctx.notifications.poll_recv(cx)) { + match ¬ification { + ExExNotification::ChainCommitted { new } => { + info!(committed_chain = ?new.range(), "Received commit"); + } + ExExNotification::ChainReorged { old, new } => { + info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg"); + } + ExExNotification::ChainReverted { old } => { + info!(reverted_chain = ?old.range(), "Received revert"); + } + }; + + if let Some(committed_chain) = notification.committed_chain() { + this.ctx + .events + .send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + } + + Poll::Ready(Ok(())) + } +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let handle = builder + .node(EthereumNode::default()) + .install_exex("my-exex", |ctx| async move { Ok(MyExEx { ctx }) }) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) +} +``` + +For those who are not familiar with how async Rust works on a lower level, that may seem scary, +but let's unpack what's going on here: + +1. Our ExEx is now a `struct` that contains the context and implements the `Future` trait. It's now pollable (hence `await`-able). +1. We can't use `self` directly inside our `poll` method, and instead need to acquire a mutable reference to the data inside of the `Pin`. + Read more about pinning in [the book](https://rust-lang.github.io/async-book/04_pinning/01_chapter.html). +1. We also can't use `await` directly inside `poll`, and instead need to poll futures manually. + We wrap the call to `poll_recv(cx)` into a [`ready!`](https://doc.rust-lang.org/std/task/macro.ready.html) macro, + so that if the channel of notifications has no value ready, we will instantly return `Poll::Pending` from our Future. +1. We initialize and return the `MyExEx` struct directly in the `install_exex` method, because it's a Future. + +With all that done, we're now free to add more fields to our `MyExEx` struct, and track some state in them. + +### Adding state + +Our ExEx will count the number of transactions in each block and log it to the console. + +```rust,norun,noplayground,ignore +use std::{ + future::Future, + pin::Pin, + task::{ready, Context, Poll}, +}; + +use reth::{api::FullNodeComponents, primitives::BlockNumber}; +use reth_exex::{ExExContext, ExExEvent}; +use reth_node_ethereum::EthereumNode; +use reth_tracing::tracing::info; + +struct MyExEx { + ctx: ExExContext, + /// First block that was committed since the start of the ExEx. + first_block: Option, + /// Total number of transactions committed. + transactions: u64, +} + +impl MyExEx { + fn new(ctx: ExExContext) -> Self { + Self { + ctx, + first_block: None, + transactions: 0, + } + } +} + +impl Future for MyExEx { + type Output = eyre::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + while let Some(notification) = ready!(this.ctx.notifications.poll_recv(cx)) { + if let Some(reverted_chain) = notification.reverted_chain() { + this.transactions = this.transactions.saturating_sub( + reverted_chain + .blocks_iter() + .map(|b| b.body.len() as u64) + .sum(), + ); + } + + if let Some(committed_chain) = notification.committed_chain() { + this.first_block.get_or_insert(committed_chain.first().number); + + this.transactions += committed_chain + .blocks_iter() + .map(|b| b.body.len() as u64) + .sum::(); + + this.ctx + .events + .send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + + if let Some(first_block) = this.first_block { + info!(%first_block, transactions = %this.transactions, "Total number of transactions"); + } + } + + Poll::Ready(Ok(())) + } +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let handle = builder + .node(EthereumNode::default()) + .install_exex("my-exex", |ctx| async move { Ok(MyExEx::new(ctx)) }) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) +} +``` + +As you can see, we added two fields to our ExEx struct: +- `first_block` to keep track of the first block that was committed since the start of the ExEx. +- `transactions` to keep track of the total number of transactions committed, accounting for reorgs and reverts. + +We also changed our `match` block to two `if` clauses: +- First one checks if there's a reverted chain using `notification.reverted_chain()`. If there is: + - We subtract the number of transactions in the reverted chain from the total number of transactions. + - It's important to do the `saturating_sub` here, because if we just started our node and + instantly received a reorg, our `transactions` field will still be zero. +- Second one checks if there's a committed chain using `notification.committed_chain()`. If there is: + - We update the `first_block` field to the first block of the committed chain. + - We add the number of transactions in the committed chain to the total number of transactions. + - We send a `FinishedHeight` event back to the main node. + +Finally, on every notification, we log the total number of transactions and +the first block that was committed since the start of the ExEx. From 43a49ed414b84949fcc8b783ce2a0dd8ac8a0c06 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:10:03 +0200 Subject: [PATCH 079/405] chore(deps): bump revm dd98b3b (#8882) --- Cargo.lock | 12 ++++++------ Cargo.toml | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ab202d1c2a2..a7898dc5926c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4494,7 +4494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] @@ -8371,7 +8371,7 @@ dependencies = [ [[package]] name = "revm" version = "9.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" +source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" dependencies = [ "auto_impl", "cfg-if", @@ -8385,7 +8385,7 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=6bbf303#6bbf3033ba0119531de69cbddad1f4b89dfa2c4a" +source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=79774a6#79774a6e4add9da9247130bf73305531092d0895" dependencies = [ "alloy-primitives", "alloy-rpc-types 0.1.0", @@ -8402,7 +8402,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "5.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" +source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" dependencies = [ "revm-primitives", "serde", @@ -8411,7 +8411,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "7.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" +source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" dependencies = [ "aurora-engine-modexp", "blst", @@ -8429,7 +8429,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=8f4c153#8f4c153a02d94d9e1e9275ba3cf73f599e23c6b6" +source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" dependencies = [ "alloy-primitives", "auto_impl", diff --git a/Cargo.toml b/Cargo.toml index 01122427cc15..b0f2e2048e8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,7 +344,7 @@ revm = { version = "9.0.0", features = [ revm-primitives = { version = "4.0.0", features = [ "std", ], default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "6bbf303" } +revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "79774a6" } # eth alloy-chains = "0.1.15" @@ -497,7 +497,7 @@ similar-asserts = "1.5.0" test-fuzz = "5" [patch.crates-io] -revm = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } -revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } -revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } -revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "8f4c153" } \ No newline at end of file +revm = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } +revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } +revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } +revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } From d131540352c93fe1343808a60aac6ea6ed6bcbd8 Mon Sep 17 00:00:00 2001 From: Krishang <93703995+kamuik16@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:50:45 +0530 Subject: [PATCH 080/405] feat: support `no_std` for `ethereum-forks` (#8877) Co-authored-by: Matthias Seitz --- Cargo.lock | 2 +- crates/ethereum-forks/Cargo.toml | 5 +++-- crates/ethereum-forks/src/forkid.rs | 21 +++++++++++++-------- crates/ethereum-forks/src/hardfork.rs | 11 +++++++++-- crates/ethereum-forks/src/head.rs | 2 +- crates/ethereum-forks/src/lib.rs | 4 ++++ 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7898dc5926c..b407d2ab8f72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6969,7 +6969,7 @@ dependencies = [ "proptest", "proptest-derive", "serde", - "thiserror", + "thiserror-no-std", ] [[package]] diff --git a/crates/ethereum-forks/Cargo.toml b/crates/ethereum-forks/Cargo.toml index fb2ad5820b4d..ac9836c5438a 100644 --- a/crates/ethereum-forks/Cargo.toml +++ b/crates/ethereum-forks/Cargo.toml @@ -22,7 +22,7 @@ crc = "3" # misc serde = { workspace = true, features = ["derive"], optional = true } -thiserror.workspace = true +thiserror-no-std = { workspace = true, default-features = false } # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } @@ -35,7 +35,8 @@ proptest.workspace = true proptest-derive.workspace = true [features] -default = ["serde"] +default = ["std", "serde"] +std = ["thiserror-no-std/std"] serde = ["dep:serde"] arbitrary = ["dep:arbitrary", "dep:proptest", "dep:proptest-derive"] optimism = [] diff --git a/crates/ethereum-forks/src/forkid.rs b/crates/ethereum-forks/src/forkid.rs index d2cab7a80ec9..48ef778fcfbf 100644 --- a/crates/ethereum-forks/src/forkid.rs +++ b/crates/ethereum-forks/src/forkid.rs @@ -3,22 +3,27 @@ //! Previously version of Apache licenced [`ethereum-forkid`](https://crates.io/crates/ethereum-forkid). use crate::Head; +#[cfg(not(feature = "std"))] +use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; use alloy_primitives::{hex, BlockNumber, B256}; use alloy_rlp::{Error as RlpError, *}; #[cfg(any(test, feature = "arbitrary"))] use arbitrary::Arbitrary; +use core::{ + cmp::Ordering, + fmt, + ops::{Add, AddAssign}, +}; use crc::*; #[cfg(any(test, feature = "arbitrary"))] use proptest_derive::Arbitrary as PropTestArbitrary; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use std::{ - cmp::Ordering, - collections::{BTreeMap, BTreeSet}, - fmt, - ops::{Add, AddAssign}, -}; -use thiserror::Error; +#[cfg(feature = "std")] +use std::collections::{BTreeMap, BTreeSet}; const CRC_32_IEEE: Crc = Crc::::new(&CRC_32_ISO_HDLC); const TIMESTAMP_BEFORE_ETHEREUM_MAINNET: u64 = 1_300_000_000; @@ -174,7 +179,7 @@ impl From for ForkId { } /// Reason for rejecting provided `ForkId`. -#[derive(Clone, Copy, Debug, Error, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, thiserror_no_std::Error, PartialEq, Eq, Hash)] pub enum ValidationError { /// Remote node is outdated and needs a software update. #[error( diff --git a/crates/ethereum-forks/src/hardfork.rs b/crates/ethereum-forks/src/hardfork.rs index 1f8346bfe4ec..fa095dea5a18 100644 --- a/crates/ethereum-forks/src/hardfork.rs +++ b/crates/ethereum-forks/src/hardfork.rs @@ -1,7 +1,14 @@ use alloy_chains::Chain; +use core::{ + fmt, + fmt::{Display, Formatter}, + str::FromStr, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use std::{fmt::Display, str::FromStr}; + +#[cfg(not(feature = "std"))] +use alloc::{format, string::String}; /// Represents the consensus type of a blockchain fork. /// @@ -562,7 +569,7 @@ impl FromStr for Hardfork { } impl Display for Hardfork { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } diff --git a/crates/ethereum-forks/src/head.rs b/crates/ethereum-forks/src/head.rs index 2cf29cca90e1..bd05cc3a772e 100644 --- a/crates/ethereum-forks/src/head.rs +++ b/crates/ethereum-forks/src/head.rs @@ -1,7 +1,7 @@ use alloy_primitives::{BlockNumber, B256, U256}; +use core::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use std::fmt; /// Describes the current head block. /// diff --git a/crates/ethereum-forks/src/lib.rs b/crates/ethereum-forks/src/lib.rs index 6dbec7c38d7c..8457a5f6206b 100644 --- a/crates/ethereum-forks/src/lib.rs +++ b/crates/ethereum-forks/src/lib.rs @@ -15,6 +15,10 @@ // TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged #![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; mod forkid; mod hardfork; From 26d8bd6a4fe8d865467346c18857b7c79d50f2f4 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 17 Jun 2024 13:47:28 +0200 Subject: [PATCH 081/405] fix(docs): `op-node` flags (#8884) --- book/run/optimism.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/book/run/optimism.md b/book/run/optimism.md index b16a7e4b9f77..3e4c76b7c763 100644 --- a/book/run/optimism.md +++ b/book/run/optimism.md @@ -83,10 +83,12 @@ op-node \ --l2.jwt-secret=/path/to/jwt.hex \ --rpc.addr=0.0.0.0 \ --rpc.port=7000 \ - --l1.trustrpc \ --l1.beacon= + --syncmode=execution-layer ``` +Consider adding the `--l1.trustrpc` flag to improve performance, if the connection to l1 is over localhost. + If you opted to build the `op-node` with the `rethdb` build tag, this feature can be enabled by appending one extra flag to the `op-node` invocation: > Note, the `reth_db_path` is the path to the `db` folder inside of the reth datadir, not the `mdbx.dat` file itself. This can be fetched from `op-reth db path [--chain ]`, or if you are using a custom datadir location via the `--datadir` flag, From 34d697eb1f447e173d47eb66210bd7244453abd7 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 17 Jun 2024 13:48:03 +0200 Subject: [PATCH 082/405] chore: use blob explorers from crates (#8885) --- Cargo.lock | 36 +++++++++++++++------------------ examples/exex/rollup/Cargo.toml | 2 +- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b407d2ab8f72..65e8b864a89c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,7 +140,8 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "0.1.1" -source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc7579e4fb5558af44810f542c90d1145dba8b92c08211c215196160c48d2ea" dependencies = [ "alloy-eips 0.1.1", "alloy-primitives", @@ -189,7 +190,8 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.1" -source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdbc8d98cc36ebe17bb5b42d0873137bc76628a4ee0f7e7acad5b8fc59d3597" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -405,15 +407,6 @@ dependencies = [ "alloy-serde 0.1.0", ] -[[package]] -name = "alloy-rpc-types" -version = "0.1.1" -source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" -dependencies = [ - "alloy-rpc-types-eth 0.1.1", - "alloy-serde 0.1.1", -] - [[package]] name = "alloy-rpc-types-admin" version = "0.1.0" @@ -490,7 +483,8 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "0.1.1" -source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce0676f144be1eae71122d1d417885a3b063add0353b35e46cdf1440d6b33b1" dependencies = [ "alloy-consensus 0.1.1", "alloy-eips 0.1.1", @@ -543,7 +537,8 @@ dependencies = [ [[package]] name = "alloy-serde" version = "0.1.1" -source = "git+https://github.com/alloy-rs/alloy#30317d9262be3aa93c4fd31ec3ea819b962c2448" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c224916316519558d8c2b6a60dc7626688c08f1b8951774702562dbcb8666ee" dependencies = [ "alloy-primitives", "serde", @@ -3116,12 +3111,13 @@ dependencies = [ [[package]] name = "foundry-blob-explorers" version = "0.1.0" -source = "git+https://github.com/foundry-rs/block-explorers#af29524f4fc7dc25f59e8ae38652022f47ebee9b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195bb5b228e1215c50d828f3e7d48a809a0af2bc0120462710ea5e7fcba3cbe2" dependencies = [ "alloy-chains", "alloy-eips 0.1.1", "alloy-primitives", - "alloy-rpc-types 0.1.1", + "alloy-rpc-types-eth 0.1.1", "alloy-serde 0.1.1", "chrono", "reqwest", @@ -6801,7 +6797,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-consensus 0.1.0", "alloy-network", - "alloy-rpc-types 0.1.0", + "alloy-rpc-types", "alloy-signer", "alloy-signer-local", "eyre", @@ -7670,7 +7666,7 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types 0.1.0", + "alloy-rpc-types", "alloy-trie", "arbitrary", "assert_matches", @@ -8025,7 +8021,7 @@ name = "reth-rpc-types" version = "1.0.0-rc.1" dependencies = [ "alloy-primitives", - "alloy-rpc-types 0.1.0", + "alloy-rpc-types", "alloy-rpc-types-admin", "alloy-rpc-types-anvil", "alloy-rpc-types-beacon", @@ -8049,7 +8045,7 @@ name = "reth-rpc-types-compat" version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", - "alloy-rpc-types 0.1.0", + "alloy-rpc-types", "reth-primitives", "reth-rpc-types", "reth-trie-common", @@ -8388,7 +8384,7 @@ version = "0.1.0" source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=79774a6#79774a6e4add9da9247130bf73305531092d0895" dependencies = [ "alloy-primitives", - "alloy-rpc-types 0.1.0", + "alloy-rpc-types", "alloy-sol-types", "anstyle", "boa_engine", diff --git a/examples/exex/rollup/Cargo.toml b/examples/exex/rollup/Cargo.toml index 5a4dcb5f4fea..e13113586110 100644 --- a/examples/exex/rollup/Cargo.toml +++ b/examples/exex/rollup/Cargo.toml @@ -25,7 +25,7 @@ alloy-consensus = { workspace = true, features = ["kzg"] } alloy-rlp.workspace = true alloy-sol-types = { workspace = true, features = ["json"] } eyre.workspace = true -foundry-blob-explorers = { git = "https://github.com/foundry-rs/block-explorers" } +foundry-blob-explorers = "0.1" once_cell.workspace = true rusqlite = { version = "0.31.0", features = ["bundled"] } serde_json.workspace = true From 4207540974ea7e26565e4bc37ad4d3566b8ba19d Mon Sep 17 00:00:00 2001 From: HAPPY Date: Mon, 17 Jun 2024 19:52:11 +0800 Subject: [PATCH 083/405] fix(commands): prevents potential arithmetic underflow in debug commands (#8883) --- bin/reth/src/commands/debug_cmd/merkle.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 12916cb5b51d..46e76d1da090 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -167,7 +167,9 @@ impl Command { OriginalValuesKnown::Yes, )?; - let checkpoint = Some(StageCheckpoint::new(block_number - 1)); + let checkpoint = Some(StageCheckpoint::new( + block_number.checked_sub(1).ok_or(eyre::eyre!("GenesisBlockHasNoParent"))?, + )); let mut account_hashing_done = false; while !account_hashing_done { From 15cca65f478fb5f7829d8cad631a383b5df95f61 Mon Sep 17 00:00:00 2001 From: guha-rahul <52607971+guha-rahul@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:08:31 +0530 Subject: [PATCH 084/405] refactor: extract Chainspec type from primitives (#8055) Co-authored-by: Matthias Seitz Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com> --- Cargo.lock | 26 ++- Cargo.toml | 2 + crates/chainspec/Cargo.toml | 53 +++++++ .../res/genesis/base.json | 0 .../res/genesis/dev.json | 0 .../res/genesis/goerli.json | 0 .../res/genesis/holesky.json | 0 .../res/genesis/mainnet.json | 0 .../res/genesis/optimism.json | 0 .../res/genesis/sepolia.json | 0 .../res/genesis/sepolia_base.json | 0 .../res/genesis/sepolia_op.json | 0 crates/chainspec/src/constants/mod.rs | 49 ++++++ crates/chainspec/src/constants/optimism.rs | 148 ++++++++++++++++++ .../src/chain => chainspec/src}/info.rs | 3 +- .../src/chain/mod.rs => chainspec/src/lib.rs} | 31 ++-- crates/{primitives => chainspec}/src/net.rs | 0 .../src/chain => chainspec/src}/spec.rs | 87 +++++----- crates/primitives/Cargo.toml | 7 +- crates/primitives/src/basefee.rs | 141 ----------------- crates/primitives/src/constants/mod.rs | 57 ------- crates/primitives/src/lib.rs | 30 ++-- 22 files changed, 360 insertions(+), 274 deletions(-) create mode 100644 crates/chainspec/Cargo.toml rename crates/{primitives => chainspec}/res/genesis/base.json (100%) rename crates/{primitives => chainspec}/res/genesis/dev.json (100%) rename crates/{primitives => chainspec}/res/genesis/goerli.json (100%) rename crates/{primitives => chainspec}/res/genesis/holesky.json (100%) rename crates/{primitives => chainspec}/res/genesis/mainnet.json (100%) rename crates/{primitives => chainspec}/res/genesis/optimism.json (100%) rename crates/{primitives => chainspec}/res/genesis/sepolia.json (100%) rename crates/{primitives => chainspec}/res/genesis/sepolia_base.json (100%) rename crates/{primitives => chainspec}/res/genesis/sepolia_op.json (100%) create mode 100644 crates/chainspec/src/constants/mod.rs create mode 100644 crates/chainspec/src/constants/optimism.rs rename crates/{primitives/src/chain => chainspec/src}/info.rs (86%) rename crates/{primitives/src/chain/mod.rs => chainspec/src/lib.rs} (82%) rename crates/{primitives => chainspec}/src/net.rs (100%) rename crates/{primitives/src/chain => chainspec/src}/spec.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 65e8b864a89c..a240975876a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6498,6 +6498,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "reth-chainspec" +version = "1.0.0-rc.1" +dependencies = [ + "alloy-chains", + "alloy-eips 0.1.0", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "derive_more", + "nybbles", + "once_cell", + "rand 0.8.5", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "reth-rpc-types", + "reth-trie-common", + "serde", + "serde_json", +] + [[package]] name = "reth-cli-runner" version = "1.0.0-rc.1" @@ -7660,7 +7683,6 @@ dependencies = [ name = "reth-primitives" version = "1.0.0-rc.1" dependencies = [ - "alloy-chains", "alloy-consensus 0.1.0", "alloy-eips 0.1.0", "alloy-genesis", @@ -7683,9 +7705,9 @@ dependencies = [ "proptest-derive", "rand 0.8.5", "rayon", + "reth-chainspec", "reth-codecs", "reth-ethereum-forks", - "reth-network-peers", "reth-primitives-traits", "reth-static-file-types", "reth-trie-common", diff --git a/Cargo.toml b/Cargo.toml index b0f2e2048e8a..63d297ab88d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "bin/reth/", "crates/blockchain-tree/", "crates/blockchain-tree-api/", + "crates/chainspec/", "crates/cli/runner/", "crates/config/", "crates/consensus/auto-seal/", @@ -246,6 +247,7 @@ reth-basic-payload-builder = { path = "crates/payload/basic" } reth-beacon-consensus = { path = "crates/consensus/beacon" } reth-blockchain-tree = { path = "crates/blockchain-tree" } reth-blockchain-tree-api = { path = "crates/blockchain-tree-api" } +reth-chainspec = { path = "crates/chainspec" } reth-cli-runner = { path = "crates/cli/runner" } reth-codecs = { path = "crates/storage/codecs" } reth-codecs-derive = { path = "crates/storage/codecs/derive" } diff --git a/crates/chainspec/Cargo.toml b/crates/chainspec/Cargo.toml new file mode 100644 index 000000000000..8c1d4d4dac4f --- /dev/null +++ b/crates/chainspec/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "reth-chainspec" +version.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-ethereum-forks.workspace = true +reth-network-peers.workspace = true +reth-trie-common.workspace = true +reth-primitives-traits.workspace = true + +# ethereum +alloy-chains = { workspace = true, features = ["serde", "rlp"] } +alloy-eips = { workspace = true, features = ["serde"] } +alloy-genesis.workspace = true +alloy-primitives = { workspace = true, features = ["rand", "rlp"] } +alloy-trie.workspace = true + +# misc +once_cell.workspace = true +serde.workspace = true +serde_json.workspace = true +derive_more.workspace = true + +[dev-dependencies] +# eth +nybbles = { workspace = true, features = ["arbitrary"] } +alloy-trie = { workspace = true, features = ["arbitrary"] } +alloy-eips = { workspace = true, features = ["arbitrary"] } +alloy-rlp = { workspace = true, features = ["arrayvec"] } +alloy-genesis.workspace = true +reth-rpc-types.workspace = true +rand.workspace = true + +[features] +default = ["std"] +optimism = [ + "reth-ethereum-forks/optimism" +] +std = [] +arbitrary = [ + "alloy-chains/arbitrary" +] + + diff --git a/crates/primitives/res/genesis/base.json b/crates/chainspec/res/genesis/base.json similarity index 100% rename from crates/primitives/res/genesis/base.json rename to crates/chainspec/res/genesis/base.json diff --git a/crates/primitives/res/genesis/dev.json b/crates/chainspec/res/genesis/dev.json similarity index 100% rename from crates/primitives/res/genesis/dev.json rename to crates/chainspec/res/genesis/dev.json diff --git a/crates/primitives/res/genesis/goerli.json b/crates/chainspec/res/genesis/goerli.json similarity index 100% rename from crates/primitives/res/genesis/goerli.json rename to crates/chainspec/res/genesis/goerli.json diff --git a/crates/primitives/res/genesis/holesky.json b/crates/chainspec/res/genesis/holesky.json similarity index 100% rename from crates/primitives/res/genesis/holesky.json rename to crates/chainspec/res/genesis/holesky.json diff --git a/crates/primitives/res/genesis/mainnet.json b/crates/chainspec/res/genesis/mainnet.json similarity index 100% rename from crates/primitives/res/genesis/mainnet.json rename to crates/chainspec/res/genesis/mainnet.json diff --git a/crates/primitives/res/genesis/optimism.json b/crates/chainspec/res/genesis/optimism.json similarity index 100% rename from crates/primitives/res/genesis/optimism.json rename to crates/chainspec/res/genesis/optimism.json diff --git a/crates/primitives/res/genesis/sepolia.json b/crates/chainspec/res/genesis/sepolia.json similarity index 100% rename from crates/primitives/res/genesis/sepolia.json rename to crates/chainspec/res/genesis/sepolia.json diff --git a/crates/primitives/res/genesis/sepolia_base.json b/crates/chainspec/res/genesis/sepolia_base.json similarity index 100% rename from crates/primitives/res/genesis/sepolia_base.json rename to crates/chainspec/res/genesis/sepolia_base.json diff --git a/crates/primitives/res/genesis/sepolia_op.json b/crates/chainspec/res/genesis/sepolia_op.json similarity index 100% rename from crates/primitives/res/genesis/sepolia_op.json rename to crates/chainspec/res/genesis/sepolia_op.json diff --git a/crates/chainspec/src/constants/mod.rs b/crates/chainspec/src/constants/mod.rs new file mode 100644 index 000000000000..9af4f946b92d --- /dev/null +++ b/crates/chainspec/src/constants/mod.rs @@ -0,0 +1,49 @@ +use crate::spec::DepositContract; +use alloy_primitives::{address, b256}; + +/// Deposit contract address: `0x00000000219ab540356cbb839cbe05303d7705fa` +pub(crate) const MAINNET_DEPOSIT_CONTRACT: DepositContract = DepositContract::new( + address!("00000000219ab540356cbb839cbe05303d7705fa"), + 11052984, + b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), +); + +#[cfg(feature = "optimism")] +pub(crate) mod optimism; + +#[cfg(test)] +mod tests { + use alloy_eips::calc_next_block_base_fee; + + #[test] + fn calculate_base_fee_success() { + let base_fee = [ + 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, + 1, 2, + ]; + let gas_used = [ + 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, + 10000000, + ]; + let gas_limit = [ + 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, + 18000000, 18000000, + ]; + let next_base_fee = [ + 1125000000, 1083333333, 1053571428, 1179939062, 1116028649, 918084097, 1063811730, 1, + 2, 3, + ]; + + for i in 0..base_fee.len() { + assert_eq!( + next_base_fee[i], + calc_next_block_base_fee( + gas_used[i] as u128, + gas_limit[i] as u128, + base_fee[i] as u128, + crate::BaseFeeParams::ethereum(), + ) as u64 + ); + } + } +} diff --git a/crates/chainspec/src/constants/optimism.rs b/crates/chainspec/src/constants/optimism.rs new file mode 100644 index 000000000000..d4a1de6d0ea4 --- /dev/null +++ b/crates/chainspec/src/constants/optimism.rs @@ -0,0 +1,148 @@ +use alloy_eips::eip1559::BaseFeeParams; +use reth_primitives_traits::constants::{ + BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, + OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, + OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, + OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, + OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +/// Get the base fee parameters for Base Sepolia. +pub(crate) const BASE_SEPOLIA_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, + elasticity_multiplier: BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +/// Get the base fee parameters for Base Sepolia (post Canyon). +pub(crate) const BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + elasticity_multiplier: BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +/// Get the base fee parameters for Optimism Sepolia. +pub(crate) const OP_SEPOLIA_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, + elasticity_multiplier: OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +/// Get the base fee parameters for Optimism Sepolia (post Canyon). +pub(crate) const OP_SEPOLIA_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + elasticity_multiplier: OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +/// Get the base fee parameters for Optimism Mainnet. +pub(crate) const OP_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, + elasticity_multiplier: OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +/// Get the base fee parameters for Optimism Mainnet (post Canyon). +pub(crate) const OP_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { + max_change_denominator: OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + elasticity_multiplier: OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, +}; + +#[cfg(test)] +mod tests { + use super::*; + use alloy_eips::calc_next_block_base_fee; + + #[test] + fn calculate_optimism_base_fee_success() { + let base_fee = [ + 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, + 1, 2, + ]; + let gas_used = [ + 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, + 10000000, + ]; + let gas_limit = [ + 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, + 18000000, 18000000, + ]; + let next_base_fee = [ + 1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1, + 2, 3, + ]; + + for i in 0..base_fee.len() { + assert_eq!( + next_base_fee[i], + calc_next_block_base_fee( + gas_used[i] as u128, + gas_limit[i] as u128, + base_fee[i] as u128, + OP_BASE_FEE_PARAMS, + ) as u64 + ); + } + } + + #[test] + fn calculate_optimism_sepolia_base_fee_success() { + let base_fee = [ + 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, + 1, 2, + ]; + let gas_used = [ + 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, + 10000000, + ]; + let gas_limit = [ + 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, + 18000000, 18000000, + ]; + let next_base_fee = [ + 1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1, + 2, 3, + ]; + + for i in 0..base_fee.len() { + assert_eq!( + next_base_fee[i], + calc_next_block_base_fee( + gas_used[i] as u128, + gas_limit[i] as u128, + base_fee[i] as u128, + OP_SEPOLIA_BASE_FEE_PARAMS, + ) as u64 + ); + } + } + + #[test] + fn calculate_base_sepolia_base_fee_success() { + let base_fee = [ + 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, + 1, 2, + ]; + let gas_used = [ + 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, + 10000000, + ]; + let gas_limit = [ + 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, + 18000000, 18000000, + ]; + let next_base_fee = [ + 1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1, + 2, 3, + ]; + + for i in 0..base_fee.len() { + assert_eq!( + next_base_fee[i], + calc_next_block_base_fee( + gas_used[i] as u128, + gas_limit[i] as u128, + base_fee[i] as u128, + BASE_SEPOLIA_BASE_FEE_PARAMS, + ) as u64 + ); + } + } +} diff --git a/crates/primitives/src/chain/info.rs b/crates/chainspec/src/info.rs similarity index 86% rename from crates/primitives/src/chain/info.rs rename to crates/chainspec/src/info.rs index 38b73e2768ae..6fe82d0a249b 100644 --- a/crates/primitives/src/chain/info.rs +++ b/crates/chainspec/src/info.rs @@ -1,4 +1,5 @@ -use crate::{BlockNumHash, BlockNumber, B256}; +use alloy_eips::BlockNumHash; +use alloy_primitives::{BlockNumber, B256}; /// Current status of the blockchain's head. #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] diff --git a/crates/primitives/src/chain/mod.rs b/crates/chainspec/src/lib.rs similarity index 82% rename from crates/primitives/src/chain/mod.rs rename to crates/chainspec/src/lib.rs index 41de1c450210..61460aa30c28 100644 --- a/crates/primitives/src/chain/mod.rs +++ b/crates/chainspec/src/lib.rs @@ -1,3 +1,13 @@ +//! The spec of an Ethereum network + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + pub use alloy_chains::{Chain, ChainKind, NamedChain}; pub use info::ChainInfo; pub use spec::{ @@ -8,21 +18,24 @@ pub use spec::{ #[cfg(feature = "optimism")] pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; -#[cfg(feature = "optimism")] -#[cfg(test)] -pub(crate) use spec::{ - BASE_SEPOLIA_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, -}; +// /// The config info module namely spec id. +// pub mod config; +/// The chain info module. +mod info; + +/// Network related constants +pub mod net; -// The chain spec module. +/// The chain spec module. mod spec; -// The chain info module. -mod info; + +/// Chain specific constants +pub(crate) mod constants; #[cfg(test)] mod tests { use super::*; - use crate::U256; + use alloy_primitives::U256; use alloy_rlp::Encodable; use std::str::FromStr; diff --git a/crates/primitives/src/net.rs b/crates/chainspec/src/net.rs similarity index 100% rename from crates/primitives/src/net.rs rename to crates/chainspec/src/net.rs diff --git a/crates/primitives/src/chain/spec.rs b/crates/chainspec/src/spec.rs similarity index 98% rename from crates/primitives/src/chain/spec.rs rename to crates/chainspec/src/spec.rs index 788acf7b1974..2462da93a12c 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -1,51 +1,52 @@ -use crate::{ - constants::{ - EIP1559_INITIAL_BASE_FEE, EMPTY_RECEIPTS, EMPTY_ROOT_HASH, EMPTY_TRANSACTIONS, - EMPTY_WITHDRAWALS, - }, - holesky_nodes, - net::{goerli_nodes, mainnet_nodes, sepolia_nodes}, - revm_primitives::{address, b256}, - Address, BlockNumber, Chain, ChainKind, ForkFilter, ForkFilterKey, ForkHash, ForkId, Genesis, - Hardfork, Head, Header, NamedChain, NodeRecord, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, - MAINNET_DEPOSIT_CONTRACT, U256, +use crate::constants::MAINNET_DEPOSIT_CONTRACT; +#[cfg(not(feature = "std"))] +use alloc::{ + collections::BTreeMap, + format, + string::{String, ToString}, + sync::Arc, + vec::Vec, }; +use alloy_chains::{Chain, ChainKind, NamedChain}; +use alloy_genesis::Genesis; +use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256}; +use alloy_trie::EMPTY_ROOT_HASH; use core::{ fmt, fmt::{Display, Formatter}, }; use derive_more::From; use once_cell::sync::Lazy; +use reth_ethereum_forks::{ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head}; +use reth_network_peers::NodeRecord; +use reth_primitives_traits::{ + constants::{ + EIP1559_INITIAL_BASE_FEE, EMPTY_OMMER_ROOT_HASH, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, + EMPTY_WITHDRAWALS, + }, + Header, SealedHeader, +}; use reth_trie_common::root::state_root_ref_unhashed; use serde::{Deserialize, Serialize}; - -#[cfg(not(feature = "std"))] -use alloc::{ - collections::BTreeMap, - format, - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; #[cfg(feature = "std")] use std::{collections::BTreeMap, sync::Arc}; +#[cfg(feature = "optimism")] +use crate::constants::optimism::{ + BASE_SEPOLIA_BASE_FEE_PARAMS, BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS, + OP_CANYON_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, OP_SEPOLIA_CANYON_BASE_FEE_PARAMS, +}; pub use alloy_eips::eip1559::BaseFeeParams; #[cfg(feature = "optimism")] -pub(crate) use crate::{ - constants::{ - BASE_SEPOLIA_BASE_FEE_PARAMS, BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS, - OP_CANYON_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, OP_SEPOLIA_CANYON_BASE_FEE_PARAMS, - }, - net::{base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes}, -}; +use crate::net::{base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes}; +use crate::net::{goerli_nodes, holesky_nodes, mainnet_nodes, sepolia_nodes}; /// The Ethereum mainnet spec pub static MAINNET: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::mainnet(), - genesis: serde_json::from_str(include_str!("../../res/genesis/mainnet.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/mainnet.json")) .expect("Can't deserialize Mainnet genesis json"), genesis_hash: Some(b256!( "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" @@ -96,7 +97,7 @@ pub static MAINNET: Lazy> = Lazy::new(|| { pub static GOERLI: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::goerli(), - genesis: serde_json::from_str(include_str!("../../res/genesis/goerli.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/goerli.json")) .expect("Can't deserialize Goerli genesis json"), genesis_hash: Some(b256!( "bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" @@ -138,7 +139,7 @@ pub static GOERLI: Lazy> = Lazy::new(|| { pub static SEPOLIA: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::sepolia(), - genesis: serde_json::from_str(include_str!("../../res/genesis/sepolia.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/sepolia.json")) .expect("Can't deserialize Sepolia genesis json"), genesis_hash: Some(b256!( "25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9" @@ -184,7 +185,7 @@ pub static SEPOLIA: Lazy> = Lazy::new(|| { pub static HOLESKY: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::holesky(), - genesis: serde_json::from_str(include_str!("../../res/genesis/holesky.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/holesky.json")) .expect("Can't deserialize Holesky genesis json"), genesis_hash: Some(b256!( "b5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4" @@ -228,7 +229,7 @@ pub static HOLESKY: Lazy> = Lazy::new(|| { pub static DEV: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::dev(), - genesis: serde_json::from_str(include_str!("../../res/genesis/dev.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/dev.json")) .expect("Can't deserialize Dev testnet genesis json"), genesis_hash: Some(b256!( "2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c" @@ -274,7 +275,7 @@ pub static OP_MAINNET: Lazy> = Lazy::new(|| { chain: Chain::optimism_mainnet(), // genesis contains empty alloc field because state at first bedrock block is imported // manually from trusted source - genesis: serde_json::from_str(include_str!("../../res/genesis/optimism.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/optimism.json")) .expect("Can't deserialize Optimism Mainnet genesis json"), genesis_hash: Some(b256!( "7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" @@ -324,7 +325,7 @@ pub static OP_MAINNET: Lazy> = Lazy::new(|| { pub static OP_SEPOLIA: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::from_named(NamedChain::OptimismSepolia), - genesis: serde_json::from_str(include_str!("../../res/genesis/sepolia_op.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/sepolia_op.json")) .expect("Can't deserialize OP Sepolia genesis json"), genesis_hash: Some(b256!( "102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d" @@ -374,7 +375,7 @@ pub static OP_SEPOLIA: Lazy> = Lazy::new(|| { pub static BASE_SEPOLIA: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::base_sepolia(), - genesis: serde_json::from_str(include_str!("../../res/genesis/sepolia_base.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/sepolia_base.json")) .expect("Can't deserialize Base Sepolia genesis json"), genesis_hash: Some(b256!( "0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4" @@ -424,7 +425,7 @@ pub static BASE_SEPOLIA: Lazy> = Lazy::new(|| { pub static BASE_MAINNET: Lazy> = Lazy::new(|| { ChainSpec { chain: Chain::base_mainnet(), - genesis: serde_json::from_str(include_str!("../../res/genesis/base.json")) + genesis: serde_json::from_str(include_str!("../res/genesis/base.json")) .expect("Can't deserialize Base genesis json"), genesis_hash: Some(b256!( "f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd" @@ -852,12 +853,6 @@ impl ChainSpec { self.fork(Hardfork::Homestead).active_at_block(block_number) } - /// The Paris hardfork (merge) is activated via ttd, if we know the block is known then this - /// returns true if the block number is greater than or equal to the Paris (merge) block. - pub fn is_paris_active_at_block(&self, block_number: u64) -> Option { - self.paris_block_and_final_difficulty.map(|(paris_block, _)| block_number >= paris_block) - } - /// Convenience method to check if [`Hardfork::Bedrock`] is active at a given block number. #[cfg(feature = "optimism")] #[inline] @@ -1523,7 +1518,7 @@ impl Display for DisplayFork { /// # Examples /// /// ``` -/// # use reth_primitives::MAINNET; +/// # use reth_chainspec::MAINNET; /// println!("{}", MAINNET.display_hardforks()); /// ``` /// @@ -1737,11 +1732,15 @@ impl OptimismGenesisInfo { #[cfg(test)] mod tests { + use alloy_chains::Chain; + use alloy_genesis::{ChainConfig, GenesisAccount}; + use reth_ethereum_forks::{ForkHash, ForkId, Head}; use reth_trie_common::TrieAccount; use super::*; - use crate::{b256, hex, ChainConfig, GenesisAccount}; + use alloy_primitives::{b256, hex}; use std::{collections::HashMap, str::FromStr}; + fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) { for (block, expected_id) in cases { let computed_id = spec.fork_id(block); diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 90fdb0e3eb3f..ff3cf016956a 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -16,14 +16,13 @@ workspace = true reth-primitives-traits.workspace = true reth-codecs.workspace = true reth-ethereum-forks.workspace = true -reth-network-peers.workspace = true reth-static-file-types.workspace = true reth-trie-common.workspace = true +reth-chainspec.workspace = true revm.workspace = true revm-primitives = { workspace = true, features = ["serde"] } # ethereum -alloy-chains = { workspace = true, features = ["serde", "rlp"] } alloy-consensus = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["rand", "rlp"] } alloy-rlp = { workspace = true, features = ["arrayvec"] } @@ -48,7 +47,6 @@ modular-bitfield.workspace = true once_cell.workspace = true rayon.workspace = true serde.workspace = true -serde_json.workspace = true tempfile = { workspace = true, optional = true } thiserror-no-std = { workspace = true , default-features = false } zstd = { version = "0.13", features = ["experimental"], optional = true } @@ -92,10 +90,10 @@ asm-keccak = ["alloy-primitives/asm-keccak"] arbitrary = [ "reth-primitives-traits/arbitrary", "revm-primitives/arbitrary", + "reth-chainspec/arbitrary", "reth-ethereum-forks/arbitrary", "nybbles/arbitrary", "alloy-trie/arbitrary", - "alloy-chains/arbitrary", "alloy-consensus/arbitrary", "alloy-eips/arbitrary", "dep:arbitrary", @@ -112,6 +110,7 @@ c-kzg = [ ] zstd-codec = ["dep:zstd"] optimism = [ + "reth-chainspec/optimism", "reth-codecs/optimism", "reth-ethereum-forks/optimism", "revm/optimism", diff --git a/crates/primitives/src/basefee.rs b/crates/primitives/src/basefee.rs index 0bafb9c89773..aa52b02a035e 100644 --- a/crates/primitives/src/basefee.rs +++ b/crates/primitives/src/basefee.rs @@ -3,144 +3,3 @@ // re-export #[doc(inline)] pub use alloy_eips::eip1559::calc_next_block_base_fee; - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(feature = "optimism")] - use crate::chain::{ - BASE_SEPOLIA_BASE_FEE_PARAMS, OP_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, - }; - - #[test] - fn calculate_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1125000000, 1083333333, 1053571428, 1179939062, 1116028649, 918084097, 1063811730, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - crate::BaseFeeParams::ethereum(), - ) as u64 - ); - } - } - - #[cfg(feature = "optimism")] - #[test] - fn calculate_optimism_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - OP_BASE_FEE_PARAMS, - ) as u64 - ); - } - } - - #[cfg(feature = "optimism")] - #[test] - fn calculate_optimism_sepolia_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - OP_SEPOLIA_BASE_FEE_PARAMS, - ) as u64 - ); - } - } - - #[cfg(feature = "optimism")] - #[test] - fn calculate_base_sepolia_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - BASE_SEPOLIA_BASE_FEE_PARAMS, - ) as u64 - ); - } - } -} diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs index 41a26d609e08..d03303edc328 100644 --- a/crates/primitives/src/constants/mod.rs +++ b/crates/primitives/src/constants/mod.rs @@ -1,70 +1,13 @@ //! Ethereum protocol-related constants -use crate::{ - chain::DepositContract, - revm_primitives::{address, b256}, -}; - pub use reth_primitives_traits::constants::*; -#[cfg(feature = "optimism")] -use crate::chain::BaseFeeParams; - /// Gas units, for example [`GIGAGAS`](gas_units::GIGAGAS). pub mod gas_units; /// [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#parameters) constants. pub mod eip4844; -/// Deposit contract address: `0x00000000219ab540356cbb839cbe05303d7705fa` -pub const MAINNET_DEPOSIT_CONTRACT: DepositContract = DepositContract::new( - address!("00000000219ab540356cbb839cbe05303d7705fa"), - 11052984, - b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), -); - -/// Get the base fee parameters for Base Sepolia. -#[cfg(feature = "optimism")] -pub const BASE_SEPOLIA_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { - max_change_denominator: OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, - elasticity_multiplier: BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, -}; - -/// Get the base fee parameters for Base Sepolia (post Canyon). -#[cfg(feature = "optimism")] -pub const BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { - max_change_denominator: OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, - elasticity_multiplier: BASE_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, -}; - -/// Get the base fee parameters for Optimism Sepolia. -#[cfg(feature = "optimism")] -pub const OP_SEPOLIA_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { - max_change_denominator: OP_SEPOLIA_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, - elasticity_multiplier: OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, -}; - -/// Get the base fee parameters for Optimism Sepolia (post Canyon). -#[cfg(feature = "optimism")] -pub const OP_SEPOLIA_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { - max_change_denominator: OP_SEPOLIA_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, - elasticity_multiplier: OP_SEPOLIA_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, -}; - -/// Get the base fee parameters for Optimism Mainnet. -#[cfg(feature = "optimism")] -pub const OP_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { - max_change_denominator: OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR, - elasticity_multiplier: OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, -}; - -/// Get the base fee parameters for Optimism Mainnet (post Canyon). -#[cfg(feature = "optimism")] -pub const OP_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { - max_change_denominator: OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, - elasticity_multiplier: OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, -}; - #[cfg(test)] mod tests { use super::*; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e73670826324..184ba3bf09ff 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -27,7 +27,6 @@ mod account; mod alloy_compat; pub mod basefee; mod block; -mod chain; #[cfg(feature = "zstd-codec")] mod compression; pub mod constants; @@ -37,7 +36,6 @@ pub mod genesis; pub mod header; mod integer_list; mod log; -mod net; pub mod proofs; mod receipt; mod request; @@ -54,31 +52,31 @@ pub use block::{ Block, BlockBody, BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, BlockWithSenders, ForkBlock, RpcBlockHash, SealedBlock, SealedBlockWithSenders, }; -pub use chain::{ - AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, Chain, ChainInfo, ChainKind, ChainSpec, - ChainSpecBuilder, DepositContract, DisplayHardforks, ForkBaseFeeParams, ForkCondition, - NamedChain, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, -}; #[cfg(feature = "zstd-codec")] pub use compression::*; pub use constants::{ DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH, - KECCAK_EMPTY, MAINNET_DEPOSIT_CONTRACT, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, + KECCAK_EMPTY, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, }; pub use error::{GotExpected, GotExpectedBoxed}; pub use genesis::{ChainConfig, Genesis, GenesisAccount}; pub use header::{Header, HeadersDirection, SealedHeader}; pub use integer_list::IntegerList; pub use log::{logs_bloom, Log}; -pub use net::{ - goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, sepolia_nodes, NodeRecord, - NodeRecordParseError, TrustedPeer, GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES, - SEPOLIA_BOOTNODES, -}; pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; pub use request::Requests; +pub use reth_chainspec::{ + net::{ + goerli_nodes, holesky_nodes, mainnet_nodes, parse_nodes, sepolia_nodes, NodeRecord, + NodeRecordParseError, TrustedPeer, GOERLI_BOOTNODES, HOLESKY_BOOTNODES, MAINNET_BOOTNODES, + SEPOLIA_BOOTNODES, + }, + AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, Chain, ChainInfo, ChainKind, ChainSpec, + ChainSpecBuilder, DepositContract, DisplayHardforks, ForkBaseFeeParams, ForkCondition, + NamedChain, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, +}; pub use static_file::StaticFileSegment; pub use storage::StorageEntry; @@ -140,13 +138,13 @@ pub use c_kzg as kzg; /// Optimism specific re-exports #[cfg(feature = "optimism")] mod optimism { - pub use crate::{ - chain::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}, + pub use crate::transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID}; + pub use reth_chainspec::{ net::{ base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes, OP_BOOTNODES, OP_TESTNET_BOOTNODES, }, - transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID}, + BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA, }; } From 01d81b4dccb6231ef65b121934540b47f9721c1f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 17 Jun 2024 15:38:39 +0200 Subject: [PATCH 085/405] feat: move builtpayload to payload types (#8858) --- crates/e2e-test-utils/src/node.rs | 12 ++++++------ crates/engine-primitives/src/lib.rs | 18 +++++++----------- crates/ethereum/engine-primitives/src/lib.rs | 2 +- crates/optimism/node/src/engine.rs | 2 +- crates/payload/primitives/src/lib.rs | 7 +++++-- examples/custom-engine-types/src/main.rs | 2 +- 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index ed2f22bf0102..524a5d5562be 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -70,13 +70,13 @@ where + Copy, ) -> eyre::Result< Vec<( - ::BuiltPayload, + ::BuiltPayload, ::PayloadBuilderAttributes, )>, > where ::ExecutionPayloadV3: - From<::BuiltPayload> + PayloadEnvelopeExt, + From<::BuiltPayload> + PayloadEnvelopeExt, { let mut chain = Vec::with_capacity(length as usize); for i in 0..length { @@ -99,12 +99,12 @@ where &mut self, attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, ) -> eyre::Result<( - <::Engine as EngineTypes>::BuiltPayload, + <::Engine as PayloadTypes>::BuiltPayload, <::Engine as PayloadTypes>::PayloadBuilderAttributes, )> where ::ExecutionPayloadV3: - From<::BuiltPayload> + PayloadEnvelopeExt, + From<::BuiltPayload> + PayloadEnvelopeExt, { // trigger new payload building draining the pool let eth_attr = self.payload.new_payload(attributes_generator).await.unwrap(); @@ -124,12 +124,12 @@ where versioned_hashes: Vec, attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, ) -> eyre::Result<( - ::BuiltPayload, + ::BuiltPayload, <::Engine as PayloadTypes>::PayloadBuilderAttributes, )> where ::ExecutionPayloadV3: - From<::BuiltPayload> + PayloadEnvelopeExt, + From<::BuiltPayload> + PayloadEnvelopeExt, { let (payload, eth_attr) = self.new_payload(attributes_generator).await?; diff --git a/crates/engine-primitives/src/lib.rs b/crates/engine-primitives/src/lib.rs index 819584fc3744..62916fe4f506 100644 --- a/crates/engine-primitives/src/lib.rs +++ b/crates/engine-primitives/src/lib.rs @@ -8,7 +8,6 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use core::fmt; pub use reth_payload_primitives::{ BuiltPayload, EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes, PayloadTypes, @@ -21,17 +20,14 @@ use serde::{de::DeserializeOwned, ser::Serialize}; /// This includes the execution payload types and payload attributes that are used to trigger a /// payload job. Hence this trait is also [`PayloadTypes`]. pub trait EngineTypes: - PayloadTypes + DeserializeOwned + Serialize + fmt::Debug + Unpin + Send + Sync + Clone + PayloadTypes< + BuiltPayload: TryInto + + TryInto + + TryInto + + TryInto, + > + DeserializeOwned + + Serialize { - /// The built payload type. - type BuiltPayload: BuiltPayload - + Clone - + Unpin - + TryInto - + TryInto - + TryInto - + TryInto; - /// Execution Payload V1 type. type ExecutionPayloadV1: DeserializeOwned + Serialize + Clone + Unpin + Send + Sync + 'static; /// Execution Payload V2 type. diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index e3b22a650d26..50b66ad02014 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -31,12 +31,12 @@ use reth_rpc_types::{ pub struct EthEngineTypes; impl PayloadTypes for EthEngineTypes { + type BuiltPayload = EthBuiltPayload; type PayloadAttributes = EthPayloadAttributes; type PayloadBuilderAttributes = EthPayloadBuilderAttributes; } impl EngineTypes for EthEngineTypes { - type BuiltPayload = EthBuiltPayload; type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadV3 = ExecutionPayloadEnvelopeV3; diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index 51dc7a5b3e49..16f924b8c051 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -22,12 +22,12 @@ use reth_rpc_types::{ pub struct OptimismEngineTypes; impl PayloadTypes for OptimismEngineTypes { + type BuiltPayload = OptimismBuiltPayload; type PayloadAttributes = OptimismPayloadAttributes; type PayloadBuilderAttributes = OptimismPayloadBuilderAttributes; } impl EngineTypes for OptimismEngineTypes { - type BuiltPayload = OptimismBuiltPayload; type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadV3 = OptimismExecutionPayloadEnvelopeV3; diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index 6bc8b1b11884..89f1d08b159a 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -9,6 +9,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod error; + pub use error::{EngineObjectValidationError, PayloadBuilderError, VersionSpecificValidationError}; /// Contains traits to abstract over payload attributes types and default implementations of the @@ -20,10 +21,12 @@ mod payload; pub use payload::PayloadOrAttributes; use reth_primitives::ChainSpec; -use std::fmt::Debug; /// The types that are used by the engine API. -pub trait PayloadTypes: Send + Sync + Unpin { +pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone { + /// The built payload type. + type BuiltPayload: BuiltPayload + Clone + Unpin; + /// The RPC payload attributes type the CL node emits via the engine API. type PayloadAttributes: PayloadAttributes + Unpin; diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index 21db41204017..7db6e0da4fca 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -164,12 +164,12 @@ impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes { pub struct CustomEngineTypes; impl PayloadTypes for CustomEngineTypes { + type BuiltPayload = EthBuiltPayload; type PayloadAttributes = CustomPayloadAttributes; type PayloadBuilderAttributes = CustomPayloadBuilderAttributes; } impl EngineTypes for CustomEngineTypes { - type BuiltPayload = EthBuiltPayload; type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadV3 = ExecutionPayloadEnvelopeV3; From 2a5c93fab39cd827357770d9a6386c3047bcc3b6 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:09:09 +0200 Subject: [PATCH 086/405] chore: use `reth_chainspec` where possible (#8891) --- Cargo.lock | 54 ++++++++++++++++++- bin/reth/Cargo.toml | 1 + bin/reth/src/cli/mod.rs | 2 +- bin/reth/src/commands/common.rs | 3 +- bin/reth/src/commands/dump_genesis.rs | 4 +- bin/reth/src/commands/import.rs | 2 +- bin/reth/src/commands/node/mod.rs | 6 +-- bin/reth/src/commands/p2p/mod.rs | 3 +- bin/reth/src/utils.rs | 2 +- crates/blockchain-tree/Cargo.toml | 1 + crates/blockchain-tree/src/blockchain_tree.rs | 6 +-- crates/chainspec/src/lib.rs | 3 ++ crates/consensus/auto-seal/Cargo.toml | 1 + crates/consensus/auto-seal/src/lib.rs | 6 +-- crates/consensus/auto-seal/src/task.rs | 3 +- crates/consensus/beacon/Cargo.toml | 1 + crates/consensus/beacon/src/engine/mod.rs | 2 +- crates/consensus/beacon/src/engine/sync.rs | 9 ++-- .../consensus/beacon/src/engine/test_utils.rs | 3 +- crates/consensus/common/Cargo.toml | 1 + crates/consensus/common/src/calc.rs | 8 +-- crates/consensus/common/src/validation.rs | 8 +-- crates/e2e-test-utils/Cargo.toml | 1 + crates/e2e-test-utils/src/lib.rs | 2 +- crates/e2e-test-utils/src/network.rs | 2 +- crates/engine-primitives/Cargo.toml | 2 +- crates/engine-primitives/src/lib.rs | 2 +- crates/ethereum/consensus/Cargo.toml | 1 + crates/ethereum/consensus/src/lib.rs | 9 ++-- crates/ethereum/consensus/src/validation.rs | 4 +- crates/ethereum/engine-primitives/Cargo.toml | 1 + crates/ethereum/engine-primitives/src/lib.rs | 2 +- .../ethereum/engine-primitives/src/payload.rs | 3 +- crates/ethereum/evm/Cargo.toml | 1 + crates/ethereum/evm/src/eip6110.rs | 6 ++- crates/ethereum/evm/src/execute.rs | 8 +-- crates/ethereum/evm/src/lib.rs | 3 +- crates/ethereum/node/Cargo.toml | 1 + crates/ethereum/node/tests/e2e/blobs.rs | 3 +- crates/ethereum/node/tests/e2e/dev.rs | 3 +- crates/ethereum/node/tests/e2e/eth.rs | 3 +- crates/ethereum/node/tests/e2e/p2p.rs | 2 +- crates/evm/Cargo.toml | 1 + crates/evm/execution-types/Cargo.toml | 3 +- .../execution-types/src/execution_outcome.rs | 2 +- crates/evm/src/lib.rs | 5 +- crates/exex/test-utils/Cargo.toml | 1 + crates/exex/test-utils/src/lib.rs | 3 +- crates/net/discv4/Cargo.toml | 1 + crates/net/discv4/src/lib.rs | 5 +- crates/net/discv5/Cargo.toml | 1 + crates/net/discv5/src/config.rs | 3 +- crates/net/discv5/src/enr.rs | 3 +- crates/net/discv5/src/lib.rs | 6 +-- crates/net/discv5/src/network_stack_id.rs | 2 +- crates/net/dns/Cargo.toml | 2 +- crates/net/dns/src/lib.rs | 2 +- crates/net/downloaders/Cargo.toml | 1 + crates/net/downloaders/src/bodies/bodies.rs | 3 +- crates/net/eth-wire-types/Cargo.toml | 1 + crates/net/eth-wire-types/src/status.rs | 14 +++-- crates/net/eth-wire/Cargo.toml | 1 + crates/net/eth-wire/src/errors/eth.rs | 3 +- crates/net/eth-wire/src/ethstream.rs | 3 +- crates/net/eth-wire/src/test_utils.rs | 3 +- crates/net/network/Cargo.toml | 1 + crates/net/network/src/config.rs | 13 +++-- crates/net/network/src/discovery.rs | 4 +- crates/net/network/src/lib.rs | 4 +- crates/net/network/src/manager.rs | 6 +-- crates/net/network/src/network.rs | 4 +- crates/net/network/src/peers/manager.rs | 4 +- crates/net/network/src/session/active.rs | 3 +- crates/net/network/src/test_utils/testnet.rs | 2 +- crates/net/network/tests/it/connect.rs | 4 +- crates/node-core/Cargo.toml | 3 +- crates/node-core/src/args/datadir_args.rs | 2 +- crates/node-core/src/args/network.rs | 3 +- crates/node-core/src/args/pruning.rs | 2 +- crates/node-core/src/args/utils.rs | 14 ++--- crates/node-core/src/dirs.rs | 2 +- crates/node-core/src/node_config.rs | 3 +- crates/node-core/src/utils.rs | 3 +- crates/node/builder/Cargo.toml | 1 + crates/node/builder/src/builder/mod.rs | 3 +- crates/node/builder/src/launch/common.rs | 3 +- crates/node/builder/src/node.rs | 2 +- crates/optimism/consensus/Cargo.toml | 1 + crates/optimism/consensus/src/lib.rs | 3 +- crates/optimism/consensus/src/validation.rs | 3 +- crates/optimism/evm/Cargo.toml | 1 + crates/optimism/evm/src/execute.rs | 9 ++-- crates/optimism/evm/src/l1.rs | 3 +- crates/optimism/evm/src/lib.rs | 3 +- crates/optimism/node/Cargo.toml | 2 + crates/optimism/node/src/engine.rs | 2 +- crates/optimism/node/src/txpool.rs | 6 ++- crates/optimism/node/tests/e2e/utils.rs | 3 +- crates/optimism/payload/Cargo.toml | 2 + crates/optimism/payload/src/builder.rs | 5 +- crates/optimism/payload/src/payload.rs | 5 +- crates/payload/basic/Cargo.toml | 1 + crates/payload/basic/src/lib.rs | 3 +- crates/payload/primitives/Cargo.toml | 1 + crates/payload/primitives/src/lib.rs | 2 +- crates/payload/primitives/src/traits.rs | 3 +- crates/payload/validator/Cargo.toml | 1 + crates/payload/validator/src/lib.rs | 3 +- crates/primitives/src/lib.rs | 10 ---- crates/primitives/src/proofs.rs | 15 +++--- crates/primitives/src/receipt.rs | 2 +- crates/primitives/src/revm/config.rs | 8 +-- crates/primitives/src/revm/env.rs | 6 +-- crates/prune/prune/Cargo.toml | 1 + crates/prune/prune/src/builder.rs | 2 +- crates/prune/prune/src/pruner.rs | 2 +- crates/revm/Cargo.toml | 1 + crates/revm/src/state_change.rs | 3 +- crates/rpc/rpc-api/src/admin.rs | 3 +- crates/rpc/rpc-builder/Cargo.toml | 1 + crates/rpc/rpc-builder/tests/it/http.rs | 4 +- crates/rpc/rpc-builder/tests/it/utils.rs | 2 +- crates/rpc/rpc-engine-api/Cargo.toml | 1 + crates/rpc/rpc-engine-api/src/engine_api.rs | 6 ++- crates/rpc/rpc/Cargo.toml | 1 + crates/rpc/rpc/src/admin.rs | 5 +- crates/rpc/rpc/src/eth/api/fee_history.rs | 3 +- crates/rpc/rpc/src/eth/api/mod.rs | 4 +- crates/rpc/rpc/src/eth/api/pending_block.rs | 5 +- crates/rpc/rpc/src/eth/api/server.rs | 5 +- crates/rpc/rpc/src/eth/filter.rs | 3 +- crates/rpc/rpc/src/eth/logs_utils.rs | 3 +- crates/stages/stages/Cargo.toml | 3 ++ crates/stages/stages/src/lib.rs | 3 +- crates/stages/stages/src/sets.rs | 2 +- crates/stages/stages/src/stages/execution.rs | 5 +- crates/stages/stages/src/stages/mod.rs | 5 +- .../stages/stages/src/test_utils/test_db.rs | 3 +- crates/storage/db-common/Cargo.toml | 1 + crates/storage/db-common/src/init.rs | 9 ++-- crates/storage/provider/Cargo.toml | 1 + .../provider/src/providers/chain_info.rs | 3 +- .../provider/src/providers/database/mod.rs | 14 ++--- .../src/providers/database/provider.rs | 9 ++-- crates/storage/provider/src/providers/mod.rs | 7 +-- .../provider/src/providers/static_file/jar.rs | 3 +- .../src/providers/static_file/manager.rs | 5 +- .../storage/provider/src/test_utils/mock.rs | 10 ++-- crates/storage/provider/src/test_utils/mod.rs | 2 +- .../storage/provider/src/test_utils/noop.rs | 7 +-- crates/storage/provider/src/traits/spec.rs | 2 +- crates/storage/storage-api/Cargo.toml | 1 + crates/storage/storage-api/src/block_id.rs | 3 +- crates/transaction-pool/Cargo.toml | 1 + crates/transaction-pool/src/lib.rs | 6 +-- crates/transaction-pool/src/maintain.rs | 3 +- crates/transaction-pool/src/test_utils/gen.rs | 3 +- crates/transaction-pool/src/validate/eth.rs | 10 ++-- crates/transaction-pool/src/validate/task.rs | 3 +- crates/trie/trie/Cargo.toml | 1 + crates/trie/trie/src/proof.rs | 3 +- examples/bsc-p2p/Cargo.toml | 1 + examples/bsc-p2p/src/chainspec.rs | 7 ++- examples/custom-dev-node/Cargo.toml | 1 + examples/custom-dev-node/src/main.rs | 3 +- examples/custom-engine-types/Cargo.toml | 1 + examples/custom-engine-types/src/main.rs | 3 +- examples/custom-evm/Cargo.toml | 1 + examples/custom-evm/src/main.rs | 3 +- examples/custom-payload-builder/Cargo.toml | 1 + .../custom-payload-builder/src/generator.rs | 3 +- examples/db-access/Cargo.toml | 1 + examples/db-access/src/main.rs | 3 +- examples/exex/rollup/Cargo.toml | 1 + examples/exex/rollup/src/main.rs | 6 +-- examples/manual-p2p/Cargo.toml | 1 + examples/manual-p2p/src/main.rs | 7 ++- examples/polygon-p2p/Cargo.toml | 1 + examples/polygon-p2p/src/chain_cfg.rs | 8 +-- examples/rpc-db/Cargo.toml | 1 + examples/rpc-db/src/main.rs | 2 +- testing/ef-tests/Cargo.toml | 1 + testing/ef-tests/src/models.rs | 6 +-- 183 files changed, 430 insertions(+), 261 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a240975876a3..57902ffc7466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1541,6 +1541,7 @@ dependencies = [ name = "bsc-p2p" version = "0.0.0" dependencies = [ + "reth-chainspec", "reth-discv4", "reth-network", "reth-network-api", @@ -2294,6 +2295,7 @@ dependencies = [ "eyre", "futures-util", "reth", + "reth-chainspec", "reth-node-core", "reth-node-ethereum", "reth-primitives", @@ -2308,6 +2310,7 @@ dependencies = [ "eyre", "reth", "reth-basic-payload-builder", + "reth-chainspec", "reth-ethereum-payload-builder", "reth-node-api", "reth-node-core", @@ -2327,6 +2330,7 @@ version = "0.0.0" dependencies = [ "eyre", "reth", + "reth-chainspec", "reth-node-api", "reth-node-core", "reth-node-ethereum", @@ -2365,6 +2369,7 @@ dependencies = [ "futures-util", "reth", "reth-basic-payload-builder", + "reth-chainspec", "reth-ethereum-payload-builder", "reth-node-api", "reth-node-ethereum", @@ -2452,6 +2457,7 @@ name = "db-access" version = "0.0.0" dependencies = [ "eyre", + "reth-chainspec", "reth-db", "reth-primitives", "reth-provider", @@ -2727,6 +2733,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "rayon", + "reth-chainspec", "reth-db", "reth-db-api", "reth-evm-ethereum", @@ -2963,6 +2970,7 @@ dependencies = [ "foundry-blob-explorers", "once_cell", "reth", + "reth-chainspec", "reth-execution-errors", "reth-exex", "reth-node-api", @@ -4779,6 +4787,7 @@ dependencies = [ "eyre", "futures", "once_cell", + "reth-chainspec", "reth-discv4", "reth-ecies", "reth-eth-wire", @@ -5679,6 +5688,7 @@ checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" name = "polygon-p2p" version = "0.0.0" dependencies = [ + "reth-chainspec", "reth-discv4", "reth-network", "reth-primitives", @@ -6265,6 +6275,7 @@ dependencies = [ "reth-basic-payload-builder", "reth-beacon-consensus", "reth-blockchain-tree", + "reth-chainspec", "reth-cli-runner", "reth-config", "reth-consensus", @@ -6327,6 +6338,7 @@ version = "1.0.0-rc.1" dependencies = [ "futures-util", "reth-beacon-consensus", + "reth-chainspec", "reth-consensus", "reth-engine-primitives", "reth-evm", @@ -6353,6 +6365,7 @@ dependencies = [ "futures-core", "futures-util", "metrics", + "reth-chainspec", "reth-metrics", "reth-payload-builder", "reth-payload-primitives", @@ -6376,6 +6389,7 @@ dependencies = [ "metrics", "reth-blockchain-tree", "reth-blockchain-tree-api", + "reth-chainspec", "reth-config", "reth-consensus", "reth-db", @@ -6466,6 +6480,7 @@ dependencies = [ "metrics", "parking_lot 0.12.3", "reth-blockchain-tree-api", + "reth-chainspec", "reth-consensus", "reth-db", "reth-db-api", @@ -6588,6 +6603,7 @@ version = "1.0.0-rc.1" dependencies = [ "mockall", "rand 0.8.5", + "reth-chainspec", "reth-consensus", "reth-primitives", "reth-storage-api", @@ -6687,6 +6703,7 @@ name = "reth-db-common" version = "1.0.0-rc.1" dependencies = [ "eyre", + "reth-chainspec", "reth-codecs", "reth-config", "reth-db", @@ -6714,6 +6731,7 @@ dependencies = [ "generic-array", "parking_lot 0.12.3", "rand 0.8.5", + "reth-chainspec", "reth-ethereum-forks", "reth-net-common", "reth-net-nat", @@ -6743,6 +6761,7 @@ dependencies = [ "metrics", "multiaddr", "rand 0.8.5", + "reth-chainspec", "reth-metrics", "reth-network-peers", "reth-primitives", @@ -6765,10 +6784,10 @@ dependencies = [ "linked_hash_set", "parking_lot 0.12.3", "rand 0.8.5", + "reth-chainspec", "reth-ethereum-forks", "reth-net-common", "reth-network-peers", - "reth-primitives", "reth-tracing", "schnellru", "secp256k1", @@ -6794,6 +6813,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "rayon", + "reth-chainspec", "reth-config", "reth-consensus", "reth-db", @@ -6827,6 +6847,7 @@ dependencies = [ "futures-util", "jsonrpsee", "reth", + "reth-chainspec", "reth-db", "reth-node-builder", "reth-payload-builder", @@ -6877,8 +6898,8 @@ dependencies = [ name = "reth-engine-primitives" version = "1.0.0-rc.1" dependencies = [ + "reth-chainspec", "reth-payload-primitives", - "reth-primitives", "serde", ] @@ -6908,6 +6929,7 @@ dependencies = [ "proptest", "proptest-derive", "rand 0.8.5", + "reth-chainspec", "reth-codecs", "reth-discv4", "reth-ecies", @@ -6940,6 +6962,7 @@ dependencies = [ "proptest", "proptest-derive", "rand 0.8.5", + "reth-chainspec", "reth-codecs-derive", "reth-net-common", "reth-primitives", @@ -6954,6 +6977,7 @@ dependencies = [ name = "reth-ethereum-consensus" version = "1.0.0-rc.1" dependencies = [ + "reth-chainspec", "reth-consensus", "reth-consensus-common", "reth-primitives", @@ -6965,6 +6989,7 @@ name = "reth-ethereum-engine-primitives" version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", + "reth-chainspec", "reth-engine-primitives", "reth-payload-primitives", "reth-primitives", @@ -7025,6 +7050,7 @@ dependencies = [ "auto_impl", "futures-util", "parking_lot 0.12.3", + "reth-chainspec", "reth-execution-errors", "reth-execution-types", "reth-primitives", @@ -7040,6 +7066,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-eips 0.1.0", "alloy-sol-types", + "reth-chainspec", "reth-ethereum-consensus", "reth-evm", "reth-execution-types", @@ -7056,6 +7083,7 @@ dependencies = [ name = "reth-evm-optimism" version = "1.0.0-rc.1" dependencies = [ + "reth-chainspec", "reth-consensus-common", "reth-evm", "reth-execution-errors", @@ -7089,6 +7117,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-eips 0.1.0", "alloy-primitives", + "reth-chainspec", "reth-execution-errors", "reth-primitives", "reth-trie", @@ -7124,6 +7153,7 @@ dependencies = [ "futures-util", "rand 0.8.5", "reth-blockchain-tree", + "reth-chainspec", "reth-config", "reth-consensus", "reth-db", @@ -7276,6 +7306,7 @@ dependencies = [ "pin-project", "pprof", "rand 0.8.5", + "reth-chainspec", "reth-consensus", "reth-discv4", "reth-discv5", @@ -7408,6 +7439,7 @@ dependencies = [ "reth-auto-seal-consensus", "reth-beacon-consensus", "reth-blockchain-tree", + "reth-chainspec", "reth-config", "reth-consensus", "reth-consensus-debug-client", @@ -7466,6 +7498,7 @@ dependencies = [ "proptest", "rand 0.8.5", "reth-beacon-consensus", + "reth-chainspec", "reth-config", "reth-consensus-common", "reth-db", @@ -7516,6 +7549,7 @@ dependencies = [ "reth-auto-seal-consensus", "reth-basic-payload-builder", "reth-beacon-consensus", + "reth-chainspec", "reth-consensus", "reth-db", "reth-e2e-test-utils", @@ -7571,6 +7605,7 @@ dependencies = [ "reth", "reth-basic-payload-builder", "reth-beacon-consensus", + "reth-chainspec", "reth-db", "reth-discv5", "reth-e2e-test-utils", @@ -7602,6 +7637,7 @@ dependencies = [ name = "reth-optimism-consensus" version = "1.0.0-rc.1" dependencies = [ + "reth-chainspec", "reth-consensus", "reth-consensus-common", "reth-primitives", @@ -7614,6 +7650,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-rlp", "reth-basic-payload-builder", + "reth-chainspec", "reth-evm", "reth-evm-optimism", "reth-payload-builder", @@ -7661,6 +7698,7 @@ dependencies = [ name = "reth-payload-primitives" version = "1.0.0-rc.1" dependencies = [ + "reth-chainspec", "reth-errors", "reth-primitives", "reth-rpc-types", @@ -7674,6 +7712,7 @@ dependencies = [ name = "reth-payload-validator" version = "1.0.0-rc.1" dependencies = [ + "reth-chainspec", "reth-primitives", "reth-rpc-types", "reth-rpc-types-compat", @@ -7765,6 +7804,7 @@ dependencies = [ "rand 0.8.5", "rayon", "reth-blockchain-tree-api", + "reth-chainspec", "reth-codecs", "reth-db", "reth-db-api", @@ -7799,6 +7839,7 @@ dependencies = [ "itertools 0.12.1", "metrics", "rayon", + "reth-chainspec", "reth-config", "reth-db", "reth-db-api", @@ -7843,6 +7884,7 @@ version = "1.0.0-rc.1" dependencies = [ "alloy-eips 0.1.0", "alloy-rlp", + "reth-chainspec", "reth-consensus-common", "reth-execution-errors", "reth-primitives", @@ -7876,6 +7918,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "rand 0.8.5", + "reth-chainspec", "reth-consensus-common", "reth-errors", "reth-evm", @@ -7949,6 +7992,7 @@ dependencies = [ "metrics", "pin-project", "reth-beacon-consensus", + "reth-chainspec", "reth-engine-primitives", "reth-ethereum-engine-primitives", "reth-evm", @@ -7991,6 +8035,7 @@ dependencies = [ "jsonrpsee-types", "metrics", "reth-beacon-consensus", + "reth-chainspec", "reth-engine-primitives", "reth-ethereum-engine-primitives", "reth-evm", @@ -8088,6 +8133,7 @@ dependencies = [ "pprof", "rand 0.8.5", "rayon", + "reth-chainspec", "reth-codecs", "reth-config", "reth-consensus", @@ -8198,6 +8244,7 @@ name = "reth-storage-api" version = "1.0.0-rc.1" dependencies = [ "auto_impl", + "reth-chainspec", "reth-db-api", "reth-execution-types", "reth-primitives", @@ -8284,6 +8331,7 @@ dependencies = [ "pprof", "proptest", "rand 0.8.5", + "reth-chainspec", "reth-eth-wire-types", "reth-fs-util", "reth-metrics", @@ -8315,6 +8363,7 @@ dependencies = [ "once_cell", "proptest", "rayon", + "reth-chainspec", "reth-db", "reth-db-api", "reth-execution-errors", @@ -8565,6 +8614,7 @@ dependencies = [ "futures", "jsonrpsee", "reth", + "reth-chainspec", "reth-db", "reth-db-api", "reth-node-ethereum", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index f6ec9bd7d440..5e0e7d2de091 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-config.workspace = true reth-primitives.workspace = true reth-fs-util.workspace = true diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index 60792cb76b14..8750b84f543a 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -13,10 +13,10 @@ use crate::{ version::{LONG_VERSION, SHORT_VERSION}, }; use clap::{value_parser, Parser, Subcommand}; +use reth_chainspec::ChainSpec; use reth_cli_runner::CliRunner; use reth_db::DatabaseEnv; use reth_node_builder::{NodeBuilder, WithLaunchContext}; -use reth_primitives::ChainSpec; use reth_tracing::FileWorkerGuard; use std::{ffi::OsString, fmt, future::Future, sync::Arc}; use tracing::info; diff --git a/bin/reth/src/commands/common.rs b/bin/reth/src/commands/common.rs index ed6a92cdbc68..329047cdddae 100644 --- a/bin/reth/src/commands/common.rs +++ b/bin/reth/src/commands/common.rs @@ -2,6 +2,7 @@ use clap::Parser; use reth_beacon_consensus::EthBeaconConsensus; +use reth_chainspec::ChainSpec; use reth_config::{config::EtlConfig, Config}; use reth_db::{init_db, open_db_read_only, DatabaseEnv}; use reth_db_common::init::init_genesis; @@ -14,7 +15,7 @@ use reth_node_core::{ }, dirs::{ChainPath, DataDirPath}, }; -use reth_primitives::{ChainSpec, B256}; +use reth_primitives::B256; use reth_provider::{providers::StaticFileProvider, ProviderFactory, StaticFileProviderFactory}; use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget}; use reth_static_file::StaticFileProducer; diff --git a/bin/reth/src/commands/dump_genesis.rs b/bin/reth/src/commands/dump_genesis.rs index 843d3d18a64b..f4208584fbda 100644 --- a/bin/reth/src/commands/dump_genesis.rs +++ b/bin/reth/src/commands/dump_genesis.rs @@ -1,7 +1,7 @@ //! Command that dumps genesis block JSON configuration to stdout use crate::args::utils::{chain_help, genesis_value_parser, SUPPORTED_CHAINS}; use clap::Parser; -use reth_primitives::ChainSpec; +use reth_chainspec::ChainSpec; use std::sync::Arc; /// Dumps genesis block JSON configuration to stdout @@ -39,7 +39,7 @@ mod tests { DumpGenesisCommand::parse_from(["reth", "--chain", chain]); assert_eq!( Ok(args.chain.chain), - chain.parse::(), + chain.parse::(), "failed to parse chain {chain}" ); } diff --git a/bin/reth/src/commands/import.rs b/bin/reth/src/commands/import.rs index baf194714573..25d1864a2434 100644 --- a/bin/reth/src/commands/import.rs +++ b/bin/reth/src/commands/import.rs @@ -237,7 +237,7 @@ mod tests { let args: ImportCommand = ImportCommand::parse_from(["reth", "--chain", chain, "."]); assert_eq!( Ok(args.env.chain.chain), - chain.parse::(), + chain.parse::(), "failed to parse chain {chain}" ); } diff --git a/bin/reth/src/commands/node/mod.rs b/bin/reth/src/commands/node/mod.rs index f11cb24ce682..606e0de42bb2 100644 --- a/bin/reth/src/commands/node/mod.rs +++ b/bin/reth/src/commands/node/mod.rs @@ -6,11 +6,11 @@ use crate::args::{ RpcServerArgs, TxPoolArgs, }; use clap::{value_parser, Args, Parser}; +use reth_chainspec::ChainSpec; use reth_cli_runner::CliContext; use reth_db::{init_db, DatabaseEnv}; use reth_node_builder::{NodeBuilder, WithLaunchContext}; use reth_node_core::{node_config::NodeConfig, version}; -use reth_primitives::ChainSpec; use std::{ffi::OsString, fmt, future::Future, net::SocketAddr, path::PathBuf, sync::Arc}; /// Start the node @@ -213,7 +213,7 @@ mod tests { fn parse_common_node_command_chain_args() { for chain in SUPPORTED_CHAINS { let args: NodeCommand = NodeCommand::::parse_from(["reth", "--chain", chain]); - assert_eq!(args.chain.chain, chain.parse::().unwrap()); + assert_eq!(args.chain.chain, chain.parse::().unwrap()); } } @@ -305,7 +305,7 @@ mod tests { #[cfg(not(feature = "optimism"))] // dev mode not yet supported in op-reth fn parse_dev() { let cmd = NodeCommand::::parse_from(["reth", "--dev"]); - let chain = reth_primitives::DEV.clone(); + let chain = reth_chainspec::DEV.clone(); assert_eq!(cmd.chain.chain, chain.chain); assert_eq!(cmd.chain.genesis_hash, chain.genesis_hash); assert_eq!( diff --git a/bin/reth/src/commands/p2p/mod.rs b/bin/reth/src/commands/p2p/mod.rs index b57a2f07aaba..2de5a9aa5cff 100644 --- a/bin/reth/src/commands/p2p/mod.rs +++ b/bin/reth/src/commands/p2p/mod.rs @@ -11,12 +11,13 @@ use crate::{ use backon::{ConstantBuilder, Retryable}; use clap::{Parser, Subcommand}; use discv5::ListenConfig; +use reth_chainspec::ChainSpec; use reth_config::Config; use reth_db::create_db; use reth_network::NetworkConfigBuilder; use reth_network_p2p::bodies::client::BodiesClient; use reth_node_core::args::DatadirArgs; -use reth_primitives::{BlockHashOrNumber, ChainSpec}; +use reth_primitives::BlockHashOrNumber; use reth_provider::{providers::StaticFileProvider, ProviderFactory}; use std::{ net::{IpAddr, SocketAddrV4, SocketAddrV6}, diff --git a/bin/reth/src/utils.rs b/bin/reth/src/utils.rs index ca25506a9124..1dd4f6893c1f 100644 --- a/bin/reth/src/utils.rs +++ b/bin/reth/src/utils.rs @@ -2,6 +2,7 @@ use boyer_moore_magiclen::BMByte; use eyre::Result; +use reth_chainspec::ChainSpec; use reth_db::{RawTable, TableRawRow}; use reth_db_api::{ cursor::{DbCursorRO, DbDupCursorRO}, @@ -11,7 +12,6 @@ use reth_db_api::{ DatabaseError, }; use reth_fs_util as fs; -use reth_primitives::ChainSpec; use reth_provider::{ChainSpecProvider, ProviderFactory}; use std::{path::Path, rc::Rc, sync::Arc}; use tracing::info; diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index 16f84c6599e4..033b20225997 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -42,6 +42,7 @@ aquamarine.workspace = true linked_hash_set.workspace = true [dev-dependencies] +reth-chainspec.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-primitives = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 5359212d1757..a3a16e954afe 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1371,6 +1371,7 @@ mod tests { use super::*; use assert_matches::assert_matches; use linked_hash_set::LinkedHashSet; + use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_consensus::test_utils::TestConsensus; use reth_db::{tables, test_utils::TempDatabase, DatabaseEnv}; use reth_db_api::transaction::DbTxMut; @@ -1385,9 +1386,8 @@ mod tests { keccak256, proofs::calculate_transaction_root, revm_primitives::AccountInfo, - Account, Address, ChainSpecBuilder, Genesis, GenesisAccount, Header, Signature, - Transaction, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, Withdrawals, B256, - MAINNET, + Account, Address, Genesis, GenesisAccount, Header, Signature, Transaction, + TransactionSigned, TransactionSignedEcRecovered, TxEip1559, Withdrawals, B256, }; use reth_provider::{ test_utils::{blocks::BlockchainTestData, create_test_provider_factory_with_chain_spec}, diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 61460aa30c28..6da5ab470c3a 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -32,6 +32,9 @@ mod spec; /// Chain specific constants pub(crate) mod constants; +/// Re-export for convenience +pub use reth_ethereum_forks::*; + #[cfg(test)] mod tests { use super::*; diff --git a/crates/consensus/auto-seal/Cargo.toml b/crates/consensus/auto-seal/Cargo.toml index 8aa6e1656d94..98fe370db087 100644 --- a/crates/consensus/auto-seal/Cargo.toml +++ b/crates/consensus/auto-seal/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-beacon-consensus.workspace = true reth-primitives.workspace = true reth-execution-errors.workspace = true diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index d95d96770ac0..8aaeca5e7ab8 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -16,15 +16,15 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use reth_beacon_consensus::BeaconEngineMessage; +use reth_chainspec::ChainSpec; use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_engine_primitives::EngineTypes; use reth_execution_errors::{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, Requests, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, - U256, + proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Header, + Requests, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, U256, }; use reth_provider::{BlockReaderIdExt, ExecutionOutcome, StateProviderFactory, StateRootProvider}; use reth_revm::database::StateProviderDatabase; diff --git a/crates/consensus/auto-seal/src/task.rs b/crates/consensus/auto-seal/src/task.rs index 5cdec4baa057..856d02631cb0 100644 --- a/crates/consensus/auto-seal/src/task.rs +++ b/crates/consensus/auto-seal/src/task.rs @@ -1,9 +1,10 @@ use crate::{mode::MiningMode, Storage}; use futures_util::{future::BoxFuture, FutureExt}; use reth_beacon_consensus::{BeaconEngineMessage, ForkchoiceStatus}; +use reth_chainspec::ChainSpec; use reth_engine_primitives::EngineTypes; use reth_evm::execute::BlockExecutorProvider; -use reth_primitives::{ChainSpec, IntoRecoveredTransaction}; +use reth_primitives::IntoRecoveredTransaction; use reth_provider::{CanonChainTracker, StateProviderFactory}; use reth_rpc_types::engine::ForkchoiceState; use reth_stages_api::PipelineEvent; diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index 3f617d1d0a3a..df88348411a5 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-ethereum-consensus.workspace = true reth-blockchain-tree-api.workspace = true reth-primitives.workspace = true diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index a0ac2dbea008..29c5c6a1c124 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1975,7 +1975,7 @@ mod tests { BeaconForkChoiceUpdateError, }; use assert_matches::assert_matches; - use reth_primitives::{ChainSpecBuilder, MAINNET}; + use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_provider::{BlockWriter, ProviderFactory}; use reth_rpc_types::engine::{ForkchoiceState, ForkchoiceUpdated, PayloadStatus}; use reth_rpc_types_compat::engine::payload::block_to_payload_v1; diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index 5ab3ab3dbd2f..76a6d0e81552 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -5,13 +5,14 @@ use crate::{ ConsensusEngineLiveSyncProgress, EthBeaconConsensus, }; use futures::FutureExt; +use reth_chainspec::ChainSpec; use reth_db_api::database::Database; use reth_network_p2p::{ bodies::client::BodiesClient, full_block::{FetchFullBlockFuture, FetchFullBlockRangeFuture, FullBlockClient}, headers::client::HeadersClient, }; -use reth_primitives::{BlockNumber, ChainSpec, SealedBlock, B256}; +use reth_primitives::{BlockNumber, SealedBlock, B256}; use reth_stages_api::{ControlFlow, Pipeline, PipelineError, PipelineTarget, PipelineWithResult}; use reth_tasks::TaskSpawner; use reth_tokio_util::EventSender; @@ -413,12 +414,10 @@ mod tests { use super::*; use assert_matches::assert_matches; use futures::poll; + use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_db::{mdbx::DatabaseEnv, test_utils::TempDatabase}; use reth_network_p2p::{either::Either, test_utils::TestFullBlockClient}; - use reth_primitives::{ - constants::ETHEREUM_BLOCK_GAS_LIMIT, BlockBody, ChainSpecBuilder, Header, SealedHeader, - MAINNET, - }; + use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, BlockBody, Header, SealedHeader}; use reth_provider::{ test_utils::create_test_provider_factory_with_chain_spec, ExecutionOutcome, }; diff --git a/crates/consensus/beacon/src/engine/test_utils.rs b/crates/consensus/beacon/src/engine/test_utils.rs index 8158a35f31ab..f58063d3ef58 100644 --- a/crates/consensus/beacon/src/engine/test_utils.rs +++ b/crates/consensus/beacon/src/engine/test_utils.rs @@ -6,6 +6,7 @@ use crate::{ use reth_blockchain_tree::{ config::BlockchainTreeConfig, externals::TreeExternals, BlockchainTree, ShareableBlockchainTree, }; +use reth_chainspec::ChainSpec; use reth_config::config::StageConfig; use reth_consensus::{test_utils::TestConsensus, Consensus}; use reth_db::{test_utils::TempDatabase, DatabaseEnv as DE}; @@ -22,7 +23,7 @@ use reth_network_p2p::{ test_utils::NoopFullBlockClient, }; use reth_payload_builder::test_utils::spawn_test_payload_service; -use reth_primitives::{BlockNumber, ChainSpec, B256}; +use reth_primitives::{BlockNumber, B256}; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, ExecutionOutcome, diff --git a/crates/consensus/common/Cargo.toml b/crates/consensus/common/Cargo.toml index fa2cac1acdf7..dca553deb4c9 100644 --- a/crates/consensus/common/Cargo.toml +++ b/crates/consensus/common/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-consensus.workspace = true diff --git a/crates/consensus/common/src/calc.rs b/crates/consensus/common/src/calc.rs index 318cbc0286e1..52206bbfd0ca 100644 --- a/crates/consensus/common/src/calc.rs +++ b/crates/consensus/common/src/calc.rs @@ -1,4 +1,5 @@ -use reth_primitives::{constants::ETH_TO_WEI, BlockNumber, Chain, ChainSpec, Hardfork, U256}; +use reth_chainspec::{Chain, ChainSpec, Hardfork}; +use reth_primitives::{constants::ETH_TO_WEI, BlockNumber, U256}; /// Calculates the base block reward. /// /// The base block reward is defined as: @@ -45,9 +46,10 @@ pub fn base_block_reward( /// # Examples /// /// ``` +/// # use reth_chainspec::MAINNET; /// # use reth_consensus_common::calc::{base_block_reward, block_reward}; /// # use reth_primitives::constants::ETH_TO_WEI; -/// # use reth_primitives::{MAINNET, U256}; +/// # use reth_primitives::U256; /// # /// // This is block 126 on mainnet. /// let block_number = 126; @@ -102,7 +104,7 @@ pub const fn ommer_reward( #[cfg(test)] mod tests { use super::*; - use reth_primitives::MAINNET; + use reth_chainspec::MAINNET; #[test] fn calc_base_block_reward() { diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 9cac6b2f9f1f..31bde166ece0 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -1,5 +1,6 @@ //! Collection of methods for block validation. +use reth_chainspec::ChainSpec; use reth_consensus::ConsensusError; use reth_primitives::{ constants::{ @@ -7,7 +8,7 @@ use reth_primitives::{ MAXIMUM_EXTRA_DATA_SIZE, }, eip4844::calculate_excess_blob_gas, - ChainSpec, GotExpected, Hardfork, Header, SealedBlock, SealedHeader, + GotExpected, Hardfork, Header, SealedBlock, SealedHeader, }; /// Gas used needs to be less than gas limit. Gas used is going to be checked after execution. @@ -270,10 +271,11 @@ mod tests { use super::*; use mockall::mock; use rand::Rng; + use reth_chainspec::ChainSpecBuilder; use reth_primitives::{ hex_literal::hex, proofs, Account, Address, BlockBody, BlockHash, BlockHashOrNumber, - BlockNumber, Bytes, ChainSpecBuilder, Signature, Transaction, TransactionSigned, TxEip4844, - Withdrawal, Withdrawals, U256, + BlockNumber, Bytes, Signature, Transaction, TransactionSigned, TxEip4844, Withdrawal, + Withdrawals, U256, }; use reth_storage_api::{ errors::provider::ProviderResult, AccountReader, HeaderProvider, WithdrawalsProvider, diff --git a/crates/e2e-test-utils/Cargo.toml b/crates/e2e-test-utils/Cargo.toml index c062bec4b2aa..2855ecc51c40 100644 --- a/crates/e2e-test-utils/Cargo.toml +++ b/crates/e2e-test-utils/Cargo.toml @@ -10,6 +10,7 @@ repository.workspace = true [dependencies] reth.workspace = true +reth-chainspec.workspace = true reth-primitives.workspace = true reth-tracing.workspace = true reth-db.workspace = true diff --git a/crates/e2e-test-utils/src/lib.rs b/crates/e2e-test-utils/src/lib.rs index aa7d46428bef..925b09b91210 100644 --- a/crates/e2e-test-utils/src/lib.rs +++ b/crates/e2e-test-utils/src/lib.rs @@ -4,11 +4,11 @@ use reth::{ builder::{NodeBuilder, NodeConfig, NodeHandle}, tasks::TaskManager, }; +use reth_chainspec::ChainSpec; use reth_db::{test_utils::TempDatabase, DatabaseEnv}; use reth_node_builder::{ components::NodeComponentsBuilder, FullNodeTypesAdapter, Node, NodeAdapter, RethFullAdapter, }; -use reth_primitives::ChainSpec; use reth_provider::providers::BlockchainProvider; use std::sync::Arc; use tracing::{span, Level}; diff --git a/crates/e2e-test-utils/src/network.rs b/crates/e2e-test-utils/src/network.rs index 5b148b09f55c..b575d7001dda 100644 --- a/crates/e2e-test-utils/src/network.rs +++ b/crates/e2e-test-utils/src/network.rs @@ -1,6 +1,6 @@ use futures_util::StreamExt; use reth::network::{NetworkEvent, NetworkEvents, NetworkHandle, PeersInfo}; -use reth_primitives::NodeRecord; +use reth_chainspec::net::NodeRecord; use reth_tokio_util::EventStream; use reth_tracing::tracing::info; diff --git a/crates/engine-primitives/Cargo.toml b/crates/engine-primitives/Cargo.toml index 46da7286d51a..b44a4a8aa4e7 100644 --- a/crates/engine-primitives/Cargo.toml +++ b/crates/engine-primitives/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] # reth -reth-primitives.workspace = true +reth-chainspec.workspace = true reth-payload-primitives.workspace = true # misc diff --git a/crates/engine-primitives/src/lib.rs b/crates/engine-primitives/src/lib.rs index 62916fe4f506..b83abc39e6cc 100644 --- a/crates/engine-primitives/src/lib.rs +++ b/crates/engine-primitives/src/lib.rs @@ -8,11 +8,11 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use reth_chainspec::ChainSpec; pub use reth_payload_primitives::{ BuiltPayload, EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes, PayloadTypes, }; -use reth_primitives::ChainSpec; use serde::{de::DeserializeOwned, ser::Serialize}; /// This type defines the versioned types of the engine API. diff --git a/crates/ethereum/consensus/Cargo.toml b/crates/ethereum/consensus/Cargo.toml index 3eae6aaa7f58..25c865a2bc18 100644 --- a/crates/ethereum/consensus/Cargo.toml +++ b/crates/ethereum/consensus/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-consensus-common.workspace = true reth-primitives.workspace = true reth-consensus.workspace = true diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 33f2b3b194e0..210f54461394 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -8,6 +8,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use reth_chainspec::{Chain, ChainSpec, Hardfork}; use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ validate_4844_header_standalone, validate_against_parent_4844, @@ -16,8 +17,8 @@ use reth_consensus_common::validation::{ validate_header_extradata, validate_header_gas, }; use reth_primitives::{ - constants::MINIMUM_GAS_LIMIT, BlockWithSenders, Chain, ChainSpec, Hardfork, Header, - SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256, + constants::MINIMUM_GAS_LIMIT, BlockWithSenders, Header, SealedBlock, SealedHeader, + EMPTY_OMMER_ROOT_HASH, U256, }; use std::{sync::Arc, time::SystemTime}; @@ -223,9 +224,9 @@ impl Consensus for EthBeaconConsensus { #[cfg(test)] mod tests { - use reth_primitives::{proofs, ChainSpecBuilder, B256}; - use super::*; + use reth_chainspec::ChainSpecBuilder; + use reth_primitives::{proofs, B256}; fn header_with_gas_limit(gas_limit: u64) -> SealedHeader { let header = Header { gas_limit, ..Default::default() }; diff --git a/crates/ethereum/consensus/src/validation.rs b/crates/ethereum/consensus/src/validation.rs index 631be1519e92..1566ec176215 100644 --- a/crates/ethereum/consensus/src/validation.rs +++ b/crates/ethereum/consensus/src/validation.rs @@ -1,7 +1,7 @@ +use reth_chainspec::ChainSpec; use reth_consensus::ConsensusError; use reth_primitives::{ - gas_spent_by_transactions, BlockWithSenders, Bloom, ChainSpec, GotExpected, Receipt, Request, - B256, + gas_spent_by_transactions, BlockWithSenders, Bloom, GotExpected, Receipt, Request, B256, }; /// Validate a block with regard to execution results: diff --git a/crates/ethereum/engine-primitives/Cargo.toml b/crates/ethereum/engine-primitives/Cargo.toml index c91e2086effd..231c7f640b39 100644 --- a/crates/ethereum/engine-primitives/Cargo.toml +++ b/crates/ethereum/engine-primitives/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-engine-primitives.workspace = true reth-payload-primitives.workspace = true diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index 50b66ad02014..c1ad788f9770 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -11,12 +11,12 @@ mod payload; pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes}; +use reth_chainspec::ChainSpec; use reth_engine_primitives::EngineTypes; use reth_payload_primitives::{ validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes, PayloadTypes, }; -use reth_primitives::ChainSpec; use reth_rpc_types::{ engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index e68630f4c266..8976f0caa5af 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -1,10 +1,11 @@ //! Contains types required for building a payload. use alloy_rlp::Encodable; +use reth_chainspec::ChainSpec; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ constants::EIP1559_INITIAL_BASE_FEE, revm::config::revm_spec_by_timestamp_after_merge, Address, - BlobTransactionSidecar, ChainSpec, Hardfork, Header, SealedBlock, Withdrawals, B256, U256, + BlobTransactionSidecar, Hardfork, Header, SealedBlock, Withdrawals, B256, U256, }; use reth_rpc_types::engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 410750c80b6f..f087bb210769 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # Reth +reth-chainspec.workspace = true reth-evm.workspace = true reth-primitives.workspace = true reth-revm.workspace = true diff --git a/crates/ethereum/evm/src/eip6110.rs b/crates/ethereum/evm/src/eip6110.rs index 074cd1f0ba5b..1552a03d3d9c 100644 --- a/crates/ethereum/evm/src/eip6110.rs +++ b/crates/ethereum/evm/src/eip6110.rs @@ -1,8 +1,9 @@ //! EIP-6110 deposit requests parsing use alloy_eips::eip6110::{DepositRequest, MAINNET_DEPOSIT_CONTRACT_ADDRESS}; use alloy_sol_types::{sol, SolEvent}; +use reth_chainspec::ChainSpec; use reth_evm::execute::BlockValidationError; -use reth_primitives::{ChainSpec, Receipt, Request}; +use reth_primitives::{Receipt, Request}; use revm_primitives::Log; sol! { @@ -85,7 +86,8 @@ fn parse_deposit_from_log(log: &Log) -> DepositRequest { #[cfg(test)] mod tests { use super::*; - use reth_primitives::{TxType, MAINNET}; + use reth_chainspec::MAINNET; + use reth_primitives::TxType; #[test] fn test_parse_deposit_from_log() { diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 42b1b0c133e4..f9a5b752d20c 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -4,6 +4,7 @@ use crate::{ dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, EthEvmConfig, }; +use reth_chainspec::{ChainSpec, MAINNET}; use reth_ethereum_consensus::validate_block_post_execution; use reth_evm::{ execute::{ @@ -14,8 +15,7 @@ use reth_evm::{ }; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ - BlockNumber, BlockWithSenders, ChainSpec, Hardfork, Header, Receipt, Request, Withdrawals, - MAINNET, U256, + BlockNumber, BlockWithSenders, Hardfork, Header, Receipt, Request, Withdrawals, U256, }; use reth_prune_types::PruneModes; use reth_revm::{ @@ -461,10 +461,10 @@ mod tests { eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS}, eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE}, }; + use reth_chainspec::{ChainSpecBuilder, ForkCondition}; use reth_primitives::{ constants::{EMPTY_ROOT_HASH, ETH_TO_WEI}, - keccak256, public_key_to_address, Account, Block, ChainSpecBuilder, ForkCondition, - Transaction, TxKind, TxLegacy, B256, + keccak256, public_key_to_address, Account, Block, Transaction, TxKind, TxLegacy, B256, }; use reth_revm::{ database::StateProviderDatabase, state_change::HISTORY_SERVE_WINDOW, diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index b6deece37134..da9248c2136c 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -8,11 +8,12 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use reth_chainspec::ChainSpec; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_primitives::{ revm::{config::revm_spec, env::fill_tx_env}, revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, - Address, ChainSpec, Head, Header, TransactionSigned, U256, + Address, Head, Header, TransactionSigned, U256, }; use reth_revm::{Database, EvmBuilder}; diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index ee1df5565db5..a8880de1b6e9 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -31,6 +31,7 @@ eyre.workspace = true [dev-dependencies] reth.workspace = true +reth-chainspec.workspace = true reth-db.workspace = true reth-exex.workspace = true reth-node-api.workspace = true diff --git a/crates/ethereum/node/tests/e2e/blobs.rs b/crates/ethereum/node/tests/e2e/blobs.rs index 3411d6db5218..60785f3226ad 100644 --- a/crates/ethereum/node/tests/e2e/blobs.rs +++ b/crates/ethereum/node/tests/e2e/blobs.rs @@ -6,11 +6,12 @@ use reth::{ rpc::types::engine::PayloadStatusEnum, tasks::TaskManager, }; +use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_e2e_test_utils::{ node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet, }; use reth_node_ethereum::EthereumNode; -use reth_primitives::{b256, ChainSpecBuilder, Genesis, MAINNET}; +use reth_primitives::{b256, Genesis}; use reth_transaction_pool::TransactionPool; use crate::utils::eth_payload_attributes; diff --git a/crates/ethereum/node/tests/e2e/dev.rs b/crates/ethereum/node/tests/e2e/dev.rs index 4570a8c0e122..0c3b3bc72560 100644 --- a/crates/ethereum/node/tests/e2e/dev.rs +++ b/crates/ethereum/node/tests/e2e/dev.rs @@ -1,8 +1,9 @@ use crate::utils::EthNode; use futures::StreamExt; use reth::rpc::eth::EthTransactions; +use reth_chainspec::ChainSpec; use reth_e2e_test_utils::setup; -use reth_primitives::{b256, hex, ChainSpec, Genesis}; +use reth_primitives::{b256, hex, Genesis}; use reth_provider::CanonStateSubscriptions; use std::sync::Arc; diff --git a/crates/ethereum/node/tests/e2e/eth.rs b/crates/ethereum/node/tests/e2e/eth.rs index 6153a55d7f65..ecbbe8f5d696 100644 --- a/crates/ethereum/node/tests/e2e/eth.rs +++ b/crates/ethereum/node/tests/e2e/eth.rs @@ -4,11 +4,12 @@ use reth::{ builder::{NodeBuilder, NodeConfig, NodeHandle}, tasks::TaskManager, }; +use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_e2e_test_utils::{ node::NodeTestContext, setup, transaction::TransactionTestContext, wallet::Wallet, }; use reth_node_ethereum::EthereumNode; -use reth_primitives::{ChainSpecBuilder, Genesis, MAINNET}; +use reth_primitives::Genesis; use std::sync::Arc; #[tokio::test] diff --git a/crates/ethereum/node/tests/e2e/p2p.rs b/crates/ethereum/node/tests/e2e/p2p.rs index c5d00b824c57..a40c1b3f4b4e 100644 --- a/crates/ethereum/node/tests/e2e/p2p.rs +++ b/crates/ethereum/node/tests/e2e/p2p.rs @@ -1,7 +1,7 @@ use crate::utils::eth_payload_attributes; +use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_e2e_test_utils::{setup, transaction::TransactionTestContext}; use reth_node_ethereum::EthereumNode; -use reth_primitives::{ChainSpecBuilder, MAINNET}; use std::sync::Arc; #[tokio::test] diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index e836148101ad..bf818bba45c0 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-execution-errors.workspace = true reth-primitives.workspace = true revm-primitives.workspace = true diff --git a/crates/evm/execution-types/Cargo.toml b/crates/evm/execution-types/Cargo.toml index 5f61a1eb8885..0f0ba75479ef 100644 --- a/crates/evm/execution-types/Cargo.toml +++ b/crates/evm/execution-types/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] reth-primitives.workspace = true +reth-chainspec = { workspace = true, optional = true } reth-execution-errors.workspace = true reth-trie.workspace = true @@ -23,4 +24,4 @@ alloy-primitives.workspace = true alloy-eips.workspace = true [features] -optimism = [] \ No newline at end of file +optimism = ["dep:reth-chainspec"] \ No newline at end of file diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs index be9fd67b14ce..8838c86d2b66 100644 --- a/crates/evm/execution-types/src/execution_outcome.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -190,7 +190,7 @@ impl ExecutionOutcome { pub fn optimism_receipts_root_slow( &self, block_number: BlockNumber, - chain_spec: &reth_primitives::ChainSpec, + chain_spec: &reth_chainspec::ChainSpec, timestamp: u64, ) -> Option { self.receipts.optimism_root_slow( diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index f74dc97c552f..c25e3ae9726e 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -8,9 +8,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use reth_primitives::{ - revm::env::fill_block_env, Address, ChainSpec, Header, TransactionSigned, U256, -}; +use reth_chainspec::ChainSpec; +use reth_primitives::{revm::env::fill_block_env, Address, Header, TransactionSigned, U256}; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; diff --git a/crates/exex/test-utils/Cargo.toml b/crates/exex/test-utils/Cargo.toml index 5e00109d8abc..7c49c43612db 100644 --- a/crates/exex/test-utils/Cargo.toml +++ b/crates/exex/test-utils/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] ## reth +reth-chainspec.workspace = true reth-blockchain-tree.workspace = true reth-config.workspace = true reth-consensus = { workspace = true, features = ["test-utils"] } diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index 76e153067088..120ff048f27f 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -10,6 +10,7 @@ use futures_util::FutureExt; use reth_blockchain_tree::noop::NoopBlockchainTree; +use reth_chainspec::{ChainSpec, MAINNET}; use reth_consensus::test_utils::TestConsensus; use reth_db::{test_utils::TempDatabase, DatabaseEnv}; use reth_db_common::init::init_genesis; @@ -30,7 +31,7 @@ use reth_node_ethereum::{ EthEngineTypes, EthEvmConfig, }; use reth_payload_builder::noop::NoopPayloadBuilderService; -use reth_primitives::{ChainSpec, Head, SealedBlockWithSenders, MAINNET}; +use reth_primitives::{Head, SealedBlockWithSenders}; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, BlockReader, Chain, ProviderFactory, diff --git a/crates/net/discv4/Cargo.toml b/crates/net/discv4/Cargo.toml index 39b19d542799..5e6a9e411559 100644 --- a/crates/net/discv4/Cargo.toml +++ b/crates/net/discv4/Cargo.toml @@ -44,6 +44,7 @@ generic-array.workspace = true serde = { workspace = true, optional = true } [dev-dependencies] +reth-chainspec.workspace = true reth-primitives.workspace = true assert_matches.workspace = true rand.workspace = true diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 52e388a0ab95..47f810d96337 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -214,9 +214,9 @@ impl Discv4 { /// ``` /// # use std::io; /// use rand::thread_rng; + /// use reth_chainspec::net::NodeRecord; /// use reth_discv4::{Discv4, Discv4Config}; /// use reth_network_peers::{pk2id, PeerId}; - /// use reth_primitives::NodeRecord; /// use secp256k1::SECP256K1; /// use std::{net::SocketAddr, str::FromStr}; /// # async fn t() -> io::Result<()> { @@ -2287,7 +2287,8 @@ mod tests { use crate::test_utils::{create_discv4, create_discv4_with_config, rng_endpoint, rng_record}; use alloy_rlp::{Decodable, Encodable}; use rand::{thread_rng, Rng}; - use reth_primitives::{hex, mainnet_nodes, EnrForkIdEntry, ForkHash}; + use reth_chainspec::net::mainnet_nodes; + use reth_primitives::{hex, EnrForkIdEntry, ForkHash}; use std::future::poll_fn; #[tokio::test] diff --git a/crates/net/discv5/Cargo.toml b/crates/net/discv5/Cargo.toml index e4ec56736e2d..1ba0fa47f0b5 100644 --- a/crates/net/discv5/Cargo.toml +++ b/crates/net/discv5/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-metrics.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index 962882a30b31..e2808a03c673 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -9,7 +9,8 @@ use std::{ use derive_more::Display; use discv5::ListenConfig; use multiaddr::{Multiaddr, Protocol}; -use reth_primitives::{Bytes, EnrForkIdEntry, ForkId, NodeRecord}; +use reth_network_peers::NodeRecord; +use reth_primitives::{Bytes, EnrForkIdEntry, ForkId}; use tracing::warn; use crate::{enr::discv4_id_to_multiaddr_id, filter::MustNotIncludeKeys, NetworkStackId}; diff --git a/crates/net/discv5/src/enr.rs b/crates/net/discv5/src/enr.rs index a1417aa439cd..eb8b6be006b2 100644 --- a/crates/net/discv5/src/enr.rs +++ b/crates/net/discv5/src/enr.rs @@ -58,7 +58,8 @@ mod tests { use super::*; use alloy_rlp::Encodable; use discv5::enr::{CombinedKey, EnrKey}; - use reth_primitives::{Hardfork, NodeRecord, MAINNET}; + use reth_chainspec::{Hardfork, MAINNET}; + use reth_network_peers::NodeRecord; #[test] fn discv5_discv4_id_conversion() { diff --git a/crates/net/discv5/src/lib.rs b/crates/net/discv5/src/lib.rs index 6ebb3b9ba6d9..728c44791d16 100644 --- a/crates/net/discv5/src/lib.rs +++ b/crates/net/discv5/src/lib.rs @@ -22,8 +22,8 @@ use enr::{discv4_id_to_discv5_id, EnrCombinedKeyWrapper}; use futures::future::join_all; use itertools::Itertools; use rand::{Rng, RngCore}; -use reth_network_peers::PeerId; -use reth_primitives::{bytes::Bytes, EnrForkIdEntry, ForkId, NodeRecord}; +use reth_network_peers::{NodeRecord, PeerId}; +use reth_primitives::{bytes::Bytes, EnrForkIdEntry, ForkId}; use secp256k1::SecretKey; use tokio::{sync::mpsc, task}; use tracing::{debug, error, trace}; @@ -652,7 +652,7 @@ pub async fn lookup( mod test { use super::*; use ::enr::{CombinedKey, EnrKey}; - use reth_primitives::MAINNET; + use reth_chainspec::MAINNET; use secp256k1::rand::thread_rng; use tracing::trace; diff --git a/crates/net/discv5/src/network_stack_id.rs b/crates/net/discv5/src/network_stack_id.rs index 6cb7507bb8bf..5fcb1ae41b55 100644 --- a/crates/net/discv5/src/network_stack_id.rs +++ b/crates/net/discv5/src/network_stack_id.rs @@ -1,7 +1,7 @@ //! Keys of ENR [`ForkId`](reth_primitives::ForkId) kv-pair. Identifies which network stack a node //! belongs to. -use reth_primitives::ChainSpec; +use reth_chainspec::ChainSpec; /// Identifies which Ethereum network stack a node belongs to, on the discovery network. #[derive(Debug)] diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index 5b4b75c2f17a..b20253a5bf3a 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -40,7 +40,7 @@ serde = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } [dev-dependencies] -reth-primitives.workspace = true +reth-chainspec.workspace = true alloy-rlp.workspace = true alloy-chains.workspace = true tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] } diff --git a/crates/net/dns/src/lib.rs b/crates/net/dns/src/lib.rs index 432cef45184e..f07fde2e4a67 100644 --- a/crates/net/dns/src/lib.rs +++ b/crates/net/dns/src/lib.rs @@ -414,8 +414,8 @@ mod tests { use alloy_chains::Chain; use alloy_rlp::{Decodable, Encodable}; use enr::EnrKey; + use reth_chainspec::MAINNET; use reth_ethereum_forks::{ForkHash, Hardfork}; - use reth_primitives::MAINNET; use secp256k1::rand::thread_rng; use std::{future::poll_fn, net::Ipv4Addr}; diff --git a/crates/net/downloaders/Cargo.toml b/crates/net/downloaders/Cargo.toml index eb55d6e9bc4a..15ede0335d3a 100644 --- a/crates/net/downloaders/Cargo.toml +++ b/crates/net/downloaders/Cargo.toml @@ -50,6 +50,7 @@ tempfile = { workspace = true, optional = true } itertools.workspace = true [dev-dependencies] +reth-chainspec.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-db-api.workspace = true reth-consensus = { workspace = true, features = ["test-utils"] } diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index 64ae86820525..27a576e2f702 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -604,9 +604,10 @@ mod tests { test_utils::{generate_bodies, TestBodiesClient}, }; use assert_matches::assert_matches; + use reth_chainspec::MAINNET; use reth_consensus::test_utils::TestConsensus; use reth_db::test_utils::{create_test_rw_db, create_test_static_files_dir}; - use reth_primitives::{BlockBody, B256, MAINNET}; + use reth_primitives::{BlockBody, B256}; use reth_provider::{providers::StaticFileProvider, ProviderFactory}; use reth_testing_utils::{generators, generators::random_block_range}; use std::collections::HashMap; diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index 0ed24a64e10e..5c5509c8c913 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-codecs-derive.workspace = true reth-primitives.workspace = true alloy-rlp = { workspace = true, features = ["derive"] } diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 904df65c61c7..5f16a623ccb5 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -1,9 +1,8 @@ use crate::EthVersion; use alloy_rlp::{RlpDecodable, RlpEncodable}; +use reth_chainspec::{Chain, ChainSpec, NamedChain, MAINNET}; use reth_codecs_derive::derive_arbitrary; -use reth_primitives::{ - hex, Chain, ChainSpec, ForkId, Genesis, Hardfork, Head, NamedChain, B256, MAINNET, U256, -}; +use reth_primitives::{hex, ForkId, Genesis, Hardfork, Head, B256, U256}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; @@ -152,8 +151,9 @@ impl Default for Status { /// /// # Example /// ``` +/// use reth_chainspec::{Chain, Hardfork, MAINNET}; /// use reth_eth_wire_types::{EthVersion, Status}; -/// use reth_primitives::{Chain, Hardfork, B256, MAINNET, MAINNET_GENESIS_HASH, U256}; +/// use reth_primitives::{B256, MAINNET_GENESIS_HASH, U256}; /// /// // this is just an example status message! /// let status = Status::builder() @@ -230,10 +230,8 @@ mod tests { use crate::{EthVersion, Status}; use alloy_rlp::{Decodable, Encodable}; use rand::Rng; - use reth_primitives::{ - hex, Chain, ChainSpec, ForkCondition, ForkHash, ForkId, Genesis, Hardfork, Head, - NamedChain, B256, U256, - }; + use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain}; + use reth_primitives::{hex, ForkHash, ForkId, Genesis, Hardfork, Head, B256, U256}; use std::str::FromStr; #[test] diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 852cc74f9a1d..c4015cc9bd7c 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-codecs.workspace = true reth-primitives.workspace = true reth-ecies.workspace = true diff --git a/crates/net/eth-wire/src/errors/eth.rs b/crates/net/eth-wire/src/errors/eth.rs index 56a1ae62bd81..485a4ae2e7a4 100644 --- a/crates/net/eth-wire/src/errors/eth.rs +++ b/crates/net/eth-wire/src/errors/eth.rs @@ -3,7 +3,8 @@ use crate::{ errors::P2PStreamError, message::MessageError, version::ParseVersionError, DisconnectReason, }; -use reth_primitives::{Chain, GotExpected, GotExpectedBoxed, ValidationError, B256}; +use reth_chainspec::Chain; +use reth_primitives::{GotExpected, GotExpectedBoxed, ValidationError, B256}; use std::io; /// Errors when sending/receiving messages diff --git a/crates/net/eth-wire/src/ethstream.rs b/crates/net/eth-wire/src/ethstream.rs index e8d4e79eaf88..fac5e05495a7 100644 --- a/crates/net/eth-wire/src/ethstream.rs +++ b/crates/net/eth-wire/src/ethstream.rs @@ -352,10 +352,11 @@ mod tests { EthMessage, EthStream, EthVersion, HelloMessageWithProtocols, PassthroughCodec, Status, }; use futures::{SinkExt, StreamExt}; + use reth_chainspec::NamedChain; use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_ecies::stream::ECIESStream; use reth_network_peers::pk2id; - use reth_primitives::{ForkFilter, Head, NamedChain, B256, U256}; + use reth_primitives::{ForkFilter, Head, B256, U256}; use secp256k1::{SecretKey, SECP256K1}; use std::time::Duration; use tokio::net::{TcpListener, TcpStream}; diff --git a/crates/net/eth-wire/src/test_utils.rs b/crates/net/eth-wire/src/test_utils.rs index 46ff1eee3ece..466bc0f1cefa 100644 --- a/crates/net/eth-wire/src/test_utils.rs +++ b/crates/net/eth-wire/src/test_utils.rs @@ -3,9 +3,10 @@ use crate::{ EthVersion, HelloMessageWithProtocols, P2PStream, ProtocolVersion, Status, UnauthedP2PStream, }; +use reth_chainspec::Chain; use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_network_peers::pk2id; -use reth_primitives::{Chain, ForkFilter, Head, B256, U256}; +use reth_primitives::{ForkFilter, Head, B256, U256}; use secp256k1::{SecretKey, SECP256K1}; use std::net::SocketAddr; use tokio::net::TcpStream; diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index ee8d4acdd52c..a99dfea97822 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-net-common.workspace = true reth-network-api.workspace = true diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 3066c9eafb46..ad865c55c816 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -8,14 +8,16 @@ use crate::{ transactions::TransactionsManagerConfig, NetworkHandle, NetworkManager, }; +use reth_chainspec::{ + net::{mainnet_nodes, sepolia_nodes, TrustedPeer}, + ChainSpec, MAINNET, +}; use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS}; use reth_discv5::NetworkStackId; use reth_dns_discovery::DnsDiscoveryConfig; use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status}; use reth_network_peers::{pk2id, PeerId}; -use reth_primitives::{ - mainnet_nodes, sepolia_nodes, ChainSpec, ForkFilter, Head, TrustedPeer, MAINNET, -}; +use reth_primitives::{ForkFilter, Head}; use reth_provider::{BlockReader, HeaderProvider}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use secp256k1::SECP256K1; @@ -417,8 +419,8 @@ impl NetworkConfigBuilder { /// Calls a closure on [`reth_discv5::ConfigBuilder`], if discv5 discovery is enabled and the /// builder has been set. /// ``` + /// use reth_chainspec::MAINNET; /// use reth_network::NetworkConfigBuilder; - /// use reth_primitives::MAINNET; /// use reth_provider::test_utils::NoopProvider; /// use secp256k1::{rand::thread_rng, SecretKey}; /// @@ -584,8 +586,9 @@ impl NetworkMode { mod tests { use super::*; use rand::thread_rng; + use reth_chainspec::Chain; use reth_dns_discovery::tree::LinkEntry; - use reth_primitives::{Chain, ForkHash}; + use reth_primitives::ForkHash; use reth_provider::test_utils::NoopProvider; use std::collections::BTreeMap; diff --git a/crates/net/network/src/discovery.rs b/crates/net/network/src/discovery.rs index 07d24859ed0c..c320c7fe1575 100644 --- a/crates/net/network/src/discovery.rs +++ b/crates/net/network/src/discovery.rs @@ -12,8 +12,8 @@ use reth_discv5::{DiscoveredPeer, Discv5}; use reth_dns_discovery::{ DnsDiscoveryConfig, DnsDiscoveryHandle, DnsDiscoveryService, DnsNodeRecordUpdate, DnsResolver, }; -use reth_network_peers::PeerId; -use reth_primitives::{EnrForkIdEntry, ForkId, NodeRecord}; +use reth_network_peers::{NodeRecord, PeerId}; +use reth_primitives::{EnrForkIdEntry, ForkId}; use secp256k1::SecretKey; use std::{ collections::VecDeque, diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index e02778f85464..b8f3f8be1697 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -46,8 +46,8 @@ //! //! ``` //! # async fn launch() { +//! use reth_chainspec::net::mainnet_nodes; //! use reth_network::{config::rng_secret_key, NetworkConfig, NetworkManager}; -//! use reth_primitives::mainnet_nodes; //! use reth_provider::test_utils::NoopProvider; //! //! // This block provider implementation is used for testing purposes. @@ -71,8 +71,8 @@ //! ### Configure all components of the Network with the [`NetworkBuilder`] //! //! ``` +//! use reth_chainspec::net::mainnet_nodes; //! use reth_network::{config::rng_secret_key, NetworkConfig, NetworkManager}; -//! use reth_primitives::mainnet_nodes; //! use reth_provider::test_utils::NoopProvider; //! use reth_transaction_pool::TransactionPool; //! async fn launch(pool: Pool) { diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 7a4092d4a4a9..d07b9e02488f 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -43,8 +43,8 @@ use reth_eth_wire::{ }; use reth_metrics::common::mpsc::UnboundedMeteredSender; use reth_network_api::ReputationChangeKind; -use reth_network_peers::PeerId; -use reth_primitives::{ForkId, NodeRecord}; +use reth_network_peers::{NodeRecord, PeerId}; +use reth_primitives::ForkId; use reth_provider::{BlockNumReader, BlockReader}; use reth_rpc_types::{admin::EthProtocolInfo, NetworkStatus}; use reth_tasks::shutdown::GracefulShutdown; @@ -281,8 +281,8 @@ where /// components of the network /// /// ``` + /// use reth_chainspec::net::mainnet_nodes; /// use reth_network::{config::rng_secret_key, NetworkConfig, NetworkManager}; - /// use reth_primitives::mainnet_nodes; /// use reth_provider::test_utils::NoopProvider; /// use reth_transaction_pool::TransactionPool; /// async fn launch(pool: Pool) { diff --git a/crates/net/network/src/network.rs b/crates/net/network/src/network.rs index 9f0db65a3d77..b26dd3e3700e 100644 --- a/crates/net/network/src/network.rs +++ b/crates/net/network/src/network.rs @@ -12,8 +12,8 @@ use reth_network_api::{ ReputationChangeKind, }; use reth_network_p2p::sync::{NetworkSyncUpdater, SyncState, SyncStateProvider}; -use reth_network_peers::PeerId; -use reth_primitives::{Head, NodeRecord, TransactionSigned, B256}; +use reth_network_peers::{NodeRecord, PeerId}; +use reth_primitives::{Head, TransactionSigned, B256}; use reth_rpc_types::NetworkStatus; use reth_tokio_util::{EventSender, EventStream}; use secp256k1::SecretKey; diff --git a/crates/net/network/src/peers/manager.rs b/crates/net/network/src/peers/manager.rs index bd7be4b3406f..7468ba1a06ad 100644 --- a/crates/net/network/src/peers/manager.rs +++ b/crates/net/network/src/peers/manager.rs @@ -14,8 +14,8 @@ use futures::StreamExt; use reth_eth_wire::{errors::EthStreamError, DisconnectReason}; use reth_net_common::ban_list::BanList; use reth_network_api::{PeerKind, ReputationChangeKind}; -use reth_network_peers::PeerId; -use reth_primitives::{ForkId, NodeRecord}; +use reth_network_peers::{NodeRecord, PeerId}; +use reth_primitives::ForkId; use std::{ collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, fmt::Display, diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 79c883d07775..9357cc325adb 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -763,13 +763,14 @@ mod tests { config::PROTOCOL_BREACH_REQUEST_TIMEOUT, handle::PendingSessionEvent, start_pending_incoming_session, }; + use reth_chainspec::MAINNET; use reth_ecies::stream::ECIESStream; use reth_eth_wire::{ EthStream, GetBlockBodies, HelloMessageWithProtocols, P2PStream, Status, StatusBuilder, UnauthedEthStream, UnauthedP2PStream, }; use reth_network_peers::pk2id; - use reth_primitives::{ForkFilter, Hardfork, MAINNET}; + use reth_primitives::{ForkFilter, Hardfork}; use secp256k1::{SecretKey, SECP256K1}; use tokio::{ net::{TcpListener, TcpStream}, diff --git a/crates/net/network/src/test_utils/testnet.rs b/crates/net/network/src/test_utils/testnet.rs index 331687dc417b..c7642accd0d8 100644 --- a/crates/net/network/src/test_utils/testnet.rs +++ b/crates/net/network/src/test_utils/testnet.rs @@ -12,10 +12,10 @@ use crate::{ }; use futures::{FutureExt, StreamExt}; use pin_project::pin_project; +use reth_chainspec::MAINNET; use reth_eth_wire::{protocol::Protocol, DisconnectReason, HelloMessageWithProtocols}; use reth_network_api::{NetworkInfo, Peers}; use reth_network_peers::PeerId; -use reth_primitives::MAINNET; use reth_provider::{ test_utils::NoopProvider, BlockReader, BlockReaderIdExt, HeaderProvider, StateProviderFactory, }; diff --git a/crates/net/network/tests/it/connect.rs b/crates/net/network/tests/it/connect.rs index ffb30a4d7994..bc61cd81befe 100644 --- a/crates/net/network/tests/it/connect.rs +++ b/crates/net/network/tests/it/connect.rs @@ -3,6 +3,7 @@ use alloy_node_bindings::Geth; use alloy_provider::{ext::AdminApi, ProviderBuilder}; use futures::StreamExt; +use reth_chainspec::net::mainnet_nodes; use reth_discv4::Discv4Config; use reth_eth_wire::DisconnectReason; use reth_net_common::ban_list::BanList; @@ -15,7 +16,8 @@ use reth_network_p2p::{ headers::client::{HeadersClient, HeadersRequest}, sync::{NetworkSyncUpdater, SyncState}, }; -use reth_primitives::{mainnet_nodes, HeadersDirection, NodeRecord}; +use reth_network_peers::NodeRecord; +use reth_primitives::HeadersDirection; use reth_provider::test_utils::NoopProvider; use reth_transaction_pool::test_utils::testing_pool; use secp256k1::SecretKey; diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index 27271cdc4702..ed6aedfc8654 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-fs-util.workspace = true reth-db = { workspace = true, features = ["mdbx"] } @@ -31,6 +32,7 @@ reth-config.workspace = true reth-discv4.workspace = true reth-discv5.workspace = true reth-net-nat.workspace = true +reth-network-peers.workspace = true reth-engine-primitives.workspace = true reth-tasks.workspace = true reth-consensus-common.workspace = true @@ -96,7 +98,6 @@ procfs = "0.16.0" [dev-dependencies] # test vectors generation proptest.workspace = true -reth-network-peers.workspace = true [features] optimism = [ diff --git a/crates/node-core/src/args/datadir_args.rs b/crates/node-core/src/args/datadir_args.rs index 3d8dc490d292..85adc49a4aed 100644 --- a/crates/node-core/src/args/datadir_args.rs +++ b/crates/node-core/src/args/datadir_args.rs @@ -2,7 +2,7 @@ use crate::dirs::{ChainPath, DataDirPath, MaybePlatformPath}; use clap::Args; -use reth_primitives::Chain; +use reth_chainspec::Chain; use std::path::PathBuf; /// Parameters for datadir configuration diff --git a/crates/node-core/src/args/network.rs b/crates/node-core/src/args/network.rs index c91e864048f4..28eb9f29bbb4 100644 --- a/crates/node-core/src/args/network.rs +++ b/crates/node-core/src/args/network.rs @@ -2,6 +2,7 @@ use crate::version::P2P_CLIENT_VERSION; use clap::Args; +use reth_chainspec::{net::mainnet_nodes, ChainSpec}; use reth_config::Config; use reth_discv4::{DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT}; use reth_discv5::{ @@ -17,7 +18,7 @@ use reth_network::{ }, HelloMessageWithProtocols, NetworkConfigBuilder, SessionsConfig, }; -use reth_primitives::{mainnet_nodes, ChainSpec, TrustedPeer}; +use reth_network_peers::TrustedPeer; use secp256k1::SecretKey; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, diff --git a/crates/node-core/src/args/pruning.rs b/crates/node-core/src/args/pruning.rs index 390597c54ab4..1621f2d8ed8b 100644 --- a/crates/node-core/src/args/pruning.rs +++ b/crates/node-core/src/args/pruning.rs @@ -1,8 +1,8 @@ //! Pruning and full node arguments use clap::Args; +use reth_chainspec::ChainSpec; use reth_config::config::PruneConfig; -use reth_primitives::ChainSpec; use reth_prune_types::{PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE}; /// Parameters for pruning and full node diff --git a/crates/node-core/src/args/utils.rs b/crates/node-core/src/args/utils.rs index 4648f6500432..527e1ac228ac 100644 --- a/crates/node-core/src/args/utils.rs +++ b/crates/node-core/src/args/utils.rs @@ -1,7 +1,8 @@ //! Clap parser utilities +use reth_chainspec::{AllGenesisFormats, ChainSpec}; use reth_fs_util as fs; -use reth_primitives::{AllGenesisFormats, BlockHashOrNumber, ChainSpec, B256}; +use reth_primitives::{BlockHashOrNumber, B256}; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs}, path::PathBuf, @@ -10,13 +11,13 @@ use std::{ time::Duration, }; -use reth_primitives::DEV; +use reth_chainspec::DEV; #[cfg(feature = "optimism")] -use reth_primitives::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; +use reth_chainspec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; #[cfg(not(feature = "optimism"))] -use reth_primitives::{GOERLI, HOLESKY, MAINNET, SEPOLIA}; +use reth_chainspec::{GOERLI, HOLESKY, MAINNET, SEPOLIA}; #[cfg(feature = "optimism")] /// Chains supported by op-reth. First value should be used as the default. @@ -168,9 +169,8 @@ pub fn parse_socket_address(value: &str) -> eyre::Result B256 { #[cfg(feature = "optimism")] pub fn calculate_receipt_root_optimism( receipts: &[ReceiptWithBloom], - chain_spec: &crate::ChainSpec, + chain_spec: &reth_chainspec::ChainSpec, timestamp: u64, ) -> B256 { // There is a minor bug in op-geth and op-erigon where in the Regolith hardfork, @@ -50,8 +50,8 @@ pub fn calculate_receipt_root_optimism( // encoding. In the Regolith Hardfork, we must strip the deposit nonce from the // receipts before calculating the receipt root. This was corrected in the Canyon // hardfork. - if chain_spec.is_fork_active_at_timestamp(crate::Hardfork::Regolith, timestamp) && - !chain_spec.is_fork_active_at_timestamp(crate::Hardfork::Canyon, timestamp) + if chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Regolith, timestamp) && + !chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Canyon, timestamp) { let receipts = receipts .iter() @@ -90,7 +90,7 @@ pub fn calculate_receipt_root_no_memo(receipts: &[&Receipt]) -> B256 { #[cfg(feature = "optimism")] pub fn calculate_receipt_root_no_memo_optimism( receipts: &[&Receipt], - chain_spec: &crate::ChainSpec, + chain_spec: &reth_chainspec::ChainSpec, timestamp: u64, ) -> B256 { // There is a minor bug in op-geth and op-erigon where in the Regolith hardfork, @@ -98,8 +98,8 @@ pub fn calculate_receipt_root_no_memo_optimism( // encoding. In the Regolith Hardfork, we must strip the deposit nonce from the // receipts before calculating the receipt root. This was corrected in the Canyon // hardfork. - if chain_spec.is_fork_active_at_timestamp(crate::Hardfork::Regolith, timestamp) && - !chain_spec.is_fork_active_at_timestamp(crate::Hardfork::Canyon, timestamp) + if chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Regolith, timestamp) && + !chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Canyon, timestamp) { let receipts = receipts .iter() @@ -137,10 +137,11 @@ mod tests { use super::*; use crate::{ bloom, constants::EMPTY_ROOT_HASH, hex_literal::hex, Block, GenesisAccount, Log, TxType, - GOERLI, HOLESKY, MAINNET, SEPOLIA, U256, + U256, }; use alloy_primitives::{b256, Address, LogData}; use alloy_rlp::Decodable; + use reth_chainspec::{GOERLI, HOLESKY, MAINNET, SEPOLIA}; use reth_trie_common::root::{state_root_ref_unhashed, state_root_unhashed}; use std::collections::HashMap; diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 56be2d0a1690..32d2a4cd80fe 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -100,7 +100,7 @@ impl Receipts { pub fn optimism_root_slow( &self, index: usize, - chain_spec: &crate::ChainSpec, + chain_spec: &reth_chainspec::ChainSpec, timestamp: u64, ) -> Option { Some(crate::proofs::calculate_receipt_root_no_memo_optimism( diff --git a/crates/primitives/src/revm/config.rs b/crates/primitives/src/revm/config.rs index 9297f71a6ef0..6ad76123f149 100644 --- a/crates/primitives/src/revm/config.rs +++ b/crates/primitives/src/revm/config.rs @@ -1,4 +1,5 @@ -use crate::{ChainSpec, Hardfork, Head}; +use reth_chainspec::ChainSpec; +use reth_ethereum_forks::{Hardfork, Head}; /// Returns the spec id at the given timestamp. /// @@ -86,7 +87,8 @@ pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm_primitives::SpecId #[cfg(test)] mod tests { use super::*; - use crate::{ChainSpecBuilder, MAINNET, U256}; + use crate::U256; + use reth_chainspec::{ChainSpecBuilder, MAINNET}; #[test] fn test_to_revm_spec() { @@ -148,7 +150,7 @@ mod tests { { #[inline(always)] fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { - let cs = ChainSpecBuilder::mainnet().chain(crate::Chain::from_id(10)); + let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); f(cs).build() } assert_eq!( diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index 0b12be6ae016..bfd5a3a69a84 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -1,9 +1,9 @@ use crate::{ recover_signer_unchecked, revm_primitives::{BlockEnv, Env, TransactTo, TxEnv}, - Address, Bytes, Chain, ChainSpec, Header, Transaction, TransactionSignedEcRecovered, TxKind, - B256, U256, + Address, Bytes, Header, Transaction, TransactionSignedEcRecovered, TxKind, B256, U256, }; +use reth_chainspec::{Chain, ChainSpec}; use alloy_eips::{eip4788::BEACON_ROOTS_ADDRESS, eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS}; #[cfg(feature = "optimism")] @@ -374,7 +374,7 @@ pub fn fill_op_tx_env>( #[cfg(test)] mod tests { use super::*; - use crate::GOERLI; + use reth_chainspec::GOERLI; #[test] fn test_recover_genesis_goerli_signer() { diff --git a/crates/prune/prune/Cargo.toml b/crates/prune/prune/Cargo.toml index 016265e1b5c6..c45ea9d3f7f1 100644 --- a/crates/prune/prune/Cargo.toml +++ b/crates/prune/prune/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-exex-types.workspace = true reth-primitives.workspace = true reth-db.workspace = true diff --git a/crates/prune/prune/src/builder.rs b/crates/prune/prune/src/builder.rs index a91a0faa6d51..233e80a71a9a 100644 --- a/crates/prune/prune/src/builder.rs +++ b/crates/prune/prune/src/builder.rs @@ -1,8 +1,8 @@ use crate::{segments::SegmentSet, Pruner}; +use reth_chainspec::MAINNET; use reth_config::PruneConfig; use reth_db_api::database::Database; use reth_exex_types::FinishedExExHeight; -use reth_primitives::MAINNET; use reth_provider::ProviderFactory; use reth_prune_types::PruneModes; use std::time::Duration; diff --git a/crates/prune/prune/src/pruner.rs b/crates/prune/prune/src/pruner.rs index da805c77fa77..47864a274e03 100644 --- a/crates/prune/prune/src/pruner.rs +++ b/crates/prune/prune/src/pruner.rs @@ -332,9 +332,9 @@ impl Pruner { mod tests { use crate::Pruner; + use reth_chainspec::MAINNET; use reth_db::test_utils::{create_test_rw_db, create_test_static_files_dir}; use reth_exex_types::FinishedExExHeight; - use reth_primitives::MAINNET; use reth_provider::{providers::StaticFileProvider, ProviderFactory}; #[test] diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index cf7c0f5fa532..e8204ee25bda 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-storage-errors.workspace = true reth-execution-errors.workspace = true diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index 7305a0c0636f..1709b54cb80d 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -3,6 +3,7 @@ use alloy_eips::{ eip7002::WithdrawalRequest, }; use alloy_rlp::Buf; +use reth_chainspec::ChainSpec; use reth_consensus_common::calc; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ @@ -10,7 +11,7 @@ use reth_primitives::{ fill_tx_env_with_beacon_root_contract_call, fill_tx_env_with_withdrawal_requests_contract_call, }, - Address, ChainSpec, Header, Request, Withdrawal, B256, U256, + Address, Header, Request, Withdrawal, B256, U256, }; use reth_storage_errors::provider::ProviderError; use revm::{ diff --git a/crates/rpc/rpc-api/src/admin.rs b/crates/rpc/rpc-api/src/admin.rs index 15904fee4989..173cd8ef7a98 100644 --- a/crates/rpc/rpc-api/src/admin.rs +++ b/crates/rpc/rpc-api/src/admin.rs @@ -1,6 +1,5 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; -use reth_network_peers::AnyNode; -use reth_primitives::NodeRecord; +use reth_network_peers::{AnyNode, NodeRecord}; use reth_rpc_types::{admin::NodeInfo, PeerInfo}; /// Admin namespace rpc interface that gives access to several non-standard RPC methods. diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index fa1aabae7118..46105030cb63 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -43,6 +43,7 @@ thiserror.workspace = true tracing.workspace = true [dev-dependencies] +reth-chainspec.workspace = true reth-beacon-consensus.workspace = true reth-network-api.workspace = true reth-evm-ethereum.workspace = true diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 34ac352eee22..caf16ebf6fce 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -11,9 +11,9 @@ use jsonrpsee::{ rpc_params, types::error::ErrorCode, }; +use reth_chainspec::net::NodeRecord; use reth_primitives::{ - hex_literal::hex, Address, BlockId, BlockNumberOrTag, Bytes, NodeRecord, TxHash, B256, B64, - U256, U64, + hex_literal::hex, Address, BlockId, BlockNumberOrTag, Bytes, TxHash, B256, B64, U256, U64, }; use reth_rpc_api::{ clients::{AdminApiClient, EthApiClient}, diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index 9d660ae3035d..d751b2d331a2 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -1,9 +1,9 @@ use reth_beacon_consensus::BeaconConsensusEngineHandle; +use reth_chainspec::MAINNET; use reth_ethereum_engine_primitives::EthEngineTypes; use reth_evm_ethereum::EthEvmConfig; use reth_network_api::noop::NoopNetwork; use reth_payload_builder::test_utils::spawn_test_payload_service; -use reth_primitives::MAINNET; use reth_provider::test_utils::{NoopProvider, TestCanonStateSubscriptions}; use reth_rpc_builder::{ auth::{AuthRpcModule, AuthServerConfig, AuthServerHandle}, diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 80c69d8a4dc4..d067515f6c2a 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-rpc-api.workspace = true reth-rpc-types.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index a856bdc1707a..8185bbe8cc04 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -2,6 +2,7 @@ use crate::{metrics::EngineApiMetrics, EngineApiError, EngineApiResult}; use async_trait::async_trait; use jsonrpsee_core::RpcResult; use reth_beacon_consensus::BeaconConsensusEngineHandle; +use reth_chainspec::ChainSpec; use reth_engine_primitives::EngineTypes; use reth_evm::provider::EvmEnvProvider; use reth_payload_builder::PayloadStore; @@ -9,7 +10,7 @@ use reth_payload_primitives::{ validate_payload_timestamp, EngineApiMessageVersion, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes, }; -use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Hardfork, B256, U64}; +use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, Hardfork, B256, U64}; use reth_rpc_api::EngineApiServer; use reth_rpc_types::engine::{ CancunPayloadFields, ClientVersionV1, ExecutionPayload, ExecutionPayloadBodiesV1, @@ -842,8 +843,9 @@ mod tests { use reth_ethereum_engine_primitives::EthEngineTypes; use reth_testing_utils::generators::random_block; + use reth_chainspec::MAINNET; use reth_payload_builder::test_utils::spawn_test_payload_service; - use reth_primitives::{SealedBlock, B256, MAINNET}; + use reth_primitives::{SealedBlock, B256}; use reth_provider::test_utils::MockEthProvider; use reth_rpc_types::engine::{ClientCode, ClientVersionV1}; use reth_rpc_types_compat::engine::payload::execution_payload_from_sealed_block; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 85ef532d22f4..378f665bf90b 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-rpc-api.workspace = true reth-rpc-server-types.workspace = true diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 2ea412bb207f..2ee093aed28e 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -2,9 +2,10 @@ use crate::result::ToRpcResult; use alloy_primitives::B256; use async_trait::async_trait; use jsonrpsee::core::RpcResult; +use reth_chainspec::ChainSpec; use reth_network_api::{NetworkInfo, PeerKind, Peers}; -use reth_network_peers::AnyNode; -use reth_primitives::{ChainConfig, ChainSpec, NodeRecord}; +use reth_network_peers::{AnyNode, NodeRecord}; +use reth_primitives::ChainConfig; use reth_rpc_api::AdminApiServer; use reth_rpc_types::{ admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo}, diff --git a/crates/rpc/rpc/src/eth/api/fee_history.rs b/crates/rpc/rpc/src/eth/api/fee_history.rs index da43be5510ab..626c670376c8 100644 --- a/crates/rpc/rpc/src/eth/api/fee_history.rs +++ b/crates/rpc/rpc/src/eth/api/fee_history.rs @@ -6,10 +6,11 @@ use futures::{ FutureExt, Stream, StreamExt, }; use metrics::atomics::AtomicU64; +use reth_chainspec::ChainSpec; use reth_primitives::{ basefee::calc_next_block_base_fee, eip4844::{calc_blob_gasprice, calculate_excess_blob_gas}, - ChainSpec, Receipt, SealedBlock, TransactionSigned, B256, + Receipt, SealedBlock, TransactionSigned, B256, }; use reth_provider::{BlockReaderIdExt, CanonStateNotification, ChainSpecProvider}; use reth_rpc_server_types::constants::gas_oracle::MAX_HEADER_HISTORY; diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 9d4e8d817c23..364a55842d3c 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -13,13 +13,13 @@ use crate::eth::{ traits::RawTransactionForwarder, }; use async_trait::async_trait; +use reth_chainspec::ChainInfo; use reth_errors::{RethError, RethResult}; use reth_evm::ConfigureEvm; use reth_network_api::NetworkInfo; use reth_primitives::{ revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg}, - Address, BlockId, BlockNumberOrTag, ChainInfo, SealedBlockWithSenders, SealedHeader, B256, - U256, U64, + Address, BlockId, BlockNumberOrTag, SealedBlockWithSenders, SealedHeader, B256, U256, U64, }; use reth_provider::{ BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory, diff --git a/crates/rpc/rpc/src/eth/api/pending_block.rs b/crates/rpc/rpc/src/eth/api/pending_block.rs index b3375e2b5554..c3645b3c85a1 100644 --- a/crates/rpc/rpc/src/eth/api/pending_block.rs +++ b/crates/rpc/rpc/src/eth/api/pending_block.rs @@ -1,6 +1,7 @@ //! Support for building a pending block via local txpool. use crate::eth::error::{EthApiError, EthResult}; +use reth_chainspec::ChainSpec; use reth_errors::ProviderError; use reth_primitives::{ constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH}, @@ -9,8 +10,8 @@ use reth_primitives::{ revm_primitives::{ BlockEnv, CfgEnvWithHandlerCfg, EVMError, Env, InvalidTransaction, ResultAndState, SpecId, }, - Block, BlockId, BlockNumberOrTag, ChainSpec, Header, IntoRecoveredTransaction, Receipt, - Requests, SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256, + Block, BlockId, BlockNumberOrTag, Header, IntoRecoveredTransaction, Receipt, Requests, + SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256, }; use reth_provider::{ChainSpecProvider, ExecutionOutcome, StateProviderFactory}; use reth_revm::{ diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 41c68da48300..f238b4da079a 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -433,11 +433,12 @@ mod tests { EthApi, }; use jsonrpsee::types::error::INVALID_PARAMS_CODE; + use reth_chainspec::BaseFeeParams; use reth_evm_ethereum::EthEvmConfig; use reth_network_api::noop::NoopNetwork; use reth_primitives::{ - constants::ETHEREUM_BLOCK_GAS_LIMIT, BaseFeeParams, Block, BlockNumberOrTag, Header, - TransactionSigned, B256, U64, + constants::ETHEREUM_BLOCK_GAS_LIMIT, Block, BlockNumberOrTag, Header, TransactionSigned, + B256, U64, }; use reth_provider::{ test_utils::{MockEthProvider, NoopProvider}, diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index 08b22aa74073..1fea2df4a4b4 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -11,7 +11,8 @@ use core::fmt; use async_trait::async_trait; use jsonrpsee::{core::RpcResult, server::IdProvider}; -use reth_primitives::{ChainInfo, IntoRecoveredTransaction, TxHash}; +use reth_chainspec::ChainInfo; +use reth_primitives::{IntoRecoveredTransaction, TxHash}; use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider, ProviderError}; use reth_rpc_api::EthFilterApiServer; use reth_rpc_types::{ diff --git a/crates/rpc/rpc/src/eth/logs_utils.rs b/crates/rpc/rpc/src/eth/logs_utils.rs index 4fdf9b3a704e..c57ce5fcb986 100644 --- a/crates/rpc/rpc/src/eth/logs_utils.rs +++ b/crates/rpc/rpc/src/eth/logs_utils.rs @@ -1,6 +1,7 @@ use super::filter::FilterError; use alloy_primitives::TxHash; -use reth_primitives::{BlockNumHash, ChainInfo, Receipt}; +use reth_chainspec::ChainInfo; +use reth_primitives::{BlockNumHash, Receipt}; use reth_provider::{BlockReader, ProviderError}; use reth_rpc_types::{FilteredParams, Log}; diff --git a/crates/stages/stages/Cargo.toml b/crates/stages/stages/Cargo.toml index 44e344f8aa58..be203b43a18a 100644 --- a/crates/stages/stages/Cargo.toml +++ b/crates/stages/stages/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec = { workspace = true, optional = true } reth-codecs.workspace = true reth-config.workspace = true reth-consensus.workspace = true @@ -48,6 +49,7 @@ tempfile = { workspace = true, optional = true } [dev-dependencies] # reth +reth-chainspec.workspace = true reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } reth-db = { workspace = true, features = ["test-utils", "mdbx"] } reth-evm-ethereum.workspace = true @@ -85,6 +87,7 @@ pprof = { workspace = true, features = [ [features] test-utils = [ + "dep:reth-chainspec", "reth-network-p2p/test-utils", "reth-db/test-utils", "reth-provider/test-utils", diff --git a/crates/stages/stages/src/lib.rs b/crates/stages/stages/src/lib.rs index ca44e586f7c4..10b2e1976db5 100644 --- a/crates/stages/stages/src/lib.rs +++ b/crates/stages/stages/src/lib.rs @@ -17,7 +17,8 @@ //! # use reth_downloaders::headers::reverse_headers::ReverseHeadersDownloaderBuilder; //! # use reth_network_p2p::test_utils::{TestBodiesClient, TestHeadersClient}; //! # use reth_evm_ethereum::execute::EthExecutorProvider; -//! # use reth_primitives::{MAINNET, B256}; +//! # use reth_primitives::B256; +//! # use reth_chainspec::MAINNET; //! # use reth_prune_types::PruneModes; //! # use reth_network_peers::PeerId; //! # use reth_stages::Pipeline; diff --git a/crates/stages/stages/src/sets.rs b/crates/stages/stages/src/sets.rs index 772f562a224d..d5d132810daa 100644 --- a/crates/stages/stages/src/sets.rs +++ b/crates/stages/stages/src/sets.rs @@ -12,7 +12,7 @@ //! ```no_run //! # use reth_stages::Pipeline; //! # use reth_stages::sets::{OfflineStages}; -//! # use reth_primitives::MAINNET; +//! # use reth_chainspec::MAINNET; //! # use reth_prune_types::PruneModes; //! # use reth_evm_ethereum::EthEvmConfig; //! # use reth_provider::StaticFileProviderFactory; diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index b5b66cca7738..9f3b229268ed 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -707,12 +707,13 @@ mod tests { use crate::test_utils::TestStageDB; use alloy_rlp::Decodable; use assert_matches::assert_matches; + use reth_chainspec::ChainSpecBuilder; use reth_db_api::{models::AccountBeforeTx, transaction::DbTxMut}; use reth_evm_ethereum::execute::EthExecutorProvider; use reth_execution_errors::BlockValidationError; use reth_primitives::{ - address, hex_literal::hex, keccak256, Account, Address, Bytecode, ChainSpecBuilder, - SealedBlock, StorageEntry, B256, U256, + address, hex_literal::hex, keccak256, Account, Address, Bytecode, SealedBlock, + StorageEntry, B256, U256, }; use reth_provider::{ test_utils::create_test_provider_factory, AccountReader, ReceiptProvider, diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index 1f7ae1e9b6a0..8d850b8ba13a 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -42,6 +42,7 @@ mod tests { use super::*; use crate::test_utils::{StorageKind, TestStageDB}; use alloy_rlp::Decodable; + use reth_chainspec::ChainSpecBuilder; use reth_db::{ mdbx::{cursor::Cursor, RW}, tables, @@ -56,8 +57,8 @@ mod tests { use reth_evm_ethereum::execute::EthExecutorProvider; use reth_exex::ExExManagerHandle; use reth_primitives::{ - address, hex_literal::hex, keccak256, Account, BlockNumber, Bytecode, ChainSpecBuilder, - SealedBlock, StaticFileSegment, B256, U256, + address, hex_literal::hex, keccak256, Account, BlockNumber, Bytecode, SealedBlock, + StaticFileSegment, B256, U256, }; use reth_provider::{ providers::StaticFileWriter, AccountExtReader, BlockReader, DatabaseProviderFactory, diff --git a/crates/stages/stages/src/test_utils/test_db.rs b/crates/stages/stages/src/test_utils/test_db.rs index f2d653c0bb2f..8f72b5aab225 100644 --- a/crates/stages/stages/src/test_utils/test_db.rs +++ b/crates/stages/stages/src/test_utils/test_db.rs @@ -1,3 +1,4 @@ +use reth_chainspec::MAINNET; use reth_db::{ tables, test_utils::{ @@ -16,7 +17,7 @@ use reth_db_api::{ }; use reth_primitives::{ keccak256, Account, Address, BlockNumber, Receipt, SealedBlock, SealedHeader, - StaticFileSegment, StorageEntry, TxHash, TxNumber, B256, MAINNET, U256, + StaticFileSegment, StorageEntry, TxHash, TxNumber, B256, U256, }; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, diff --git a/crates/storage/db-common/Cargo.toml b/crates/storage/db-common/Cargo.toml index 0e6a3720dc87..c6eebae87e55 100644 --- a/crates/storage/db-common/Cargo.toml +++ b/crates/storage/db-common/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-primitives.workspace = true reth-db = { workspace = true, features = ["mdbx"] } reth-db-api.workspace = true diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 9f5cc692e365..d8bf583dfbb3 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -1,13 +1,14 @@ //! Reth genesis initialization utility functions. +use reth_chainspec::ChainSpec; use reth_codecs::Compact; use reth_config::config::EtlConfig; use reth_db::tables; use reth_db_api::{database::Database, transaction::DbTxMut, DatabaseError}; use reth_etl::Collector; use reth_primitives::{ - Account, Address, Bytecode, ChainSpec, GenesisAccount, Receipts, StaticFileSegment, - StorageEntry, B256, U256, + Account, Address, Bytecode, GenesisAccount, Receipts, StaticFileSegment, StorageEntry, B256, + U256, }; use reth_provider::{ bundle_state::{BundleStateInit, RevertsInit}, @@ -524,6 +525,7 @@ struct GenesisAccountWithAddress { #[cfg(test)] mod tests { use super::*; + use reth_chainspec::{Chain, GOERLI, MAINNET, SEPOLIA}; use reth_db::DatabaseEnv; use reth_db_api::{ cursor::DbCursorRO, @@ -532,8 +534,7 @@ mod tests { transaction::DbTx, }; use reth_primitives::{ - Chain, Genesis, IntegerList, GOERLI, GOERLI_GENESIS_HASH, MAINNET, MAINNET_GENESIS_HASH, - SEPOLIA, SEPOLIA_GENESIS_HASH, + Genesis, IntegerList, GOERLI_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, }; use reth_provider::test_utils::create_test_provider_factory_with_chain_spec; diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index de2fea7578d9..c1f87891ce4d 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-blockchain-tree-api.workspace = true reth-execution-types.workspace = true reth-primitives.workspace = true diff --git a/crates/storage/provider/src/providers/chain_info.rs b/crates/storage/provider/src/providers/chain_info.rs index 905be1287ee5..2e53a78acd78 100644 --- a/crates/storage/provider/src/providers/chain_info.rs +++ b/crates/storage/provider/src/providers/chain_info.rs @@ -1,5 +1,6 @@ use parking_lot::RwLock; -use reth_primitives::{BlockNumHash, BlockNumber, ChainInfo, SealedHeader}; +use reth_chainspec::ChainInfo; +use reth_primitives::{BlockNumHash, BlockNumber, SealedHeader}; use std::{ sync::{ atomic::{AtomicU64, Ordering}, diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 683b50dce075..57e7e9615217 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -7,15 +7,16 @@ use crate::{ PruneCheckpointReader, RequestsProvider, StageCheckpointReader, StateProviderBox, StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; +use reth_chainspec::{ChainInfo, ChainSpec}; use reth_db::{init_db, mdbx::DatabaseArguments, DatabaseEnv}; use reth_db_api::{database::Database, models::StoredBlockBodyIndices}; use reth_errors::{RethError, RethResult}; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ - Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, ChainInfo, - ChainSpec, Header, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, - StaticFileSegment, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, - TxNumber, Withdrawal, Withdrawals, B256, U256, + Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Header, Receipt, + SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, TransactionMeta, + TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, + U256, }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; @@ -597,14 +598,13 @@ mod tests { use alloy_rlp::Decodable; use assert_matches::assert_matches; use rand::Rng; + use reth_chainspec::ChainSpecBuilder; use reth_db::{ mdbx::DatabaseArguments, tables, test_utils::{create_test_static_files_dir, ERROR_TEMPDIR}, }; - use reth_primitives::{ - hex_literal::hex, ChainSpecBuilder, SealedBlock, StaticFileSegment, TxNumber, B256, U256, - }; + use reth_primitives::{hex_literal::hex, SealedBlock, StaticFileSegment, TxNumber, B256, U256}; use reth_prune_types::{PruneMode, PruneModes}; use reth_storage_errors::provider::ProviderError; use reth_testing_utils::{ diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index a305d8ddc9d0..f0932f45dbf4 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -14,6 +14,7 @@ use crate::{ WithdrawalsProvider, }; use itertools::{izip, Itertools}; +use reth_chainspec::{ChainInfo, ChainSpec}; use reth_db::{tables, BlockNumberList}; use reth_db_api::{ common::KeyValue, @@ -33,10 +34,10 @@ use reth_primitives::{ keccak256, revm::{config::revm_spec, env::fill_block_env}, Account, Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, - ChainInfo, ChainSpec, GotExpected, Head, Header, Receipt, Requests, SealedBlock, - SealedBlockWithSenders, SealedHeader, StaticFileSegment, StorageEntry, TransactionMeta, - TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxHash, TxNumber, - Withdrawal, Withdrawals, B256, U256, + GotExpected, Head, Header, Receipt, Requests, SealedBlock, SealedBlockWithSenders, + SealedHeader, StaticFileSegment, StorageEntry, TransactionMeta, TransactionSigned, + TransactionSignedEcRecovered, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, + Withdrawals, B256, U256, }; use reth_prune_types::{PruneCheckpoint, PruneLimiter, PruneModes, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 7cf994870c94..9da41269c137 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -12,6 +12,7 @@ use reth_blockchain_tree_api::{ BlockValidationKind, BlockchainTreeEngine, BlockchainTreeViewer, CanonicalOutcome, InsertPayloadOk, }; +use reth_chainspec::{ChainInfo, ChainSpec}; use reth_db_api::{ database::Database, models::{AccountBeforeTx, StoredBlockBodyIndices}, @@ -19,9 +20,9 @@ use reth_db_api::{ use reth_evm::ConfigureEvmEnv; use reth_primitives::{ Account, Address, Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumHash, BlockNumber, - BlockNumberOrTag, BlockWithSenders, ChainInfo, ChainSpec, Header, Receipt, SealedBlock, - SealedBlockWithSenders, SealedHeader, TransactionMeta, TransactionSigned, - TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, + BlockNumberOrTag, BlockWithSenders, Header, Receipt, SealedBlock, SealedBlockWithSenders, + SealedHeader, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, + Withdrawal, Withdrawals, B256, U256, }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; diff --git a/crates/storage/provider/src/providers/static_file/jar.rs b/crates/storage/provider/src/providers/static_file/jar.rs index 22b257804e69..5e20572e3d61 100644 --- a/crates/storage/provider/src/providers/static_file/jar.rs +++ b/crates/storage/provider/src/providers/static_file/jar.rs @@ -6,10 +6,11 @@ use crate::{ to_range, BlockHashReader, BlockNumReader, HeaderProvider, ReceiptProvider, TransactionsProvider, }; +use reth_chainspec::ChainInfo; use reth_db::static_file::{HeaderMask, ReceiptMask, StaticFileCursor, TransactionMask}; use reth_db_api::models::CompactU256; use reth_primitives::{ - Address, BlockHash, BlockHashOrNumber, BlockNumber, ChainInfo, Header, Receipt, SealedHeader, + Address, BlockHash, BlockHashOrNumber, BlockNumber, Header, Receipt, SealedHeader, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, B256, U256, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 114284f229b7..39e588c7f5f4 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -9,6 +9,7 @@ use crate::{ }; use dashmap::{mapref::entry::Entry as DashMapEntry, DashMap}; use parking_lot::RwLock; +use reth_chainspec::ChainInfo; use reth_db::{ lockfile::StorageLock, static_file::{iter_static_files, HeaderMask, ReceiptMask, StaticFileCursor, TransactionMask}, @@ -24,8 +25,8 @@ use reth_nippy_jar::NippyJar; use reth_primitives::{ keccak256, static_file::{find_fixed_range, HighestStaticFiles, SegmentHeader, SegmentRangeInclusive}, - Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, ChainInfo, Header, - Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, TransactionMeta, + Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Header, Receipt, + SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, }; diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 0788ddf1e816..974982121a7e 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -6,14 +6,14 @@ use crate::{ StateRootProvider, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; use parking_lot::Mutex; +use reth_chainspec::{ChainInfo, ChainSpec}; use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices}; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ keccak256, Account, Address, Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumber, - BlockWithSenders, Bytecode, Bytes, ChainInfo, ChainSpec, Header, Receipt, SealedBlock, - SealedBlockWithSenders, SealedHeader, StorageKey, StorageValue, TransactionMeta, - TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, - U256, + BlockWithSenders, Bytecode, Bytes, Header, Receipt, SealedBlock, SealedBlockWithSenders, + SealedHeader, StorageKey, StorageValue, TransactionMeta, TransactionSigned, + TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{updates::TrieUpdates, AccountProof}; @@ -46,7 +46,7 @@ impl Default for MockEthProvider { blocks: Default::default(), headers: Default::default(), accounts: Default::default(), - chain_spec: Arc::new(reth_primitives::ChainSpecBuilder::mainnet().build()), + chain_spec: Arc::new(reth_chainspec::ChainSpecBuilder::mainnet().build()), } } } diff --git a/crates/storage/provider/src/test_utils/mod.rs b/crates/storage/provider/src/test_utils/mod.rs index 6f5ecd526783..4d40ad54e990 100644 --- a/crates/storage/provider/src/test_utils/mod.rs +++ b/crates/storage/provider/src/test_utils/mod.rs @@ -1,9 +1,9 @@ use crate::{providers::StaticFileProvider, ProviderFactory}; +use reth_chainspec::{ChainSpec, MAINNET}; use reth_db::{ test_utils::{create_test_rw_db, create_test_static_files_dir, TempDatabase}, DatabaseEnv, }; -use reth_primitives::{ChainSpec, MAINNET}; use std::sync::Arc; pub mod blocks; diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index b681586a1427..74577732d2a7 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -6,13 +6,14 @@ use crate::{ StateProviderFactory, StateRootProvider, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; +use reth_chainspec::{ChainInfo, ChainSpec, MAINNET}; use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices}; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ Account, Address, Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumber, BlockWithSenders, - Bytecode, ChainInfo, ChainSpec, Header, Receipt, SealedBlock, SealedBlockWithSenders, - SealedHeader, StorageKey, StorageValue, TransactionMeta, TransactionSigned, - TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, MAINNET, U256, + Bytecode, Header, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, StorageKey, + StorageValue, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, + Withdrawal, Withdrawals, B256, U256, }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; diff --git a/crates/storage/provider/src/traits/spec.rs b/crates/storage/provider/src/traits/spec.rs index 47d95fbd586b..917051d97aca 100644 --- a/crates/storage/provider/src/traits/spec.rs +++ b/crates/storage/provider/src/traits/spec.rs @@ -1,4 +1,4 @@ -use reth_primitives::ChainSpec; +use reth_chainspec::ChainSpec; use std::sync::Arc; /// A trait for reading the current chainspec. diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index 2c6f5bc1460a..36a0c2a138f0 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-execution-types.workspace = true reth-db-api.workspace = true reth-primitives.workspace = true diff --git a/crates/storage/storage-api/src/block_id.rs b/crates/storage/storage-api/src/block_id.rs index e648aa609eda..ca92b8a2f184 100644 --- a/crates/storage/storage-api/src/block_id.rs +++ b/crates/storage/storage-api/src/block_id.rs @@ -1,5 +1,6 @@ use crate::BlockHashReader; -use reth_primitives::{BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, ChainInfo, B256}; +use reth_chainspec::ChainInfo; +use reth_primitives::{BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, B256}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; /// Client trait for getting important block numbers (such as the latest block number), converting diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index e742c569b76a..03b5d1d5f21a 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-eth-wire-types.workspace = true reth-primitives.workspace = true reth-fs-util.workspace = true diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index e02e49ca9885..527c3412e1aa 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -79,7 +79,7 @@ //! Listen for new transactions and print them: //! //! ``` -//! use reth_primitives::MAINNET; +//! use reth_chainspec::MAINNET; //! use reth_provider::{BlockReaderIdExt, ChainSpecProvider, StateProviderFactory}; //! use reth_tasks::TokioTaskExecutor; //! use reth_transaction_pool::{TransactionValidationTaskExecutor, Pool, TransactionPool}; @@ -107,7 +107,7 @@ //! //! ``` //! use futures_util::Stream; -//! use reth_primitives::MAINNET; +//! use reth_chainspec::MAINNET; //! use reth_provider::{BlockReaderIdExt, CanonStateNotification, ChainSpecProvider, StateProviderFactory}; //! use reth_tasks::TokioTaskExecutor; //! use reth_tasks::TaskSpawner; @@ -285,7 +285,7 @@ where /// # Example /// /// ``` - /// use reth_primitives::MAINNET; + /// use reth_chainspec::MAINNET; /// use reth_provider::{BlockReaderIdExt, StateProviderFactory}; /// use reth_tasks::TokioTaskExecutor; /// use reth_transaction_pool::{ diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 02c259f9a0a9..99b29ed01289 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -681,8 +681,9 @@ mod tests { blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder, CoinbaseTipOrdering, EthPooledTransaction, Pool, PoolTransaction, TransactionOrigin, }; + use reth_chainspec::MAINNET; use reth_fs_util as fs; - use reth_primitives::{hex, PooledTransactionsElement, MAINNET, U256}; + use reth_primitives::{hex, PooledTransactionsElement, U256}; use reth_provider::test_utils::{ExtendedAccount, MockEthProvider}; use reth_tasks::TaskManager; diff --git a/crates/transaction-pool/src/test_utils/gen.rs b/crates/transaction-pool/src/test_utils/gen.rs index 87c2ba580d38..2be4d8aa9a9a 100644 --- a/crates/transaction-pool/src/test_utils/gen.rs +++ b/crates/transaction-pool/src/test_utils/gen.rs @@ -1,9 +1,10 @@ use crate::EthPooledTransaction; use rand::Rng; +use reth_chainspec::MAINNET; use reth_primitives::{ constants::MIN_PROTOCOL_BASE_FEE, sign_message, AccessList, Address, Bytes, Transaction, TransactionSigned, TryFromRecoveredTransaction, TxEip1559, TxEip4844, TxKind, TxLegacy, B256, - MAINNET, U256, + U256, }; /// A generator for transactions for testing purposes. diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 3e7e7d519a2f..3b3779cfdcb5 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -9,6 +9,7 @@ use crate::{ EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig, PoolTransaction, TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator, }; +use reth_chainspec::ChainSpec; use reth_primitives::{ constants::{ eip4844::{MAINNET_KZG_TRUSTED_SETUP, MAX_BLOBS_PER_BLOCK}, @@ -16,8 +17,8 @@ use reth_primitives::{ }, kzg::KzgSettings, revm::compat::calculate_intrinsic_gas_after_merge, - ChainSpec, GotExpected, InvalidTransactionError, SealedBlock, EIP1559_TX_TYPE_ID, - EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, + GotExpected, InvalidTransactionError, SealedBlock, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, + EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, }; use reth_provider::{AccountReader, BlockReaderIdExt, StateProviderFactory}; use reth_tasks::TaskSpawner; @@ -734,9 +735,8 @@ mod tests { blobstore::InMemoryBlobStore, error::PoolErrorKind, CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool, }; - use reth_primitives::{ - hex, FromRecoveredPooledTransaction, PooledTransactionsElement, MAINNET, U256, - }; + use reth_chainspec::MAINNET; + use reth_primitives::{hex, FromRecoveredPooledTransaction, PooledTransactionsElement, U256}; use reth_provider::test_utils::{ExtendedAccount, MockEthProvider}; fn get_transaction() -> EthPooledTransaction { diff --git a/crates/transaction-pool/src/validate/task.rs b/crates/transaction-pool/src/validate/task.rs index f8f98e3f4755..72ab1d81a13a 100644 --- a/crates/transaction-pool/src/validate/task.rs +++ b/crates/transaction-pool/src/validate/task.rs @@ -7,7 +7,8 @@ use crate::{ TransactionValidator, }; use futures_util::{lock::Mutex, StreamExt}; -use reth_primitives::{ChainSpec, SealedBlock}; +use reth_chainspec::ChainSpec; +use reth_primitives::SealedBlock; use reth_provider::BlockReaderIdExt; use reth_tasks::TaskSpawner; use std::{future::Future, pin::Pin, sync::Arc}; diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index d01914759a87..d4e9b856bed5 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -42,6 +42,7 @@ triehash = { version = "0.8", optional = true } [dev-dependencies] # reth +reth-chainspec.workspace = true reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } reth-db = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } diff --git a/crates/trie/trie/src/proof.rs b/crates/trie/trie/src/proof.rs index 19c8b7fabbc6..04e7952f5db7 100644 --- a/crates/trie/trie/src/proof.rs +++ b/crates/trie/trie/src/proof.rs @@ -164,8 +164,9 @@ mod tests { use super::*; use crate::StateRoot; use once_cell::sync::Lazy; + use reth_chainspec::{Chain, ChainSpec, HOLESKY, MAINNET}; use reth_db_api::database::Database; - use reth_primitives::{Account, Bytes, Chain, ChainSpec, StorageEntry, HOLESKY, MAINNET, U256}; + use reth_primitives::{Account, Bytes, StorageEntry, U256}; use reth_provider::{test_utils::create_test_provider_factory, HashingWriter, ProviderFactory}; use reth_storage_errors::provider::ProviderResult; use std::{str::FromStr, sync::Arc}; diff --git a/examples/bsc-p2p/Cargo.toml b/examples/bsc-p2p/Cargo.toml index 984130590cfd..1683676d873d 100644 --- a/examples/bsc-p2p/Cargo.toml +++ b/examples/bsc-p2p/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +reth-chainspec.workspace = true reth-discv4 = { workspace = true, features = ["test-utils"] } reth-network = { workspace = true, features = ["test-utils"] } reth-network-api.workspace = true diff --git a/examples/bsc-p2p/src/chainspec.rs b/examples/bsc-p2p/src/chainspec.rs index 65169c734155..d9c3a868297a 100644 --- a/examples/bsc-p2p/src/chainspec.rs +++ b/examples/bsc-p2p/src/chainspec.rs @@ -1,6 +1,5 @@ -use reth_primitives::{ - b256, BaseFeeParams, Chain, ChainSpec, ForkCondition, Hardfork, NodeRecord, B256, -}; +use reth_chainspec::{net::NodeRecord, BaseFeeParams, Chain, ChainSpec, ForkCondition, Hardfork}; +use reth_primitives::{b256, B256}; use std::{collections::BTreeMap, sync::Arc}; @@ -16,7 +15,7 @@ pub(crate) fn bsc_chain_spec() -> Arc { paris_block_and_final_difficulty: None, hardforks: BTreeMap::from([(Hardfork::Shanghai, ForkCondition::Timestamp(SHANGHAI_TIME))]), deposit_contract: None, - base_fee_params: reth_primitives::BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), + base_fee_params: reth_chainspec::BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 0, } .into() diff --git a/examples/custom-dev-node/Cargo.toml b/examples/custom-dev-node/Cargo.toml index 3cd624cea1fc..c84478e7e337 100644 --- a/examples/custom-dev-node/Cargo.toml +++ b/examples/custom-dev-node/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] reth.workspace = true +reth-chainspec.workspace = true reth-node-core.workspace = true reth-primitives.workspace = true reth-node-ethereum.workspace = true diff --git a/examples/custom-dev-node/src/main.rs b/examples/custom-dev-node/src/main.rs index 4788e02b8b15..498971dbd1b6 100644 --- a/examples/custom-dev-node/src/main.rs +++ b/examples/custom-dev-node/src/main.rs @@ -10,9 +10,10 @@ use reth::{ rpc::eth::EthTransactions, tasks::TaskManager, }; +use reth_chainspec::ChainSpec; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::EthereumNode; -use reth_primitives::{b256, hex, ChainSpec, Genesis}; +use reth_primitives::{b256, hex, Genesis}; use std::sync::Arc; #[tokio::main] diff --git a/examples/custom-engine-types/Cargo.toml b/examples/custom-engine-types/Cargo.toml index 7386313068a0..8012b50ce471 100644 --- a/examples/custom-engine-types/Cargo.toml +++ b/examples/custom-engine-types/Cargo.toml @@ -7,6 +7,7 @@ license.workspace = true [dependencies] reth.workspace = true +reth-chainspec.workspace = true reth-rpc-types.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index 7db6e0da4fca..07fd78c5ffc9 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -38,6 +38,7 @@ use reth_basic_payload_builder::{ BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig, BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig, }; +use reth_chainspec::{Chain, ChainSpec}; use reth_node_api::{ payload::{EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes}, validate_version_specific_fields, EngineTypes, PayloadAttributes, PayloadBuilderAttributes, @@ -50,7 +51,7 @@ use reth_payload_builder::{ error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes, PayloadBuilderHandle, PayloadBuilderService, }; -use reth_primitives::{Address, Chain, ChainSpec, Genesis, Header, Withdrawals, B256}; +use reth_primitives::{Address, Genesis, Header, Withdrawals, B256}; use reth_rpc_types::{ engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, diff --git a/examples/custom-evm/Cargo.toml b/examples/custom-evm/Cargo.toml index d1b5221ed567..b837eecfc446 100644 --- a/examples/custom-evm/Cargo.toml +++ b/examples/custom-evm/Cargo.toml @@ -7,6 +7,7 @@ license.workspace = true [dependencies] reth.workspace = true +reth-chainspec.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true reth-primitives.workspace = true diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 786e6241d70d..07147a52539d 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -17,10 +17,11 @@ use reth::{ }, tasks::TaskManager, }; +use reth_chainspec::{Chain, ChainSpec}; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider, EthereumNode}; -use reth_primitives::{Chain, ChainSpec, Genesis, Header, TransactionSigned}; +use reth_primitives::{Genesis, Header, TransactionSigned}; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; diff --git a/examples/custom-payload-builder/Cargo.toml b/examples/custom-payload-builder/Cargo.toml index 58fa775271c6..d29c444f1aae 100644 --- a/examples/custom-payload-builder/Cargo.toml +++ b/examples/custom-payload-builder/Cargo.toml @@ -7,6 +7,7 @@ license.workspace = true [dependencies] reth.workspace = true +reth-chainspec.workspace = true reth-primitives.workspace = true reth-node-api.workspace = true reth-basic-payload-builder.workspace = true diff --git a/examples/custom-payload-builder/src/generator.rs b/examples/custom-payload-builder/src/generator.rs index 288b20de43c0..a220315cb427 100644 --- a/examples/custom-payload-builder/src/generator.rs +++ b/examples/custom-payload-builder/src/generator.rs @@ -5,9 +5,10 @@ use reth::{ transaction_pool::TransactionPool, }; use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadBuilder, PayloadConfig}; +use reth_chainspec::ChainSpec; use reth_node_api::PayloadBuilderAttributes; use reth_payload_builder::{error::PayloadBuilderError, PayloadJobGenerator}; -use reth_primitives::{BlockNumberOrTag, Bytes, ChainSpec}; +use reth_primitives::{BlockNumberOrTag, Bytes}; use std::sync::Arc; /// The generator type that creates new jobs that builds empty blocks. diff --git a/examples/db-access/Cargo.toml b/examples/db-access/Cargo.toml index e447493c2783..32881590ce64 100644 --- a/examples/db-access/Cargo.toml +++ b/examples/db-access/Cargo.toml @@ -7,6 +7,7 @@ license.workspace = true [dependencies] +reth-chainspec.workspace = true reth-db.workspace = true reth-primitives.workspace = true reth-provider.workspace = true diff --git a/examples/db-access/src/main.rs b/examples/db-access/src/main.rs index c43aec47ce0c..27047fd3f8ec 100644 --- a/examples/db-access/src/main.rs +++ b/examples/db-access/src/main.rs @@ -1,5 +1,6 @@ +use reth_chainspec::ChainSpecBuilder; use reth_db::open_db_read_only; -use reth_primitives::{Address, ChainSpecBuilder, B256}; +use reth_primitives::{Address, B256}; use reth_provider::{ providers::StaticFileProvider, AccountReader, BlockReader, BlockSource, HeaderProvider, ProviderFactory, ReceiptProvider, StateProvider, TransactionsProvider, diff --git a/examples/exex/rollup/Cargo.toml b/examples/exex/rollup/Cargo.toml index e13113586110..738c90360a0d 100644 --- a/examples/exex/rollup/Cargo.toml +++ b/examples/exex/rollup/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] # reth reth.workspace = true +reth-chainspec.workspace = true reth-exex.workspace = true reth-node-api.workspace = true reth-node-ethereum.workspace = true diff --git a/examples/exex/rollup/src/main.rs b/examples/exex/rollup/src/main.rs index f1af0c1ae0f2..f8a2ffc401f6 100644 --- a/examples/exex/rollup/src/main.rs +++ b/examples/exex/rollup/src/main.rs @@ -8,13 +8,11 @@ use alloy_sol_types::{sol, SolEventInterface, SolInterface}; use db::Database; use execution::execute_block; use once_cell::sync::Lazy; +use reth_chainspec::{ChainSpec, ChainSpecBuilder}; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::FullNodeComponents; use reth_node_ethereum::EthereumNode; -use reth_primitives::{ - address, Address, ChainSpec, ChainSpecBuilder, Genesis, SealedBlockWithSenders, - TransactionSigned, U256, -}; +use reth_primitives::{address, Address, Genesis, SealedBlockWithSenders, TransactionSigned, U256}; use reth_provider::Chain; use reth_tracing::tracing::{error, info}; use rusqlite::Connection; diff --git a/examples/manual-p2p/Cargo.toml b/examples/manual-p2p/Cargo.toml index 5a9a999b9953..3876c490852c 100644 --- a/examples/manual-p2p/Cargo.toml +++ b/examples/manual-p2p/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true license.workspace = true [dependencies] +reth-chainspec.workspace = true reth-primitives.workspace = true reth-network.workspace = true reth-discv4.workspace = true diff --git a/examples/manual-p2p/src/main.rs b/examples/manual-p2p/src/main.rs index ba01307b211f..2b89b5539d4f 100644 --- a/examples/manual-p2p/src/main.rs +++ b/examples/manual-p2p/src/main.rs @@ -10,16 +10,15 @@ use std::time::Duration; use futures::StreamExt; use once_cell::sync::Lazy; +use reth_chainspec::{net::mainnet_nodes, Chain, MAINNET}; use reth_discv4::{DiscoveryUpdate, Discv4, Discv4ConfigBuilder, DEFAULT_DISCOVERY_ADDRESS}; use reth_ecies::stream::ECIESStream; use reth_eth_wire::{ EthMessage, EthStream, HelloMessage, P2PStream, Status, UnauthedEthStream, UnauthedP2PStream, }; use reth_network::config::rng_secret_key; -use reth_network_peers::pk2id; -use reth_primitives::{ - mainnet_nodes, Chain, Hardfork, Head, NodeRecord, MAINNET, MAINNET_GENESIS_HASH, -}; +use reth_network_peers::{pk2id, NodeRecord}; +use reth_primitives::{Hardfork, Head, MAINNET_GENESIS_HASH}; use secp256k1::{SecretKey, SECP256K1}; use tokio::net::TcpStream; diff --git a/examples/polygon-p2p/Cargo.toml b/examples/polygon-p2p/Cargo.toml index b1f5c9870839..d0813467c662 100644 --- a/examples/polygon-p2p/Cargo.toml +++ b/examples/polygon-p2p/Cargo.toml @@ -11,6 +11,7 @@ license.workspace = true secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } tokio.workspace = true reth-network.workspace = true +reth-chainspec.workspace = true reth-primitives.workspace = true serde_json.workspace = true reth-tracing.workspace = true diff --git a/examples/polygon-p2p/src/chain_cfg.rs b/examples/polygon-p2p/src/chain_cfg.rs index 5860cdb1d39d..b178d13499d7 100644 --- a/examples/polygon-p2p/src/chain_cfg.rs +++ b/examples/polygon-p2p/src/chain_cfg.rs @@ -1,6 +1,6 @@ -use reth_primitives::{ - b256, BaseFeeParams, Chain, ChainSpec, ForkCondition, Hardfork, Head, NodeRecord, B256, -}; +use reth_chainspec::{BaseFeeParams, Chain, ChainSpec, ForkCondition, Hardfork}; +use reth_discv4::NodeRecord; +use reth_primitives::{b256, Head, B256}; use std::{collections::BTreeMap, sync::Arc}; @@ -24,7 +24,7 @@ pub(crate) fn polygon_chain_spec() -> Arc { (Hardfork::Shanghai, ForkCondition::Block(SHANGAI_BLOCK)), ]), deposit_contract: None, - base_fee_params: reth_primitives::BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), + base_fee_params: reth_chainspec::BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 0, } .into() diff --git a/examples/rpc-db/Cargo.toml b/examples/rpc-db/Cargo.toml index 51f53cd39c29..0237ff7fb615 100644 --- a/examples/rpc-db/Cargo.toml +++ b/examples/rpc-db/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true futures.workspace = true jsonrpsee.workspace = true reth.workspace = true +reth-chainspec.workspace = true reth-db.workspace = true reth-db-api.workspace = true reth-node-ethereum.workspace = true diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index 732e4ad38135..96863d4f009f 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -13,13 +13,13 @@ //! ``` use reth::{ - primitives::ChainSpecBuilder, providers::{ providers::{BlockchainProvider, StaticFileProvider}, ProviderFactory, }, utils::db::open_db_read_only, }; +use reth_chainspec::ChainSpecBuilder; use reth_db::mdbx::DatabaseArguments; use reth_db_api::models::ClientVersion; diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index ea991402e0ce..74206884a126 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -16,6 +16,7 @@ ef-tests = [] asm-keccak = ["reth-primitives/asm-keccak"] [dependencies] +reth-chainspec.workspace = true reth-primitives.workspace = true reth-db = { workspace = true, features = ["mdbx", "test-utils", "disable-lock"] } reth-db-api.workspace = true diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index 53c3bf05ef2f..2c580dc54ea8 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -1,15 +1,15 @@ //! Shared models for use crate::{assert::assert_equal, Error}; +use reth_chainspec::{ChainSpec, ChainSpecBuilder}; use reth_db::tables; use reth_db_api::{ cursor::DbDupCursorRO, transaction::{DbTx, DbTxMut}, }; use reth_primitives::{ - keccak256, Account as RethAccount, Address, Bloom, Bytecode, Bytes, ChainSpec, - ChainSpecBuilder, Header as RethHeader, SealedHeader, StorageEntry, Withdrawals, B256, B64, - U256, + keccak256, Account as RethAccount, Address, Bloom, Bytecode, Bytes, Header as RethHeader, + SealedHeader, StorageEntry, Withdrawals, B256, B64, U256, }; use serde::Deserialize; use std::{collections::BTreeMap, ops::Deref}; From 2219d7d23e3b68118652ad5764005b56bb0dd1f3 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 17 Jun 2024 17:19:55 +0100 Subject: [PATCH 087/405] deps: bump alloy to 0.1 (#8892) --- Cargo.lock | 282 ++++++++++++++------------------ Cargo.toml | 58 +++---- examples/custom-evm/src/main.rs | 14 +- 3 files changed, 160 insertions(+), 194 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57902ffc7466..d6cb12085f7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,33 +121,20 @@ dependencies = [ "strum", ] -[[package]] -name = "alloy-consensus" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" -dependencies = [ - "alloy-eips 0.1.0", - "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.1.0", - "arbitrary", - "c-kzg", - "proptest", - "proptest-derive", - "serde", -] - [[package]] name = "alloy-consensus" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cc7579e4fb5558af44810f542c90d1145dba8b92c08211c215196160c48d2ea" dependencies = [ - "alloy-eips 0.1.1", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.1", + "alloy-serde", + "arbitrary", "c-kzg", + "proptest", + "proptest-derive", "serde", ] @@ -171,12 +158,13 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdbc8d98cc36ebe17bb5b42d0873137bc76628a4ee0f7e7acad5b8fc59d3597" dependencies = [ "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0", + "alloy-serde", "arbitrary", "c-kzg", "derive_more", @@ -188,27 +176,13 @@ dependencies = [ ] [[package]] -name = "alloy-eips" +name = "alloy-genesis" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdbc8d98cc36ebe17bb5b42d0873137bc76628a4ee0f7e7acad5b8fc59d3597" +checksum = "5e10a047066076b32d52b3228e95a4f7793db7a204f648aa1a1ea675085bffd8" dependencies = [ "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.1.1", - "c-kzg", - "once_cell", - "serde", - "sha2 0.10.8", -] - -[[package]] -name = "alloy-genesis" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" -dependencies = [ - "alloy-primitives", - "alloy-serde 0.1.0", + "alloy-serde", "serde", "serde_json", ] @@ -227,8 +201,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06d33b79246313c4103ef9596c721674a926f1ddc8b605aa2bac4d8ba94ee34" dependencies = [ "alloy-primitives", "serde", @@ -239,15 +214,16 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef742b478a2db5c27063cde82128dfbecffcd38237d7f682a91d3ecf6aa1836c" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-json-rpc", "alloy-primitives", - "alloy-rpc-types-eth 0.1.0", - "alloy-serde 0.1.0", + "alloy-rpc-types-eth", + "alloy-serde", "alloy-signer", "alloy-sol-types", "async-trait", @@ -258,8 +234,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4890cdebc750890b1cd3b5a61f72df992e262c5bfb5423b2999f0d81449f04e" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -300,12 +277,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200b786259a17acf318b9c423afe9669bec24ce9cdf59de153ff9a4009914bb6" dependencies = [ "alloy-chains", - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-json-rpc", "alloy-network", "alloy-primitives", @@ -313,7 +291,7 @@ dependencies = [ "alloy-rpc-client", "alloy-rpc-types-admin", "alloy-rpc-types-engine", - "alloy-rpc-types-eth 0.1.0", + "alloy-rpc-types-eth", "alloy-transport", "alloy-transport-http", "alloy-transport-ws", @@ -335,8 +313,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e6e6c1eab938a18a8e88d430cc9d548edf54c850a550873888285c85428eca" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -375,8 +354,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328a6a14aba6152ddf6d01bac5e17a70dbe9d6f343bf402b995c30bac63a1fbf" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -398,19 +378,21 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3164e7d8a718a22ede70b2c1d2bb554a8b4bd8e56c07ab630b75c74c06c53752" dependencies = [ "alloy-rpc-types-engine", - "alloy-rpc-types-eth 0.1.0", + "alloy-rpc-types-eth", "alloy-rpc-types-trace", - "alloy-serde 0.1.0", + "alloy-serde", ] [[package]] name = "alloy-rpc-types-admin" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b61b27c1a30ee71c751fe8a1b926d897e6795527bba2832f458acd432408df20" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -420,20 +402,22 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21cdf2617cfb0ae95ef462a0144abfef5ff56d25b56915d7a08f8a5a7b92926" dependencies = [ "alloy-primitives", - "alloy-serde 0.1.0", + "alloy-serde", "serde", ] [[package]] name = "alloy-rpc-types-beacon" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a3c7fd58772e4eadec5f08153573aa0ce8d998bd03f239ca74cd2927022d4c" dependencies = [ - "alloy-eips 0.1.0", + "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "serde", @@ -443,15 +427,16 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c3de574f90d9b939e3ee666a74bea29fb1a2ae66f1569b111bb6a922b1c762" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types-eth 0.1.0", - "alloy-serde 0.1.0", + "alloy-rpc-types-eth", + "alloy-serde", "jsonrpsee-types", "jsonwebtoken", "rand 0.8.5", @@ -461,14 +446,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce0676f144be1eae71122d1d417885a3b063add0353b35e46cdf1440d6b33b1" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.1.0", + "alloy-serde", "alloy-sol-types", "arbitrary", "itertools 0.13.0", @@ -481,50 +467,35 @@ dependencies = [ ] [[package]] -name = "alloy-rpc-types-eth" +name = "alloy-rpc-types-trace" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bce0676f144be1eae71122d1d417885a3b063add0353b35e46cdf1440d6b33b1" +checksum = "a39c52613dc4d9995ff284b496158594ae63f9bfc58b5ef04e48ec5da2e3d747" dependencies = [ - "alloy-consensus 0.1.1", - "alloy-eips 0.1.1", "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.1.1", - "alloy-sol-types", - "itertools 0.13.0", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "alloy-rpc-types-trace" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" -dependencies = [ - "alloy-primitives", - "alloy-rpc-types-eth 0.1.0", - "alloy-serde 0.1.0", + "alloy-rpc-types-eth", + "alloy-serde", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-txpool" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aeb7995e8859f3931b6199e13a533c9fde89affa900addb7218db2f15f9687d" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth 0.1.0", - "alloy-serde 0.1.0", + "alloy-rpc-types-eth", + "alloy-serde", "serde", ] [[package]] name = "alloy-serde" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c224916316519558d8c2b6a60dc7626688c08f1b8951774702562dbcb8666ee" dependencies = [ "alloy-primitives", "arbitrary", @@ -535,20 +506,10 @@ dependencies = [ ] [[package]] -name = "alloy-serde" +name = "alloy-signer" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c224916316519558d8c2b6a60dc7626688c08f1b8951774702562dbcb8666ee" -dependencies = [ - "alloy-primitives", - "serde", - "serde_json", -] - -[[package]] -name = "alloy-signer" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +checksum = "227c5fd0ed6e06e1ccc30593f8ff6d9fb907ac5f03a709a6d687f0943494a229" dependencies = [ "alloy-primitives", "async-trait", @@ -560,10 +521,11 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c44057ac1e8707f8c6a983db9f83ac1265c9e05be81d432acf2aad2880e1c0" dependencies = [ - "alloy-consensus 0.1.0", + "alloy-consensus", "alloy-network", "alloy-primitives", "alloy-signer", @@ -649,8 +611,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3628d81530263fe837a09cd527022f5728202a669973f04270942f4d390b5f5" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -666,8 +629,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f35d34e7a51503c9ff267404a5850bd58f991b7ab524b892f364901e3576376" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -680,8 +644,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d2f106151a583f7d258fe8cc846c5196da90a9f502d4b3516c56d63e1f25a2" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -698,8 +663,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy?rev=6cb3713#6cb3713a14c047cd5fcf17b99d27055341c41aaf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a80da44d3709c4ceaf47745ad820eae8f121404b9ffd8e285522ac4eb06681" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -2963,7 +2929,7 @@ dependencies = [ name = "exex-rollup" version = "0.0.0" dependencies = [ - "alloy-consensus 0.1.0", + "alloy-consensus", "alloy-rlp", "alloy-sol-types", "eyre", @@ -3123,10 +3089,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "195bb5b228e1215c50d828f3e7d48a809a0af2bc0120462710ea5e7fcba3cbe2" dependencies = [ "alloy-chains", - "alloy-eips 0.1.1", + "alloy-eips", "alloy-primitives", - "alloy-rpc-types-eth 0.1.1", - "alloy-serde 0.1.1", + "alloy-rpc-types-eth", + "alloy-serde", "chrono", "reqwest", "serde", @@ -6433,8 +6399,8 @@ dependencies = [ name = "reth-bench" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-json-rpc", "alloy-provider", "alloy-pubsub", @@ -6518,7 +6484,7 @@ name = "reth-chainspec" version = "1.0.0-rc.1" dependencies = [ "alloy-chains", - "alloy-eips 0.1.0", + "alloy-eips", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -6549,8 +6515,8 @@ dependencies = [ name = "reth-codecs" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-genesis", "alloy-primitives", "arbitrary", @@ -6613,8 +6579,8 @@ dependencies = [ name = "reth-consensus-debug-client" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-provider", "auto_impl", "eyre", @@ -6838,7 +6804,7 @@ dependencies = [ name = "reth-e2e-test-utils" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0", + "alloy-consensus", "alloy-network", "alloy-rpc-types", "alloy-signer", @@ -7064,7 +7030,7 @@ dependencies = [ name = "reth-evm-ethereum" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0", + "alloy-eips", "alloy-sol-types", "reth-chainspec", "reth-ethereum-consensus", @@ -7102,7 +7068,7 @@ dependencies = [ name = "reth-execution-errors" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0", + "alloy-eips", "alloy-primitives", "reth-consensus", "reth-prune-types", @@ -7115,7 +7081,7 @@ dependencies = [ name = "reth-execution-types" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0", + "alloy-eips", "alloy-primitives", "reth-chainspec", "reth-execution-errors", @@ -7722,8 +7688,8 @@ dependencies = [ name = "reth-primitives" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -7769,12 +7735,12 @@ dependencies = [ name = "reth-primitives-traits" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0", - "alloy-eips 0.1.0", + "alloy-consensus", + "alloy-eips", "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types-eth 0.1.0", + "alloy-rpc-types-eth", "arbitrary", "bytes", "derive_more", @@ -7882,7 +7848,7 @@ dependencies = [ name = "reth-revm" version = "1.0.0-rc.1" dependencies = [ - "alloy-eips 0.1.0", + "alloy-eips", "alloy-rlp", "reth-chainspec", "reth-consensus-common", @@ -8095,7 +8061,7 @@ dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 0.1.0", + "alloy-serde", "arbitrary", "bytes", "jsonrpsee-types", @@ -8386,7 +8352,7 @@ dependencies = [ name = "reth-trie-common" version = "1.0.0-rc.1" dependencies = [ - "alloy-consensus 0.1.0", + "alloy-consensus", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -8438,7 +8404,7 @@ dependencies = [ [[package]] name = "revm" version = "9.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "auto_impl", "cfg-if", @@ -8452,7 +8418,7 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=79774a6#79774a6e4add9da9247130bf73305531092d0895" +source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=4fe17f0#4fe17f08797450d9d5df315e724d14c9f3749b3f" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -8469,7 +8435,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "5.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "revm-primitives", "serde", @@ -8478,7 +8444,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "7.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "aurora-engine-modexp", "blst", @@ -8496,7 +8462,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=dd98b3b#dd98b3bb977396d23966e0a2f40d97678d931573" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "alloy-primitives", "auto_impl", diff --git a/Cargo.toml b/Cargo.toml index 63d297ab88d0..7a8183eb855a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -346,7 +346,7 @@ revm = { version = "9.0.0", features = [ revm-primitives = { version = "4.0.0", features = [ "std", ], default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "79774a6" } +revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "4fe17f0" } # eth alloy-chains = "0.1.15" @@ -355,36 +355,36 @@ alloy-dyn-abi = "0.7.2" alloy-sol-types = "0.7.2" alloy-rlp = "0.3.4" alloy-trie = "0.4" -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false, features = [ +alloy-rpc-types = { version = "0.1", default-features = false, features = [ "eth", ] } -alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false, features = [ +alloy-rpc-types-anvil = { version = "0.1", default-features = false } +alloy-rpc-types-beacon = { version = "0.1", default-features = false } +alloy-rpc-types-admin = { version = "0.1", default-features = false } +alloy-rpc-types-txpool = { version = "0.1", default-features = false } +alloy-serde = { version = "0.1", default-features = false } +alloy-rpc-types-engine = { version = "0.1", default-features = false } +alloy-rpc-types-eth = { version = "0.1", default-features = false } +alloy-rpc-types-trace = { version = "0.1", default-features = false } +alloy-genesis = { version = "0.1", default-features = false } +alloy-node-bindings = { version = "0.1", default-features = false } +alloy-provider = { version = "0.1", default-features = false, features = [ "reqwest", ] } -alloy-eips = { git = "https://github.com/alloy-rs/alloy", default-features = false, rev = "6cb3713" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", features = [ +alloy-eips = { version = "0.1", default-features = false } +alloy-signer = { version = "0.1", default-features = false } +alloy-signer-local = { version = "0.1", default-features = false } +alloy-network = { version = "0.1", default-features = false } +alloy-consensus = { version = "0.1", default-features = false } +alloy-transport = { version = "0.1" } +alloy-transport-http = { version = "0.1", features = [ "reqwest-rustls-tls", ], default-features = false } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "6cb3713", default-features = false } +alloy-transport-ws = { version = "0.1", default-features = false } +alloy-transport-ipc = { version = "0.1", default-features = false } +alloy-pubsub = { version = "0.1", default-features = false } +alloy-json-rpc = { version = "0.1", default-features = false } +alloy-rpc-client = { version = "0.1", default-features = false } # misc auto_impl = "1" @@ -499,7 +499,7 @@ similar-asserts = "1.5.0" test-fuzz = "5" [patch.crates-io] -revm = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } -revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } -revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } -revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "dd98b3b" } +revm = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } +revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } +revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } +revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 07147a52539d..f5751a3c63a1 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -12,8 +12,8 @@ use reth::{ revm::{ handler::register::EvmHandler, inspector_handle_register, - precompile::{Precompile, PrecompileOutput, PrecompileSpecId, Precompiles}, - Database, Evm, EvmBuilder, GetInspector, + precompile::{Precompile, PrecompileOutput, PrecompileSpecId}, + ContextPrecompiles, Database, Evm, EvmBuilder, GetInspector, }, tasks::TaskManager, }; @@ -46,12 +46,12 @@ impl MyEvmConfig { // install the precompiles handler.pre_execution.load_precompiles = Arc::new(move || { - let mut precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).clone(); - precompiles.inner.insert( + let mut precompiles = ContextPrecompiles::new(PrecompileSpecId::from_spec_id(spec_id)); + precompiles.extend([( address!("0000000000000000000000000000000000000999"), - Precompile::Env(Self::my_precompile), - ); - precompiles.into() + Precompile::Env(Self::my_precompile).into(), + )]); + precompiles }); } From 8182d2e632b80e390c0eecd64255bcc0c5726130 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:28:12 +0200 Subject: [PATCH 088/405] chore: remove `reth-primitives` dependency from `reth-prune` (#8897) --- Cargo.lock | 2 +- crates/prune/prune/Cargo.toml | 2 +- crates/prune/prune/src/pruner.rs | 3 ++- crates/prune/prune/src/segments/account_history.rs | 2 +- crates/prune/prune/src/segments/headers.rs | 2 +- crates/prune/prune/src/segments/history.rs | 2 +- crates/prune/prune/src/segments/receipts.rs | 2 +- crates/prune/prune/src/segments/receipts_by_logs.rs | 2 +- crates/prune/prune/src/segments/sender_recovery.rs | 2 +- crates/prune/prune/src/segments/storage_history.rs | 2 +- crates/prune/prune/src/segments/transaction_lookup.rs | 2 +- crates/prune/prune/src/segments/transactions.rs | 2 +- 12 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6cb12085f7d..a6e31b062ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7812,10 +7812,10 @@ dependencies = [ "reth-errors", "reth-exex-types", "reth-metrics", - "reth-primitives", "reth-provider", "reth-prune-types", "reth-stages", + "reth-static-file-types", "reth-testing-utils", "reth-tokio-util", "reth-tracing", diff --git a/crates/prune/prune/Cargo.toml b/crates/prune/prune/Cargo.toml index c45ea9d3f7f1..b5d9059c95b8 100644 --- a/crates/prune/prune/Cargo.toml +++ b/crates/prune/prune/Cargo.toml @@ -15,7 +15,6 @@ workspace = true # reth reth-chainspec.workspace = true reth-exex-types.workspace = true -reth-primitives.workspace = true reth-db.workspace = true reth-db-api.workspace = true reth-errors.workspace = true @@ -23,6 +22,7 @@ reth-provider.workspace = true reth-tokio-util.workspace = true reth-config.workspace = true reth-prune-types.workspace = true +reth-static-file-types.workspace = true # metrics reth-metrics.workspace = true diff --git a/crates/prune/prune/src/pruner.rs b/crates/prune/prune/src/pruner.rs index 47864a274e03..656aa69adeb2 100644 --- a/crates/prune/prune/src/pruner.rs +++ b/crates/prune/prune/src/pruner.rs @@ -5,13 +5,14 @@ use crate::{ segments::{PruneInput, Segment}, Metrics, PrunerError, PrunerEvent, }; +use alloy_primitives::BlockNumber; use reth_db_api::database::Database; use reth_exex_types::FinishedExExHeight; -use reth_primitives::{BlockNumber, StaticFileSegment}; use reth_provider::{ DatabaseProviderRW, ProviderFactory, PruneCheckpointReader, StaticFileProviderFactory, }; use reth_prune_types::{PruneLimiter, PruneMode, PruneProgress, PrunePurpose, PruneSegment}; +use reth_static_file_types::StaticFileSegment; use reth_tokio_util::{EventSender, EventStream}; use std::{ collections::BTreeMap, diff --git a/crates/prune/prune/src/segments/account_history.rs b/crates/prune/prune/src/segments/account_history.rs index 78ca36219d04..ab2800a31718 100644 --- a/crates/prune/prune/src/segments/account_history.rs +++ b/crates/prune/prune/src/segments/account_history.rs @@ -106,9 +106,9 @@ mod tests { account_history::ACCOUNT_HISTORY_TABLES_TO_PRUNE, AccountHistory, PruneInput, PruneOutput, Segment, }; + use alloy_primitives::{BlockNumber, B256}; use assert_matches::assert_matches; use reth_db::{tables, BlockNumberList}; - use reth_primitives::{BlockNumber, B256}; use reth_provider::PruneCheckpointReader; use reth_prune_types::{ PruneCheckpoint, PruneInterruptReason, PruneLimiter, PruneMode, PruneProgress, PruneSegment, diff --git a/crates/prune/prune/src/segments/headers.rs b/crates/prune/prune/src/segments/headers.rs index 3cf2beeba2e3..1c49fe8c1086 100644 --- a/crates/prune/prune/src/segments/headers.rs +++ b/crates/prune/prune/src/segments/headers.rs @@ -188,10 +188,10 @@ where #[cfg(test)] mod tests { + use alloy_primitives::{BlockNumber, B256, U256}; use assert_matches::assert_matches; use reth_db::tables; use reth_db_api::transaction::DbTx; - use reth_primitives::{BlockNumber, B256, U256}; use reth_provider::PruneCheckpointReader; use reth_prune_types::{ PruneCheckpoint, PruneInterruptReason, PruneLimiter, PruneMode, PruneProgress, PruneSegment, diff --git a/crates/prune/prune/src/segments/history.rs b/crates/prune/prune/src/segments/history.rs index 253d453aa597..ee841ef89717 100644 --- a/crates/prune/prune/src/segments/history.rs +++ b/crates/prune/prune/src/segments/history.rs @@ -1,3 +1,4 @@ +use alloy_primitives::BlockNumber; use reth_db::BlockNumberList; use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW}, @@ -7,7 +8,6 @@ use reth_db_api::{ transaction::DbTxMut, DatabaseError, }; -use reth_primitives::BlockNumber; use reth_provider::DatabaseProviderRW; /// Prune history indices up to the provided block, inclusive. diff --git a/crates/prune/prune/src/segments/receipts.rs b/crates/prune/prune/src/segments/receipts.rs index ef6e502c7e9f..5b8118778898 100644 --- a/crates/prune/prune/src/segments/receipts.rs +++ b/crates/prune/prune/src/segments/receipts.rs @@ -94,13 +94,13 @@ impl Segment for Receipts { #[cfg(test)] mod tests { use crate::segments::{PruneInput, PruneOutput, Receipts, Segment}; + use alloy_primitives::{BlockNumber, TxNumber, B256}; use assert_matches::assert_matches; use itertools::{ FoldWhile::{Continue, Done}, Itertools, }; use reth_db::tables; - use reth_primitives::{BlockNumber, TxNumber, B256}; use reth_provider::PruneCheckpointReader; use reth_prune_types::{ PruneCheckpoint, PruneInterruptReason, PruneLimiter, PruneMode, PruneProgress, PruneSegment, diff --git a/crates/prune/prune/src/segments/receipts_by_logs.rs b/crates/prune/prune/src/segments/receipts_by_logs.rs index f4ccad810ce3..63b5941c34b5 100644 --- a/crates/prune/prune/src/segments/receipts_by_logs.rs +++ b/crates/prune/prune/src/segments/receipts_by_logs.rs @@ -216,10 +216,10 @@ impl Segment for ReceiptsByLogs { #[cfg(test)] mod tests { use crate::segments::{receipts_by_logs::ReceiptsByLogs, PruneInput, Segment}; + use alloy_primitives::B256; use assert_matches::assert_matches; use reth_db::tables; use reth_db_api::{cursor::DbCursorRO, transaction::DbTx}; - use reth_primitives::B256; use reth_provider::{PruneCheckpointReader, TransactionsProvider}; use reth_prune_types::{PruneLimiter, PruneMode, PruneSegment, ReceiptsLogPruneConfig}; use reth_stages::test_utils::{StorageKind, TestStageDB}; diff --git a/crates/prune/prune/src/segments/sender_recovery.rs b/crates/prune/prune/src/segments/sender_recovery.rs index f13dfde254a4..94ef6ffb7c78 100644 --- a/crates/prune/prune/src/segments/sender_recovery.rs +++ b/crates/prune/prune/src/segments/sender_recovery.rs @@ -77,13 +77,13 @@ impl Segment for SenderRecovery { #[cfg(test)] mod tests { use crate::segments::{PruneInput, PruneOutput, Segment, SenderRecovery}; + use alloy_primitives::{BlockNumber, TxNumber, B256}; use assert_matches::assert_matches; use itertools::{ FoldWhile::{Continue, Done}, Itertools, }; use reth_db::tables; - use reth_primitives::{BlockNumber, TxNumber, B256}; use reth_provider::PruneCheckpointReader; use reth_prune_types::{PruneCheckpoint, PruneLimiter, PruneMode, PruneProgress, PruneSegment}; use reth_stages::test_utils::{StorageKind, TestStageDB}; diff --git a/crates/prune/prune/src/segments/storage_history.rs b/crates/prune/prune/src/segments/storage_history.rs index 18af8134cf5f..3e7ad86a7da4 100644 --- a/crates/prune/prune/src/segments/storage_history.rs +++ b/crates/prune/prune/src/segments/storage_history.rs @@ -109,9 +109,9 @@ mod tests { storage_history::STORAGE_HISTORY_TABLES_TO_PRUNE, PruneInput, PruneOutput, Segment, StorageHistory, }; + use alloy_primitives::{BlockNumber, B256}; use assert_matches::assert_matches; use reth_db::{tables, BlockNumberList}; - use reth_primitives::{BlockNumber, B256}; use reth_provider::PruneCheckpointReader; use reth_prune_types::{PruneCheckpoint, PruneLimiter, PruneMode, PruneProgress, PruneSegment}; use reth_stages::test_utils::{StorageKind, TestStageDB}; diff --git a/crates/prune/prune/src/segments/transaction_lookup.rs b/crates/prune/prune/src/segments/transaction_lookup.rs index b77b0193329c..457f551c167b 100644 --- a/crates/prune/prune/src/segments/transaction_lookup.rs +++ b/crates/prune/prune/src/segments/transaction_lookup.rs @@ -104,13 +104,13 @@ impl Segment for TransactionLookup { #[cfg(test)] mod tests { use crate::segments::{PruneInput, PruneOutput, Segment, TransactionLookup}; + use alloy_primitives::{BlockNumber, TxNumber, B256}; use assert_matches::assert_matches; use itertools::{ FoldWhile::{Continue, Done}, Itertools, }; use reth_db::tables; - use reth_primitives::{BlockNumber, TxNumber, B256}; use reth_provider::PruneCheckpointReader; use reth_prune_types::{ PruneCheckpoint, PruneInterruptReason, PruneLimiter, PruneMode, PruneProgress, PruneSegment, diff --git a/crates/prune/prune/src/segments/transactions.rs b/crates/prune/prune/src/segments/transactions.rs index 5a7305a1b50a..fd24a380de5b 100644 --- a/crates/prune/prune/src/segments/transactions.rs +++ b/crates/prune/prune/src/segments/transactions.rs @@ -76,13 +76,13 @@ impl Segment for Transactions { #[cfg(test)] mod tests { use crate::segments::{PruneInput, PruneOutput, Segment, Transactions}; + use alloy_primitives::{BlockNumber, TxNumber, B256}; use assert_matches::assert_matches; use itertools::{ FoldWhile::{Continue, Done}, Itertools, }; use reth_db::tables; - use reth_primitives::{BlockNumber, TxNumber, B256}; use reth_provider::PruneCheckpointReader; use reth_prune_types::{ PruneCheckpoint, PruneInterruptReason, PruneLimiter, PruneMode, PruneProgress, PruneSegment, From 20102ee4aa5948ab7ae82c404f7d1ed0a8f9efaf Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:38:33 +0200 Subject: [PATCH 089/405] chore: remove `reth-primitives` dependency from `reth-node-ethereum` tests (#8898) --- Cargo.lock | 3 ++- crates/ethereum/node/Cargo.toml | 3 ++- crates/ethereum/node/tests/e2e/blobs.rs | 3 ++- crates/ethereum/node/tests/e2e/dev.rs | 3 ++- crates/ethereum/node/tests/e2e/eth.rs | 2 +- crates/ethereum/node/tests/e2e/utils.rs | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6e31b062ab3..fd04abebe7df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7508,6 +7508,8 @@ dependencies = [ name = "reth-node-ethereum" version = "1.0.0-rc.1" dependencies = [ + "alloy-genesis", + "alloy-primitives", "eyre", "futures", "futures-util", @@ -7528,7 +7530,6 @@ dependencies = [ "reth-node-builder", "reth-node-core", "reth-payload-builder", - "reth-primitives", "reth-provider", "reth-tracing", "reth-transaction-pool", diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index a8880de1b6e9..54e54a0ebb76 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -36,8 +36,9 @@ reth-db.workspace = true reth-exex.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true -reth-primitives.workspace = true reth-e2e-test-utils.workspace = true +alloy-primitives.workspace = true +alloy-genesis.workspace = true futures.workspace = true tokio.workspace = true futures-util.workspace = true diff --git a/crates/ethereum/node/tests/e2e/blobs.rs b/crates/ethereum/node/tests/e2e/blobs.rs index 60785f3226ad..9390b34f444a 100644 --- a/crates/ethereum/node/tests/e2e/blobs.rs +++ b/crates/ethereum/node/tests/e2e/blobs.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use alloy_genesis::Genesis; +use alloy_primitives::b256; use reth::{ args::RpcServerArgs, builder::{NodeBuilder, NodeConfig, NodeHandle}, @@ -11,7 +13,6 @@ use reth_e2e_test_utils::{ node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet, }; use reth_node_ethereum::EthereumNode; -use reth_primitives::{b256, Genesis}; use reth_transaction_pool::TransactionPool; use crate::utils::eth_payload_attributes; diff --git a/crates/ethereum/node/tests/e2e/dev.rs b/crates/ethereum/node/tests/e2e/dev.rs index 0c3b3bc72560..990c6f0bf2b0 100644 --- a/crates/ethereum/node/tests/e2e/dev.rs +++ b/crates/ethereum/node/tests/e2e/dev.rs @@ -1,9 +1,10 @@ use crate::utils::EthNode; +use alloy_genesis::Genesis; +use alloy_primitives::{b256, hex}; use futures::StreamExt; use reth::rpc::eth::EthTransactions; use reth_chainspec::ChainSpec; use reth_e2e_test_utils::setup; -use reth_primitives::{b256, hex, Genesis}; use reth_provider::CanonStateSubscriptions; use std::sync::Arc; diff --git a/crates/ethereum/node/tests/e2e/eth.rs b/crates/ethereum/node/tests/e2e/eth.rs index ecbbe8f5d696..8e6938b47fe4 100644 --- a/crates/ethereum/node/tests/e2e/eth.rs +++ b/crates/ethereum/node/tests/e2e/eth.rs @@ -1,4 +1,5 @@ use crate::utils::eth_payload_attributes; +use alloy_genesis::Genesis; use reth::{ args::RpcServerArgs, builder::{NodeBuilder, NodeConfig, NodeHandle}, @@ -9,7 +10,6 @@ use reth_e2e_test_utils::{ node::NodeTestContext, setup, transaction::TransactionTestContext, wallet::Wallet, }; use reth_node_ethereum::EthereumNode; -use reth_primitives::Genesis; use std::sync::Arc; #[tokio::test] diff --git a/crates/ethereum/node/tests/e2e/utils.rs b/crates/ethereum/node/tests/e2e/utils.rs index 2c1dc373b82e..001cf02ce017 100644 --- a/crates/ethereum/node/tests/e2e/utils.rs +++ b/crates/ethereum/node/tests/e2e/utils.rs @@ -1,8 +1,8 @@ +use alloy_primitives::{Address, B256}; use reth::rpc::types::engine::PayloadAttributes; use reth_e2e_test_utils::NodeHelperType; use reth_node_ethereum::EthereumNode; use reth_payload_builder::EthPayloadBuilderAttributes; -use reth_primitives::{Address, B256}; /// Ethereum Node Helper type pub(crate) type EthNode = NodeHelperType; From ed9f36b54f0ec2a302a819a996cd1abb07296c07 Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 18 Jun 2024 01:38:54 +0800 Subject: [PATCH 090/405] feat: calc block base reward use block number only (#8876) Signed-off-by: jsvisa Co-authored-by: Matthias Seitz --- crates/chainspec/src/spec.rs | 7 +++++++ crates/consensus/common/src/calc.rs | 22 ++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 2462da93a12c..aa9e90f8c470 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -853,6 +853,13 @@ impl ChainSpec { self.fork(Hardfork::Homestead).active_at_block(block_number) } + /// The Paris hardfork (merge) is activated via ttd. If we have knowledge of the block, this + /// function will return true if the block number is greater than or equal to the Paris + /// (merge) block. + pub fn is_paris_active_at_block(&self, block_number: u64) -> Option { + self.paris_block_and_final_difficulty.map(|(paris_block, _)| block_number >= paris_block) + } + /// Convenience method to check if [`Hardfork::Bedrock`] is active at a given block number. #[cfg(feature = "optimism")] #[inline] diff --git a/crates/consensus/common/src/calc.rs b/crates/consensus/common/src/calc.rs index 52206bbfd0ca..27320700b33e 100644 --- a/crates/consensus/common/src/calc.rs +++ b/crates/consensus/common/src/calc.rs @@ -1,5 +1,6 @@ use reth_chainspec::{Chain, ChainSpec, Hardfork}; use reth_primitives::{constants::ETH_TO_WEI, BlockNumber, U256}; + /// Calculates the base block reward. /// /// The base block reward is defined as: @@ -25,16 +26,25 @@ pub fn base_block_reward( block_difficulty: U256, total_difficulty: U256, ) -> Option { - if chain_spec.chain == Chain::goerli() || - chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, block_difficulty) + if chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, block_difficulty) || + chain_spec.chain == Chain::goerli() { None - } else if chain_spec.fork(Hardfork::Constantinople).active_at_block(block_number) { - Some(ETH_TO_WEI * 2) + } else { + Some(base_block_reward_pre_merge(chain_spec, block_number)) + } +} + +/// Calculates the base block reward __before__ the merge (Paris hardfork). +/// +/// Caution: The caller must ensure that the block number is before the merge. +pub fn base_block_reward_pre_merge(chain_spec: &ChainSpec, block_number: BlockNumber) -> u128 { + if chain_spec.fork(Hardfork::Constantinople).active_at_block(block_number) { + ETH_TO_WEI * 2 } else if chain_spec.fork(Hardfork::Byzantium).active_at_block(block_number) { - Some(ETH_TO_WEI * 3) + ETH_TO_WEI * 3 } else { - Some(ETH_TO_WEI * 5) + ETH_TO_WEI * 5 } } From f967f9eb40fe5b753f12f76f1e04a211f502cfb7 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 17 Jun 2024 19:39:18 +0200 Subject: [PATCH 091/405] feat: replace engine types in payload stack (#8893) --- Cargo.lock | 1 - crates/payload/builder/Cargo.toml | 1 - crates/payload/builder/src/events.rs | 8 +++---- crates/payload/builder/src/noop.rs | 9 ++++---- crates/payload/builder/src/service.rs | 29 ++++++++++++------------ crates/payload/builder/src/test_utils.rs | 6 ++--- 6 files changed, 25 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd04abebe7df..3bf7e7f4c233 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7644,7 +7644,6 @@ version = "1.0.0-rc.1" dependencies = [ "futures-util", "metrics", - "reth-engine-primitives", "reth-errors", "reth-ethereum-engine-primitives", "reth-metrics", diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index ce82ae7ff8a7..735831e41ca1 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -18,7 +18,6 @@ reth-rpc-types.workspace = true reth-transaction-pool.workspace = true reth-errors.workspace = true reth-provider.workspace = true -reth-engine-primitives.workspace = true reth-payload-primitives.workspace = true reth-ethereum-engine-primitives.workspace = true diff --git a/crates/payload/builder/src/events.rs b/crates/payload/builder/src/events.rs index 4df81030fca8..271eb2267ec4 100644 --- a/crates/payload/builder/src/events.rs +++ b/crates/payload/builder/src/events.rs @@ -1,4 +1,4 @@ -use reth_engine_primitives::EngineTypes; +use reth_payload_primitives::PayloadTypes; use tokio::sync::broadcast; use tokio_stream::{ wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}, @@ -7,7 +7,7 @@ use tokio_stream::{ /// Payload builder events. #[derive(Clone, Debug)] -pub enum Events { +pub enum Events { /// The payload attributes as /// they are received from the CL through the engine api. Attributes(Engine::PayloadBuilderAttributes), @@ -19,11 +19,11 @@ pub enum Events { /// Represents a receiver for various payload events. #[derive(Debug)] -pub struct PayloadEvents { +pub struct PayloadEvents { pub receiver: broadcast::Receiver>, } -impl PayloadEvents { +impl PayloadEvents { // Convert this receiver into a stream of PayloadEvents. pub fn into_stream(self) -> BroadcastStream> { BroadcastStream::new(self.receiver) diff --git a/crates/payload/builder/src/noop.rs b/crates/payload/builder/src/noop.rs index ef919ecf76e8..91ab90732166 100644 --- a/crates/payload/builder/src/noop.rs +++ b/crates/payload/builder/src/noop.rs @@ -2,8 +2,7 @@ use crate::{service::PayloadServiceCommand, PayloadBuilderHandle}; use futures_util::{ready, StreamExt}; -use reth_engine_primitives::EngineTypes; -use reth_payload_primitives::PayloadBuilderAttributes; +use reth_payload_primitives::{PayloadBuilderAttributes, PayloadTypes}; use std::{ future::Future, pin::Pin, @@ -14,14 +13,14 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// A service task that does not build any payloads. #[derive(Debug)] -pub struct NoopPayloadBuilderService { +pub struct NoopPayloadBuilderService { /// Receiver half of the command channel. command_rx: UnboundedReceiverStream>, } impl NoopPayloadBuilderService where - Engine: EngineTypes + 'static, + Engine: PayloadTypes + 'static, { /// Creates a new [`NoopPayloadBuilderService`]. pub fn new() -> (Self, PayloadBuilderHandle) { @@ -35,7 +34,7 @@ where impl Future for NoopPayloadBuilderService where - Engine: EngineTypes, + Engine: PayloadTypes, { type Output = (); diff --git a/crates/payload/builder/src/service.rs b/crates/payload/builder/src/service.rs index 98790ef7d8b7..8946cd587506 100644 --- a/crates/payload/builder/src/service.rs +++ b/crates/payload/builder/src/service.rs @@ -11,8 +11,7 @@ use crate::{ KeepPayloadJobAlive, PayloadJob, }; use futures_util::{future::FutureExt, Stream, StreamExt}; -use reth_engine_primitives::EngineTypes; -use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; +use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes, PayloadTypes}; use reth_provider::CanonStateNotification; use reth_rpc_types::engine::PayloadId; use std::{ @@ -32,7 +31,7 @@ type PayloadFuture

= Pin { +pub struct PayloadStore { inner: PayloadBuilderHandle, } @@ -40,7 +39,7 @@ pub struct PayloadStore { impl PayloadStore where - Engine: EngineTypes + 'static, + Engine: PayloadTypes + 'static, { /// Resolves the payload job and returns the best payload that has been built so far. /// @@ -76,7 +75,7 @@ where impl Clone for PayloadStore where - Engine: EngineTypes, + Engine: PayloadTypes, { fn clone(&self) -> Self { Self { inner: self.inner.clone() } @@ -85,7 +84,7 @@ where impl From> for PayloadStore where - Engine: EngineTypes, + Engine: PayloadTypes, { fn from(inner: PayloadBuilderHandle) -> Self { Self { inner } @@ -96,7 +95,7 @@ where /// /// This is the API used to create new payloads and to get the current state of existing ones. #[derive(Debug)] -pub struct PayloadBuilderHandle { +pub struct PayloadBuilderHandle { /// Sender half of the message channel to the [`PayloadBuilderService`]. to_service: mpsc::UnboundedSender>, } @@ -105,7 +104,7 @@ pub struct PayloadBuilderHandle { impl PayloadBuilderHandle where - Engine: EngineTypes + 'static, + Engine: PayloadTypes + 'static, { /// Creates a new payload builder handle for the given channel. /// @@ -191,7 +190,7 @@ where impl Clone for PayloadBuilderHandle where - Engine: EngineTypes, + Engine: PayloadTypes, { fn clone(&self) -> Self { Self { to_service: self.to_service.clone() } @@ -210,7 +209,7 @@ where #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct PayloadBuilderService where - Engine: EngineTypes, + Engine: PayloadTypes, Gen: PayloadJobGenerator, Gen::Job: PayloadJob, { @@ -236,7 +235,7 @@ const PAYLOAD_EVENTS_BUFFER_SIZE: usize = 20; impl PayloadBuilderService where - Engine: EngineTypes + 'static, + Engine: PayloadTypes + 'static, Gen: PayloadJobGenerator, Gen::Job: PayloadJob, ::BuiltPayload: Into, @@ -327,7 +326,7 @@ where impl PayloadBuilderService where - Engine: EngineTypes, + Engine: PayloadTypes, Gen: PayloadJobGenerator, Gen::Job: PayloadJob, ::BuiltPayload: Into, @@ -353,7 +352,7 @@ where impl Future for PayloadBuilderService where - Engine: EngineTypes + 'static, + Engine: PayloadTypes + 'static, Gen: PayloadJobGenerator + Unpin + 'static, ::Job: Unpin + 'static, St: Stream + Send + Unpin + 'static, @@ -453,7 +452,7 @@ where } /// Message type for the [`PayloadBuilderService`]. -pub enum PayloadServiceCommand { +pub enum PayloadServiceCommand { /// Start building a new payload. BuildNewPayload( Engine::PayloadBuilderAttributes, @@ -477,7 +476,7 @@ pub enum PayloadServiceCommand { impl fmt::Debug for PayloadServiceCommand where - Engine: EngineTypes, + Engine: PayloadTypes, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/payload/builder/src/test_utils.rs b/crates/payload/builder/src/test_utils.rs index 26a3fab3fc2c..62f697ddd6cb 100644 --- a/crates/payload/builder/src/test_utils.rs +++ b/crates/payload/builder/src/test_utils.rs @@ -5,7 +5,7 @@ use crate::{ EthPayloadBuilderAttributes, PayloadBuilderHandle, PayloadBuilderService, PayloadJob, PayloadJobGenerator, }; -use reth_engine_primitives::EngineTypes; +use reth_payload_primitives::PayloadTypes; use reth_primitives::{Block, U256}; use reth_provider::CanonStateNotification; use std::{ @@ -24,7 +24,7 @@ pub fn test_payload_service() -> ( PayloadBuilderHandle, ) where - Engine: EngineTypes< + Engine: PayloadTypes< PayloadBuilderAttributes = EthPayloadBuilderAttributes, BuiltPayload = EthBuiltPayload, > + 'static, @@ -35,7 +35,7 @@ where /// Creates a new [`PayloadBuilderService`] for testing purposes and spawns it in the background. pub fn spawn_test_payload_service() -> PayloadBuilderHandle where - Engine: EngineTypes< + Engine: PayloadTypes< PayloadBuilderAttributes = EthPayloadBuilderAttributes, BuiltPayload = EthBuiltPayload, > + 'static, From d05be8a467a6068d021b05dacdf82352ade25375 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:47:54 +0200 Subject: [PATCH 092/405] chore: remove `reth-primitives` dependency from `discv5`, `discv4` and `etl` crates (#8900) --- Cargo.lock | 6 +++--- crates/etl/Cargo.toml | 2 +- crates/etl/src/lib.rs | 2 +- crates/net/discv4/Cargo.toml | 1 - crates/net/discv4/src/lib.rs | 3 ++- crates/net/discv4/src/proto.rs | 3 ++- crates/net/discv5/Cargo.toml | 3 ++- crates/net/discv5/src/config.rs | 5 +++-- crates/net/discv5/src/error.rs | 2 +- crates/net/discv5/src/lib.rs | 5 +++-- crates/net/discv5/src/network_stack_id.rs | 4 ++-- 11 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bf7e7f4c233..6fde8edd7596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6702,7 +6702,6 @@ dependencies = [ "reth-net-common", "reth-net-nat", "reth-network-peers", - "reth-primitives", "reth-tracing", "schnellru", "secp256k1", @@ -6717,6 +6716,7 @@ dependencies = [ name = "reth-discv5" version = "1.0.0-rc.1" dependencies = [ + "alloy-primitives", "alloy-rlp", "derive_more", "discv5", @@ -6728,9 +6728,9 @@ dependencies = [ "multiaddr", "rand 0.8.5", "reth-chainspec", + "reth-ethereum-forks", "reth-metrics", "reth-network-peers", - "reth-primitives", "reth-tracing", "secp256k1", "thiserror", @@ -7003,9 +7003,9 @@ dependencies = [ name = "reth-etl" version = "1.0.0-rc.1" dependencies = [ + "alloy-primitives", "rayon", "reth-db-api", - "reth-primitives", "tempfile", ] diff --git a/crates/etl/Cargo.toml b/crates/etl/Cargo.toml index e05524306d6f..1ca10d620d04 100644 --- a/crates/etl/Cargo.toml +++ b/crates/etl/Cargo.toml @@ -13,4 +13,4 @@ reth-db-api.workspace = true rayon.workspace = true [dev-dependencies] -reth-primitives.workspace = true +alloy-primitives.workspace = true diff --git a/crates/etl/src/lib.rs b/crates/etl/src/lib.rs index 2dbf4cada4fa..137a96fff1c4 100644 --- a/crates/etl/src/lib.rs +++ b/crates/etl/src/lib.rs @@ -271,7 +271,7 @@ impl EtlFile { #[cfg(test)] mod tests { - use reth_primitives::{TxHash, TxNumber}; + use alloy_primitives::{TxHash, TxNumber}; use super::*; diff --git a/crates/net/discv4/Cargo.toml b/crates/net/discv4/Cargo.toml index 5e6a9e411559..8c823d5209ec 100644 --- a/crates/net/discv4/Cargo.toml +++ b/crates/net/discv4/Cargo.toml @@ -45,7 +45,6 @@ serde = { workspace = true, optional = true } [dev-dependencies] reth-chainspec.workspace = true -reth-primitives.workspace = true assert_matches.workspace = true rand.workspace = true tokio = { workspace = true, features = ["macros"] } diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 47f810d96337..50311fd3c00f 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -2285,10 +2285,11 @@ pub enum DiscoveryUpdate { mod tests { use super::*; use crate::test_utils::{create_discv4, create_discv4_with_config, rng_endpoint, rng_record}; + use alloy_primitives::hex; use alloy_rlp::{Decodable, Encodable}; use rand::{thread_rng, Rng}; use reth_chainspec::net::mainnet_nodes; - use reth_primitives::{hex, EnrForkIdEntry, ForkHash}; + use reth_ethereum_forks::{EnrForkIdEntry, ForkHash}; use std::future::poll_fn; #[tokio::test] diff --git a/crates/net/discv4/src/proto.rs b/crates/net/discv4/src/proto.rs index 610628d56b4f..6d2639f2d6f9 100644 --- a/crates/net/discv4/src/proto.rs +++ b/crates/net/discv4/src/proto.rs @@ -542,10 +542,11 @@ mod tests { test_utils::{rng_endpoint, rng_ipv4_record, rng_ipv6_record, rng_message}, DEFAULT_DISCOVERY_PORT, SAFE_MAX_DATAGRAM_NEIGHBOUR_RECORDS, }; + use alloy_primitives::hex; use assert_matches::assert_matches; use enr::EnrPublicKey; use rand::{thread_rng, Rng, RngCore}; - use reth_primitives::{hex, ForkHash}; + use reth_ethereum_forks::ForkHash; #[test] fn test_endpoint_ipv_v4() { diff --git a/crates/net/discv5/Cargo.toml b/crates/net/discv5/Cargo.toml index 1ba0fa47f0b5..e92618466ff3 100644 --- a/crates/net/discv5/Cargo.toml +++ b/crates/net/discv5/Cargo.toml @@ -14,11 +14,12 @@ workspace = true [dependencies] # reth reth-chainspec.workspace = true -reth-primitives.workspace = true +reth-ethereum-forks.workspace = true reth-metrics.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } # ethereum +alloy-primitives.workspace = true alloy-rlp.workspace = true discv5 = { workspace = true, features = ["libp2p"] } enr.workspace = true diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index e2808a03c673..9cb5cf041969 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -6,11 +6,12 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, }; +use alloy_primitives::Bytes; use derive_more::Display; use discv5::ListenConfig; use multiaddr::{Multiaddr, Protocol}; +use reth_ethereum_forks::{EnrForkIdEntry, ForkId}; use reth_network_peers::NodeRecord; -use reth_primitives::{Bytes, EnrForkIdEntry, ForkId}; use tracing::warn; use crate::{enr::discv4_id_to_multiaddr_id, filter::MustNotIncludeKeys, NetworkStackId}; @@ -448,7 +449,7 @@ impl BootNode { mod test { use std::net::SocketAddrV4; - use reth_primitives::hex; + use alloy_primitives::hex; use super::*; diff --git a/crates/net/discv5/src/error.rs b/crates/net/discv5/src/error.rs index 27763146481c..73b05ee81b2a 100644 --- a/crates/net/discv5/src/error.rs +++ b/crates/net/discv5/src/error.rs @@ -17,7 +17,7 @@ pub enum Error { /// Missing key used to identify rlpx network. #[error("fork missing on enr, key missing")] ForkMissing(&'static [u8]), - /// Failed to decode [`ForkId`](reth_primitives::ForkId) rlp value. + /// Failed to decode [`ForkId`](reth_ethereum_forks::ForkId) rlp value. #[error("failed to decode fork id, 'eth': {0:?}")] ForkIdDecodeError(#[from] alloy_rlp::Error), /// Peer is unreachable over discovery. diff --git a/crates/net/discv5/src/lib.rs b/crates/net/discv5/src/lib.rs index 728c44791d16..4061f20e83f1 100644 --- a/crates/net/discv5/src/lib.rs +++ b/crates/net/discv5/src/lib.rs @@ -17,13 +17,14 @@ use std::{ }; use ::enr::Enr; +use alloy_primitives::bytes::Bytes; use discv5::ListenConfig; use enr::{discv4_id_to_discv5_id, EnrCombinedKeyWrapper}; use futures::future::join_all; use itertools::Itertools; use rand::{Rng, RngCore}; +use reth_ethereum_forks::{EnrForkIdEntry, ForkId}; use reth_network_peers::{NodeRecord, PeerId}; -use reth_primitives::{bytes::Bytes, EnrForkIdEntry, ForkId}; use secp256k1::SecretKey; use tokio::{sync::mpsc, task}; use tracing::{debug, error, trace}; @@ -777,11 +778,11 @@ mod test { #[allow(unused)] #[allow(clippy::assign_op_pattern)] mod sigp { + use alloy_primitives::U256; use enr::{ k256::sha2::digest::generic_array::{typenum::U32, GenericArray}, NodeId, }; - use reth_primitives::U256; /// A `Key` is a cryptographic hash, identifying both the nodes participating in /// the Kademlia DHT, as well as records stored in the DHT. diff --git a/crates/net/discv5/src/network_stack_id.rs b/crates/net/discv5/src/network_stack_id.rs index 5fcb1ae41b55..e41284c31698 100644 --- a/crates/net/discv5/src/network_stack_id.rs +++ b/crates/net/discv5/src/network_stack_id.rs @@ -1,5 +1,5 @@ -//! Keys of ENR [`ForkId`](reth_primitives::ForkId) kv-pair. Identifies which network stack a node -//! belongs to. +//! Keys of ENR [`ForkId`](reth_ethereum_forks::ForkId) kv-pair. Identifies which network stack a +//! node belongs to. use reth_chainspec::ChainSpec; From de6cdff7e3bc062b271b52602a7483f35860e86d Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 17 Jun 2024 20:01:35 +0200 Subject: [PATCH 093/405] chore: remove `reth-primitives` dependency from `reth-stages-api` (#8902) --- Cargo.lock | 4 +++- crates/stages/api/Cargo.toml | 5 ++++- crates/stages/api/src/error.rs | 4 +++- crates/stages/api/src/metrics/listener.rs | 3 ++- crates/stages/api/src/pipeline/builder.rs | 2 +- crates/stages/api/src/pipeline/ctrl.rs | 3 ++- crates/stages/api/src/pipeline/event.rs | 2 +- crates/stages/api/src/pipeline/mod.rs | 19 +++++++++---------- crates/stages/api/src/pipeline/progress.rs | 2 +- crates/stages/api/src/stage.rs | 2 +- 10 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fde8edd7596..a652fefd00e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8133,6 +8133,7 @@ dependencies = [ name = "reth-stages-api" version = "1.0.0-rc.1" dependencies = [ + "alloy-primitives", "aquamarine", "assert_matches", "auto_impl", @@ -8143,11 +8144,12 @@ dependencies = [ "reth-errors", "reth-metrics", "reth-network-p2p", - "reth-primitives", + "reth-primitives-traits", "reth-provider", "reth-prune", "reth-stages-types", "reth-static-file", + "reth-static-file-types", "reth-testing-utils", "reth-tokio-util", "thiserror", diff --git a/crates/stages/api/Cargo.toml b/crates/stages/api/Cargo.toml index cb58818b503c..a5db5b9fb20c 100644 --- a/crates/stages/api/Cargo.toml +++ b/crates/stages/api/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] # reth -reth-primitives.workspace = true +reth-primitives-traits.workspace = true reth-provider.workspace = true reth-db-api.workspace = true reth-static-file.workspace = true @@ -22,6 +22,9 @@ reth-consensus.workspace = true reth-prune.workspace = true reth-errors.workspace = true reth-stages-types.workspace = true +reth-static-file-types.workspace = true + +alloy-primitives.workspace = true # metrics reth-metrics.workspace = true diff --git a/crates/stages/api/src/error.rs b/crates/stages/api/src/error.rs index ab525f8f5331..2f113f2fa813 100644 --- a/crates/stages/api/src/error.rs +++ b/crates/stages/api/src/error.rs @@ -1,9 +1,11 @@ use crate::PipelineEvent; +use alloy_primitives::{BlockNumber, TxNumber}; use reth_consensus::ConsensusError; use reth_errors::{BlockExecutionError, DatabaseError, RethError}; use reth_network_p2p::error::DownloadError; -use reth_primitives::{BlockNumber, SealedHeader, StaticFileSegment, TxNumber}; +use reth_primitives_traits::SealedHeader; use reth_provider::ProviderError; +use reth_static_file_types::StaticFileSegment; use thiserror::Error; use tokio::sync::broadcast::error::SendError; diff --git a/crates/stages/api/src/metrics/listener.rs b/crates/stages/api/src/metrics/listener.rs index 408ecab4a95c..e703367bcd67 100644 --- a/crates/stages/api/src/metrics/listener.rs +++ b/crates/stages/api/src/metrics/listener.rs @@ -1,5 +1,6 @@ use crate::{metrics::SyncMetrics, StageCheckpoint, StageId}; -use reth_primitives::{constants::MGAS_TO_GAS, BlockNumber}; +use alloy_primitives::BlockNumber; +use reth_primitives_traits::constants::MGAS_TO_GAS; use std::{ future::Future, pin::Pin, diff --git a/crates/stages/api/src/pipeline/builder.rs b/crates/stages/api/src/pipeline/builder.rs index ada5ffd3734a..1e83af4c3c82 100644 --- a/crates/stages/api/src/pipeline/builder.rs +++ b/crates/stages/api/src/pipeline/builder.rs @@ -1,6 +1,6 @@ use crate::{pipeline::BoxedStage, MetricEventsSender, Pipeline, Stage, StageId, StageSet}; +use alloy_primitives::{BlockNumber, B256}; use reth_db_api::database::Database; -use reth_primitives::{BlockNumber, B256}; use reth_provider::ProviderFactory; use reth_static_file::StaticFileProducer; use tokio::sync::watch; diff --git a/crates/stages/api/src/pipeline/ctrl.rs b/crates/stages/api/src/pipeline/ctrl.rs index 83dbe0cf394c..8fc64c2ab708 100644 --- a/crates/stages/api/src/pipeline/ctrl.rs +++ b/crates/stages/api/src/pipeline/ctrl.rs @@ -1,4 +1,5 @@ -use reth_primitives::{BlockNumber, SealedHeader}; +use alloy_primitives::BlockNumber; +use reth_primitives_traits::SealedHeader; /// Determines the control flow during pipeline execution. /// diff --git a/crates/stages/api/src/pipeline/event.rs b/crates/stages/api/src/pipeline/event.rs index 9bbaaa79b468..879725886cf9 100644 --- a/crates/stages/api/src/pipeline/event.rs +++ b/crates/stages/api/src/pipeline/event.rs @@ -2,7 +2,7 @@ use crate::{ stage::{ExecOutput, UnwindInput, UnwindOutput}, StageCheckpoint, StageId, }; -use reth_primitives::BlockNumber; +use alloy_primitives::BlockNumber; use std::fmt::{Display, Formatter}; /// An event emitted by a [Pipeline][crate::Pipeline]. diff --git a/crates/stages/api/src/pipeline/mod.rs b/crates/stages/api/src/pipeline/mod.rs index 2b7fcf6cbec4..67ef53855b15 100644 --- a/crates/stages/api/src/pipeline/mod.rs +++ b/crates/stages/api/src/pipeline/mod.rs @@ -2,19 +2,18 @@ mod ctrl; mod event; pub use crate::pipeline::ctrl::ControlFlow; use crate::{PipelineTarget, StageCheckpoint, StageId}; +use alloy_primitives::{BlockNumber, B256}; pub use event::*; use futures_util::Future; use reth_db_api::database::Database; -use reth_primitives::{ - constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH, static_file::HighestStaticFiles, BlockNumber, - B256, -}; +use reth_primitives_traits::constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH; use reth_provider::{ providers::StaticFileWriter, FinalizedBlockReader, FinalizedBlockWriter, ProviderFactory, StageCheckpointReader, StageCheckpointWriter, StaticFileProviderFactory, }; use reth_prune::PrunerBuilder; use reth_static_file::StaticFileProducer; +use reth_static_file_types::HighestStaticFiles; use reth_tokio_util::{EventSender, EventStream}; use std::pin::Pin; use tokio::sync::watch; @@ -237,13 +236,13 @@ where /// Run [static file producer](StaticFileProducer) and [pruner](reth_prune::Pruner) to **move** /// all data from the database to static files for corresponding - /// [segments](reth_primitives::static_file::StaticFileSegment), according to their [stage + /// [segments](reth_static_file_types::StaticFileSegment), according to their [stage /// checkpoints](StageCheckpoint): - /// - [`StaticFileSegment::Headers`](reth_primitives::static_file::StaticFileSegment::Headers) - /// -> [`StageId::Headers`] - /// - [`StaticFileSegment::Receipts`](reth_primitives::static_file::StaticFileSegment::Receipts) - /// -> [`StageId::Execution`] - /// - [`StaticFileSegment::Transactions`](reth_primitives::static_file::StaticFileSegment::Transactions) + /// - [`StaticFileSegment::Headers`](reth_static_file_types::StaticFileSegment::Headers) -> + /// [`StageId::Headers`] + /// - [`StaticFileSegment::Receipts`](reth_static_file_types::StaticFileSegment::Receipts) -> + /// [`StageId::Execution`] + /// - [`StaticFileSegment::Transactions`](reth_static_file_types::StaticFileSegment::Transactions) /// -> [`StageId::Bodies`] /// /// CAUTION: This method locks the static file producer Mutex, hence can block the thread if the diff --git a/crates/stages/api/src/pipeline/progress.rs b/crates/stages/api/src/pipeline/progress.rs index 4bb1848543a9..4138f95ddf59 100644 --- a/crates/stages/api/src/pipeline/progress.rs +++ b/crates/stages/api/src/pipeline/progress.rs @@ -1,5 +1,5 @@ use crate::{util::opt, ControlFlow}; -use reth_primitives::BlockNumber; +use alloy_primitives::BlockNumber; #[derive(Debug, Default)] pub(crate) struct PipelineProgress { diff --git a/crates/stages/api/src/stage.rs b/crates/stages/api/src/stage.rs index 381acf5f2d99..b8178df46d51 100644 --- a/crates/stages/api/src/stage.rs +++ b/crates/stages/api/src/stage.rs @@ -1,6 +1,6 @@ use crate::{error::StageError, StageCheckpoint, StageId}; +use alloy_primitives::{BlockNumber, TxNumber}; use reth_db_api::database::Database; -use reth_primitives::{BlockNumber, TxNumber}; use reth_provider::{BlockReader, DatabaseProviderRW, ProviderError, TransactionsProvider}; use std::{ cmp::{max, min}, From 3db1069554c8a715be845f4fba2d007fc3f42f0c Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 17 Jun 2024 20:18:26 +0200 Subject: [PATCH 094/405] chore: remove `reth-primitives` dependency from `reth-static-file` (#8903) --- Cargo.lock | 3 ++- crates/static-file/static-file/Cargo.toml | 4 +++- crates/static-file/static-file/src/lib.rs | 3 +++ .../static-file/static-file/src/segments/headers.rs | 3 ++- crates/static-file/static-file/src/segments/mod.rs | 12 +++++------- .../static-file/static-file/src/segments/receipts.rs | 6 ++---- .../static-file/src/segments/transactions.rs | 6 ++---- .../static-file/src/static_file_producer.rs | 6 ++++-- 8 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a652fefd00e3..21c5c73e16ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8179,16 +8179,17 @@ dependencies = [ name = "reth-static-file" version = "1.0.0-rc.1" dependencies = [ + "alloy-primitives", "assert_matches", "parking_lot 0.12.3", "rayon", "reth-db", "reth-db-api", "reth-nippy-jar", - "reth-primitives", "reth-provider", "reth-prune-types", "reth-stages", + "reth-static-file-types", "reth-storage-errors", "reth-testing-utils", "reth-tokio-util", diff --git a/crates/static-file/static-file/Cargo.toml b/crates/static-file/static-file/Cargo.toml index 4d4e31509aeb..29a601f050d0 100644 --- a/crates/static-file/static-file/Cargo.toml +++ b/crates/static-file/static-file/Cargo.toml @@ -13,7 +13,6 @@ workspace = true [dependencies] # reth -reth-primitives.workspace = true reth-db.workspace = true reth-db-api.workspace = true reth-provider.workspace = true @@ -21,6 +20,9 @@ reth-storage-errors.workspace = true reth-nippy-jar.workspace = true reth-tokio-util.workspace = true reth-prune-types.workspace = true +reth-static-file-types.workspace = true + +alloy-primitives.workspace = true # misc tracing.workspace = true diff --git a/crates/static-file/static-file/src/lib.rs b/crates/static-file/static-file/src/lib.rs index f545298ebd47..1bfe4134e954 100644 --- a/crates/static-file/static-file/src/lib.rs +++ b/crates/static-file/static-file/src/lib.rs @@ -16,3 +16,6 @@ pub use static_file_producer::{ StaticFileProducer, StaticFileProducerInner, StaticFileProducerResult, StaticFileProducerWithResult, StaticFileTargets, }; + +// Re-export for convenience. +pub use reth_static_file_types::*; diff --git a/crates/static-file/static-file/src/segments/headers.rs b/crates/static-file/static-file/src/segments/headers.rs index 5fb1a4422e82..e87c1fdc58e8 100644 --- a/crates/static-file/static-file/src/segments/headers.rs +++ b/crates/static-file/static-file/src/segments/headers.rs @@ -1,11 +1,12 @@ use crate::segments::{dataset_for_compression, prepare_jar, Segment, SegmentHeader}; +use alloy_primitives::BlockNumber; use reth_db::{static_file::create_static_file_T1_T2_T3, tables, RawKey, RawTable}; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; -use reth_primitives::{static_file::SegmentConfig, BlockNumber, StaticFileSegment}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, DatabaseProviderRO, }; +use reth_static_file_types::{SegmentConfig, StaticFileSegment}; use reth_storage_errors::provider::ProviderResult; use std::{ops::RangeInclusive, path::Path}; diff --git a/crates/static-file/static-file/src/segments/mod.rs b/crates/static-file/static-file/src/segments/mod.rs index e21f8ad7a12b..77798dd08542 100644 --- a/crates/static-file/static-file/src/segments/mod.rs +++ b/crates/static-file/static-file/src/segments/mod.rs @@ -9,19 +9,17 @@ pub use headers::Headers; mod receipts; pub use receipts::Receipts; +use alloy_primitives::BlockNumber; use reth_db::{RawKey, RawTable}; use reth_db_api::{cursor::DbCursorRO, database::Database, table::Table, transaction::DbTx}; use reth_nippy_jar::NippyJar; -use reth_primitives::{ - static_file::{ - find_fixed_range, Compression, Filters, InclusionFilter, PerfectHashingFunction, - SegmentConfig, SegmentHeader, - }, - BlockNumber, StaticFileSegment, -}; use reth_provider::{ providers::StaticFileProvider, DatabaseProviderRO, ProviderError, TransactionsProviderExt, }; +use reth_static_file_types::{ + find_fixed_range, Compression, Filters, InclusionFilter, PerfectHashingFunction, SegmentConfig, + SegmentHeader, StaticFileSegment, +}; use reth_storage_errors::provider::ProviderResult; use std::{ops::RangeInclusive, path::Path}; diff --git a/crates/static-file/static-file/src/segments/receipts.rs b/crates/static-file/static-file/src/segments/receipts.rs index e0ed58086722..e09b5e690df9 100644 --- a/crates/static-file/static-file/src/segments/receipts.rs +++ b/crates/static-file/static-file/src/segments/receipts.rs @@ -1,14 +1,12 @@ use crate::segments::{dataset_for_compression, prepare_jar, Segment}; +use alloy_primitives::{BlockNumber, TxNumber}; use reth_db::{static_file::create_static_file_T1, tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; -use reth_primitives::{ - static_file::{SegmentConfig, SegmentHeader}, - BlockNumber, StaticFileSegment, TxNumber, -}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, BlockReader, DatabaseProviderRO, TransactionsProviderExt, }; +use reth_static_file_types::{SegmentConfig, SegmentHeader, StaticFileSegment}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use std::{ops::RangeInclusive, path::Path}; diff --git a/crates/static-file/static-file/src/segments/transactions.rs b/crates/static-file/static-file/src/segments/transactions.rs index 47eaa727250c..c7daeba0675f 100644 --- a/crates/static-file/static-file/src/segments/transactions.rs +++ b/crates/static-file/static-file/src/segments/transactions.rs @@ -1,14 +1,12 @@ use crate::segments::{dataset_for_compression, prepare_jar, Segment}; +use alloy_primitives::{BlockNumber, TxNumber}; use reth_db::{static_file::create_static_file_T1, tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; -use reth_primitives::{ - static_file::{SegmentConfig, SegmentHeader}, - BlockNumber, StaticFileSegment, TxNumber, -}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, BlockReader, DatabaseProviderRO, TransactionsProviderExt, }; +use reth_static_file_types::{SegmentConfig, SegmentHeader, StaticFileSegment}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use std::{ops::RangeInclusive, path::Path}; diff --git a/crates/static-file/static-file/src/static_file_producer.rs b/crates/static-file/static-file/src/static_file_producer.rs index 396fdd5ed08d..44ea3a5c84b4 100644 --- a/crates/static-file/static-file/src/static_file_producer.rs +++ b/crates/static-file/static-file/src/static_file_producer.rs @@ -1,12 +1,13 @@ //! Support for producing static files. use crate::{segments, segments::Segment, StaticFileProducerEvent}; +use alloy_primitives::BlockNumber; use parking_lot::Mutex; use rayon::prelude::*; use reth_db_api::database::Database; -use reth_primitives::{static_file::HighestStaticFiles, BlockNumber}; use reth_provider::{providers::StaticFileWriter, ProviderFactory, StaticFileProviderFactory}; use reth_prune_types::PruneModes; +use reth_static_file_types::HighestStaticFiles; use reth_storage_errors::provider::ProviderResult; use reth_tokio_util::{EventSender, EventStream}; use std::{ @@ -228,15 +229,16 @@ mod tests { use crate::static_file_producer::{ StaticFileProducer, StaticFileProducerInner, StaticFileTargets, }; + use alloy_primitives::{B256, U256}; use assert_matches::assert_matches; use reth_db::{test_utils::TempDatabase, DatabaseEnv}; use reth_db_api::{database::Database, transaction::DbTx}; - use reth_primitives::{static_file::HighestStaticFiles, StaticFileSegment, B256, U256}; use reth_provider::{ providers::StaticFileWriter, ProviderError, ProviderFactory, StaticFileProviderFactory, }; use reth_prune_types::PruneModes; use reth_stages::test_utils::{StorageKind, TestStageDB}; + use reth_static_file_types::{HighestStaticFiles, StaticFileSegment}; use reth_testing_utils::{ generators, generators::{random_block_range, random_receipt}, From 7d55a14b1fca006260737dec1f9d3e93cd4c2008 Mon Sep 17 00:00:00 2001 From: Brian Bland Date: Mon, 17 Jun 2024 13:13:48 -0700 Subject: [PATCH 095/405] fix: Add missing fjord condition to revm_spec_by_timestamp_after_merge (#8906) --- crates/primitives/src/revm/config.rs | 63 +++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/crates/primitives/src/revm/config.rs b/crates/primitives/src/revm/config.rs index 6ad76123f149..5914dbf59348 100644 --- a/crates/primitives/src/revm/config.rs +++ b/crates/primitives/src/revm/config.rs @@ -11,7 +11,9 @@ pub fn revm_spec_by_timestamp_after_merge( ) -> revm_primitives::SpecId { #[cfg(feature = "optimism")] if chain_spec.is_optimism() { - return if chain_spec.fork(Hardfork::Ecotone).active_at_timestamp(timestamp) { + return if chain_spec.fork(Hardfork::Fjord).active_at_timestamp(timestamp) { + revm_primitives::FJORD + } else if chain_spec.fork(Hardfork::Ecotone).active_at_timestamp(timestamp) { revm_primitives::ECOTONE } else if chain_spec.fork(Hardfork::Canyon).active_at_timestamp(timestamp) { revm_primitives::CANYON @@ -90,6 +92,56 @@ mod tests { use crate::U256; use reth_chainspec::{ChainSpecBuilder, MAINNET}; + #[test] + fn test_revm_spec_by_timestamp_after_merge() { + assert_eq!( + revm_spec_by_timestamp_after_merge( + &ChainSpecBuilder::mainnet().cancun_activated().build(), + 0 + ), + revm_primitives::CANCUN + ); + assert_eq!( + revm_spec_by_timestamp_after_merge( + &ChainSpecBuilder::mainnet().shanghai_activated().build(), + 0 + ), + revm_primitives::SHANGHAI + ); + assert_eq!( + revm_spec_by_timestamp_after_merge(&ChainSpecBuilder::mainnet().build(), 0), + revm_primitives::MERGE + ); + #[cfg(feature = "optimism")] + { + #[inline(always)] + fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { + let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); + f(cs).build() + } + assert_eq!( + revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.fjord_activated()), 0), + revm_primitives::FJORD + ); + assert_eq!( + revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.ecotone_activated()), 0), + revm_primitives::ECOTONE + ); + assert_eq!( + revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.canyon_activated()), 0), + revm_primitives::CANYON + ); + assert_eq!( + revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.bedrock_activated()), 0), + revm_primitives::BEDROCK + ); + assert_eq!( + revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.regolith_activated()), 0), + revm_primitives::REGOLITH + ); + } + } + #[test] fn test_to_revm_spec() { assert_eq!( @@ -178,6 +230,15 @@ mod tests { #[test] fn test_eth_spec() { + assert_eq!( + revm_spec(&MAINNET, Head { timestamp: 1710338135, ..Default::default() }), + revm_primitives::CANCUN + ); + assert_eq!( + revm_spec(&MAINNET, Head { timestamp: 1681338455, ..Default::default() }), + revm_primitives::SHANGHAI + ); + assert_eq!( revm_spec( &MAINNET, From 5f998f704c2182225763bec019a39d408072e05d Mon Sep 17 00:00:00 2001 From: 0xAtreides <103257861+JackG-eth@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:49:21 +0100 Subject: [PATCH 096/405] feat: no_std CI integration (#8871) --- .github/scripts/check_no_std.sh | 25 +++++++++++++++++++++++++ .github/workflows/lint.yml | 25 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100755 .github/scripts/check_no_std.sh diff --git a/.github/scripts/check_no_std.sh b/.github/scripts/check_no_std.sh new file mode 100755 index 000000000000..8d64ad172b3b --- /dev/null +++ b/.github/scripts/check_no_std.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -eo pipefail + +# List of no_std packages +no_std_packages=( + reth-db + reth-network-peers +) + +# Loop through each package and check it for no_std compliance +for package in "${no_std_packages[@]}"; do + cmd="cargo +stable check -p $package --no-default-features" + + if [ -n "$CI" ]; then + echo "::group::$cmd" + else + printf "\n%s:\n %s\n" "$package" "$cmd" + fi + + $cmd + + if [ -n "$CI" ]; then + echo "::endgroup::" + fi +done diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ffcb7f239a57..c07cee38830b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -45,6 +45,21 @@ jobs: env: RUSTFLAGS: -D warnings + no-std: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: riscv32imac-unknown-none-elf + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Run no_std checks + run: .github/scripts/check_no_std.sh + crate-checks: runs-on: ubuntu-latest timeout-minutes: 30 @@ -149,7 +164,15 @@ jobs: name: lint success runs-on: ubuntu-latest if: always() - needs: [clippy-binaries, clippy, crate-checks, docs, fmt, book, codespell, grafana] + needs: + - clippy-binaries + - clippy + - crate-checks + - docs + - fmt + - book + - codespell + - grafana timeout-minutes: 30 steps: - name: Decide whether the needed jobs succeeded or failed From c008a1fa0990089e96c7f7661587ac080fbea4c5 Mon Sep 17 00:00:00 2001 From: chirag-bgh <76247491+chirag-bgh@users.noreply.github.com> Date: Tue, 18 Jun 2024 04:15:48 +0530 Subject: [PATCH 097/405] fix: typo (#8908) --- book/run/transactions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/run/transactions.md b/book/run/transactions.md index 65aa979e238e..61327b57300a 100644 --- a/book/run/transactions.md +++ b/book/run/transactions.md @@ -1,6 +1,6 @@ # Transaction types -Over time, the Ethereum network has undergone various upgrades and improvements to enhance transaction efficiency, security, and user experience. Three significant transaction types that have evolved are: +Over time, the Ethereum network has undergone various upgrades and improvements to enhance transaction efficiency, security, and user experience. Four significant transaction types that have evolved are: - Legacy Transactions, - EIP-2930 Transactions, @@ -46,4 +46,4 @@ Alongside the legacy parameters & parameters from EIP-1559, the EIP-4844 transac - `max_fee_per_blob_gas`, The maximum total fee per gas the sender is willing to pay for blob gas in wei - `blob_versioned_hashes`, List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. -The actual blob fee is deducted from the sender balance before transaction execution and burned, and is not refunded in case of transaction failure. \ No newline at end of file +The actual blob fee is deducted from the sender balance before transaction execution and burned, and is not refunded in case of transaction failure. From 8a9591155cbb53a202066a02b1556ab858157d86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:05:59 +0200 Subject: [PATCH 098/405] chore(deps): bump docker/build-push-action from 5 to 6 (#8910) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/hive.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index 6151c9569df1..e34470e4dedd 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and export reth image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . tags: ghcr.io/paradigmxyz/reth:latest From 9de1cc6c9ab8406466e0a93aa91a6bcbb3ef36c5 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 18 Jun 2024 13:07:56 +0200 Subject: [PATCH 099/405] chore: fix repeat words (#8916) --- crates/net/discv5/src/config.rs | 2 +- crates/net/discv5/src/lib.rs | 2 +- crates/rpc/rpc-builder/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index 9cb5cf041969..da36d8f1c81f 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -190,7 +190,7 @@ impl ConfigBuilder { self } - /// Sets the the number of times at which to run boost lookup queries to bootstrap the node. + /// Sets the number of times at which to run boost lookup queries to bootstrap the node. pub const fn bootstrap_lookup_countdown(mut self, counts: u64) -> Self { self.bootstrap_lookup_countdown = Some(counts); self diff --git a/crates/net/discv5/src/lib.rs b/crates/net/discv5/src/lib.rs index 4061f20e83f1..5b0044a721f5 100644 --- a/crates/net/discv5/src/lib.rs +++ b/crates/net/discv5/src/lib.rs @@ -67,7 +67,7 @@ pub const DEFAULT_MIN_TARGET_KBUCKET_INDEX: usize = 0; pub struct Discv5 { /// sigp/discv5 node. discv5: Arc, - /// [`IpMode`] of the the `RLPx` network. + /// [`IpMode`] of the `RLPx` network. rlpx_ip_mode: IpMode, /// Key used in kv-pair to ID chain, e.g. 'opstack' or 'eth'. fork_key: Option<&'static [u8]>, diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index d05ec8239722..93d4e6b264ee 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1306,7 +1306,7 @@ impl RpcServerConfig { /// Returns true if any server is configured. /// - /// If no server is configured, no server will be be launched on [`RpcServerConfig::start`]. + /// If no server is configured, no server will be launched on [`RpcServerConfig::start`]. pub const fn has_server(&self) -> bool { self.http_server_config.is_some() || self.ws_server_config.is_some() || From 7c773a1d3afe8811e82e7fba33490dec160c5cdd Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 18 Jun 2024 07:27:06 -0400 Subject: [PATCH 100/405] feat: add stateful precompile example (#8911) Co-authored-by: Matthias Seitz --- Cargo.lock | 17 ++ Cargo.toml | 1 + examples/stateful-precompile/Cargo.toml | 20 ++ examples/stateful-precompile/src/main.rs | 237 +++++++++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 examples/stateful-precompile/Cargo.toml create mode 100644 examples/stateful-precompile/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 21c5c73e16ef..879f8663e9b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9373,6 +9373,23 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stateful-precompile" +version = "0.0.0" +dependencies = [ + "eyre", + "parking_lot 0.12.3", + "reth", + "reth-chainspec", + "reth-node-api", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives", + "reth-tracing", + "schnellru", + "tokio", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7a8183eb855a..148eba4835be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ members = [ "examples/custom-dev-node/", "examples/custom-engine-types/", "examples/custom-evm/", + "examples/stateful-precompile/", "examples/custom-inspector/", "examples/custom-node-components/", "examples/custom-payload-builder/", diff --git a/examples/stateful-precompile/Cargo.toml b/examples/stateful-precompile/Cargo.toml new file mode 100644 index 000000000000..2a248fbefb00 --- /dev/null +++ b/examples/stateful-precompile/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "stateful-precompile" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth.workspace = true +reth-chainspec.workspace = true +reth-node-api.workspace = true +reth-node-core.workspace = true +reth-primitives.workspace = true +reth-node-ethereum.workspace = true +reth-tracing.workspace = true + +eyre.workspace = true +parking_lot.workspace = true +schnellru.workspace = true +tokio.workspace = true diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs new file mode 100644 index 000000000000..0cd495e85a53 --- /dev/null +++ b/examples/stateful-precompile/src/main.rs @@ -0,0 +1,237 @@ +//! This example shows how to implement a node with a custom EVM that uses a stateful precompile + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use parking_lot::RwLock; +use reth::{ + builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, + primitives::{ + revm_primitives::{CfgEnvWithHandlerCfg, Env, PrecompileResult, TxEnv}, + Address, Bytes, U256, + }, + revm::{ + handler::register::EvmHandler, + inspector_handle_register, + precompile::{Precompile, PrecompileSpecId}, + ContextPrecompile, ContextPrecompiles, Database, Evm, EvmBuilder, GetInspector, + }, + tasks::TaskManager, +}; +use reth_chainspec::{Chain, ChainSpec}; +use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; +use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; +use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider, EthereumNode}; +use reth_primitives::{ + revm_primitives::{SpecId, StatefulPrecompileMut}, + Genesis, Header, TransactionSigned, +}; +use reth_tracing::{RethTracer, Tracer}; +use schnellru::{ByLength, LruMap}; +use std::{collections::HashMap, sync::Arc}; + +/// A cache for precompile inputs / outputs. +/// +/// This assumes that the precompile is a standard precompile, as in `StandardPrecompileFn`, meaning +/// its inputs are only `(Bytes, u64)`. +/// +/// NOTE: This does not work with "context stateful precompiles", ie `ContextStatefulPrecompile` or +/// `ContextStatefulPrecompileMut`. They are explicitly banned. +#[derive(Debug, Default)] +pub struct PrecompileCache { + /// Caches for each precompile input / output. + #[allow(clippy::type_complexity)] + cache: HashMap<(Address, SpecId), Arc>>>, +} + +/// Custom EVM configuration +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct MyEvmConfig { + precompile_cache: Arc>, +} + +impl MyEvmConfig { + /// Sets the precompiles to the EVM handler + /// + /// This will be invoked when the EVM is created via [ConfigureEvm::evm] or + /// [ConfigureEvm::evm_with_inspector] + /// + /// This will use the default mainnet precompiles and wrap them with a cache. + pub fn set_precompiles( + handler: &mut EvmHandler, + cache: Arc>, + ) where + DB: Database, + { + // first we need the evm spec id, which determines the precompiles + let spec_id = handler.cfg.spec_id; + + let mut loaded_precompiles: ContextPrecompiles = + ContextPrecompiles::new(PrecompileSpecId::from_spec_id(spec_id)); + for (address, precompile) in loaded_precompiles.to_mut().iter_mut() { + // get or insert the cache for this address / spec + let mut cache = cache.write(); + let cache = cache + .cache + .entry((*address, spec_id)) + .or_insert(Arc::new(RwLock::new(LruMap::new(ByLength::new(1024))))); + + *precompile = Self::wrap_precompile(precompile.clone(), cache.clone()); + } + + // install the precompiles + handler.pre_execution.load_precompiles = Arc::new(move || loaded_precompiles.clone()); + } + + /// Given a [`ContextPrecompile`] and cache for a specific precompile, create a new precompile + /// that wraps the precompile with the cache. + fn wrap_precompile( + precompile: ContextPrecompile, + cache: Arc>>, + ) -> ContextPrecompile + where + DB: Database, + { + let ContextPrecompile::Ordinary(precompile) = precompile else { + // context stateful precompiles are not supported, due to lifetime issues or skill + // issues + panic!("precompile is not ordinary"); + }; + + let wrapped = WrappedPrecompile { precompile, cache: cache.clone() }; + + ContextPrecompile::Ordinary(Precompile::StatefulMut(Box::new(wrapped))) + } +} + +/// A custom precompile that contains the cache and precompile it wraps. +#[derive(Clone)] +pub struct WrappedPrecompile { + /// The precompile to wrap. + precompile: Precompile, + /// The cache to use. + cache: Arc>>, +} + +impl StatefulPrecompileMut for WrappedPrecompile { + fn call_mut(&mut self, bytes: &Bytes, gas_price: u64, _env: &Env) -> PrecompileResult { + let mut cache = self.cache.write(); + let key = (bytes.clone(), gas_price); + + // get the result if it exists + if let Some(result) = cache.get(&key) { + return result.clone() + } + + // call the precompile if cache miss + let output = self.precompile.call(bytes, gas_price, _env); + cache.insert(key, output.clone()); + + output + } +} + +impl ConfigureEvmEnv for MyEvmConfig { + fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + EthEvmConfig::fill_tx_env(tx_env, transaction, sender) + } + + fn fill_cfg_env( + cfg_env: &mut CfgEnvWithHandlerCfg, + chain_spec: &ChainSpec, + header: &Header, + total_difficulty: U256, + ) { + EthEvmConfig::fill_cfg_env(cfg_env, chain_spec, header, total_difficulty) + } +} + +impl ConfigureEvm for MyEvmConfig { + type DefaultExternalContext<'a> = (); + + fn evm<'a, DB: Database + 'a>(&self, db: DB) -> Evm<'a, Self::DefaultExternalContext<'a>, DB> { + let new_cache = self.precompile_cache.clone(); + EvmBuilder::default() + .with_db(db) + // add additional precompiles + .append_handler_register_box(Box::new(move |handler| { + MyEvmConfig::set_precompiles(handler, new_cache.clone()) + })) + .build() + } + + fn evm_with_inspector<'a, DB, I>(&self, db: DB, inspector: I) -> Evm<'a, I, DB> + where + DB: Database + 'a, + I: GetInspector, + { + let new_cache = self.precompile_cache.clone(); + EvmBuilder::default() + .with_db(db) + .with_external_context(inspector) + // add additional precompiles + .append_handler_register_box(Box::new(move |handler| { + MyEvmConfig::set_precompiles(handler, new_cache.clone()) + })) + .append_handler_register(inspector_handle_register) + .build() + } +} + +/// Builds a regular ethereum block executor that uses the custom EVM. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct MyExecutorBuilder { + /// The precompile cache to use for all executors. + precompile_cache: Arc>, +} + +impl ExecutorBuilder for MyExecutorBuilder +where + Node: FullNodeTypes, +{ + type EVM = MyEvmConfig; + type Executor = EthExecutorProvider; + + async fn build_evm( + self, + ctx: &BuilderContext, + ) -> eyre::Result<(Self::EVM, Self::Executor)> { + let evm_config = MyEvmConfig { precompile_cache: self.precompile_cache.clone() }; + Ok((evm_config.clone(), EthExecutorProvider::new(ctx.chain_spec(), evm_config))) + } +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let _guard = RethTracer::new().init()?; + + let tasks = TaskManager::current(); + + // create a custom chain spec + let spec = ChainSpec::builder() + .chain(Chain::mainnet()) + .genesis(Genesis::default()) + .london_activated() + .paris_activated() + .shanghai_activated() + .cancun_activated() + .build(); + + let node_config = + NodeConfig::test().with_rpc(RpcServerArgs::default().with_http()).with_chain(spec); + + let handle = NodeBuilder::new(node_config) + .testing_node(tasks.executor()) + // configure the node with regular ethereum types + .with_types::() + // use default ethereum components but with our executor + .with_components(EthereumNode::components().executor(MyExecutorBuilder::default())) + .launch() + .await + .unwrap(); + + println!("Node started"); + + handle.node_exit_future.await +} From d865e44475b2249ad0db587969cdf28f78675159 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 18 Jun 2024 07:32:20 -0400 Subject: [PATCH 101/405] fix: edge case divide by zero (#8912) Co-authored-by: Matthias Seitz --- crates/rpc/rpc/src/eth/api/fees.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs index 68789674ce5d..2493d6055778 100644 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ b/crates/rpc/rpc/src/eth/api/fees.rs @@ -153,8 +153,10 @@ where } for header in &headers { + let ratio = if header.gas_limit > 0 {header.gas_used as f64 / header.gas_limit as f64} else {1.0}; + base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default() as u128); - gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64); + gas_used_ratio.push(ratio); base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default()); blob_gas_used_ratio.push( header.blob_gas_used.unwrap_or_default() as f64 / From fe817c045ab46e84097bf1f26054b339499c6dd8 Mon Sep 17 00:00:00 2001 From: Darshan Kathiriya <8559992+lakshya-sky@users.noreply.github.com> Date: Tue, 18 Jun 2024 07:55:53 -0400 Subject: [PATCH 102/405] move forkcondition and displayhardfork types to fork crate (#8909) Co-authored-by: Matthias Seitz --- crates/chainspec/src/lib.rs | 3 +- crates/chainspec/src/spec.rs | 279 +-------------------- crates/ethereum-forks/src/display.rs | 181 +++++++++++++ crates/ethereum-forks/src/forkcondition.rs | 103 ++++++++ crates/ethereum-forks/src/lib.rs | 5 + 5 files changed, 294 insertions(+), 277 deletions(-) create mode 100644 crates/ethereum-forks/src/display.rs create mode 100644 crates/ethereum-forks/src/forkcondition.rs diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 6da5ab470c3a..892cfa1814b7 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -12,8 +12,7 @@ pub use alloy_chains::{Chain, ChainKind, NamedChain}; pub use info::ChainInfo; pub use spec::{ AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, - DepositContract, DisplayHardforks, ForkBaseFeeParams, ForkCondition, DEV, GOERLI, HOLESKY, - MAINNET, SEPOLIA, + DepositContract, ForkBaseFeeParams, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, }; #[cfg(feature = "optimism")] pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index aa9e90f8c470..b28c75778022 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -11,13 +11,11 @@ use alloy_chains::{Chain, ChainKind, NamedChain}; use alloy_genesis::Genesis; use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256}; use alloy_trie::EMPTY_ROOT_HASH; -use core::{ - fmt, - fmt::{Display, Formatter}, -}; use derive_more::From; use once_cell::sync::Lazy; -use reth_ethereum_forks::{ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head}; +use reth_ethereum_forks::{ + DisplayHardforks, ForkCondition, ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head, +}; use reth_network_peers::NodeRecord; use reth_primitives_traits::{ constants::{ @@ -1367,275 +1365,6 @@ impl From<&Arc> for ChainSpecBuilder { } } -/// The condition at which a fork is activated. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub enum ForkCondition { - /// The fork is activated after a certain block. - Block(BlockNumber), - /// The fork is activated after a total difficulty has been reached. - TTD { - /// The block number at which TTD is reached, if it is known. - /// - /// This should **NOT** be set unless you want this block advertised as [EIP-2124][eip2124] - /// `FORK_NEXT`. This is currently only the case for Sepolia and Holesky. - /// - /// [eip2124]: https://eips.ethereum.org/EIPS/eip-2124 - fork_block: Option, - /// The total difficulty after which the fork is activated. - total_difficulty: U256, - }, - /// The fork is activated after a specific timestamp. - Timestamp(u64), - /// The fork is never activated - #[default] - Never, -} - -impl ForkCondition { - /// Returns true if the fork condition is timestamp based. - pub const fn is_timestamp(&self) -> bool { - matches!(self, Self::Timestamp(_)) - } - - /// Checks whether the fork condition is satisfied at the given block. - /// - /// For TTD conditions, this will only return true if the activation block is already known. - /// - /// For timestamp conditions, this will always return false. - pub const fn active_at_block(&self, current_block: BlockNumber) -> bool { - matches!(self, Self::Block(block) - | Self::TTD { fork_block: Some(block), .. } if current_block >= *block) - } - - /// Checks if the given block is the first block that satisfies the fork condition. - /// - /// This will return false for any condition that is not block based. - pub const fn transitions_at_block(&self, current_block: BlockNumber) -> bool { - matches!(self, Self::Block(block) if current_block == *block) - } - - /// Checks whether the fork condition is satisfied at the given total difficulty and difficulty - /// of a current block. - /// - /// The fork is considered active if the _previous_ total difficulty is above the threshold. - /// To achieve that, we subtract the passed `difficulty` from the current block's total - /// difficulty, and check if it's above the Fork Condition's total difficulty (here: - /// `58_750_000_000_000_000_000_000`) - /// - /// This will return false for any condition that is not TTD-based. - pub fn active_at_ttd(&self, ttd: U256, difficulty: U256) -> bool { - matches!(self, Self::TTD { total_difficulty, .. } - if ttd.saturating_sub(difficulty) >= *total_difficulty) - } - - /// Checks whether the fork condition is satisfied at the given timestamp. - /// - /// This will return false for any condition that is not timestamp-based. - pub const fn active_at_timestamp(&self, timestamp: u64) -> bool { - matches!(self, Self::Timestamp(time) if timestamp >= *time) - } - - /// Checks whether the fork condition is satisfied at the given head block. - /// - /// This will return true if: - /// - /// - The condition is satisfied by the block number; - /// - The condition is satisfied by the timestamp; - /// - or the condition is satisfied by the total difficulty - pub fn active_at_head(&self, head: &Head) -> bool { - self.active_at_block(head.number) || - self.active_at_timestamp(head.timestamp) || - self.active_at_ttd(head.total_difficulty, head.difficulty) - } - - /// Get the total terminal difficulty for this fork condition. - /// - /// Returns `None` for fork conditions that are not TTD based. - pub const fn ttd(&self) -> Option { - match self { - Self::TTD { total_difficulty, .. } => Some(*total_difficulty), - _ => None, - } - } - - /// Returns the timestamp of the fork condition, if it is timestamp based. - pub const fn as_timestamp(&self) -> Option { - match self { - Self::Timestamp(timestamp) => Some(*timestamp), - _ => None, - } - } -} - -/// A container to pretty-print a hardfork. -/// -/// The fork is formatted depending on its fork condition: -/// -/// - Block and timestamp based forks are formatted in the same manner (`{name} <({eip})> -/// @{condition}`) -/// - TTD based forks are formatted separately as `{name} <({eip})> @{ttd} (network is known -/// to be merged)` -/// -/// An optional EIP can be attached to the fork to display as well. This should generally be in the -/// form of just `EIP-x`, e.g. `EIP-1559`. -#[derive(Debug)] -struct DisplayFork { - /// The name of the hardfork (e.g. Frontier) - name: String, - /// The fork condition - activated_at: ForkCondition, - /// An optional EIP (e.g. `EIP-1559`). - eip: Option, -} - -impl Display for DisplayFork { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let name_with_eip = if let Some(eip) = &self.eip { - format!("{} ({})", self.name, eip) - } else { - self.name.clone() - }; - - match self.activated_at { - ForkCondition::Block(at) | ForkCondition::Timestamp(at) => { - write!(f, "{name_with_eip:32} @{at}")?; - } - ForkCondition::TTD { fork_block, total_difficulty } => { - write!( - f, - "{:32} @{} ({})", - name_with_eip, - total_difficulty, - if fork_block.is_some() { - "network is known to be merged" - } else { - "network is not known to be merged" - } - )?; - } - ForkCondition::Never => unreachable!(), - } - - Ok(()) - } -} - -/// A container for pretty-printing a list of hardforks. -/// -/// # Examples -/// -/// ``` -/// # use reth_chainspec::MAINNET; -/// println!("{}", MAINNET.display_hardforks()); -/// ``` -/// -/// An example of the output: -/// -/// ```text -/// Pre-merge hard forks (block based): -// - Frontier @0 -// - Homestead @1150000 -// - Dao @1920000 -// - Tangerine @2463000 -// - SpuriousDragon @2675000 -// - Byzantium @4370000 -// - Constantinople @7280000 -// - Petersburg @7280000 -// - Istanbul @9069000 -// - MuirGlacier @9200000 -// - Berlin @12244000 -// - London @12965000 -// - ArrowGlacier @13773000 -// - GrayGlacier @15050000 -// Merge hard forks: -// - Paris @58750000000000000000000 (network is known to be merged) -// Post-merge hard forks (timestamp based): -// - Shanghai @1681338455 -/// ``` -#[derive(Debug)] -pub struct DisplayHardforks { - /// A list of pre-merge (block based) hardforks - pre_merge: Vec, - /// A list of merge (TTD based) hardforks - with_merge: Vec, - /// A list of post-merge (timestamp based) hardforks - post_merge: Vec, -} - -impl Display for DisplayHardforks { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - fn format( - header: &str, - forks: &[DisplayFork], - next_is_empty: bool, - f: &mut Formatter<'_>, - ) -> fmt::Result { - writeln!(f, "{header}:")?; - let mut iter = forks.iter().peekable(); - while let Some(fork) = iter.next() { - write!(f, "- {fork}")?; - if !next_is_empty || iter.peek().is_some() { - writeln!(f)?; - } - } - Ok(()) - } - - format( - "Pre-merge hard forks (block based)", - &self.pre_merge, - self.with_merge.is_empty(), - f, - )?; - - if !self.with_merge.is_empty() { - format("Merge hard forks", &self.with_merge, self.post_merge.is_empty(), f)?; - } - - if !self.post_merge.is_empty() { - format("Post-merge hard forks (timestamp based)", &self.post_merge, true, f)?; - } - - Ok(()) - } -} - -impl DisplayHardforks { - /// Creates a new [`DisplayHardforks`] from an iterator of hardforks. - pub fn new( - hardforks: &BTreeMap, - known_paris_block: Option, - ) -> Self { - let mut pre_merge = Vec::new(); - let mut with_merge = Vec::new(); - let mut post_merge = Vec::new(); - - for (fork, condition) in hardforks { - let mut display_fork = - DisplayFork { name: fork.to_string(), activated_at: *condition, eip: None }; - - match condition { - ForkCondition::Block(_) => { - pre_merge.push(display_fork); - } - ForkCondition::TTD { total_difficulty, .. } => { - display_fork.activated_at = ForkCondition::TTD { - fork_block: known_paris_block, - total_difficulty: *total_difficulty, - }; - with_merge.push(display_fork); - } - ForkCondition::Timestamp(_) => { - post_merge.push(display_fork); - } - ForkCondition::Never => continue, - } - } - - Self { pre_merge, with_merge, post_merge } - } -} - /// `PoS` deposit contract details. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DepositContract { @@ -1741,7 +1470,7 @@ impl OptimismGenesisInfo { mod tests { use alloy_chains::Chain; use alloy_genesis::{ChainConfig, GenesisAccount}; - use reth_ethereum_forks::{ForkHash, ForkId, Head}; + use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head}; use reth_trie_common::TrieAccount; use super::*; diff --git a/crates/ethereum-forks/src/display.rs b/crates/ethereum-forks/src/display.rs new file mode 100644 index 000000000000..3c1424083638 --- /dev/null +++ b/crates/ethereum-forks/src/display.rs @@ -0,0 +1,181 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + collections::BTreeMap, + format, + string::{String, ToString}, + vec::Vec, +}; + +use crate::{ForkCondition, Hardfork}; +#[cfg(feature = "std")] +use std::collections::BTreeMap; + +/// A container to pretty-print a hardfork. +/// +/// The fork is formatted depending on its fork condition: +/// +/// - Block and timestamp based forks are formatted in the same manner (`{name} <({eip})> +/// @{condition}`) +/// - TTD based forks are formatted separately as `{name} <({eip})> @{ttd} (network is known +/// to be merged)` +/// +/// An optional EIP can be attached to the fork to display as well. This should generally be in the +/// form of just `EIP-x`, e.g. `EIP-1559`. +#[derive(Debug)] +struct DisplayFork { + /// The name of the hardfork (e.g. Frontier) + name: String, + /// The fork condition + activated_at: ForkCondition, + /// An optional EIP (e.g. `EIP-1559`). + eip: Option, +} + +impl core::fmt::Display for DisplayFork { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let name_with_eip = if let Some(eip) = &self.eip { + format!("{} ({})", self.name, eip) + } else { + self.name.clone() + }; + + match self.activated_at { + ForkCondition::Block(at) | ForkCondition::Timestamp(at) => { + write!(f, "{name_with_eip:32} @{at}")?; + } + ForkCondition::TTD { fork_block, total_difficulty } => { + write!( + f, + "{:32} @{} ({})", + name_with_eip, + total_difficulty, + if fork_block.is_some() { + "network is known to be merged" + } else { + "network is not known to be merged" + } + )?; + } + ForkCondition::Never => unreachable!(), + } + + Ok(()) + } +} + +// Todo: This will result in dep cycle so currently commented out +// # Examples +// +// ``` +// # use reth_chainspec::MAINNET; +// println!("{}", MAINNET.display_hardforks()); +// ``` +// +/// A container for pretty-printing a list of hardforks. +/// +/// An example of the output: +/// +/// ```text +/// Pre-merge hard forks (block based): +// - Frontier @0 +// - Homestead @1150000 +// - Dao @1920000 +// - Tangerine @2463000 +// - SpuriousDragon @2675000 +// - Byzantium @4370000 +// - Constantinople @7280000 +// - Petersburg @7280000 +// - Istanbul @9069000 +// - MuirGlacier @9200000 +// - Berlin @12244000 +// - London @12965000 +// - ArrowGlacier @13773000 +// - GrayGlacier @15050000 +// Merge hard forks: +// - Paris @58750000000000000000000 (network is known to be merged) +// Post-merge hard forks (timestamp based): +// - Shanghai @1681338455 +/// ``` +#[derive(Debug)] +pub struct DisplayHardforks { + /// A list of pre-merge (block based) hardforks + pre_merge: Vec, + /// A list of merge (TTD based) hardforks + with_merge: Vec, + /// A list of post-merge (timestamp based) hardforks + post_merge: Vec, +} + +impl core::fmt::Display for DisplayHardforks { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fn format( + header: &str, + forks: &[DisplayFork], + next_is_empty: bool, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + writeln!(f, "{header}:")?; + let mut iter = forks.iter().peekable(); + while let Some(fork) = iter.next() { + write!(f, "- {fork}")?; + if !next_is_empty || iter.peek().is_some() { + writeln!(f)?; + } + } + Ok(()) + } + + format( + "Pre-merge hard forks (block based)", + &self.pre_merge, + self.with_merge.is_empty(), + f, + )?; + + if !self.with_merge.is_empty() { + format("Merge hard forks", &self.with_merge, self.post_merge.is_empty(), f)?; + } + + if !self.post_merge.is_empty() { + format("Post-merge hard forks (timestamp based)", &self.post_merge, true, f)?; + } + + Ok(()) + } +} + +impl DisplayHardforks { + /// Creates a new [`DisplayHardforks`] from an iterator of hardforks. + pub fn new( + hardforks: &BTreeMap, + known_paris_block: Option, + ) -> Self { + let mut pre_merge = Vec::new(); + let mut with_merge = Vec::new(); + let mut post_merge = Vec::new(); + + for (fork, condition) in hardforks { + let mut display_fork = + DisplayFork { name: fork.to_string(), activated_at: *condition, eip: None }; + + match condition { + ForkCondition::Block(_) => { + pre_merge.push(display_fork); + } + ForkCondition::TTD { total_difficulty, .. } => { + display_fork.activated_at = ForkCondition::TTD { + fork_block: known_paris_block, + total_difficulty: *total_difficulty, + }; + with_merge.push(display_fork); + } + ForkCondition::Timestamp(_) => { + post_merge.push(display_fork); + } + ForkCondition::Never => continue, + } + } + + Self { pre_merge, with_merge, post_merge } + } +} diff --git a/crates/ethereum-forks/src/forkcondition.rs b/crates/ethereum-forks/src/forkcondition.rs new file mode 100644 index 000000000000..f57216845cc9 --- /dev/null +++ b/crates/ethereum-forks/src/forkcondition.rs @@ -0,0 +1,103 @@ +use crate::Head; +use alloy_primitives::{BlockNumber, U256}; + +/// The condition at which a fork is activated. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ForkCondition { + /// The fork is activated after a certain block. + Block(BlockNumber), + /// The fork is activated after a total difficulty has been reached. + TTD { + /// The block number at which TTD is reached, if it is known. + /// + /// This should **NOT** be set unless you want this block advertised as [EIP-2124][eip2124] + /// `FORK_NEXT`. This is currently only the case for Sepolia and Holesky. + /// + /// [eip2124]: https://eips.ethereum.org/EIPS/eip-2124 + fork_block: Option, + /// The total difficulty after which the fork is activated. + total_difficulty: U256, + }, + /// The fork is activated after a specific timestamp. + Timestamp(u64), + /// The fork is never activated + #[default] + Never, +} + +impl ForkCondition { + /// Returns true if the fork condition is timestamp based. + pub const fn is_timestamp(&self) -> bool { + matches!(self, Self::Timestamp(_)) + } + + /// Checks whether the fork condition is satisfied at the given block. + /// + /// For TTD conditions, this will only return true if the activation block is already known. + /// + /// For timestamp conditions, this will always return false. + pub const fn active_at_block(&self, current_block: BlockNumber) -> bool { + matches!(self, Self::Block(block) + | Self::TTD { fork_block: Some(block), .. } if current_block >= *block) + } + + /// Checks if the given block is the first block that satisfies the fork condition. + /// + /// This will return false for any condition that is not block based. + pub const fn transitions_at_block(&self, current_block: BlockNumber) -> bool { + matches!(self, Self::Block(block) if current_block == *block) + } + + /// Checks whether the fork condition is satisfied at the given total difficulty and difficulty + /// of a current block. + /// + /// The fork is considered active if the _previous_ total difficulty is above the threshold. + /// To achieve that, we subtract the passed `difficulty` from the current block's total + /// difficulty, and check if it's above the Fork Condition's total difficulty (here: + /// `58_750_000_000_000_000_000_000`) + /// + /// This will return false for any condition that is not TTD-based. + pub fn active_at_ttd(&self, ttd: U256, difficulty: U256) -> bool { + matches!(self, Self::TTD { total_difficulty, .. } + if ttd.saturating_sub(difficulty) >= *total_difficulty) + } + + /// Checks whether the fork condition is satisfied at the given timestamp. + /// + /// This will return false for any condition that is not timestamp-based. + pub const fn active_at_timestamp(&self, timestamp: u64) -> bool { + matches!(self, Self::Timestamp(time) if timestamp >= *time) + } + + /// Checks whether the fork condition is satisfied at the given head block. + /// + /// This will return true if: + /// + /// - The condition is satisfied by the block number; + /// - The condition is satisfied by the timestamp; + /// - or the condition is satisfied by the total difficulty + pub fn active_at_head(&self, head: &Head) -> bool { + self.active_at_block(head.number) || + self.active_at_timestamp(head.timestamp) || + self.active_at_ttd(head.total_difficulty, head.difficulty) + } + + /// Get the total terminal difficulty for this fork condition. + /// + /// Returns `None` for fork conditions that are not TTD based. + pub const fn ttd(&self) -> Option { + match self { + Self::TTD { total_difficulty, .. } => Some(*total_difficulty), + _ => None, + } + } + + /// Returns the timestamp of the fork condition, if it is timestamp based. + pub const fn as_timestamp(&self) -> Option { + match self { + Self::Timestamp(timestamp) => Some(*timestamp), + _ => None, + } + } +} diff --git a/crates/ethereum-forks/src/lib.rs b/crates/ethereum-forks/src/lib.rs index 8457a5f6206b..c7831026905f 100644 --- a/crates/ethereum-forks/src/lib.rs +++ b/crates/ethereum-forks/src/lib.rs @@ -20,6 +20,8 @@ #[cfg(not(feature = "std"))] extern crate alloc; +mod display; +mod forkcondition; mod forkid; mod hardfork; mod head; @@ -30,5 +32,8 @@ pub use forkid::{ pub use hardfork::Hardfork; pub use head::Head; +pub use display::DisplayHardforks; +pub use forkcondition::ForkCondition; + #[cfg(any(test, feature = "arbitrary"))] pub use arbitrary; From 770cac874c17ed33401c2804b604b14dfcf73b38 Mon Sep 17 00:00:00 2001 From: 0xAtreides <103257861+JackG-eth@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:58:10 +0100 Subject: [PATCH 103/405] feat: reth-primitives-trait no-std support (#8869) Co-authored-by: Matthias Seitz --- crates/primitives-traits/Cargo.toml | 2 ++ crates/primitives-traits/src/constants.rs | 2 +- crates/primitives-traits/src/header/mod.rs | 2 +- crates/primitives-traits/src/header/sealed.rs | 2 +- crates/primitives-traits/src/lib.rs | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index 11f714779fbe..97ed1a34433c 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -42,6 +42,8 @@ test-fuzz.workspace = true rand.workspace = true [features] +default = ["std"] +std = [] test-utils = ["arbitrary"] arbitrary = [ "dep:arbitrary", diff --git a/crates/primitives-traits/src/constants.rs b/crates/primitives-traits/src/constants.rs index bda693976305..6c32ab351c4d 100644 --- a/crates/primitives-traits/src/constants.rs +++ b/crates/primitives-traits/src/constants.rs @@ -1,7 +1,7 @@ //! Ethereum protocol-related constants use alloy_primitives::{b256, B256, U256}; -use std::time::Duration; +use core::time::Duration; /// The client version: `reth/v{major}.{minor}.{patch}` pub const RETH_CLIENT_VERSION: &str = concat!("reth/v", env!("CARGO_PKG_VERSION")); diff --git a/crates/primitives-traits/src/header/mod.rs b/crates/primitives-traits/src/header/mod.rs index 5ec41d41450c..2b112954c267 100644 --- a/crates/primitives-traits/src/header/mod.rs +++ b/crates/primitives-traits/src/header/mod.rs @@ -15,9 +15,9 @@ use alloy_eips::{ use alloy_primitives::{keccak256, Address, BlockNumber, Bloom, Bytes, B256, B64, U256}; use alloy_rlp::{length_of_length, Decodable, Encodable}; use bytes::BufMut; +use core::mem; use reth_codecs::{main_codec, Compact}; use revm_primitives::{calc_blob_gasprice, calc_excess_blob_gas}; -use std::mem; /// Block header #[main_codec] diff --git a/crates/primitives-traits/src/header/sealed.rs b/crates/primitives-traits/src/header/sealed.rs index 91918b6877dc..894bd28aedae 100644 --- a/crates/primitives-traits/src/header/sealed.rs +++ b/crates/primitives-traits/src/header/sealed.rs @@ -5,11 +5,11 @@ use alloy_primitives::{keccak256, BlockHash}; use alloy_primitives::{BlockNumber, B256, U256}; use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; +use core::mem; use derive_more::{AsRef, Deref}; #[cfg(any(test, feature = "arbitrary"))] use proptest::prelude::*; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; -use std::mem; /// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want /// to modify header. diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 68161709cd3b..0051f6c7df32 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -9,6 +9,7 @@ // TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged #![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "alloy-compat")] mod alloy_compat; From a5054084d704e11815ff39cfcce9702daf1bb56b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 18 Jun 2024 14:03:44 +0200 Subject: [PATCH 104/405] chore: release 1.0.0-rc.2 (#8918) --- Cargo.lock | 194 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 98 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 879f8663e9b8..545e39190424 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2695,7 +2695,7 @@ dependencies = [ [[package]] name = "ef-tests" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "rayon", @@ -6213,7 +6213,7 @@ dependencies = [ [[package]] name = "reth" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "ahash", "alloy-rlp", @@ -6300,7 +6300,7 @@ dependencies = [ [[package]] name = "reth-auto-seal-consensus" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "futures-util", "reth-beacon-consensus", @@ -6325,7 +6325,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "futures-core", @@ -6347,7 +6347,7 @@ dependencies = [ [[package]] name = "reth-beacon-consensus" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "assert_matches", "futures", @@ -6397,7 +6397,7 @@ dependencies = [ [[package]] name = "reth-bench" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6438,7 +6438,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "aquamarine", "assert_matches", @@ -6470,7 +6470,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -6481,7 +6481,7 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-chains", "alloy-eips", @@ -6504,7 +6504,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-tasks", "tokio", @@ -6513,7 +6513,7 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6532,7 +6532,7 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "convert_case 0.6.0", "proc-macro2", @@ -6543,7 +6543,7 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "confy", "humantime-serde", @@ -6556,7 +6556,7 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "auto_impl", "reth-primitives", @@ -6565,7 +6565,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "mockall", "rand 0.8.5", @@ -6577,7 +6577,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6599,7 +6599,7 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "arbitrary", "assert_matches", @@ -6637,7 +6637,7 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "arbitrary", "assert_matches", @@ -6666,7 +6666,7 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "eyre", "reth-chainspec", @@ -6687,7 +6687,7 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6714,7 +6714,7 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6740,7 +6740,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-chains", "alloy-primitives", @@ -6768,7 +6768,7 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "assert_matches", @@ -6802,7 +6802,7 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-consensus", "alloy-network", @@ -6832,7 +6832,7 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "aes 0.8.4", "alloy-primitives", @@ -6862,7 +6862,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-chainspec", "reth-payload-primitives", @@ -6871,7 +6871,7 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-blockchain-tree-api", "reth-consensus", @@ -6883,7 +6883,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "arbitrary", @@ -6918,7 +6918,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "arbitrary", @@ -6941,7 +6941,7 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-chainspec", "reth-consensus", @@ -6952,7 +6952,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "reth-chainspec", @@ -6969,7 +6969,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-chains", "alloy-primitives", @@ -6984,7 +6984,7 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-basic-payload-builder", "reth-errors", @@ -7001,7 +7001,7 @@ dependencies = [ [[package]] name = "reth-etl" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "rayon", @@ -7011,7 +7011,7 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "auto_impl", "futures-util", @@ -7028,7 +7028,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-eips", "alloy-sol-types", @@ -7047,7 +7047,7 @@ dependencies = [ [[package]] name = "reth-evm-optimism" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-chainspec", "reth-consensus-common", @@ -7066,7 +7066,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7079,7 +7079,7 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7092,7 +7092,7 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "eyre", "metrics", @@ -7113,7 +7113,7 @@ dependencies = [ [[package]] name = "reth-exex-test-utils" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "eyre", "futures-util", @@ -7142,14 +7142,14 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-fs-util" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "serde_json", "thiserror", @@ -7157,7 +7157,7 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "async-trait", "bytes", @@ -7179,7 +7179,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "bitflags 2.5.0", "byteorder", @@ -7199,7 +7199,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "bindgen", "cc", @@ -7207,7 +7207,7 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "futures", "metrics", @@ -7218,7 +7218,7 @@ dependencies = [ [[package]] name = "reth-metrics-derive" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "metrics", "once_cell", @@ -7232,7 +7232,7 @@ dependencies = [ [[package]] name = "reth-net-common" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "tokio", @@ -7240,7 +7240,7 @@ dependencies = [ [[package]] name = "reth-net-nat" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "futures-util", "reqwest", @@ -7252,7 +7252,7 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-node-bindings", "alloy-provider", @@ -7308,7 +7308,7 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "enr", @@ -7322,7 +7322,7 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "auto_impl", "futures", @@ -7340,7 +7340,7 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7356,7 +7356,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "anyhow", "bincode", @@ -7377,7 +7377,7 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-db-api", "reth-engine-primitives", @@ -7392,7 +7392,7 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "aquamarine", "backon", @@ -7442,7 +7442,7 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rpc-types-engine", "clap", @@ -7506,7 +7506,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -7539,7 +7539,7 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rpc-types-engine", "futures", @@ -7560,7 +7560,7 @@ dependencies = [ [[package]] name = "reth-node-optimism" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "async-trait", @@ -7602,7 +7602,7 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-chainspec", "reth-consensus", @@ -7613,7 +7613,7 @@ dependencies = [ [[package]] name = "reth-optimism-payload-builder" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "reth-basic-payload-builder", @@ -7636,11 +7636,11 @@ dependencies = [ [[package]] name = "reth-optimism-primitives" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" [[package]] name = "reth-payload-builder" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "futures-util", "metrics", @@ -7662,7 +7662,7 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-chainspec", "reth-errors", @@ -7676,7 +7676,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-chainspec", "reth-primitives", @@ -7686,7 +7686,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7733,7 +7733,7 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7756,7 +7756,7 @@ dependencies = [ [[package]] name = "reth-provider" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "alloy-rpc-types-engine", @@ -7798,7 +7798,7 @@ dependencies = [ [[package]] name = "reth-prune" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "assert_matches", @@ -7826,7 +7826,7 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "arbitrary", @@ -7846,7 +7846,7 @@ dependencies = [ [[package]] name = "reth-revm" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-eips", "alloy-rlp", @@ -7864,7 +7864,7 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -7922,7 +7922,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-dyn-abi", "jsonrpsee", @@ -7936,7 +7936,7 @@ dependencies = [ [[package]] name = "reth-rpc-api-testing-util" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "futures", "jsonrpsee", @@ -7950,7 +7950,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "clap", "http 1.1.0", @@ -7992,7 +7992,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "assert_matches", @@ -8025,7 +8025,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rpc-types-engine", "assert_matches", @@ -8042,7 +8042,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "serde", @@ -8051,7 +8051,7 @@ dependencies = [ [[package]] name = "reth-rpc-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -8075,7 +8075,7 @@ dependencies = [ [[package]] name = "reth-rpc-types-compat" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "alloy-rpc-types", @@ -8087,7 +8087,7 @@ dependencies = [ [[package]] name = "reth-stages" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "assert_matches", @@ -8131,7 +8131,7 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "aquamarine", @@ -8160,7 +8160,7 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "arbitrary", @@ -8177,7 +8177,7 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "assert_matches", @@ -8199,7 +8199,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "clap", @@ -8210,7 +8210,7 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "auto_impl", "reth-chainspec", @@ -8226,7 +8226,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "reth-fs-util", "reth-primitives", @@ -8235,7 +8235,7 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "dyn-clone", "futures-util", @@ -8251,7 +8251,7 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-genesis", "rand 0.8.5", @@ -8261,7 +8261,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "tokio", "tokio-stream", @@ -8270,7 +8270,7 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "clap", "eyre", @@ -8284,7 +8284,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "aquamarine", @@ -8322,7 +8322,7 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "auto_impl", @@ -8353,7 +8353,7 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -8381,7 +8381,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "alloy-rlp", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 148eba4835be..e0f5b2087670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" edition = "2021" rust-version = "1.79" license = "MIT OR Apache-2.0" From 148c11e222f1df287dec50e8c6727fe8310931c6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 18 Jun 2024 14:03:53 +0200 Subject: [PATCH 105/405] fix: enable enr secp256k1 feature (#8919) --- crates/net/peers/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/net/peers/Cargo.toml b/crates/net/peers/Cargo.toml index 854ca3fbd5c8..6b107432632e 100644 --- a/crates/net/peers/Cargo.toml +++ b/crates/net/peers/Cargo.toml @@ -34,4 +34,4 @@ secp256k1 = { workspace = true, features = ["rand"] } serde_json.workspace = true [features] -secp256k1 = ["dep:secp256k1"] +secp256k1 = ["dep:secp256k1", "enr/secp256k1"] From 3a1e4e9ad4297303e8316939a56b0d21994c35b5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 18 Jun 2024 14:29:49 +0200 Subject: [PATCH 106/405] chore(deps): enable missing arbitrary in tests (#8921) --- crates/primitives/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index ff3cf016956a..1b2dfab727f5 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -59,10 +59,12 @@ proptest-derive = { workspace = true, optional = true } [dev-dependencies] # eth +reth-primitives-traits = { workspace = true, features = ["arbitrary"] } revm-primitives = { workspace = true, features = ["arbitrary"] } nybbles = { workspace = true, features = ["arbitrary"] } alloy-trie = { workspace = true, features = ["arbitrary"] } alloy-eips = { workspace = true, features = ["arbitrary"] } +alloy-consensus = { workspace = true, features = ["arbitrary"] } assert_matches.workspace = true arbitrary = { workspace = true, features = ["derive"] } From d786b459d93a6e1f7cf903a37f0542aafd671e7f Mon Sep 17 00:00:00 2001 From: Zach Obront Date: Tue, 18 Jun 2024 07:33:19 -0500 Subject: [PATCH 107/405] feat: implement conversion of optimism deposit tx from alloy to reth (#8763) Co-authored-by: Matthias Seitz --- crates/primitives-traits/src/constants.rs | 5 +- crates/primitives/src/alloy_compat.rs | 136 +++++++++++++++++++++- 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/crates/primitives-traits/src/constants.rs b/crates/primitives-traits/src/constants.rs index 6c32ab351c4d..f8b427389b22 100644 --- a/crates/primitives-traits/src/constants.rs +++ b/crates/primitives-traits/src/constants.rs @@ -1,6 +1,6 @@ //! Ethereum protocol-related constants -use alloy_primitives::{b256, B256, U256}; +use alloy_primitives::{address, b256, Address, B256, U256}; use core::time::Duration; /// The client version: `reth/v{major}.{minor}.{patch}` @@ -131,6 +131,9 @@ pub const EMPTY_OMMER_ROOT_HASH: B256 = pub const EMPTY_ROOT_HASH: B256 = b256!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); +/// From address from Optimism system txs: `0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001` +pub const OP_SYSTEM_TX_FROM_ADDR: Address = address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001"); + /// Transactions root of empty receipts set. pub const EMPTY_RECEIPTS: B256 = EMPTY_ROOT_HASH; diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index d193b787fd52..8b61796a735d 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -182,7 +182,30 @@ impl TryFrom for Transaction { })) } #[cfg(feature = "optimism")] - Some(TxType::Deposit) => todo!(), + Some(TxType::Deposit) => Ok(Self::Deposit(crate::transaction::TxDeposit { + source_hash: tx + .other + .get_deserialized::("sourceHash") + .ok_or_else(|| ConversionError::Custom("MissingSourceHash".to_string()))? + .map_err(|_| ConversionError::Custom("MissingSourceHash".to_string()))? + .parse() + .map_err(|_| ConversionError::Custom("InvalidSourceHash".to_string()))?, + from: tx.from, + to: TxKind::from(tx.to), + mint: Option::transpose( + tx.other.get_deserialized::("mint"), + ) + .map_err(|_| ConversionError::Custom("MissingMintValue".to_string()))? + .map(|num| num.to::()) + .filter(|num| *num > 0), + value: tx.value, + gas_limit: tx + .gas + .try_into() + .map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?, + is_system_transaction: tx.from == crate::constants::OP_SYSTEM_TX_FROM_ADDR, + input: tx.input, + })), } } } @@ -247,3 +270,114 @@ impl TryFrom for Signature { Ok(Self { r: signature.r, s: signature.s, odd_y_parity }) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{B256, U256}; + use alloy_rpc_types::Transaction as AlloyTransaction; + use revm_primitives::{address, Address}; + + #[test] + #[cfg(feature = "optimism")] + fn optimism_deposit_tx_conversion_no_mint() { + let input = r#"{ + "blockHash": "0xef664d656f841b5ad6a2b527b963f1eb48b97d7889d742f6cbff6950388e24cd", + "blockNumber": "0x73a78fd", + "depositReceiptVersion": "0x1", + "from": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2", + "gas": "0xc27a8", + "gasPrice": "0x0", + "hash": "0x0bf1845c5d7a82ec92365d5027f7310793d53004f3c86aa80965c67bf7e7dc80", + "input": "0xd764ad0b000100000000000000000000000000000000000000000000000000000001cf5400000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be100000000000000000000000042000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007a12000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e40166a07a0000000000000000000000000994206dfe8de6ec6920ff4d779b0d950605fb53000000000000000000000000d533a949740bb3306d119cc777fa900ba034cd52000000000000000000000000ca74f404e0c7bfa35b13b511097df966d5a65597000000000000000000000000ca74f404e0c7bfa35b13b511097df966d5a65597000000000000000000000000000000000000000000000216614199391dbba2ba00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "mint": "0x0", + "nonce": "0x74060", + "r": "0x0", + "s": "0x0", + "sourceHash": "0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3", + "to": "0x4200000000000000000000000000000000000007", + "transactionIndex": "0x1", + "type": "0x7e", + "v": "0x0", + "value": "0x0" + }"#; + let alloy_tx: AlloyTransaction = + serde_json::from_str(input).expect("failed to deserialize"); + + let reth_tx: Transaction = alloy_tx.try_into().expect("failed to convert"); + if let Transaction::Deposit(deposit_tx) = reth_tx { + assert_eq!( + deposit_tx.source_hash, + "0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3" + .parse::() + .unwrap() + ); + assert_eq!( + deposit_tx.from, + "0x36bde71c97b33cc4729cf772ae268934f7ab70b2".parse::

().unwrap() + ); + assert_eq!( + deposit_tx.to, + TxKind::from(address!("4200000000000000000000000000000000000007")) + ); + assert_eq!(deposit_tx.mint, None); + assert_eq!(deposit_tx.value, U256::ZERO); + assert_eq!(deposit_tx.gas_limit, 796584); + assert!(!deposit_tx.is_system_transaction); + } else { + panic!("Expected Deposit transaction"); + } + } + + #[test] + #[cfg(feature = "optimism")] + fn optimism_deposit_tx_conversion_mint() { + let input = r#"{ + "blockHash": "0x7194f63b105e93fb1a27c50d23d62e422d4185a68536c55c96284911415699b2", + "blockNumber": "0x73a82cc", + "depositReceiptVersion": "0x1", + "from": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2", + "gas": "0x7812e", + "gasPrice": "0x0", + "hash": "0xf7e83886d3c6864f78e01c453ebcd57020c5795d96089e8f0e0b90a467246ddb", + "input": "0xd764ad0b000100000000000000000000000000000000000000000000000000000001cf5f00000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be100000000000000000000000042000000000000000000000000000000000000100000000000000000000000000000000000000000000000239c2e16a5ca5900000000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e41635f5fd0000000000000000000000002ce910fbba65b454bbaf6a18c952a70f3bcd82990000000000000000000000002ce910fbba65b454bbaf6a18c952a70f3bcd82990000000000000000000000000000000000000000000000239c2e16a5ca590000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "mint": "0x239c2e16a5ca590000", + "nonce": "0x7406b", + "r": "0x0", + "s": "0x0", + "sourceHash": "0xe0358cd2b2686d297c5c859646a613124a874fb9d9c4a2c88636a46a65c06e48", + "to": "0x4200000000000000000000000000000000000007", + "transactionIndex": "0x1", + "type": "0x7e", + "v": "0x0", + "value": "0x239c2e16a5ca590000" + }"#; + let alloy_tx: AlloyTransaction = + serde_json::from_str(input).expect("failed to deserialize"); + + let reth_tx: Transaction = alloy_tx.try_into().expect("failed to convert"); + + if let Transaction::Deposit(deposit_tx) = reth_tx { + assert_eq!( + deposit_tx.source_hash, + "0xe0358cd2b2686d297c5c859646a613124a874fb9d9c4a2c88636a46a65c06e48" + .parse::() + .unwrap() + ); + assert_eq!( + deposit_tx.from, + "0x36bde71c97b33cc4729cf772ae268934f7ab70b2".parse::
().unwrap() + ); + assert_eq!( + deposit_tx.to, + TxKind::from(address!("4200000000000000000000000000000000000007")) + ); + assert_eq!(deposit_tx.mint, Some(656890000000000000000)); + assert_eq!(deposit_tx.value, U256::from(0x239c2e16a5ca590000_u128)); + assert_eq!(deposit_tx.gas_limit, 491822); + assert!(!deposit_tx.is_system_transaction); + } else { + panic!("Expected Deposit transaction"); + } + } +} From 636e856380774c0a1b84b3f3aa5cc9bf8e56e2ba Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 18 Jun 2024 15:29:07 +0200 Subject: [PATCH 108/405] chore: use `BLOCKHASH_SERVE_WINDOW` from revm (#8924) --- crates/ethereum/evm/src/execute.rs | 11 +++++------ crates/revm/src/state_change.rs | 8 +++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index f9a5b752d20c..8bc6a9e8507c 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -467,11 +467,10 @@ mod tests { keccak256, public_key_to_address, Account, Block, Transaction, TxKind, TxLegacy, B256, }; use reth_revm::{ - database::StateProviderDatabase, state_change::HISTORY_SERVE_WINDOW, - test_utils::StateProviderTest, TransitionState, + database::StateProviderDatabase, test_utils::StateProviderTest, TransitionState, }; use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; - use revm_primitives::{b256, fixed_bytes, Bytes}; + use revm_primitives::{b256, fixed_bytes, Bytes, BLOCKHASH_SERVE_WINDOW}; use secp256k1::{Keypair, Secp256k1}; use std::collections::HashMap; @@ -976,7 +975,7 @@ mod tests { #[test] fn eip_2935_fork_activation_within_window_bounds() { - let fork_activation_block = HISTORY_SERVE_WINDOW - 10; + let fork_activation_block = (BLOCKHASH_SERVE_WINDOW - 10) as u64; let db = create_state_provider_with_block_hashes(fork_activation_block); let chain_spec = Arc::new( @@ -1039,7 +1038,7 @@ mod tests { #[test] fn eip_2935_fork_activation_outside_window_bounds() { - let fork_activation_block = HISTORY_SERVE_WINDOW + 256; + let fork_activation_block = (BLOCKHASH_SERVE_WINDOW + 256) as u64; let db = create_state_provider_with_block_hashes(fork_activation_block); let chain_spec = Arc::new( @@ -1090,7 +1089,7 @@ mod tests { .state_mut() .storage( HISTORY_STORAGE_ADDRESS, - U256::from(fork_activation_block % HISTORY_SERVE_WINDOW - 1) + U256::from(fork_activation_block % BLOCKHASH_SERVE_WINDOW as u64 - 1) ) .unwrap(), U256::ZERO diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index 1709b54cb80d..947c08da7efc 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -17,7 +17,8 @@ use reth_storage_errors::provider::ProviderError; use revm::{ interpreter::Host, primitives::{ - Account, AccountInfo, Bytecode, EvmStorageSlot, ExecutionResult, FixedBytes, ResultAndState, + Account, AccountInfo, Bytecode, EvmStorageSlot, ExecutionResult, FixedBytes, + ResultAndState, BLOCKHASH_SERVE_WINDOW, }, Database, DatabaseCommit, Evm, }; @@ -67,9 +68,6 @@ pub fn post_block_balance_increments( balance_increments } -/// todo: temporary move over of constants from revm until we've migrated to the latest version -pub const HISTORY_SERVE_WINDOW: u64 = 8192; - /// Applies the pre-block state change outlined in [EIP-2935] to store historical blockhashes in a /// system contract. /// @@ -131,7 +129,7 @@ fn eip2935_block_hash_slot>( block_number: u64, block_hash: B256, ) -> Result<(U256, EvmStorageSlot), BlockValidationError> { - let slot = U256::from(block_number % HISTORY_SERVE_WINDOW); + let slot = U256::from(block_number % BLOCKHASH_SERVE_WINDOW as u64); let current_hash = db .storage(HISTORY_STORAGE_ADDRESS, slot) .map_err(BlockValidationError::BlockHashAccountLoadingFailed)?; From e25e1d72ef08ca370b3d8fabbef34d3126fd0362 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 18 Jun 2024 15:36:58 +0200 Subject: [PATCH 109/405] chore: move audit (#8922) --- README.md | 2 +- .../sigma_prime_audit_v1.pdf | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename Sigma_Prime_Paradigm_Reth_Security_Assessment_Report_v1_0.pdf => audit/sigma_prime_audit_v1.pdf (100%) diff --git a/README.md b/README.md index 00b575a798e2..bfb1c6c65f1b 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Reth is production ready, and suitable for usage in mission-critical environment More historical context below: * We released 1.0 "production-ready" stable Reth in June 2024. - * Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./Sigma_Prime_Paradigm_Reth_Security_Assessment_Report_v1_0.pdf). + * Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v1.pdf). * Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. * We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3rd 2024 the last beta release. * We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4th 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives. diff --git a/Sigma_Prime_Paradigm_Reth_Security_Assessment_Report_v1_0.pdf b/audit/sigma_prime_audit_v1.pdf similarity index 100% rename from Sigma_Prime_Paradigm_Reth_Security_Assessment_Report_v1_0.pdf rename to audit/sigma_prime_audit_v1.pdf From a4f0d5738fe9b4f39eb943c256880d89cb7d4dfa Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:44:05 +0200 Subject: [PATCH 110/405] chore: remove enr TODO (#8926) --- Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0f5b2087670..65a33445da55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -474,9 +474,7 @@ secp256k1 = { version = "0.29", default-features = false, features = [ "global-context", "recovery", ] } -# TODO: Remove `k256` feature: https://github.com/sigp/enr/pull/74 -enr = { version = "0.12.0", default-features = false, features = [ - "k256", +enr = { version = "0.12.1", default-features = false, features = [ "rust-secp256k1", ] } From cc502523ca6f7a0856dc09a6282257c53f87c4f7 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 18 Jun 2024 16:17:09 +0200 Subject: [PATCH 111/405] chore: rm codecov.yml (#8923) --- codecov.yml | 68 ----------------------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 5bd75590b504..000000000000 --- a/codecov.yml +++ /dev/null @@ -1,68 +0,0 @@ -coverage: - status: - patch: off - project: - default: - threshold: null - informational: true -github_checks: - annotations: false -comment: - layout: "reach, files, flags, components" - require_changes: true -component_management: - individual_components: - - component_id: reth_binary - name: reth binary - paths: - - bin/** - - crates/config/** - - crates/metrics/** - - crates/tracing/** - - component_id: blockchain_tree - name: blockchain tree - paths: - - crates/blockchain-tree/** - - component_id: staged_sync - name: pipeline - paths: - - crates/stages/** - - component_id: storage - name: storage (db) - paths: - - crates/storage/** - - component_id: trie - name: trie - paths: - - crates/trie/** - - component_id: txpool - name: txpool - paths: - - crates/transaction-pool/** - - component_id: networking - name: networking - paths: - - crates/net/** - - component_id: rpc - name: rpc - paths: - - crates/rpc/** - - component_id: consensus - name: consensus - paths: - - crates/consensus/** - - component_id: revm - name: revm - paths: - - crates/revm/** - - component_id: builder - name: payload builder - paths: - - crates/payload/** - - component_id: primitives - name: primitives - paths: - - crates/primitives/** - - crates/tasks/** - - crates/rlp/** - - crates/interfaces/** \ No newline at end of file From 1f2bd941d9f3d05b5e2a1245c5ad4ddebebea65b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:30:42 +0200 Subject: [PATCH 112/405] chore: simplify optimism tx compat (#8925) --- crates/primitives/src/alloy_compat.rs | 46 +++++++++---------- crates/primitives/src/block.rs | 2 +- crates/storage/codecs/derive/src/arbitrary.rs | 5 +- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index 8b61796a735d..e53be884423b 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -182,30 +182,27 @@ impl TryFrom for Transaction { })) } #[cfg(feature = "optimism")] - Some(TxType::Deposit) => Ok(Self::Deposit(crate::transaction::TxDeposit { - source_hash: tx + Some(TxType::Deposit) => { + let fields = tx .other - .get_deserialized::("sourceHash") - .ok_or_else(|| ConversionError::Custom("MissingSourceHash".to_string()))? - .map_err(|_| ConversionError::Custom("MissingSourceHash".to_string()))? - .parse() - .map_err(|_| ConversionError::Custom("InvalidSourceHash".to_string()))?, - from: tx.from, - to: TxKind::from(tx.to), - mint: Option::transpose( - tx.other.get_deserialized::("mint"), - ) - .map_err(|_| ConversionError::Custom("MissingMintValue".to_string()))? - .map(|num| num.to::()) - .filter(|num| *num > 0), - value: tx.value, - gas_limit: tx - .gas - .try_into() - .map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?, - is_system_transaction: tx.from == crate::constants::OP_SYSTEM_TX_FROM_ADDR, - input: tx.input, - })), + .deserialize_into::() + .map_err(|e| ConversionError::Custom(e.to_string()))?; + Ok(Self::Deposit(crate::transaction::TxDeposit { + source_hash: fields + .source_hash + .ok_or_else(|| ConversionError::Custom("MissingSourceHash".to_string()))?, + from: tx.from, + to: TxKind::from(tx.to), + mint: fields.mint.map(|n| n.to::()).filter(|n| *n != 0), + value: tx.value, + gas_limit: tx + .gas + .try_into() + .map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?, + is_system_transaction: fields.is_system_tx.unwrap_or(false), + input: tx.input, + })) + } } } } @@ -272,6 +269,7 @@ impl TryFrom for Signature { } #[cfg(test)] +#[cfg(feature = "optimism")] mod tests { use super::*; use alloy_primitives::{B256, U256}; @@ -279,7 +277,6 @@ mod tests { use revm_primitives::{address, Address}; #[test] - #[cfg(feature = "optimism")] fn optimism_deposit_tx_conversion_no_mint() { let input = r#"{ "blockHash": "0xef664d656f841b5ad6a2b527b963f1eb48b97d7889d742f6cbff6950388e24cd", @@ -330,7 +327,6 @@ mod tests { } #[test] - #[cfg(feature = "optimism")] fn optimism_deposit_tx_conversion_mint() { let input = r#"{ "blockHash": "0x7194f63b105e93fb1a27c50d23d62e422d4185a68536c55c96284911415699b2", diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 1e14392bffb6..08c261a39089 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -256,7 +256,7 @@ impl BlockWithSenders { /// Sealed Ethereum full block. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. -#[derive_arbitrary(rlp)] +#[derive_arbitrary(rlp 32)] #[derive( Debug, Clone, diff --git a/crates/storage/codecs/derive/src/arbitrary.rs b/crates/storage/codecs/derive/src/arbitrary.rs index 3e36f940527a..32271c5f0c5b 100644 --- a/crates/storage/codecs/derive/src/arbitrary.rs +++ b/crates/storage/codecs/derive/src/arbitrary.rs @@ -47,10 +47,10 @@ pub fn maybe_generate_tests(args: TokenStream, ast: &DeriveInput) -> TokenStream #[test] fn malformed_rlp_header_check() { - use rand::RngCore; + use rand::RngCore; // get random instance of type - let mut raw = [0u8;1024]; + let mut raw = [0u8; 1024]; rand::thread_rng().fill_bytes(&mut raw); let mut unstructured = arbitrary::Unstructured::new(&raw[..]); let val = ::arbitrary(&mut unstructured); @@ -72,7 +72,6 @@ pub fn maybe_generate_tests(args: TokenStream, ast: &DeriveInput) -> TokenStream let res = super::#type_ident::decode(&mut b.as_ref()); assert!(res.is_err(), "malformed header was decoded"); } - }); } else if let Ok(num) = arg.to_string().parse() { default_cases = num; From b5f55d9a2b36f6994d9e91b82f22c1980cdf8359 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 18 Jun 2024 17:33:04 +0200 Subject: [PATCH 113/405] chore: make reth-revm compile with no-std (#8931) --- crates/revm/Cargo.toml | 2 ++ crates/revm/src/batch.rs | 9 ++++++--- crates/revm/src/database.rs | 2 +- crates/revm/src/lib.rs | 4 ++++ crates/revm/src/state_change.rs | 14 +++++++++++--- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index e8204ee25bda..5462da7383da 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -36,4 +36,6 @@ tracing.workspace = true reth-trie.workspace = true [features] +default = ["std"] +std = [] test-utils = ["dep:reth-trie"] diff --git a/crates/revm/src/batch.rs b/crates/revm/src/batch.rs index 6f1b55b2195d..a9a44bff1bce 100644 --- a/crates/revm/src/batch.rs +++ b/crates/revm/src/batch.rs @@ -1,13 +1,16 @@ //! Helper for handling execution of multiple blocks. use crate::{precompile::Address, primitives::alloy_primitives::BlockNumber}; +use core::time::Duration; use reth_execution_errors::BlockExecutionError; use reth_primitives::{Receipt, Receipts, Request, Requests}; use reth_prune_types::{PruneMode, PruneModes, PruneSegmentError, MINIMUM_PRUNING_DISTANCE}; use revm::db::states::bundle_state::BundleRetention; -use std::time::Duration; use tracing::debug; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + /// Takes care of: /// - recording receipts during execution of multiple blocks. /// - pruning receipts according to the pruning configuration. @@ -78,7 +81,7 @@ impl BlockBatchRecord { /// Returns all recorded receipts. pub fn take_receipts(&mut self) -> Receipts { - std::mem::take(&mut self.receipts) + core::mem::take(&mut self.receipts) } /// Returns the recorded requests. @@ -88,7 +91,7 @@ impl BlockBatchRecord { /// Returns all recorded requests. pub fn take_requests(&mut self) -> Vec { - std::mem::take(&mut self.requests) + core::mem::take(&mut self.requests) } /// Returns the [`BundleRetention`] for the given block based on the configured prune modes. diff --git a/crates/revm/src/database.rs b/crates/revm/src/database.rs index fc40f474a2fb..3b31788dbdaf 100644 --- a/crates/revm/src/database.rs +++ b/crates/revm/src/database.rs @@ -1,4 +1,5 @@ use crate::primitives::alloy_primitives::{BlockNumber, StorageKey, StorageValue}; +use core::ops::{Deref, DerefMut}; use reth_primitives::{Account, Address, B256, KECCAK_EMPTY, U256}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use revm::{ @@ -6,7 +7,6 @@ use revm::{ primitives::{AccountInfo, Bytecode}, Database, }; -use std::ops::{Deref, DerefMut}; /// A helper trait responsible for providing that necessary state for the EVM execution. /// diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 8e5419567010..4fb6c30d1c50 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -7,6 +7,10 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; /// Contains glue code for integrating reth database into revm's [Database]. pub mod database; diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index 947c08da7efc..b9fba6b2578e 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -22,6 +22,14 @@ use revm::{ }, Database, DatabaseCommit, Evm, }; + +// reuse revm's hashbrown implementation for no-std +#[cfg(not(feature = "std"))] +use crate::precompile::HashMap; +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, format, string::ToString, vec::Vec}; + +#[cfg(feature = "std")] use std::collections::HashMap; /// Collect all balance changes at the end of the block. @@ -86,7 +94,7 @@ pub fn apply_blockhashes_update + DatabaseCo parent_block_hash: B256, ) -> Result<(), BlockExecutionError> where - DB::Error: std::fmt::Display, + DB::Error: core::fmt::Display, { // If Prague is not activated or this is the genesis block, no hashes are added. if !chain_spec.is_prague_active_at_timestamp(block_timestamp) || block_number == 0 { @@ -153,7 +161,7 @@ pub fn apply_beacon_root_contract_call( evm: &mut Evm<'_, EXT, DB>, ) -> Result<(), BlockExecutionError> where - DB::Error: std::fmt::Display, + DB::Error: core::fmt::Display, { if !chain_spec.is_cancun_active_at_timestamp(block_timestamp) { return Ok(()) @@ -256,7 +264,7 @@ pub fn apply_withdrawal_requests_contract_call, ) -> Result, BlockExecutionError> where - DB::Error: std::fmt::Display, + DB::Error: core::fmt::Display, { // get previous env let previous_env = Box::new(evm.context.env().clone()); From 87d6bcde62aa769c3fd53b8ff5fe469c03303fc0 Mon Sep 17 00:00:00 2001 From: Marquis Shanahan <29431502+9547@users.noreply.github.com> Date: Tue, 18 Jun 2024 23:48:25 +0800 Subject: [PATCH 114/405] chore(cargo): rm enr default features (#8933) Signed-off-by: 9547 <29431502+9547@users.noreply.github.com> --- Cargo.lock | 160 ++++++++++++++++++++++++++++------------------------- Cargo.toml | 4 +- 2 files changed, 85 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 545e39190424..49cc5c49d8ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2feb5f466b3a786d5a622d8926418bc6a0d38bf632909f6ee9298a4a1d8c6e0" +checksum = "cd47e5f8545bdf53beb545d3c039b4afa16040bdf69c50100581579b08776afd" dependencies = [ "alloy-rlp", "arbitrary", @@ -671,7 +671,7 @@ dependencies = [ "alloy-transport", "futures", "http 1.1.0", - "rustls 0.23.10", + "rustls", "serde_json", "tokio", "tokio-tungstenite", @@ -3606,9 +3606,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -3659,23 +3659,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper", - "hyper-util", - "rustls 0.22.4", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.25.0", - "tower-service", -] - [[package]] name = "hyper-rustls" version = "0.27.2" @@ -3687,11 +3670,13 @@ dependencies = [ "hyper", "hyper-util", "log", - "rustls 0.23.10", + "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -3958,18 +3943,18 @@ dependencies = [ [[package]] name = "include_dir" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ "proc-macro2", "quote", @@ -4244,13 +4229,13 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core", "pin-project", - "rustls 0.23.10", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "soketto", "thiserror", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tokio-util", "tracing", "url", @@ -4295,11 +4280,11 @@ dependencies = [ "base64 0.22.1", "http-body", "hyper", - "hyper-rustls 0.27.2", + "hyper-rustls", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls 0.23.10", + "rustls", "rustls-platform-verifier", "serde", "serde_json", @@ -4464,7 +4449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -4914,9 +4899,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -5914,6 +5899,53 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +dependencies = [ + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -6159,9 +6191,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64 0.22.1", "bytes", @@ -6171,7 +6203,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls 0.26.0", + "hyper-rustls", "hyper-util", "ipnet", "js-sys", @@ -6180,7 +6212,8 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.22.4", + "quinn", + "rustls", "rustls-native-certs", "rustls-pemfile", "rustls-pki-types", @@ -6189,7 +6222,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -8684,20 +8717,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.10" @@ -8753,7 +8772,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.10", + "rustls", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", @@ -9518,9 +9537,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "synstructure" @@ -9841,24 +9860,13 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.10", + "rustls", "rustls-pki-types", "tokio", ] @@ -9883,10 +9891,10 @@ checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", "log", - "rustls 0.23.10", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tungstenite", "webpki-roots", ] @@ -10224,7 +10232,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "rustls 0.23.10", + "rustls", "rustls-pki-types", "sha1", "thiserror", @@ -10572,9 +10580,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] @@ -11053,9 +11061,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.11+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 65a33445da55..4cf1778781be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -474,9 +474,7 @@ secp256k1 = { version = "0.29", default-features = false, features = [ "global-context", "recovery", ] } -enr = { version = "0.12.1", default-features = false, features = [ - "rust-secp256k1", -] } +enr = { version = "0.12.1", default-features = false } # for eip-4844 c-kzg = "1.0.0" From 0f88965fdcb2fae0502f7bb06a9577a95df7eae4 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 18 Jun 2024 18:07:36 +0200 Subject: [PATCH 115/405] chore: make reth-evm compile with no-std (#8934) --- crates/evm/Cargo.toml | 2 ++ crates/evm/src/execute.rs | 3 +++ crates/evm/src/lib.rs | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index bf818bba45c0..23f7e1b25768 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -30,4 +30,6 @@ parking_lot = { workspace = true, optional = true } parking_lot.workspace = true [features] +default = ["std"] +std = [] test-utils = ["dep:parking_lot"] diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index bf479271be06..ea8b7abb0779 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -6,6 +6,9 @@ use reth_prune_types::PruneModes; use revm::db::BundleState; use revm_primitives::db::Database; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + pub use reth_execution_errors::{BlockExecutionError, BlockValidationError}; pub use reth_storage_errors::provider::ProviderError; diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index c25e3ae9726e..a3e643e88b4c 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -7,6 +7,10 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; use reth_chainspec::ChainSpec; use reth_primitives::{revm::env::fill_block_env, Address, Header, TransactionSigned, U256}; From 63248bc4ecb1e803f41f0b404c38367ddf8e423c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 18 Jun 2024 18:15:02 +0200 Subject: [PATCH 116/405] chore: make reth-ethereum-evm compile with no-std (#8935) --- crates/ethereum/evm/Cargo.toml | 3 +++ crates/ethereum/evm/src/eip6110.rs | 3 +++ crates/ethereum/evm/src/execute.rs | 5 +++++ crates/ethereum/evm/src/lib.rs | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index f087bb210769..1d996e5d3995 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -33,3 +33,6 @@ reth-revm = { workspace = true, features = ["test-utils"] } secp256k1.workspace = true serde_json.workspace = true +[features] +default = ["std"] +std = [] \ No newline at end of file diff --git a/crates/ethereum/evm/src/eip6110.rs b/crates/ethereum/evm/src/eip6110.rs index 1552a03d3d9c..722c38da76dc 100644 --- a/crates/ethereum/evm/src/eip6110.rs +++ b/crates/ethereum/evm/src/eip6110.rs @@ -6,6 +6,9 @@ use reth_evm::execute::BlockValidationError; use reth_primitives::{Receipt, Request}; use revm_primitives::Log; +#[cfg(not(feature = "std"))] +use alloc::{string::ToString, vec::Vec}; + sol! { #[allow(missing_docs)] event DepositEvent( diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 8bc6a9e8507c..149fbc9557b9 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -31,6 +31,11 @@ use revm_primitives::{ db::{Database, DatabaseCommit}, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState, }; + +#[cfg(not(feature = "std"))] +use alloc::{sync::Arc, vec, vec::Vec}; + +#[cfg(feature = "std")] use std::sync::Arc; /// Provides executors to execute regular ethereum blocks diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index da9248c2136c..4134849ea8f9 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -7,6 +7,10 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; use reth_chainspec::ChainSpec; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; From 8d4cf43d6e383712e15c34cb12124ab720f6773e Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 18 Jun 2024 12:24:44 -0400 Subject: [PATCH 117/405] feat: re-export test types (#8936) --- crates/exex/test-utils/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index 120ff048f27f..2675be3d23f0 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -136,13 +136,18 @@ where } } -type TmpDB = Arc>; -type Adapter = NodeAdapter< +/// A shared [`TempDatabase`] used for testing +pub type TmpDB = Arc>; +/// The [`NodeAdapter`] for the [`TestExExContext`]. Contains type necessary to +/// boot the testing environment +pub type Adapter = NodeAdapter< RethFullAdapter, <>>>::ComponentsBuilder as NodeComponentsBuilder< RethFullAdapter, >>::Components, >; +/// An [`ExExContext`] using the [`Adapter`] type. +pub type TestExExContext = ExExContext; /// A helper type for testing Execution Extensions. #[derive(Debug)] From 655799f5bf831f7fbbf15ec6ad8b2e4875e1f00e Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Tue, 18 Jun 2024 18:47:28 +0200 Subject: [PATCH 118/405] feat: extend LaunchContext with more components (#8937) --- crates/node/builder/src/launch/common.rs | 188 +++++++++++++++++++---- crates/node/builder/src/launch/mod.rs | 44 ++---- 2 files changed, 172 insertions(+), 60 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 58eb3b87e7fd..45c5fe01cbe4 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -5,6 +5,7 @@ use eyre::Context; use rayon::ThreadPoolBuilder; use reth_auto_seal_consensus::MiningMode; use reth_beacon_consensus::EthBeaconConsensus; +use reth_blockchain_tree::{noop::NoopBlockchainTree, BlockchainTreeConfig}; use reth_chainspec::{Chain, ChainSpec}; use reth_config::{config::EtlConfig, PruneConfig}; use reth_db_api::{database::Database, database_metrics::DatabaseMetrics}; @@ -17,16 +18,22 @@ use reth_node_core::{ node_config::NodeConfig, }; use reth_primitives::{BlockNumber, Head, B256}; -use reth_provider::{providers::StaticFileProvider, ProviderFactory, StaticFileProviderFactory}; +use reth_provider::{ + providers::{BlockchainProvider, StaticFileProvider}, + CanonStateNotificationSender, ProviderFactory, StaticFileProviderFactory, +}; use reth_prune::{PruneModes, PrunerBuilder}; use reth_rpc_builder::config::RethRpcServerConfig; use reth_rpc_layer::JwtSecret; -use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget}; +use reth_stages::{sets::DefaultStages, MetricEvent, Pipeline, PipelineTarget}; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; use reth_tracing::tracing::{debug, error, info, warn}; use std::{sync::Arc, thread::available_parallelism}; -use tokio::sync::{mpsc::Receiver, oneshot, watch}; +use tokio::sync::{ + mpsc::{unbounded_channel, Receiver, UnboundedSender}, + oneshot, watch, +}; /// Reusable setup for launching a node. /// @@ -438,12 +445,23 @@ where self.right().static_file_provider() } - /// Creates a new [`StaticFileProducer`] with the attached database. - pub fn static_file_producer(&self) -> StaticFileProducer { - StaticFileProducer::new( - self.provider_factory().clone(), - self.prune_modes().unwrap_or_default(), - ) + /// Convenience function to [`Self::start_prometheus_endpoint`] + pub async fn with_prometheus(self) -> eyre::Result { + self.start_prometheus_endpoint().await?; + Ok(self) + } + + /// Starts the prometheus endpoint. + pub async fn start_prometheus_endpoint(&self) -> eyre::Result<()> { + let prometheus_handle = self.node_config().install_prometheus_recorder()?; + self.node_config() + .start_metrics_endpoint( + prometheus_handle, + self.database().clone(), + self.static_file_provider(), + self.task_executor().clone(), + ) + .await } /// Convenience function to [`Self::init_genesis`] @@ -457,6 +475,100 @@ where init_genesis(self.provider_factory().clone()) } + /// Creates a new `WithMeteredProvider` container and attaches it to the + /// launch context. + pub fn with_metrics(self) -> LaunchContextWith>> { + let (metrics_sender, metrics_receiver) = unbounded_channel(); + + let with_metrics = + WithMeteredProvider { provider_factory: self.right().clone(), metrics_sender }; + + debug!(target: "reth::cli", "Spawning stages metrics listener task"); + let sync_metrics_listener = reth_stages::MetricsListener::new(metrics_receiver); + self.task_executor().spawn_critical("stages metrics listener task", sync_metrics_listener); + + LaunchContextWith { + inner: self.inner, + attachment: self.attachment.map_right(|_| with_metrics), + } + } +} + +impl LaunchContextWith>> +where + DB: Database + DatabaseMetrics + Send + Sync + Clone + 'static, +{ + /// Returns the configured `ProviderFactory`. + const fn provider_factory(&self) -> &ProviderFactory { + &self.right().provider_factory + } + + /// Returns the metrics sender. + fn sync_metrics_tx(&self) -> UnboundedSender { + self.right().metrics_sender.clone() + } + + /// Creates a `BlockchainProvider` and attaches it to the launch context. + pub async fn with_blockchain_db( + self, + ) -> eyre::Result>>> { + let tree_config = BlockchainTreeConfig::default(); + + // NOTE: This is a temporary workaround to provide the canon state notification sender to the components builder because there's a cyclic dependency between the blockchain provider and the tree component. This will be removed once the Blockchain provider no longer depends on an instance of the tree: + let (canon_state_notification_sender, _receiver) = + tokio::sync::broadcast::channel(tree_config.max_reorg_depth() as usize * 2); + + let blockchain_db = BlockchainProvider::new( + self.provider_factory().clone(), + Arc::new(NoopBlockchainTree::with_canon_state_notifications( + canon_state_notification_sender.clone(), + )), + )?; + + let metered_providers = WithMeteredProviders { + provider_factory: self.provider_factory().clone(), + blockchain_db, + metrics_sender: self.sync_metrics_tx(), + tree_config, + canon_state_notification_sender, + }; + + let ctx = LaunchContextWith { + inner: self.inner, + attachment: self.attachment.map_right(|_| metered_providers), + }; + + Ok(ctx) + } +} + +impl LaunchContextWith>> +where + DB: Database + DatabaseMetrics + Send + Sync + Clone + 'static, +{ + /// Returns access to the underlying database. + pub fn database(&self) -> &DB { + self.provider_factory().db_ref() + } + + /// Returns the configured `ProviderFactory`. + pub const fn provider_factory(&self) -> &ProviderFactory { + &self.right().provider_factory + } + + /// Returns the static file provider to interact with the static files. + pub fn static_file_provider(&self) -> StaticFileProvider { + self.provider_factory().static_file_provider() + } + + /// Creates a new [`StaticFileProducer`] with the attached database. + pub fn static_file_producer(&self) -> StaticFileProducer { + StaticFileProducer::new( + self.provider_factory().clone(), + self.prune_modes().unwrap_or_default(), + ) + } + /// Returns the max block that the node should run to, looking it up from the network if /// necessary pub async fn max_block(&self, client: C) -> eyre::Result> @@ -466,25 +578,6 @@ where self.node_config().max_block(client, self.provider_factory().clone()).await } - /// Convenience function to [`Self::start_prometheus_endpoint`] - pub async fn with_prometheus(self) -> eyre::Result { - self.start_prometheus_endpoint().await?; - Ok(self) - } - - /// Starts the prometheus endpoint. - pub async fn start_prometheus_endpoint(&self) -> eyre::Result<()> { - let prometheus_handle = self.node_config().install_prometheus_recorder()?; - self.node_config() - .start_metrics_endpoint( - prometheus_handle, - self.database().clone(), - self.static_file_provider(), - self.task_executor().clone(), - ) - .await - } - /// Fetches the head block from the database. /// /// If the database is empty, returns the genesis block. @@ -493,6 +586,26 @@ where .lookup_head(self.provider_factory().clone()) .wrap_err("the head block is missing") } + + /// Returns the metrics sender. + pub fn sync_metrics_tx(&self) -> UnboundedSender { + self.right().metrics_sender.clone() + } + + /// Returns a reference to the `BlockchainProvider`. + pub const fn blockchain_db(&self) -> &BlockchainProvider { + &self.right().blockchain_db + } + + /// Returns a reference to the `BlockchainTreeConfig`. + pub const fn tree_config(&self) -> &BlockchainTreeConfig { + &self.right().tree_config + } + + /// Returns the `CanonStateNotificationSender`. + pub fn canon_state_notification_sender(&self) -> CanonStateNotificationSender { + self.right().canon_state_notification_sender.clone() + } } /// Joins two attachments together. @@ -555,6 +668,25 @@ pub struct WithConfigs { pub toml_config: reth_config::Config, } +/// Helper container to bundle the [`ProviderFactory`], [`BlockchainProvider`] +/// and a metrics sender. +#[allow(missing_debug_implementations)] +pub struct WithMeteredProviders { + provider_factory: ProviderFactory, + blockchain_db: BlockchainProvider, + metrics_sender: UnboundedSender, + canon_state_notification_sender: CanonStateNotificationSender, + tree_config: BlockchainTreeConfig, +} + +/// Helper container type to bundle athe [`ProviderFactory`] and the metrics +/// sender. +#[derive(Debug)] +pub struct WithMeteredProvider { + provider_factory: ProviderFactory, + metrics_sender: UnboundedSender, +} + #[cfg(test)] mod tests { use super::{LaunchContext, NodeConfig}; diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 7cb1b2703753..9326e3b14c04 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -12,10 +12,7 @@ use reth_beacon_consensus::{ hooks::{EngineHooks, PruneHook, StaticFileHook}, BeaconConsensusEngine, }; -use reth_blockchain_tree::{ - noop::NoopBlockchainTree, BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, - TreeExternals, -}; +use reth_blockchain_tree::{BlockchainTree, ShareableBlockchainTree, TreeExternals}; use reth_consensus::Consensus; use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider}; use reth_exex::ExExManagerHandle; @@ -128,33 +125,16 @@ where .with_genesis()? .inspect(|this| { info!(target: "reth::cli", "\n{}", this.chain_spec().display_hardforks()); - }); - - debug!(target: "reth::cli", "Spawning stages metrics listener task"); - let (sync_metrics_tx, sync_metrics_rx) = unbounded_channel(); - let sync_metrics_listener = reth_stages::MetricsListener::new(sync_metrics_rx); - ctx.task_executor().spawn_critical("stages metrics listener task", sync_metrics_listener); + }) + .with_metrics() + .with_blockchain_db().await?; // fetch the head block from the database let head = ctx.lookup_head()?; - // Configure the blockchain tree for the node - let tree_config = BlockchainTreeConfig::default(); - - // NOTE: This is a temporary workaround to provide the canon state notification sender to the components builder because there's a cyclic dependency between the blockchain provider and the tree component. This will be removed once the Blockchain provider no longer depends on an instance of the tree: - let (canon_state_notification_sender, _receiver) = - tokio::sync::broadcast::channel(tree_config.max_reorg_depth() as usize * 2); - - let blockchain_db = BlockchainProvider::new( - ctx.provider_factory().clone(), - Arc::new(NoopBlockchainTree::with_canon_state_notifications( - canon_state_notification_sender.clone(), - )), - )?; - let builder_ctx = BuilderContext::new( head, - blockchain_db.clone(), + ctx.blockchain_db().clone(), ctx.task_executor().clone(), ctx.configs().clone(), ); @@ -169,17 +149,17 @@ where consensus.clone(), components.block_executor().clone(), ); - let tree = BlockchainTree::new(tree_externals, tree_config, ctx.prune_modes())? - .with_sync_metrics_tx(sync_metrics_tx.clone()) + let tree = BlockchainTree::new(tree_externals, *ctx.tree_config(), ctx.prune_modes())? + .with_sync_metrics_tx(ctx.sync_metrics_tx()) // Note: This is required because we need to ensure that both the components and the // tree are using the same channel for canon state notifications. This will be removed // once the Blockchain provider no longer depends on an instance of the tree - .with_canon_state_notification_sender(canon_state_notification_sender); + .with_canon_state_notification_sender(ctx.canon_state_notification_sender()); let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree)); // Replace the tree component with the actual tree - let blockchain_db = blockchain_db.with_tree(blockchain_tree); + let blockchain_db = ctx.blockchain_db().clone().with_tree(blockchain_tree); debug!(target: "reth::cli", "configured blockchain tree"); @@ -255,7 +235,7 @@ where consensus.clone(), ctx.provider_factory().clone(), ctx.task_executor(), - sync_metrics_tx, + ctx.sync_metrics_tx(), ctx.prune_config(), max_block, static_file_producer, @@ -277,7 +257,7 @@ where consensus.clone(), ctx.provider_factory().clone(), ctx.task_executor(), - sync_metrics_tx, + ctx.sync_metrics_tx(), ctx.prune_config(), max_block, static_file_producer, @@ -294,7 +274,7 @@ where let initial_target = ctx.node_config().debug.tip; let mut pruner_builder = - ctx.pruner_builder().max_reorg_depth(tree_config.max_reorg_depth() as usize); + ctx.pruner_builder().max_reorg_depth(ctx.tree_config().max_reorg_depth() as usize); if let Some(exex_manager_handle) = &exex_manager_handle { pruner_builder = pruner_builder.finished_exex_height(exex_manager_handle.finished_height()); From 1557b7b5707f46b118be71de539b2c00dc0c9757 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 18 Jun 2024 19:40:14 +0200 Subject: [PATCH 119/405] ci: fix no_std script (#8938) --- .github/scripts/check_no_std.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/scripts/check_no_std.sh b/.github/scripts/check_no_std.sh index 8d64ad172b3b..f19e39ddac90 100755 --- a/.github/scripts/check_no_std.sh +++ b/.github/scripts/check_no_std.sh @@ -1,16 +1,24 @@ #!/usr/bin/env bash set -eo pipefail -# List of no_std packages +# TODO no_std_packages=( - reth-db - reth-network-peers +# reth-codecs +# reth-consensus +# reth-db +# reth-errors +# reth-ethereum-forks +# reth-evm +# reth-evm-ethereum +# reth-network-peers +# reth-primitives +# reth-primitives-traits +# reth-revm ) -# Loop through each package and check it for no_std compliance for package in "${no_std_packages[@]}"; do - cmd="cargo +stable check -p $package --no-default-features" - + cmd="cargo +stable build -p $package --target riscv32imac-unknown-none-elf --no-default-features" + if [ -n "$CI" ]; then echo "::group::$cmd" else From 450f06aea14923bf466742a991754fbbbf590f02 Mon Sep 17 00:00:00 2001 From: 0xAtreides <103257861+JackG-eth@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:35:34 +0100 Subject: [PATCH 120/405] feat: Add `no-std` support to `reth-chainspec` (#8939) --- crates/chainspec/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 892cfa1814b7..51f30311a742 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -7,6 +7,7 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] pub use alloy_chains::{Chain, ChainKind, NamedChain}; pub use info::ChainInfo; @@ -17,6 +18,9 @@ pub use spec::{ #[cfg(feature = "optimism")] pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; +#[cfg(not(feature = "std"))] +extern crate alloc; + // /// The config info module namely spec id. // pub mod config; /// The chain info module. From da0004e361520973511134589c52a635a2237e90 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 18 Jun 2024 17:54:20 -0400 Subject: [PATCH 121/405] fix: don't drop the TaskManager (#8941) --- crates/exex/test-utils/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index 2675be3d23f0..b8ca75c9e4d4 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -160,6 +160,8 @@ pub struct TestExExHandle { pub events_rx: UnboundedReceiver, /// Channel for sending notifications to the Execution Extension pub notifications_tx: Sender, + /// Node task manager + pub tasks: TaskManager, } impl TestExExHandle { @@ -283,7 +285,7 @@ pub async fn test_exex_context_with_chain_spec( components, }; - Ok((ctx, TestExExHandle { genesis, provider_factory, events_rx, notifications_tx })) + Ok((ctx, TestExExHandle { genesis, provider_factory, events_rx, notifications_tx, tasks })) } /// Creates a new [`ExExContext`] with (mainnet)[`MAINNET`] chain spec. From 7bfc604a10379b484f636e6041cdf488cc94e7da Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 19 Jun 2024 07:13:20 +0800 Subject: [PATCH 122/405] fix(rpc/trace): include block rewards in trace_filter rpc (#8868) Signed-off-by: jsvisa Co-authored-by: Matthias Seitz --- crates/chainspec/src/spec.rs | 4 +- crates/rpc/rpc/src/trace.rs | 133 +++++++++++++++++++++++++---------- 2 files changed, 97 insertions(+), 40 deletions(-) diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index b28c75778022..ff2407313c5d 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -851,8 +851,8 @@ impl ChainSpec { self.fork(Hardfork::Homestead).active_at_block(block_number) } - /// The Paris hardfork (merge) is activated via ttd. If we have knowledge of the block, this - /// function will return true if the block number is greater than or equal to the Paris + /// The Paris hardfork (merge) is activated via block number. If we have knowledge of the block, + /// this function will return true if the block number is greater than or equal to the Paris /// (merge) block. pub fn is_paris_active_at_block(&self, block_number: u64) -> Option { self.paris_block_and_final_difficulty.map(|(paris_block, _)| block_number >= paris_block) diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index fdbf7163bd9d..e959a367e3f3 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -6,8 +6,10 @@ use crate::eth::{ }; use async_trait::async_trait; use jsonrpsee::core::RpcResult as Result; -use reth_consensus_common::calc::{base_block_reward, block_reward, ommer_reward}; -use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, SealedHeader, B256, U256}; +use reth_consensus_common::calc::{ + base_block_reward, base_block_reward_pre_merge, block_reward, ommer_reward, +}; +use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, Header, B256, U256}; use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::TraceApiServer; @@ -266,7 +268,7 @@ where // find relevant blocks to trace let mut target_blocks = Vec::new(); - for block in blocks { + for block in &blocks { let mut transaction_indices = HashSet::new(); let mut highest_matching_index = 0; for (tx_idx, tx) in block.body.iter().enumerate() { @@ -308,11 +310,26 @@ where } let block_traces = futures::future::try_join_all(block_traces).await?; - let all_traces = block_traces + let mut all_traces = block_traces .into_iter() .flatten() .flat_map(|traces| traces.into_iter().flatten().flat_map(|traces| traces.into_iter())) - .collect(); + .collect::>(); + + // add reward traces for all blocks + for block in &blocks { + if let Some(base_block_reward) = self.calculate_base_block_reward(&block.header)? { + all_traces.extend(self.extract_reward_traces( + &block.header, + &block.ommers, + base_block_reward, + )); + } else { + // no block reward, means we're past the Paris hardfork and don't expect any rewards + // because the blocks in ascending order + break + } + } Ok(all_traces) } @@ -362,36 +379,12 @@ where maybe_traces.map(|traces| traces.into_iter().flatten().collect::>()); if let (Some(block), Some(traces)) = (maybe_block, maybe_traces.as_mut()) { - if let Some(header_td) = self.provider().header_td(&block.header.hash())? { - if let Some(base_block_reward) = base_block_reward( - self.provider().chain_spec().as_ref(), - block.header.number, - block.header.difficulty, - header_td, - ) { - let block_reward = block_reward(base_block_reward, block.ommers.len()); - traces.push(reward_trace( - &block.header, - RewardAction { - author: block.header.beneficiary, - reward_type: RewardType::Block, - value: U256::from(block_reward), - }, - )); - - for uncle in &block.ommers { - let uncle_reward = - ommer_reward(base_block_reward, block.header.number, uncle.number); - traces.push(reward_trace( - &block.header, - RewardAction { - author: uncle.beneficiary, - reward_type: RewardType::Uncle, - value: U256::from(uncle_reward), - }, - )); - } - } + if let Some(base_block_reward) = self.calculate_base_block_reward(&block.header)? { + traces.extend(self.extract_reward_traces( + &block.header, + &block.ommers, + base_block_reward, + )); } } @@ -481,10 +474,74 @@ where Ok(Some(BlockOpcodeGas { block_hash: block.hash(), - block_number: block.number, + block_number: block.header.number, transactions, })) } + + /// Calculates the base block reward for the given block: + /// + /// - if Paris hardfork is activated, no block rewards are given + /// - if Paris hardfork is not activated, calculate block rewards with block number only + /// - if Paris hardfork is unknown, calculate block rewards with block number and ttd + fn calculate_base_block_reward(&self, header: &Header) -> EthResult> { + let chain_spec = self.provider().chain_spec(); + let is_paris_activated = chain_spec.is_paris_active_at_block(header.number); + + Ok(match is_paris_activated { + Some(true) => None, + Some(false) => Some(base_block_reward_pre_merge(&chain_spec, header.number)), + None => { + // if Paris hardfork is unknown, we need to fetch the total difficulty at the + // block's height and check if it is pre-merge to calculate the base block reward + if let Some(header_td) = self.provider().header_td_by_number(header.number)? { + base_block_reward( + chain_spec.as_ref(), + header.number, + header.difficulty, + header_td, + ) + } else { + None + } + } + }) + } + + /// Extracts the reward traces for the given block: + /// - block reward + /// - uncle rewards + fn extract_reward_traces( + &self, + header: &Header, + ommers: &[Header], + base_block_reward: u128, + ) -> Vec { + let mut traces = Vec::with_capacity(ommers.len() + 1); + + let block_reward = block_reward(base_block_reward, ommers.len()); + traces.push(reward_trace( + header, + RewardAction { + author: header.beneficiary, + reward_type: RewardType::Block, + value: U256::from(block_reward), + }, + )); + + for uncle in ommers { + let uncle_reward = ommer_reward(base_block_reward, header.number, uncle.number); + traces.push(reward_trace( + header, + RewardAction { + author: uncle.beneficiary, + reward_type: RewardType::Uncle, + value: U256::from(uncle_reward), + }, + )); + } + traces + } } #[async_trait] @@ -628,9 +685,9 @@ struct TraceApiInner { /// Helper to construct a [`LocalizedTransactionTrace`] that describes a reward to the block /// beneficiary. -fn reward_trace(header: &SealedHeader, reward: RewardAction) -> LocalizedTransactionTrace { +fn reward_trace(header: &Header, reward: RewardAction) -> LocalizedTransactionTrace { LocalizedTransactionTrace { - block_hash: Some(header.hash()), + block_hash: Some(header.hash_slow()), block_number: Some(header.number), transaction_hash: None, transaction_position: None, From 9d49abb2b86a08fd3a09590eec80eb273ed35ee2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 19 Jun 2024 13:37:33 +0200 Subject: [PATCH 123/405] chore(deps): bump curve25519-dalek (#8947) --- Cargo.lock | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49cc5c49d8ca..44bc3c899bf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2228,16 +2228,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version 0.4.0", "subtle", "zeroize", @@ -5595,12 +5594,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "plotters" version = "0.3.6" From 5edf4496331c69ffc202b09426737d82e76682ab Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 19 Jun 2024 13:30:20 +0200 Subject: [PATCH 124/405] feat(consensus-auto-seal): fix missing logs_bloom gas_used receipts_root for dev env (#8946) Co-authored-by: Stas Stepanov --- crates/consensus/auto-seal/Cargo.toml | 3 +- crates/consensus/auto-seal/src/lib.rs | 50 +++++++++++++++++++-------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/crates/consensus/auto-seal/Cargo.toml b/crates/consensus/auto-seal/Cargo.toml index 98fe370db087..f5f29b9051ac 100644 --- a/crates/consensus/auto-seal/Cargo.toml +++ b/crates/consensus/auto-seal/Cargo.toml @@ -36,5 +36,4 @@ tokio-stream.workspace = true tracing.workspace = true [features] -# Included solely to ignore certain tests. -optimism = [] +optimism = ["reth-provider/optimism"] diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index 8aaeca5e7ab8..cd4c09e10f51 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -21,9 +21,8 @@ use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_engine_primitives::EngineTypes; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ - constants::{EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, - eip4844::calculate_excess_blob_gas, - proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Header, + constants::ETHEREUM_BLOCK_GAS_LIMIT, eip4844::calculate_excess_blob_gas, proofs, Block, + BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Bloom, Header, Requests, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, U256, }; use reth_provider::{BlockReaderIdExt, ExecutionOutcome, StateProviderFactory, StateRootProvider}; @@ -263,7 +262,7 @@ impl StorageInner { ommers: &[Header], withdrawals: Option<&Withdrawals>, requests: Option<&Requests>, - chain_spec: Arc, + chain_spec: &ChainSpec, ) -> Header { // check previous block for base fee let base_fee_per_gas = self.headers.get(&self.best_block).and_then(|parent| { @@ -287,7 +286,7 @@ impl StorageInner { ommers_hash: proofs::calculate_ommers_root(ommers), beneficiary: Default::default(), state_root: Default::default(), - transactions_root: Default::default(), + transactions_root: proofs::calculate_transaction_root(transactions), receipts_root: Default::default(), withdrawals_root: withdrawals.map(|w| proofs::calculate_withdrawals_root(w)), logs_bloom: Default::default(), @@ -327,12 +326,6 @@ impl StorageInner { Some(calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) } - header.transactions_root = if transactions.is_empty() { - EMPTY_TRANSACTIONS - } else { - proofs::calculate_transaction_root(transactions) - }; - header } @@ -367,7 +360,7 @@ impl StorageInner { &ommers, withdrawals.as_ref(), requests.as_ref(), - chain_spec, + &chain_spec, ); let block = Block { @@ -387,8 +380,13 @@ impl StorageInner { ); // execute the block - let BlockExecutionOutput { state, receipts, requests: block_execution_requests, .. } = - executor.executor(&mut db).execute((&block, U256::ZERO).into())?; + let BlockExecutionOutput { + state, + receipts, + requests: block_execution_requests, + gas_used, + .. + } = executor.executor(&mut db).execute((&block, U256::ZERO).into())?; let execution_outcome = ExecutionOutcome::new( state, receipts.into(), @@ -405,8 +403,30 @@ impl StorageInner { trace!(target: "consensus::auto", ?execution_outcome, ?header, ?body, "executed block, calculating state root and completing header"); - // calculate the state root + // now we need to update certain header fields with the results of the execution header.state_root = db.state_root(execution_outcome.state())?; + header.gas_used = gas_used; + + let receipts = execution_outcome.receipts_by_block(header.number); + + // update logs bloom + let receipts_with_bloom = + receipts.iter().map(|r| r.as_ref().unwrap().bloom_slow()).collect::>(); + header.logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | *r); + + // update receipts root + header.receipts_root = { + #[cfg(feature = "optimism")] + let receipts_root = execution_outcome + .optimism_receipts_root_slow(header.number, &chain_spec, header.timestamp) + .expect("Receipts is present"); + + #[cfg(not(feature = "optimism"))] + let receipts_root = + execution_outcome.receipts_root_slow(header.number).expect("Receipts is present"); + + receipts_root + }; trace!(target: "consensus::auto", root=?header.state_root, ?body, "calculated root"); // finally insert into storage From a3fd112915ed5fc42b36f25a01d76e57ec49b3b9 Mon Sep 17 00:00:00 2001 From: Krishang <93703995+kamuik16@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:40:47 +0530 Subject: [PATCH 125/405] feat: move calculate_intrinsic_gas_after_merge to tx pool (#8914) Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Matthias Seitz --- Cargo.lock | 2 +- crates/optimism/evm/Cargo.toml | 1 + crates/optimism/node/Cargo.toml | 1 + crates/optimism/payload/Cargo.toml | 3 ++- crates/primitives/Cargo.toml | 18 ++++-------------- crates/primitives/src/revm/compat.rs | 17 +---------------- crates/revm/Cargo.toml | 4 +++- crates/rpc/rpc/Cargo.toml | 1 + crates/transaction-pool/Cargo.toml | 1 + crates/transaction-pool/src/validate/eth.rs | 20 +++++++++++++++++--- 10 files changed, 32 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44bc3c899bf8..1d55ac9a90ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7742,7 +7742,6 @@ dependencies = [ "reth-primitives-traits", "reth-static-file-types", "reth-trie-common", - "revm", "revm-primitives", "roaring", "secp256k1", @@ -8334,6 +8333,7 @@ dependencies = [ "reth-provider", "reth-tasks", "reth-tracing", + "revm", "rustc-hash", "schnellru", "serde", diff --git a/crates/optimism/evm/Cargo.toml b/crates/optimism/evm/Cargo.toml index 61c3474c16af..c7886ebf2845 100644 --- a/crates/optimism/evm/Cargo.toml +++ b/crates/optimism/evm/Cargo.toml @@ -40,4 +40,5 @@ optimism = [ "reth-primitives/optimism", "reth-provider/optimism", "reth-optimism-consensus/optimism", + "reth-revm/optimism", ] diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index 607031ea5324..8b9ecf0a03cc 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -66,4 +66,5 @@ optimism = [ "reth-evm-optimism/optimism", "reth-optimism-payload-builder/optimism", "reth-beacon-consensus/optimism", + "reth-revm/optimism", ] diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index ca6fb8e100d1..357f13956e1a 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -42,4 +42,5 @@ optimism = [ "reth-provider/optimism", "reth-rpc-types-compat/optimism", "reth-evm-optimism/optimism", -] \ No newline at end of file + "reth-revm/optimism", +] diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 1b2dfab727f5..f5ae16277681 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -19,7 +19,6 @@ reth-ethereum-forks.workspace = true reth-static-file-types.workspace = true reth-trie-common.workspace = true reth-chainspec.workspace = true -revm.workspace = true revm-primitives = { workspace = true, features = ["serde"] } # ethereum @@ -48,7 +47,7 @@ once_cell.workspace = true rayon.workspace = true serde.workspace = true tempfile = { workspace = true, optional = true } -thiserror-no-std = { workspace = true , default-features = false } +thiserror-no-std = { workspace = true, default-features = false } zstd = { version = "0.13", features = ["experimental"], optional = true } roaring = "0.10.2" @@ -103,24 +102,15 @@ 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-primitives/c-kzg", "dep:tempfile", "alloy-eips/kzg"] zstd-codec = ["dep:zstd"] optimism = [ "reth-chainspec/optimism", "reth-codecs/optimism", "reth-ethereum-forks/optimism", - "revm/optimism", -] -alloy-compat = [ - "reth-primitives-traits/alloy-compat", - "alloy-rpc-types", + "revm-primitives/optimism", ] +alloy-compat = ["reth-primitives-traits/alloy-compat", "dep:alloy-rpc-types"] std = ["thiserror-no-std/std"] test-utils = ["reth-primitives-traits/test-utils"] diff --git a/crates/primitives/src/revm/compat.rs b/crates/primitives/src/revm/compat.rs index cb492e6aa1b7..808ac98bbd9b 100644 --- a/crates/primitives/src/revm/compat.rs +++ b/crates/primitives/src/revm/compat.rs @@ -1,5 +1,4 @@ -use crate::{revm_primitives::AccountInfo, Account, Address, TxKind, KECCAK_EMPTY, U256}; -use revm::{interpreter::gas::validate_initial_tx_gas, primitives::SpecId}; +use crate::{revm_primitives::AccountInfo, Account, KECCAK_EMPTY}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -27,17 +26,3 @@ pub fn into_revm_acc(reth_acc: Account) -> AccountInfo { code: None, } } - -/// Calculates the Intrinsic Gas usage for a Transaction -/// -/// Caution: This only checks past the Merge hardfork. -#[inline] -pub fn calculate_intrinsic_gas_after_merge( - input: &[u8], - kind: &TxKind, - access_list: &[(Address, Vec)], - is_shanghai: bool, -) -> u64 { - let spec_id = if is_shanghai { SpecId::SHANGHAI } else { SpecId::MERGE }; - validate_initial_tx_gas(spec_id, input, kind.is_create(), access_list) -} diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 5462da7383da..4d7a7f684de0 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -36,6 +36,8 @@ tracing.workspace = true reth-trie.workspace = true [features] -default = ["std"] +default = ["std", "c-kzg"] std = [] +c-kzg = ["revm/c-kzg"] test-utils = ["dep:reth-trie"] +optimism = ["revm/optimism"] diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 378f665bf90b..096409ab251b 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -96,4 +96,5 @@ optimism = [ "reth-provider/optimism", "dep:reth-evm-optimism", "reth-evm-optimism/optimism", + "reth-revm/optimism", ] diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index 03b5d1d5f21a..a90b16083f42 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -19,6 +19,7 @@ reth-primitives.workspace = true reth-fs-util.workspace = true reth-provider.workspace = true reth-tasks.workspace = true +revm.workspace = true # ethereum alloy-rlp.workspace = true diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 3b3779cfdcb5..8bdd68d62154 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -16,12 +16,12 @@ use reth_primitives::{ ETHEREUM_BLOCK_GAS_LIMIT, }, kzg::KzgSettings, - revm::compat::calculate_intrinsic_gas_after_merge, - GotExpected, InvalidTransactionError, SealedBlock, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, - EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, + Address, GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID, + EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, }; use reth_provider::{AccountReader, BlockReaderIdExt, StateProviderFactory}; use reth_tasks::TaskSpawner; +use revm::{interpreter::gas::validate_initial_tx_gas, primitives::SpecId}; use std::{ marker::PhantomData, sync::{atomic::AtomicBool, Arc}, @@ -728,6 +728,20 @@ pub fn ensure_intrinsic_gas( } } +/// Calculates the Intrinsic Gas usage for a Transaction +/// +/// Caution: This only checks past the Merge hardfork. +#[inline] +pub fn calculate_intrinsic_gas_after_merge( + input: &[u8], + kind: &TxKind, + access_list: &[(Address, Vec)], + is_shanghai: bool, +) -> u64 { + let spec_id = if is_shanghai { SpecId::SHANGHAI } else { SpecId::MERGE }; + validate_initial_tx_gas(spec_id, input, kind.is_create(), access_list) +} + #[cfg(test)] mod tests { use super::*; From d0b241c0c2c3d9123eb884b17efae120ca8b5e18 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:13:31 +0200 Subject: [PATCH 126/405] chore: move `IntegerList` to `reth-primitives-traits` (#8948) --- Cargo.lock | 7 +- crates/primitives-traits/Cargo.toml | 7 +- .../src/integer_list.rs | 0 crates/primitives-traits/src/lib.rs | 3 + crates/primitives/Cargo.toml | 4 - crates/primitives/benches/integer_list.rs | 244 ------------------ crates/primitives/src/lib.rs | 2 - crates/storage/db-api/Cargo.toml | 1 + .../storage/db-api/src/models/integer_list.rs | 2 +- crates/storage/db-common/Cargo.toml | 3 + crates/storage/db-common/src/init.rs | 3 +- crates/storage/db/Cargo.toml | 1 + .../storage/db/src/implementation/mdbx/mod.rs | 3 +- .../db/src/tables/codecs/fuzz/inputs.rs | 2 +- .../storage/db/src/tables/codecs/fuzz/mod.rs | 3 + crates/storage/db/src/tables/mod.rs | 5 +- 16 files changed, 32 insertions(+), 258 deletions(-) rename crates/{primitives => primitives-traits}/src/integer_list.rs (100%) delete mode 100644 crates/primitives/benches/integer_list.rs diff --git a/Cargo.lock b/Cargo.lock index 1d55ac9a90ae..a1fc1a92d031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6646,6 +6646,7 @@ dependencies = [ "reth-metrics", "reth-nippy-jar", "reth-primitives", + "reth-primitives-traits", "reth-prune-types", "reth-stages-types", "reth-storage-errors", @@ -6681,6 +6682,7 @@ dependencies = [ "rand 0.8.5", "reth-codecs", "reth-primitives", + "reth-primitives-traits", "reth-prune-types", "reth-stages-types", "reth-storage-errors", @@ -6702,6 +6704,7 @@ dependencies = [ "reth-db-api", "reth-etl", "reth-primitives", + "reth-primitives-traits", "reth-provider", "reth-stages-types", "reth-trie", @@ -7743,7 +7746,6 @@ dependencies = [ "reth-static-file-types", "reth-trie-common", "revm-primitives", - "roaring", "secp256k1", "serde", "serde_json", @@ -7775,8 +7777,11 @@ dependencies = [ "rand 0.8.5", "reth-codecs", "revm-primitives", + "roaring", "serde", + "serde_json", "test-fuzz", + "thiserror-no-std", ] [[package]] diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index 97ed1a34433c..871d9ea31c45 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -24,6 +24,10 @@ alloy-rpc-types-eth = { workspace = true, optional = true } derive_more.workspace = true revm-primitives.workspace = true +# misc +thiserror-no-std = { workspace = true, default-features = false } +roaring = "0.10.2" + # required by reth-codecs modular-bitfield.workspace = true bytes.workspace = true @@ -40,10 +44,11 @@ proptest.workspace = true proptest-derive.workspace = true test-fuzz.workspace = true rand.workspace = true +serde_json.workspace = true [features] default = ["std"] -std = [] +std = ["thiserror-no-std/std"] test-utils = ["arbitrary"] arbitrary = [ "dep:arbitrary", diff --git a/crates/primitives/src/integer_list.rs b/crates/primitives-traits/src/integer_list.rs similarity index 100% rename from crates/primitives/src/integer_list.rs rename to crates/primitives-traits/src/integer_list.rs diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 0051f6c7df32..3a352f292298 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -21,6 +21,9 @@ pub mod constants; pub mod account; pub use account::Account; +mod integer_list; +pub use integer_list::IntegerList; + /// Common header types pub mod header; #[cfg(any(test, feature = "arbitrary", feature = "test-utils"))] diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index f5ae16277681..37b67fe464a6 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -49,7 +49,6 @@ serde.workspace = true tempfile = { workspace = true, optional = true } thiserror-no-std = { workspace = true, default-features = false } zstd = { version = "0.13", features = ["experimental"], optional = true } -roaring = "0.10.2" # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } @@ -123,6 +122,3 @@ name = "validate_blob_tx" required-features = ["arbitrary", "c-kzg"] harness = false -[[bench]] -name = "integer_list" -harness = false diff --git a/crates/primitives/benches/integer_list.rs b/crates/primitives/benches/integer_list.rs deleted file mode 100644 index 097280748e2a..000000000000 --- a/crates/primitives/benches/integer_list.rs +++ /dev/null @@ -1,244 +0,0 @@ -#![allow(missing_docs)] -use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; -use rand::prelude::*; - -pub fn new_pre_sorted(c: &mut Criterion) { - let mut group = c.benchmark_group("new_pre_sorted"); - - for delta in [1, 100, 1000, 10000] { - let integers_usize = generate_integers(2000, delta); - assert_eq!(integers_usize.len(), 2000); - - let integers_u64 = integers_usize.iter().map(|v| *v as u64).collect::>(); - assert_eq!(integers_u64.len(), 2000); - - group.bench_function(BenchmarkId::new("Elias-Fano", delta), |b| { - b.iter(|| elias_fano::IntegerList::new_pre_sorted(black_box(&integers_usize))); - }); - - group.bench_function(BenchmarkId::new("Roaring Bitmaps", delta), |b| { - b.iter(|| reth_primitives::IntegerList::new_pre_sorted(black_box(&integers_u64))); - }); - } -} - -pub fn rank_select(c: &mut Criterion) { - let mut group = c.benchmark_group("rank + select"); - - for delta in [1, 100, 1000, 10000] { - let integers_usize = generate_integers(2000, delta); - assert_eq!(integers_usize.len(), 2000); - - let integers_u64 = integers_usize.iter().map(|v| *v as u64).collect::>(); - assert_eq!(integers_u64.len(), 2000); - - group.bench_function(BenchmarkId::new("Elias-Fano", delta), |b| { - b.iter_batched( - || { - let (index, element) = - integers_usize.iter().enumerate().choose(&mut thread_rng()).unwrap(); - (elias_fano::IntegerList::new_pre_sorted(&integers_usize).0, index, *element) - }, - |(list, index, element)| { - let list = list.enable_rank(); - list.rank(element); - list.select(index); - }, - BatchSize::PerIteration, - ); - }); - - group.bench_function(BenchmarkId::new("Roaring Bitmaps", delta), |b| { - b.iter_batched( - || { - let (index, element) = - integers_u64.iter().enumerate().choose(&mut thread_rng()).unwrap(); - ( - reth_primitives::IntegerList::new_pre_sorted(&integers_u64), - index as u64, - *element, - ) - }, - |(list, index, element)| { - list.rank(element); - list.select(index); - }, - BatchSize::PerIteration, - ); - }); - } -} - -fn generate_integers(n: usize, delta: usize) -> Vec { - (0..n).fold(Vec::new(), |mut vec, _| { - vec.push(vec.last().map_or(0, |last| { - last + thread_rng().gen_range(delta - delta / 2..=delta + delta / 2) - })); - vec - }) -} - -criterion_group! { - name = benches; - config = Criterion::default(); - targets = new_pre_sorted, rank_select -} -criterion_main!(benches); - -/// Implementation from -/// adapted to work with `sucds = "0.8.1"` -#[allow(unused, unreachable_pub)] -mod elias_fano { - use derive_more::Deref; - use std::{fmt, ops::Deref}; - use sucds::{mii_sequences::EliasFano, Serializable}; - - #[derive(Clone, PartialEq, Eq, Default, Deref)] - pub struct IntegerList(pub EliasFano); - - impl fmt::Debug for IntegerList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let vec: Vec = self.0.iter(0).collect(); - write!(f, "IntegerList {vec:?}") - } - } - - impl IntegerList { - /// Creates an `IntegerList` from a list of integers. `usize` is safe to use since - /// [`sucds::EliasFano`] restricts its compilation to 64bits. - /// - /// # Returns - /// - /// Returns an error if the list is empty or not pre-sorted. - pub fn new>(list: T) -> Result { - let mut builder = EliasFanoBuilder::new( - list.as_ref().iter().max().map_or(0, |max| max + 1), - list.as_ref().len(), - ) - .map_err(|err| EliasFanoError::InvalidInput(err.to_string()))?; - builder.extend(list.as_ref().iter().copied()); - Ok(Self(builder.build())) - } - - // Creates an IntegerList from a pre-sorted list of integers. `usize` is safe to use since - /// [`sucds::EliasFano`] restricts its compilation to 64bits. - /// - /// # Panics - /// - /// Panics if the list is empty or not pre-sorted. - pub fn new_pre_sorted>(list: T) -> Self { - Self::new(list).expect("IntegerList must be pre-sorted and non-empty.") - } - - /// Serializes a [`IntegerList`] into a sequence of bytes. - pub fn to_bytes(&self) -> Vec { - let mut vec = Vec::with_capacity(self.0.size_in_bytes()); - self.0.serialize_into(&mut vec).expect("not able to encode integer list."); - vec - } - - /// Serializes a [`IntegerList`] into a sequence of bytes. - pub fn to_mut_bytes(&self, buf: &mut B) { - let len = self.0.size_in_bytes(); - let mut vec = Vec::with_capacity(len); - self.0.serialize_into(&mut vec).unwrap(); - buf.put_slice(vec.as_slice()); - } - - /// Deserializes a sequence of bytes into a proper [`IntegerList`]. - pub fn from_bytes(data: &[u8]) -> Result { - Ok(Self( - EliasFano::deserialize_from(data).map_err(|_| EliasFanoError::FailedDeserialize)?, - )) - } - } - - macro_rules! impl_uint { - ($($w:tt),+) => { - $( - impl From> for IntegerList { - fn from(v: Vec<$w>) -> Self { - let v: Vec = v.iter().map(|v| *v as usize).collect(); - Self::new(v.as_slice()).expect("could not create list.") - } - } - )+ - }; - } - - impl_uint!(usize, u64, u32, u8, u16); - - impl Serialize for IntegerList { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let vec = self.0.iter(0).collect::>(); - let mut seq = serializer.serialize_seq(Some(self.len()))?; - for e in vec { - seq.serialize_element(&e)?; - } - seq.end() - } - } - - struct IntegerListVisitor; - impl<'de> Visitor<'de> for IntegerListVisitor { - type Value = IntegerList; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("a usize array") - } - - fn visit_seq(self, mut seq: E) -> Result - where - E: SeqAccess<'de>, - { - let mut list = Vec::new(); - while let Some(item) = seq.next_element()? { - list.push(item); - } - - IntegerList::new(list) - .map_err(|_| serde::de::Error::invalid_value(Unexpected::Seq, &self)) - } - } - - impl<'de> Deserialize<'de> for IntegerList { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_byte_buf(IntegerListVisitor) - } - } - - #[cfg(any(test, feature = "arbitrary"))] - use arbitrary::{Arbitrary, Unstructured}; - use serde::{ - de::{SeqAccess, Unexpected, Visitor}, - ser::SerializeSeq, - Deserialize, Deserializer, Serialize, Serializer, - }; - use sucds::mii_sequences::EliasFanoBuilder; - - #[cfg(any(test, feature = "arbitrary"))] - impl<'a> Arbitrary<'a> for IntegerList { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let mut nums: Vec = Vec::arbitrary(u)?; - nums.sort(); - Self::new(&nums).map_err(|_| arbitrary::Error::IncorrectFormat) - } - } - - /// Primitives error type. - #[derive(Debug, thiserror_no_std::Error)] - pub enum EliasFanoError { - /// The provided input is invalid. - #[error("{0}")] - InvalidInput(String), - /// Failed to deserialize data into type. - #[error("failed to deserialize data into type")] - FailedDeserialize, - } -} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index b965de1673df..e5b0f4a6d7a2 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -34,7 +34,6 @@ pub mod eip4844; mod error; pub mod genesis; pub mod header; -mod integer_list; mod log; pub mod proofs; mod receipt; @@ -61,7 +60,6 @@ pub use constants::{ pub use error::{GotExpected, GotExpectedBoxed}; pub use genesis::{ChainConfig, Genesis, GenesisAccount}; pub use header::{Header, HeadersDirection, SealedHeader}; -pub use integer_list::IntegerList; pub use log::{logs_bloom, Log}; pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, diff --git a/crates/storage/db-api/Cargo.toml b/crates/storage/db-api/Cargo.toml index a3ecc56f8534..1f55e6d7fa7d 100644 --- a/crates/storage/db-api/Cargo.toml +++ b/crates/storage/db-api/Cargo.toml @@ -15,6 +15,7 @@ workspace = true # reth reth-codecs.workspace = true reth-primitives.workspace = true +reth-primitives-traits.workspace = true reth-prune-types.workspace = true reth-storage-errors.workspace = true reth-stages-types.workspace = true diff --git a/crates/storage/db-api/src/models/integer_list.rs b/crates/storage/db-api/src/models/integer_list.rs index e419a9435129..f47605bf88b5 100644 --- a/crates/storage/db-api/src/models/integer_list.rs +++ b/crates/storage/db-api/src/models/integer_list.rs @@ -4,7 +4,7 @@ use crate::{ table::{Compress, Decompress}, DatabaseError, }; -use reth_primitives::IntegerList; +use reth_primitives_traits::IntegerList; impl Compress for IntegerList { type Compressed = Vec; diff --git a/crates/storage/db-common/Cargo.toml b/crates/storage/db-common/Cargo.toml index c6eebae87e55..3299bcc12560 100644 --- a/crates/storage/db-common/Cargo.toml +++ b/crates/storage/db-common/Cargo.toml @@ -31,5 +31,8 @@ serde_json.workspace = true # tracing tracing.workspace = true +[dev-dependencies] +reth-primitives-traits.workspace = true + [lints] workspace = true diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index d8bf583dfbb3..3907efd58e57 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -534,8 +534,9 @@ mod tests { transaction::DbTx, }; use reth_primitives::{ - Genesis, IntegerList, GOERLI_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, + Genesis, GOERLI_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, }; + use reth_primitives_traits::IntegerList; use reth_provider::test_utils::create_test_provider_factory_with_chain_spec; fn collect_table_entries( diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 2bac4c107867..b563e1d787e2 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -15,6 +15,7 @@ workspace = true # reth reth-db-api.workspace = true reth-primitives.workspace = true +reth-primitives-traits.workspace = true reth-fs-util.workspace = true reth-storage-errors.workspace = true reth-libmdbx = { workspace = true, optional = true, features = [ diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index 4191d7aae364..cc9f055e9533 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -482,7 +482,8 @@ mod tests { table::{Encode, Table}, }; use reth_libmdbx::Error; - use reth_primitives::{Account, Address, Header, IntegerList, StorageEntry, B256, U256}; + use reth_primitives::{Account, Address, Header, StorageEntry, B256, U256}; + use reth_primitives_traits::IntegerList; use reth_storage_errors::db::{DatabaseWriteError, DatabaseWriteOperation}; use std::str::FromStr; use tempfile::TempDir; diff --git a/crates/storage/db/src/tables/codecs/fuzz/inputs.rs b/crates/storage/db/src/tables/codecs/fuzz/inputs.rs index 533b6b6926dd..2c944e158eb1 100644 --- a/crates/storage/db/src/tables/codecs/fuzz/inputs.rs +++ b/crates/storage/db/src/tables/codecs/fuzz/inputs.rs @@ -1,6 +1,6 @@ //! Curates the input coming from the fuzzer for certain types. -use reth_primitives::IntegerList; +use reth_primitives_traits::IntegerList; use serde::{Deserialize, Serialize}; /// Makes sure that the list provided by the fuzzer is not empty and pre-sorted diff --git a/crates/storage/db/src/tables/codecs/fuzz/mod.rs b/crates/storage/db/src/tables/codecs/fuzz/mod.rs index 826f44d43f0b..1d038bf7e65d 100644 --- a/crates/storage/db/src/tables/codecs/fuzz/mod.rs +++ b/crates/storage/db/src/tables/codecs/fuzz/mod.rs @@ -19,6 +19,9 @@ macro_rules! impl_fuzzer_with_input { #[allow(unused_imports)] use reth_primitives::*; + #[allow(unused_imports)] + use reth_primitives_traits::*; + #[allow(unused_imports)] use super::inputs::*; diff --git a/crates/storage/db/src/tables/mod.rs b/crates/storage/db/src/tables/mod.rs index 367a7dd5531e..c968647a982d 100644 --- a/crates/storage/db/src/tables/mod.rs +++ b/crates/storage/db/src/tables/mod.rs @@ -32,9 +32,10 @@ use reth_db_api::{ table::{Decode, DupSort, Encode, Table}, }; use reth_primitives::{ - Account, Address, BlockHash, BlockNumber, Bytecode, Header, IntegerList, Receipt, Requests, - StorageEntry, TransactionSignedNoHash, TxHash, TxNumber, B256, + Account, Address, BlockHash, BlockNumber, Bytecode, Header, Receipt, Requests, StorageEntry, + TransactionSignedNoHash, TxHash, TxNumber, B256, }; +use reth_primitives_traits::IntegerList; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::StageCheckpoint; use reth_trie_common::{StorageTrieEntry, StoredBranchNode, StoredNibbles, StoredNibblesSubKey}; From 590356b3415cffec09f10189ff0ec006d9447d04 Mon Sep 17 00:00:00 2001 From: Roshan <48975233+Pythonberg1997@users.noreply.github.com> Date: Wed, 19 Jun 2024 22:17:09 +0800 Subject: [PATCH 127/405] fix(ethereum-forks): add missing `transitions_at_timestamp` (#8944) --- crates/ethereum-forks/src/forkcondition.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/ethereum-forks/src/forkcondition.rs b/crates/ethereum-forks/src/forkcondition.rs index f57216845cc9..80c7fff647bd 100644 --- a/crates/ethereum-forks/src/forkcondition.rs +++ b/crates/ethereum-forks/src/forkcondition.rs @@ -70,6 +70,13 @@ impl ForkCondition { matches!(self, Self::Timestamp(time) if timestamp >= *time) } + /// Checks if the given block is the first block that satisfies the fork condition. + /// + /// This will return false for any condition that is not timestamp based. + pub const fn transitions_at_timestamp(&self, timestamp: u64, parent_timestamp: u64) -> bool { + matches!(self, Self::Timestamp(time) if timestamp >= *time && parent_timestamp < *time) + } + /// Checks whether the fork condition is satisfied at the given head block. /// /// This will return true if: From 32500aa9a6f60b9c452ab8a6e5ed522c14f85f8e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:21:00 +0200 Subject: [PATCH 128/405] chore: move `Bytecode`, `Requests` and `Withdrawals` to `reth-primitives-traits` (#8954) --- Cargo.lock | 3 +- crates/primitives-traits/Cargo.toml | 6 +- crates/primitives-traits/src/account.rs | 152 ++++++++++++++++- crates/primitives-traits/src/lib.rs | 8 +- .../src/request.rs | 2 +- .../src/withdrawal.rs | 2 +- crates/primitives/Cargo.toml | 4 - crates/primitives/src/account.rs | 154 ------------------ crates/primitives/src/block.rs | 3 +- crates/primitives/src/lib.rs | 9 +- 10 files changed, 168 insertions(+), 175 deletions(-) rename crates/{primitives => primitives-traits}/src/request.rs (97%) rename crates/{primitives => primitives-traits}/src/withdrawal.rs (99%) delete mode 100644 crates/primitives/src/account.rs diff --git a/Cargo.lock b/Cargo.lock index a1fc1a92d031..904d1f124bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7717,7 +7717,6 @@ dependencies = [ name = "reth-primitives" version = "1.0.0-rc.2" dependencies = [ - "alloy-consensus", "alloy-eips", "alloy-genesis", "alloy-primitives", @@ -7726,7 +7725,6 @@ dependencies = [ "alloy-trie", "arbitrary", "assert_matches", - "byteorder", "bytes", "c-kzg", "criterion", @@ -7769,6 +7767,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "arbitrary", + "byteorder", "bytes", "derive_more", "modular-bitfield", diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index 871d9ea31c45..050f84c031c2 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] reth-codecs.workspace = true -alloy-consensus.workspace = true +alloy-consensus = { workspace = true, features = ["serde"] } alloy-eips.workspace = true alloy-genesis.workspace = true alloy-primitives.workspace = true @@ -22,11 +22,12 @@ alloy-rlp.workspace = true alloy-rpc-types-eth = { workspace = true, optional = true } derive_more.workspace = true -revm-primitives.workspace = true +revm-primitives = { workspace = true, features = ["serde"] } # misc thiserror-no-std = { workspace = true, default-features = false } roaring = "0.10.2" +byteorder = "1" # required by reth-codecs modular-bitfield.workspace = true @@ -51,6 +52,7 @@ default = ["std"] std = ["thiserror-no-std/std"] test-utils = ["arbitrary"] arbitrary = [ + "alloy-consensus/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive" diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index 3c3bb30335bf..df32be1dc07e 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -1,7 +1,12 @@ use alloy_consensus::constants::KECCAK_EMPTY; use alloy_genesis::GenesisAccount; -use alloy_primitives::{keccak256, B256, U256}; +use alloy_primitives::{keccak256, Bytes, B256, U256}; +use byteorder::{BigEndian, ReadBytesExt}; +use bytes::Buf; +use derive_more::Deref; use reth_codecs::{main_codec, Compact}; +use revm_primitives::{Bytecode as RevmBytecode, JumpTable}; +use serde::{Deserialize, Serialize}; /// An Ethereum account. #[main_codec] @@ -45,3 +50,148 @@ impl Account { self.bytecode_hash.unwrap_or(KECCAK_EMPTY) } } + +/// Bytecode for an account. +/// +/// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Deref)] +pub struct Bytecode(pub RevmBytecode); + +impl Bytecode { + /// Create new bytecode from raw bytes. + /// + /// No analysis will be performed. + pub fn new_raw(bytes: Bytes) -> Self { + Self(RevmBytecode::new_raw(bytes)) + } +} + +impl Compact for Bytecode { + fn to_compact(self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + let bytecode = &self.0.bytecode()[..]; + buf.put_u32(bytecode.len() as u32); + buf.put_slice(bytecode); + let len = match &self.0 { + RevmBytecode::LegacyRaw(_) => { + buf.put_u8(0); + 1 + } + // `1` has been removed. + RevmBytecode::LegacyAnalyzed(analyzed) => { + buf.put_u8(2); + buf.put_u64(analyzed.original_len() as u64); + let map = analyzed.jump_table().as_slice(); + buf.put_slice(map); + 1 + 8 + map.len() + } + RevmBytecode::Eof(_) => { + // buf.put_u8(3); + // TODO(EOF) + todo!("EOF") + } + }; + len + bytecode.len() + 4 + } + + // # Panics + // + // A panic will be triggered if a bytecode variant of 1 or greater than 2 is passed from the + // database. + fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) { + let len = buf.read_u32::().expect("could not read bytecode length"); + let bytes = Bytes::from(buf.copy_to_bytes(len as usize)); + let variant = buf.read_u8().expect("could not read bytecode variant"); + let decoded = match variant { + 0 => Self(RevmBytecode::new_raw(bytes)), + 1 => unreachable!("Junk data in database: checked Bytecode variant was removed"), + 2 => Self(unsafe { + RevmBytecode::new_analyzed( + bytes, + buf.read_u64::().unwrap() as usize, + JumpTable::from_slice(buf), + ) + }), + // TODO(EOF) + 3 => todo!("EOF"), + _ => unreachable!("Junk data in database: unknown Bytecode variant"), + }; + (decoded, &[]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{hex_literal::hex, B256, U256}; + use revm_primitives::LegacyAnalyzedBytecode; + + #[test] + fn test_account() { + let mut buf = vec![]; + let mut acc = Account::default(); + let len = acc.to_compact(&mut buf); + assert_eq!(len, 2); + + acc.balance = U256::from(2); + let len = acc.to_compact(&mut buf); + assert_eq!(len, 3); + + acc.nonce = 2; + let len = acc.to_compact(&mut buf); + assert_eq!(len, 4); + } + + #[test] + fn test_empty_account() { + let mut acc = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None }; + // Nonce 0, balance 0, and bytecode hash set to None is considered empty. + assert!(acc.is_empty()); + + acc.bytecode_hash = Some(KECCAK_EMPTY); + // Nonce 0, balance 0, and bytecode hash set to KECCAK_EMPTY is considered empty. + assert!(acc.is_empty()); + + acc.balance = U256::from(2); + // Non-zero balance makes it non-empty. + assert!(!acc.is_empty()); + + acc.balance = U256::ZERO; + acc.nonce = 10; + // Non-zero nonce makes it non-empty. + assert!(!acc.is_empty()); + + acc.nonce = 0; + acc.bytecode_hash = Some(B256::from(U256::ZERO)); + // Non-empty bytecode hash makes it non-empty. + assert!(!acc.is_empty()); + } + + #[test] + fn test_bytecode() { + let mut buf = vec![]; + let bytecode = Bytecode::new_raw(Bytes::default()); + let len = bytecode.to_compact(&mut buf); + assert_eq!(len, 5); + + let mut buf = vec![]; + let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff"))); + let len = bytecode.to_compact(&mut buf); + assert_eq!(len, 7); + + let mut buf = vec![]; + let bytecode = Bytecode(RevmBytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new( + Bytes::from(&hex!("ffff")), + 2, + JumpTable::from_slice(&[0]), + ))); + let len = bytecode.clone().to_compact(&mut buf); + assert_eq!(len, 16); + + let (decoded, remainder) = Bytecode::from_compact(&buf, len); + assert_eq!(decoded, bytecode); + assert!(remainder.is_empty()); + } +} diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 3a352f292298..1517090d62c9 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -19,11 +19,17 @@ pub mod constants; /// Minimal account pub mod account; -pub use account::Account; +pub use account::{Account, Bytecode}; mod integer_list; pub use integer_list::IntegerList; +pub mod request; +pub use request::{Request, Requests}; + +mod withdrawal; +pub use withdrawal::{Withdrawal, Withdrawals}; + /// Common header types pub mod header; #[cfg(any(test, feature = "arbitrary", feature = "test-utils"))] diff --git a/crates/primitives/src/request.rs b/crates/primitives-traits/src/request.rs similarity index 97% rename from crates/primitives/src/request.rs rename to crates/primitives-traits/src/request.rs index e3b5f220beb3..99c2375e2606 100644 --- a/crates/primitives/src/request.rs +++ b/crates/primitives-traits/src/request.rs @@ -1,6 +1,6 @@ //! EIP-7685 requests. -use crate::Request; +pub use alloy_consensus::Request; use alloy_eips::eip7685::{Decodable7685, Encodable7685}; use alloy_rlp::{Decodable, Encodable}; use derive_more::{Deref, DerefMut, From, IntoIterator}; diff --git a/crates/primitives/src/withdrawal.rs b/crates/primitives-traits/src/withdrawal.rs similarity index 99% rename from crates/primitives/src/withdrawal.rs rename to crates/primitives-traits/src/withdrawal.rs index cfd0de2268c3..d9285f6bc52b 100644 --- a/crates/primitives/src/withdrawal.rs +++ b/crates/primitives-traits/src/withdrawal.rs @@ -68,7 +68,7 @@ impl Withdrawals { #[cfg(test)] mod tests { use super::*; - use crate::Address; + use alloy_primitives::Address; use alloy_rlp::{RlpDecodable, RlpEncodable}; use proptest::proptest; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 37b67fe464a6..29db6c993a39 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -22,7 +22,6 @@ reth-chainspec.workspace = true revm-primitives = { workspace = true, features = ["serde"] } # ethereum -alloy-consensus = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["rand", "rlp"] } alloy-rlp = { workspace = true, features = ["arrayvec"] } alloy-rpc-types = { workspace = true, optional = true } @@ -40,7 +39,6 @@ c-kzg = { workspace = true, features = ["serde"], optional = true } # misc bytes.workspace = true -byteorder = "1" derive_more.workspace = true modular-bitfield.workspace = true once_cell.workspace = true @@ -62,7 +60,6 @@ revm-primitives = { workspace = true, features = ["arbitrary"] } nybbles = { workspace = true, features = ["arbitrary"] } alloy-trie = { workspace = true, features = ["arbitrary"] } alloy-eips = { workspace = true, features = ["arbitrary"] } -alloy-consensus = { workspace = true, features = ["arbitrary"] } assert_matches.workspace = true arbitrary = { workspace = true, features = ["derive"] } @@ -94,7 +91,6 @@ arbitrary = [ "reth-ethereum-forks/arbitrary", "nybbles/arbitrary", "alloy-trie/arbitrary", - "alloy-consensus/arbitrary", "alloy-eips/arbitrary", "dep:arbitrary", "dep:proptest", diff --git a/crates/primitives/src/account.rs b/crates/primitives/src/account.rs deleted file mode 100644 index 0fa55108e7c3..000000000000 --- a/crates/primitives/src/account.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::revm_primitives::{Bytecode as RevmBytecode, Bytes}; -use byteorder::{BigEndian, ReadBytesExt}; -use bytes::Buf; -use derive_more::Deref; -use reth_codecs::Compact; -use revm_primitives::JumpTable; -use serde::{Deserialize, Serialize}; - -pub use reth_primitives_traits::Account; - -/// Bytecode for an account. -/// -/// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Deref)] -pub struct Bytecode(pub RevmBytecode); - -impl Bytecode { - /// Create new bytecode from raw bytes. - /// - /// No analysis will be performed. - pub fn new_raw(bytes: Bytes) -> Self { - Self(RevmBytecode::new_raw(bytes)) - } -} - -impl Compact for Bytecode { - fn to_compact(self, buf: &mut B) -> usize - where - B: bytes::BufMut + AsMut<[u8]>, - { - let bytecode = &self.0.bytecode()[..]; - buf.put_u32(bytecode.len() as u32); - buf.put_slice(bytecode); - let len = match &self.0 { - RevmBytecode::LegacyRaw(_) => { - buf.put_u8(0); - 1 - } - // `1` has been removed. - RevmBytecode::LegacyAnalyzed(analyzed) => { - buf.put_u8(2); - buf.put_u64(analyzed.original_len() as u64); - let map = analyzed.jump_table().as_slice(); - buf.put_slice(map); - 1 + 8 + map.len() - } - RevmBytecode::Eof(_) => { - // buf.put_u8(3); - // TODO(EOF) - todo!("EOF") - } - }; - len + bytecode.len() + 4 - } - - // # Panics - // - // A panic will be triggered if a bytecode variant of 1 or greater than 2 is passed from the - // database. - fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) { - let len = buf.read_u32::().expect("could not read bytecode length"); - let bytes = Bytes::from(buf.copy_to_bytes(len as usize)); - let variant = buf.read_u8().expect("could not read bytecode variant"); - let decoded = match variant { - 0 => Self(RevmBytecode::new_raw(bytes)), - 1 => unreachable!("Junk data in database: checked Bytecode variant was removed"), - 2 => Self(unsafe { - RevmBytecode::new_analyzed( - bytes, - buf.read_u64::().unwrap() as usize, - JumpTable::from_slice(buf), - ) - }), - // TODO(EOF) - 3 => todo!("EOF"), - _ => unreachable!("Junk data in database: unknown Bytecode variant"), - }; - (decoded, &[]) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{hex_literal::hex, B256, KECCAK_EMPTY, U256}; - use revm_primitives::LegacyAnalyzedBytecode; - - #[test] - fn test_account() { - let mut buf = vec![]; - let mut acc = Account::default(); - let len = acc.to_compact(&mut buf); - assert_eq!(len, 2); - - acc.balance = U256::from(2); - let len = acc.to_compact(&mut buf); - assert_eq!(len, 3); - - acc.nonce = 2; - let len = acc.to_compact(&mut buf); - assert_eq!(len, 4); - } - - #[test] - fn test_empty_account() { - let mut acc = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: None }; - // Nonce 0, balance 0, and bytecode hash set to None is considered empty. - assert!(acc.is_empty()); - - acc.bytecode_hash = Some(KECCAK_EMPTY); - // Nonce 0, balance 0, and bytecode hash set to KECCAK_EMPTY is considered empty. - assert!(acc.is_empty()); - - acc.balance = U256::from(2); - // Non-zero balance makes it non-empty. - assert!(!acc.is_empty()); - - acc.balance = U256::ZERO; - acc.nonce = 10; - // Non-zero nonce makes it non-empty. - assert!(!acc.is_empty()); - - acc.nonce = 0; - acc.bytecode_hash = Some(B256::from(U256::ZERO)); - // Non-empty bytecode hash makes it non-empty. - assert!(!acc.is_empty()); - } - - #[test] - fn test_bytecode() { - let mut buf = vec![]; - let bytecode = Bytecode::new_raw(Bytes::default()); - let len = bytecode.to_compact(&mut buf); - assert_eq!(len, 5); - - let mut buf = vec![]; - let bytecode = Bytecode::new_raw(Bytes::from(&hex!("ffff"))); - let len = bytecode.to_compact(&mut buf); - assert_eq!(len, 7); - - let mut buf = vec![]; - let bytecode = Bytecode(RevmBytecode::LegacyAnalyzed(LegacyAnalyzedBytecode::new( - Bytes::from(&hex!("ffff")), - 2, - JumpTable::from_slice(&[0]), - ))); - let len = bytecode.clone().to_compact(&mut buf); - assert_eq!(len, 16); - - let (decoded, remainder) = Bytecode::from_compact(&buf, len); - assert_eq!(decoded, bytecode); - assert!(remainder.is_empty()); - } -} diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 08c261a39089..3bd41e2cdacf 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -1,5 +1,5 @@ use crate::{ - Address, Bytes, GotExpected, Header, Requests, SealedHeader, TransactionSigned, + Address, Bytes, GotExpected, Header, SealedHeader, TransactionSigned, TransactionSignedEcRecovered, Withdrawals, B256, }; pub use alloy_eips::eip1898::{ @@ -12,6 +12,7 @@ use proptest::prelude::prop_compose; use reth_codecs::derive_arbitrary; #[cfg(any(test, feature = "arbitrary"))] pub use reth_primitives_traits::test_utils::{generate_valid_header, valid_header_strategy}; +use reth_primitives_traits::Requests; use serde::{Deserialize, Serialize}; #[cfg(not(feature = "std"))] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e5b0f4a6d7a2..e1cc48c00c6d 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -22,7 +22,6 @@ #[cfg(not(feature = "std"))] extern crate alloc; -mod account; #[cfg(feature = "alloy-compat")] mod alloy_compat; pub mod basefee; @@ -37,14 +36,11 @@ pub mod header; mod log; pub mod proofs; mod receipt; -mod request; /// Helpers for working with revm pub mod revm; pub use reth_static_file_types as static_file; mod storage; pub mod transaction; -mod withdrawal; -pub use account::{Account, Bytecode}; #[cfg(any(test, feature = "arbitrary"))] pub use block::{generate_valid_header, valid_header_strategy}; pub use block::{ @@ -64,7 +60,7 @@ pub use log::{logs_bloom, Log}; pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; -pub use request::Requests; +pub use reth_primitives_traits::{Account, Bytecode, Request, Requests, Withdrawal, Withdrawals}; pub use static_file::StaticFileSegment; pub use storage::StorageEntry; @@ -85,11 +81,8 @@ pub use transaction::{ LEGACY_TX_TYPE_ID, }; -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}, From 01766217ba3658f1e4e5c00c381ebcb199d4eda0 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:37:25 +0200 Subject: [PATCH 129/405] chore(makefile): remove cfg-check (#8959) --- Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile b/Makefile index de7b496e129a..d42c426671d4 100644 --- a/Makefile +++ b/Makefile @@ -466,11 +466,7 @@ test: make test-doc && \ make test-other-targets -cfg-check: - cargo +nightly -Zcheck-cfg c - pr: - make cfg-check && \ make lint && \ make update-book-cli && \ make test From a21a2b72eb12622fb537c5f7ce0c067aaee4cbec Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:39:30 +0200 Subject: [PATCH 130/405] chore: move `StorageEntry` to `reth-primitives-traits` (#8949) --- crates/primitives-traits/src/lib.rs | 3 +++ crates/{primitives => primitives-traits}/src/storage.rs | 2 +- crates/primitives/src/lib.rs | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) rename crates/{primitives => primitives-traits}/src/storage.rs (97%) diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 1517090d62c9..b4b03d0dab05 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -30,6 +30,9 @@ pub use request::{Request, Requests}; mod withdrawal; pub use withdrawal::{Withdrawal, Withdrawals}; +mod storage; +pub use storage::StorageEntry; + /// Common header types pub mod header; #[cfg(any(test, feature = "arbitrary", feature = "test-utils"))] diff --git a/crates/primitives/src/storage.rs b/crates/primitives-traits/src/storage.rs similarity index 97% rename from crates/primitives/src/storage.rs rename to crates/primitives-traits/src/storage.rs index ef3b2f0827d8..96e7ba15c171 100644 --- a/crates/primitives/src/storage.rs +++ b/crates/primitives-traits/src/storage.rs @@ -1,4 +1,4 @@ -use super::{B256, U256}; +use alloy_primitives::{B256, U256}; use reth_codecs::{derive_arbitrary, Compact}; use serde::{Deserialize, Serialize}; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e1cc48c00c6d..d7f663d560eb 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -39,7 +39,6 @@ mod receipt; /// Helpers for working with revm pub mod revm; pub use reth_static_file_types as static_file; -mod storage; pub mod transaction; #[cfg(any(test, feature = "arbitrary"))] pub use block::{generate_valid_header, valid_header_strategy}; @@ -60,9 +59,10 @@ pub use log::{logs_bloom, Log}; pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; -pub use reth_primitives_traits::{Account, Bytecode, Request, Requests, Withdrawal, Withdrawals}; +pub use reth_primitives_traits::{ + Account, Bytecode, Request, Requests, StorageEntry, Withdrawal, Withdrawals, +}; pub use static_file::StaticFileSegment; -pub use storage::StorageEntry; pub use transaction::{ BlobTransaction, BlobTransactionSidecar, FromRecoveredPooledTransaction, From b5b15f03a08dc89b2a1a11552525d57dcbc94719 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:42:54 +0200 Subject: [PATCH 131/405] chore: remove `revm/compat` from `reth-primitives` (#8960) --- .../execution-types/src/execution_outcome.rs | 14 ++++------ crates/primitives-traits/src/account.rs | 24 +++++++++++++++- crates/primitives/src/revm/compat.rs | 28 ------------------- crates/primitives/src/revm/mod.rs | 9 ------ .../bundle_state_with_receipts.rs | 28 +++++++++---------- .../src/bundle_state/state_changes.rs | 4 +-- .../src/bundle_state/state_reverts.rs | 4 +-- .../storage/provider/src/test_utils/blocks.rs | 8 +++--- crates/trie/trie/benches/hash_post_state.rs | 4 +-- crates/trie/trie/src/state.rs | 6 ++-- 10 files changed, 54 insertions(+), 75 deletions(-) delete mode 100644 crates/primitives/src/revm/compat.rs diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs index 8838c86d2b66..e7e861cf1b93 100644 --- a/crates/evm/execution-types/src/execution_outcome.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -1,8 +1,6 @@ use reth_primitives::{ - logs_bloom, - revm::compat::{into_reth_acc, into_revm_acc}, - Account, Address, BlockNumber, Bloom, Bytecode, Log, Receipt, Receipts, Requests, StorageEntry, - B256, U256, + logs_bloom, Account, Address, BlockNumber, Bloom, Bytecode, Log, Receipt, Receipts, Requests, + StorageEntry, B256, U256, }; use reth_trie::HashedPostState; use revm::{ @@ -81,8 +79,8 @@ impl ExecutionOutcome { state_init.into_iter().map(|(address, (original, present, storage))| { ( address, - original.map(into_revm_acc), - present.map(into_revm_acc), + original.map(Into::into), + present.map(Into::into), storage.into_iter().map(|(k, s)| (k.into(), s)).collect(), ) }), @@ -91,7 +89,7 @@ impl ExecutionOutcome { reverts.into_iter().map(|(address, (original, storage))| { ( address, - original.map(|i| i.map(into_revm_acc)), + original.map(|i| i.map(Into::into)), storage.into_iter().map(|entry| (entry.key.into(), entry.value)), ) }) @@ -129,7 +127,7 @@ impl ExecutionOutcome { /// Get account if account is known. pub fn account(&self, address: &Address) -> Option> { - self.bundle.account(address).map(|a| a.info.clone().map(into_reth_acc)) + self.bundle.account(address).map(|a| a.info.clone().map(Into::into)) } /// Get storage if value is known. diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index df32be1dc07e..21a8d199b3a9 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -5,7 +5,7 @@ use byteorder::{BigEndian, ReadBytesExt}; use bytes::Buf; use derive_more::Deref; use reth_codecs::{main_codec, Compact}; -use revm_primitives::{Bytecode as RevmBytecode, JumpTable}; +use revm_primitives::{AccountInfo, Bytecode as RevmBytecode, JumpTable}; use serde::{Deserialize, Serialize}; /// An Ethereum account. @@ -122,6 +122,28 @@ impl Compact for Bytecode { } } +impl From for Account { + fn from(revm_acc: AccountInfo) -> Self { + let code_hash = revm_acc.code_hash; + Self { + balance: revm_acc.balance, + nonce: revm_acc.nonce, + bytecode_hash: (code_hash != KECCAK_EMPTY).then_some(code_hash), + } + } +} + +impl From for AccountInfo { + fn from(reth_acc: Account) -> Self { + Self { + balance: reth_acc.balance, + nonce: reth_acc.nonce, + code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY), + code: None, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/primitives/src/revm/compat.rs b/crates/primitives/src/revm/compat.rs deleted file mode 100644 index 808ac98bbd9b..000000000000 --- a/crates/primitives/src/revm/compat.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{revm_primitives::AccountInfo, Account, KECCAK_EMPTY}; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -/// Converts a Revm [`AccountInfo`] into a Reth [`Account`]. -/// -/// Sets `bytecode_hash` to `None` if `code_hash` is [`KECCAK_EMPTY`]. -pub fn into_reth_acc(revm_acc: AccountInfo) -> Account { - let code_hash = revm_acc.code_hash; - Account { - balance: revm_acc.balance, - nonce: revm_acc.nonce, - bytecode_hash: (code_hash != KECCAK_EMPTY).then_some(code_hash), - } -} - -/// Converts a Revm [`AccountInfo`] into a Reth [`Account`]. -/// -/// Sets `code_hash` to [`KECCAK_EMPTY`] if `bytecode_hash` is `None`. -pub fn into_revm_acc(reth_acc: Account) -> AccountInfo { - AccountInfo { - balance: reth_acc.balance, - nonce: reth_acc.nonce, - code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY), - code: None, - } -} diff --git a/crates/primitives/src/revm/mod.rs b/crates/primitives/src/revm/mod.rs index f3c4ac62d9f1..9937a209b93e 100644 --- a/crates/primitives/src/revm/mod.rs +++ b/crates/primitives/src/revm/mod.rs @@ -1,14 +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. -/// -/// The included conversion methods can be used to convert between: -/// * reth's [Log](crate::Log) type and revm's [Log](revm_primitives::Log) type. -/// * reth's [Account](crate::Account) type and revm's [`AccountInfo`](revm_primitives::AccountInfo) -/// type. -pub mod compat; - /// Reth block execution/validation configuration and constants pub mod config; 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 a790d92fcfcb..4d3b92b05734 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 @@ -76,9 +76,7 @@ mod tests { models::{AccountBeforeTx, BlockNumberAddress}, }; use reth_primitives::{ - keccak256, - revm::compat::{into_reth_acc, into_revm_acc}, - Account, Address, Receipt, Receipts, StorageEntry, B256, U256, + keccak256, Account, Address, Receipt, Receipts, StorageEntry, B256, U256, }; use reth_trie::{test_utils::state_root, StateRoot}; use revm::{ @@ -149,9 +147,9 @@ mod tests { .write_to_db(provider.tx_ref(), 1) .expect("Could not write reverts to DB"); - let reth_account_a = into_reth_acc(account_a); - let reth_account_b = into_reth_acc(account_b); - let reth_account_b_changed = into_reth_acc(account_b_changed.clone()); + let reth_account_a = account_a.into(); + let reth_account_b = account_b.into(); + let reth_account_b_changed = account_b_changed.clone().into(); // Check plain state assert_eq!( @@ -926,7 +924,7 @@ mod tests { // destroy account 1 let address1 = Address::with_last_byte(1); let account1_old = prestate.remove(&address1).unwrap(); - state.insert_account(address1, into_revm_acc(account1_old.0)); + state.insert_account(address1, account1_old.0.into()); state.commit(HashMap::from([( address1, RevmAccount { @@ -946,7 +944,7 @@ mod tests { let account2_slot2_old_value = *account2.1.get(&slot2_key).unwrap(); state.insert_account_with_storage( address2, - into_revm_acc(account2.0), + account2.0.into(), HashMap::from([(slot2, account2_slot2_old_value)]), ); @@ -956,7 +954,7 @@ mod tests { address2, RevmAccount { status: AccountStatus::Touched, - info: into_revm_acc(account2.0), + info: account2.0.into(), storage: HashMap::from_iter([( slot2, EvmStorageSlot::new_changed(account2_slot2_old_value, account2_slot2_new_value), @@ -969,14 +967,14 @@ mod tests { // change balance of account 3 let address3 = Address::with_last_byte(3); let account3 = prestate.get_mut(&address3).unwrap(); - state.insert_account(address3, into_revm_acc(account3.0)); + state.insert_account(address3, account3.0.into()); account3.0.balance = U256::from(24); state.commit(HashMap::from([( address3, RevmAccount { status: AccountStatus::Touched, - info: into_revm_acc(account3.0), + info: account3.0.into(), storage: HashMap::default(), }, )])); @@ -986,14 +984,14 @@ mod tests { // change nonce of account 4 let address4 = Address::with_last_byte(4); let account4 = prestate.get_mut(&address4).unwrap(); - state.insert_account(address4, into_revm_acc(account4.0)); + state.insert_account(address4, account4.0.into()); account4.0.nonce = 128; state.commit(HashMap::from([( address4, RevmAccount { status: AccountStatus::Touched, - info: into_revm_acc(account4.0), + info: account4.0.into(), storage: HashMap::default(), }, )])); @@ -1008,7 +1006,7 @@ mod tests { address1, RevmAccount { status: AccountStatus::Touched | AccountStatus::Created, - info: into_revm_acc(account1_new), + info: account1_new.into(), storage: HashMap::default(), }, )])); @@ -1024,7 +1022,7 @@ mod tests { address1, RevmAccount { status: AccountStatus::Touched | AccountStatus::Created, - info: into_revm_acc(account1_new), + info: account1_new.into(), storage: HashMap::from_iter([( slot20, EvmStorageSlot::new_changed(U256::ZERO, account1_slot20_value), diff --git a/crates/storage/provider/src/bundle_state/state_changes.rs b/crates/storage/provider/src/bundle_state/state_changes.rs index 0587f36933f2..57c3b837f3e0 100644 --- a/crates/storage/provider/src/bundle_state/state_changes.rs +++ b/crates/storage/provider/src/bundle_state/state_changes.rs @@ -4,7 +4,7 @@ use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, transaction::{DbTx, DbTxMut}, }; -use reth_primitives::{revm::compat::into_reth_acc, Bytecode, StorageEntry, U256}; +use reth_primitives::{Bytecode, StorageEntry, U256}; use reth_storage_errors::db::DatabaseError; use revm::db::states::{PlainStorageChangeset, StateChangeset}; @@ -34,7 +34,7 @@ impl StateChanges { for (address, account) in self.0.accounts { if let Some(account) = account { tracing::trace!(target: "provider::bundle_state", ?address, "Updating plain state account"); - accounts_cursor.upsert(address, into_reth_acc(account))?; + accounts_cursor.upsert(address, account.into())?; } else if accounts_cursor.seek_exact(address)?.is_some() { tracing::trace!(target: "provider::bundle_state", ?address, "Deleting plain state account"); accounts_cursor.delete_current()?; diff --git a/crates/storage/provider/src/bundle_state/state_reverts.rs b/crates/storage/provider/src/bundle_state/state_reverts.rs index 3736b5148be8..d65fcaa829f1 100644 --- a/crates/storage/provider/src/bundle_state/state_reverts.rs +++ b/crates/storage/provider/src/bundle_state/state_reverts.rs @@ -5,7 +5,7 @@ use reth_db_api::{ models::{AccountBeforeTx, BlockNumberAddress}, transaction::{DbTx, DbTxMut}, }; -use reth_primitives::{revm::compat::into_reth_acc, BlockNumber, StorageEntry, B256, U256}; +use reth_primitives::{BlockNumber, StorageEntry, B256, U256}; use reth_storage_errors::db::DatabaseError; use revm::db::states::{PlainStateReverts, PlainStorageRevert, RevertToSlot}; use std::iter::Peekable; @@ -82,7 +82,7 @@ impl StateReverts { for (address, info) in account_block_reverts { account_changeset_cursor.append_dup( block_number, - AccountBeforeTx { address, info: info.map(into_reth_acc) }, + AccountBeforeTx { address, info: info.map(Into::into) }, )?; } } diff --git a/crates/storage/provider/src/test_utils/blocks.rs b/crates/storage/provider/src/test_utils/blocks.rs index 5fb8beeb2435..2a0f900a5e16 100644 --- a/crates/storage/provider/src/test_utils/blocks.rs +++ b/crates/storage/provider/src/test_utils/blocks.rs @@ -5,9 +5,9 @@ use alloy_rlp::Decodable; use reth_db::tables; use reth_db_api::{database::Database, models::StoredBlockBodyIndices}; use reth_primitives::{ - alloy_primitives, b256, hex_literal::hex, revm::compat::into_reth_acc, Address, BlockNumber, - Bytes, Header, Receipt, Requests, SealedBlock, SealedBlockWithSenders, TxType, Withdrawal, - Withdrawals, B256, U256, + alloy_primitives, b256, hex_literal::hex, Account, Address, BlockNumber, Bytes, Header, + Receipt, Requests, SealedBlock, SealedBlockWithSenders, TxType, Withdrawal, Withdrawals, B256, + U256, }; use reth_trie::root::{state_root_unhashed, storage_root_unhashed}; use revm::{ @@ -119,7 +119,7 @@ fn bundle_state_root(execution_outcome: &ExecutionOutcome) -> B256 { ( address, ( - into_reth_acc(info.clone()), + Into::::into(info.clone()), storage_root_unhashed( account .storage diff --git a/crates/trie/trie/benches/hash_post_state.rs b/crates/trie/trie/benches/hash_post_state.rs index dced866cf31f..636ce4462173 100644 --- a/crates/trie/trie/benches/hash_post_state.rs +++ b/crates/trie/trie/benches/hash_post_state.rs @@ -1,7 +1,7 @@ #![allow(missing_docs, unreachable_pub)] use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner}; -use reth_primitives::{keccak256, revm::compat::into_reth_acc, Address, B256, U256}; +use reth_primitives::{keccak256, Address, B256, U256}; use reth_trie::{HashedPostState, HashedStorage}; use revm::db::{states::BundleBuilder, BundleAccount}; use std::collections::HashMap; @@ -30,7 +30,7 @@ fn from_bundle_state_seq(state: &HashMap) -> HashedPostS for (address, account) in state { let hashed_address = keccak256(address); - this.accounts.insert(hashed_address, account.info.clone().map(into_reth_acc)); + this.accounts.insert(hashed_address, account.info.clone().map(Into::into)); let hashed_storage = HashedStorage::from_iter( account.status.was_destroyed(), diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index 13f91a697dcb..821dcc971b62 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -12,9 +12,7 @@ use reth_db_api::{ transaction::DbTx, }; use reth_execution_errors::StateRootError; -use reth_primitives::{ - keccak256, revm::compat::into_reth_acc, Account, Address, BlockNumber, B256, U256, -}; +use reth_primitives::{keccak256, Account, Address, BlockNumber, B256, U256}; use revm::db::BundleAccount; use std::{ collections::{hash_map, HashMap, HashSet}, @@ -41,7 +39,7 @@ impl HashedPostState { .into_par_iter() .map(|(address, account)| { let hashed_address = keccak256(address); - let hashed_account = account.info.clone().map(into_reth_acc); + let hashed_account = account.info.clone().map(Into::into); let hashed_storage = HashedStorage::from_iter( account.status.was_destroyed(), account.storage.iter().map(|(key, value)| { From 254647c4252ae217621a4df19e8bc28cd96befdf Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 19 Jun 2024 17:47:57 +0100 Subject: [PATCH 132/405] chore(ci): exclude examples from docs (#8953) --- .github/workflows/book.yml | 2 +- .github/workflows/integration.yml | 2 +- .github/workflows/unit.yml | 2 +- Cargo.lock | 540 +++++++++--------- .../beacon-api-sidecar-fetcher/Cargo.toml | 2 +- examples/beacon-api-sse/Cargo.toml | 2 +- examples/bsc-p2p/Cargo.toml | 2 +- examples/custom-dev-node/Cargo.toml | 2 +- examples/custom-engine-types/Cargo.toml | 2 +- examples/custom-evm/Cargo.toml | 2 +- examples/custom-inspector/Cargo.toml | 4 +- examples/custom-node-components/Cargo.toml | 2 +- examples/custom-payload-builder/Cargo.toml | 4 +- examples/db-access/Cargo.toml | 2 +- examples/exex/in-memory-state/Cargo.toml | 2 +- examples/exex/minimal/Cargo.toml | 2 +- examples/exex/op-bridge/Cargo.toml | 2 +- examples/exex/rollup/Cargo.toml | 2 +- examples/manual-p2p/Cargo.toml | 2 +- examples/network-txpool/Cargo.toml | 2 +- examples/network/Cargo.toml | 4 +- examples/node-custom-rpc/Cargo.toml | 2 +- examples/node-event-hooks/Cargo.toml | 2 +- examples/polygon-p2p/Cargo.toml | 4 +- examples/rpc-db/Cargo.toml | 2 +- examples/stateful-precompile/Cargo.toml | 2 +- examples/txpool-tracing/Cargo.toml | 4 +- 27 files changed, 301 insertions(+), 301 deletions(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 449908f45078..56d5c427466e 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -78,7 +78,7 @@ jobs: run: mdbook build - name: Build docs - run: cargo docs + run: cargo docs --exclude "example-*" env: # Keep in sync with ./ci.yml:jobs.docs RUSTDOCFLAGS: diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 319896154b19..8a0bb0948c59 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -41,7 +41,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude examples --exclude ef-tests \ + --workspace --exclude ef-tests \ -E "kind(test)" - if: matrix.network == 'optimism' name: Run tests diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 05ff0960916c..a6663aea8843 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -39,7 +39,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude examples --exclude ef-tests \ + --workspace --exclude ef-tests \ --partition hash:${{ matrix.partition }}/2 \ -E "!kind(test)" diff --git a/Cargo.lock b/Cargo.lock index 904d1f124bd5..0ad71595e519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1145,36 +1145,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "beacon-api-sidecar-fetcher" -version = "0.1.0" -dependencies = [ - "alloy-rpc-types-beacon", - "clap", - "eyre", - "futures-util", - "reqwest", - "reth", - "reth-node-ethereum", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "beacon-api-sse" -version = "0.0.0" -dependencies = [ - "alloy-rpc-types-beacon", - "clap", - "futures-util", - "mev-share-sse", - "reth", - "reth-node-ethereum", - "tokio", - "tracing", -] - [[package]] name = "bech32" version = "0.9.1" @@ -1503,22 +1473,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "bsc-p2p" -version = "0.0.0" -dependencies = [ - "reth-chainspec", - "reth-discv4", - "reth-network", - "reth-network-api", - "reth-primitives", - "reth-tracing", - "secp256k1", - "serde_json", - "tokio", - "tokio-stream", -] - [[package]] name = "bstr" version = "0.2.17" @@ -2253,96 +2207,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "custom-dev-node" -version = "0.0.0" -dependencies = [ - "eyre", - "futures-util", - "reth", - "reth-chainspec", - "reth-node-core", - "reth-node-ethereum", - "reth-primitives", - "serde_json", - "tokio", -] - -[[package]] -name = "custom-engine-types" -version = "0.0.0" -dependencies = [ - "eyre", - "reth", - "reth-basic-payload-builder", - "reth-chainspec", - "reth-ethereum-payload-builder", - "reth-node-api", - "reth-node-core", - "reth-node-ethereum", - "reth-payload-builder", - "reth-primitives", - "reth-rpc-types", - "reth-tracing", - "serde", - "thiserror", - "tokio", -] - -[[package]] -name = "custom-evm" -version = "0.0.0" -dependencies = [ - "eyre", - "reth", - "reth-chainspec", - "reth-node-api", - "reth-node-core", - "reth-node-ethereum", - "reth-primitives", - "reth-tracing", - "tokio", -] - -[[package]] -name = "custom-inspector" -version = "0.0.0" -dependencies = [ - "clap", - "futures-util", - "reth", - "reth-node-ethereum", - "reth-rpc-types", -] - -[[package]] -name = "custom-node-components" -version = "0.0.0" -dependencies = [ - "eyre", - "reth", - "reth-node-ethereum", - "reth-tracing", - "reth-transaction-pool", -] - -[[package]] -name = "custom-payload-builder" -version = "0.0.0" -dependencies = [ - "eyre", - "futures-util", - "reth", - "reth-basic-payload-builder", - "reth-chainspec", - "reth-ethereum-payload-builder", - "reth-node-api", - "reth-node-ethereum", - "reth-payload-builder", - "reth-primitives", - "tracing", -] - [[package]] name = "darling" version = "0.20.9" @@ -2417,18 +2281,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "db-access" -version = "0.0.0" -dependencies = [ - "eyre", - "reth-chainspec", - "reth-db", - "reth-primitives", - "reth-provider", - "reth-rpc-types", -] - [[package]] name = "debug-helper" version = "0.3.13" @@ -2873,7 +2725,155 @@ dependencies = [ ] [[package]] -name = "exex-in-memory-state" +name = "example-beacon-api-sidecar-fetcher" +version = "0.1.0" +dependencies = [ + "alloy-rpc-types-beacon", + "clap", + "eyre", + "futures-util", + "reqwest", + "reth", + "reth-node-ethereum", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "example-beacon-api-sse" +version = "0.0.0" +dependencies = [ + "alloy-rpc-types-beacon", + "clap", + "futures-util", + "mev-share-sse", + "reth", + "reth-node-ethereum", + "tokio", + "tracing", +] + +[[package]] +name = "example-bsc-p2p" +version = "0.0.0" +dependencies = [ + "reth-chainspec", + "reth-discv4", + "reth-network", + "reth-network-api", + "reth-primitives", + "reth-tracing", + "secp256k1", + "serde_json", + "tokio", + "tokio-stream", +] + +[[package]] +name = "example-custom-dev-node" +version = "0.0.0" +dependencies = [ + "eyre", + "futures-util", + "reth", + "reth-chainspec", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives", + "serde_json", + "tokio", +] + +[[package]] +name = "example-custom-engine-types" +version = "0.0.0" +dependencies = [ + "eyre", + "reth", + "reth-basic-payload-builder", + "reth-chainspec", + "reth-ethereum-payload-builder", + "reth-node-api", + "reth-node-core", + "reth-node-ethereum", + "reth-payload-builder", + "reth-primitives", + "reth-rpc-types", + "reth-tracing", + "serde", + "thiserror", + "tokio", +] + +[[package]] +name = "example-custom-evm" +version = "0.0.0" +dependencies = [ + "eyre", + "reth", + "reth-chainspec", + "reth-node-api", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives", + "reth-tracing", + "tokio", +] + +[[package]] +name = "example-custom-inspector" +version = "0.0.0" +dependencies = [ + "clap", + "futures-util", + "reth", + "reth-node-ethereum", + "reth-rpc-types", +] + +[[package]] +name = "example-custom-node-components" +version = "0.0.0" +dependencies = [ + "eyre", + "reth", + "reth-node-ethereum", + "reth-tracing", + "reth-transaction-pool", +] + +[[package]] +name = "example-custom-payload-builder" +version = "0.0.0" +dependencies = [ + "eyre", + "futures-util", + "reth", + "reth-basic-payload-builder", + "reth-chainspec", + "reth-ethereum-payload-builder", + "reth-node-api", + "reth-node-ethereum", + "reth-payload-builder", + "reth-primitives", + "tracing", +] + +[[package]] +name = "example-db-access" +version = "0.0.0" +dependencies = [ + "eyre", + "reth-chainspec", + "reth-db", + "reth-primitives", + "reth-provider", + "reth-rpc-types", +] + +[[package]] +name = "example-exex-in-memory-state" version = "0.0.0" dependencies = [ "eyre", @@ -2888,7 +2888,7 @@ dependencies = [ ] [[package]] -name = "exex-minimal" +name = "example-exex-minimal" version = "0.0.0" dependencies = [ "eyre", @@ -2903,7 +2903,7 @@ dependencies = [ ] [[package]] -name = "exex-op-bridge" +name = "example-exex-op-bridge" version = "0.0.0" dependencies = [ "alloy-sol-types", @@ -2925,7 +2925,7 @@ dependencies = [ ] [[package]] -name = "exex-rollup" +name = "example-exex-rollup" version = "0.0.0" dependencies = [ "alloy-consensus", @@ -2951,6 +2951,124 @@ dependencies = [ "tokio", ] +[[package]] +name = "example-manual-p2p" +version = "0.0.0" +dependencies = [ + "eyre", + "futures", + "once_cell", + "reth-chainspec", + "reth-discv4", + "reth-ecies", + "reth-eth-wire", + "reth-network", + "reth-network-peers", + "reth-primitives", + "secp256k1", + "tokio", +] + +[[package]] +name = "example-network" +version = "0.0.0" +dependencies = [ + "eyre", + "futures", + "reth-network", + "reth-provider", + "tokio", +] + +[[package]] +name = "example-network-txpool" +version = "0.0.0" +dependencies = [ + "eyre", + "reth-network", + "reth-provider", + "reth-transaction-pool", + "tokio", +] + +[[package]] +name = "example-node-custom-rpc" +version = "0.0.0" +dependencies = [ + "clap", + "jsonrpsee", + "reth", + "reth-node-ethereum", + "reth-transaction-pool", + "tokio", +] + +[[package]] +name = "example-node-event-hooks" +version = "0.0.0" +dependencies = [ + "reth", + "reth-node-ethereum", +] + +[[package]] +name = "example-polygon-p2p" +version = "0.0.0" +dependencies = [ + "reth-chainspec", + "reth-discv4", + "reth-network", + "reth-primitives", + "reth-provider", + "reth-tracing", + "secp256k1", + "serde_json", + "tokio", + "tokio-stream", +] + +[[package]] +name = "example-rpc-db" +version = "0.0.0" +dependencies = [ + "eyre", + "futures", + "jsonrpsee", + "reth", + "reth-chainspec", + "reth-db", + "reth-db-api", + "reth-node-ethereum", + "tokio", +] + +[[package]] +name = "example-stateful-precompile" +version = "0.0.0" +dependencies = [ + "eyre", + "parking_lot 0.12.3", + "reth", + "reth-chainspec", + "reth-node-api", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives", + "reth-tracing", + "schnellru", + "tokio", +] + +[[package]] +name = "example-txpool-tracing" +version = "0.0.0" +dependencies = [ + "clap", + "futures-util", + "reth", + "reth-node-ethereum", +] + [[package]] name = "eyre" version = "0.6.12" @@ -4730,24 +4848,6 @@ dependencies = [ "libc", ] -[[package]] -name = "manual-p2p" -version = "0.0.0" -dependencies = [ - "eyre", - "futures", - "once_cell", - "reth-chainspec", - "reth-discv4", - "reth-ecies", - "reth-eth-wire", - "reth-network", - "reth-network-peers", - "reth-primitives", - "secp256k1", - "tokio", -] - [[package]] name = "match_cfg" version = "0.1.0" @@ -5025,28 +5125,6 @@ dependencies = [ "unsigned-varint 0.7.2", ] -[[package]] -name = "network" -version = "0.0.0" -dependencies = [ - "eyre", - "futures", - "reth-network", - "reth-provider", - "tokio", -] - -[[package]] -name = "network-txpool" -version = "0.0.0" -dependencies = [ - "eyre", - "reth-network", - "reth-provider", - "reth-transaction-pool", - "tokio", -] - [[package]] name = "nibble_vec" version = "0.1.0" @@ -5067,26 +5145,6 @@ dependencies = [ "libc", ] -[[package]] -name = "node-custom-rpc" -version = "0.0.0" -dependencies = [ - "clap", - "jsonrpsee", - "reth", - "reth-node-ethereum", - "reth-transaction-pool", - "tokio", -] - -[[package]] -name = "node-event-hooks" -version = "0.0.0" -dependencies = [ - "reth", - "reth-node-ethereum", -] - [[package]] name = "nom" version = "7.1.3" @@ -5628,22 +5686,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" -[[package]] -name = "polygon-p2p" -version = "0.0.0" -dependencies = [ - "reth-chainspec", - "reth-discv4", - "reth-network", - "reth-primitives", - "reth-provider", - "reth-tracing", - "secp256k1", - "serde_json", - "tokio", - "tokio-stream", -] - [[package]] name = "polyval" version = "0.5.3" @@ -8605,21 +8647,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" -[[package]] -name = "rpc-db" -version = "0.0.0" -dependencies = [ - "eyre", - "futures", - "jsonrpsee", - "reth", - "reth-chainspec", - "reth-db", - "reth-db-api", - "reth-node-ethereum", - "tokio", -] - [[package]] name = "ruint" version = "1.12.3" @@ -9389,23 +9416,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stateful-precompile" -version = "0.0.0" -dependencies = [ - "eyre", - "parking_lot 0.12.3", - "reth", - "reth-chainspec", - "reth-node-api", - "reth-node-core", - "reth-node-ethereum", - "reth-primitives", - "reth-tracing", - "schnellru", - "tokio", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -10236,16 +10246,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "txpool-tracing" -version = "0.0.0" -dependencies = [ - "clap", - "futures-util", - "reth", - "reth-node-ethereum", -] - [[package]] name = "typenum" version = "1.17.0" diff --git a/examples/beacon-api-sidecar-fetcher/Cargo.toml b/examples/beacon-api-sidecar-fetcher/Cargo.toml index 3da9788262d3..80f5f726d96d 100644 --- a/examples/beacon-api-sidecar-fetcher/Cargo.toml +++ b/examples/beacon-api-sidecar-fetcher/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "beacon-api-sidecar-fetcher" +name = "example-beacon-api-sidecar-fetcher" version = "0.1.0" publish = false edition.workspace = true diff --git a/examples/beacon-api-sse/Cargo.toml b/examples/beacon-api-sse/Cargo.toml index 35bc4be0c304..8667ae7ab1b3 100644 --- a/examples/beacon-api-sse/Cargo.toml +++ b/examples/beacon-api-sse/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "beacon-api-sse" +name = "example-beacon-api-sse" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/bsc-p2p/Cargo.toml b/examples/bsc-p2p/Cargo.toml index 1683676d873d..c4d1dbf77032 100644 --- a/examples/bsc-p2p/Cargo.toml +++ b/examples/bsc-p2p/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bsc-p2p" +name = "example-bsc-p2p" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/custom-dev-node/Cargo.toml b/examples/custom-dev-node/Cargo.toml index c84478e7e337..cc21c97d2a80 100644 --- a/examples/custom-dev-node/Cargo.toml +++ b/examples/custom-dev-node/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "custom-dev-node" +name = "example-custom-dev-node" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/custom-engine-types/Cargo.toml b/examples/custom-engine-types/Cargo.toml index 8012b50ce471..6728888336c9 100644 --- a/examples/custom-engine-types/Cargo.toml +++ b/examples/custom-engine-types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "custom-engine-types" +name = "example-custom-engine-types" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/custom-evm/Cargo.toml b/examples/custom-evm/Cargo.toml index b837eecfc446..3177f5932043 100644 --- a/examples/custom-evm/Cargo.toml +++ b/examples/custom-evm/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "custom-evm" +name = "example-custom-evm" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/custom-inspector/Cargo.toml b/examples/custom-inspector/Cargo.toml index 0e71af297d6f..443cf57b3166 100644 --- a/examples/custom-inspector/Cargo.toml +++ b/examples/custom-inspector/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "custom-inspector" +name = "example-custom-inspector" version = "0.0.0" publish = false edition.workspace = true @@ -10,4 +10,4 @@ reth.workspace = true reth-node-ethereum.workspace = true reth-rpc-types.workspace = true clap = { workspace = true, features = ["derive"] } -futures-util.workspace = true \ No newline at end of file +futures-util.workspace = true diff --git a/examples/custom-node-components/Cargo.toml b/examples/custom-node-components/Cargo.toml index 761eeaf1b60f..467914102112 100644 --- a/examples/custom-node-components/Cargo.toml +++ b/examples/custom-node-components/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "custom-node-components" +name = "example-custom-node-components" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/custom-payload-builder/Cargo.toml b/examples/custom-payload-builder/Cargo.toml index d29c444f1aae..f10bd8058b64 100644 --- a/examples/custom-payload-builder/Cargo.toml +++ b/examples/custom-payload-builder/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "custom-payload-builder" +name = "example-custom-payload-builder" version = "0.0.0" publish = false edition.workspace = true @@ -17,4 +17,4 @@ reth-ethereum-payload-builder.workspace = true tracing.workspace = true futures-util.workspace = true -eyre.workspace = true \ No newline at end of file +eyre.workspace = true diff --git a/examples/db-access/Cargo.toml b/examples/db-access/Cargo.toml index 32881590ce64..692a1175ded1 100644 --- a/examples/db-access/Cargo.toml +++ b/examples/db-access/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "db-access" +name = "example-db-access" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/exex/in-memory-state/Cargo.toml b/examples/exex/in-memory-state/Cargo.toml index a7ae5baee5d5..1ea633b071eb 100644 --- a/examples/exex/in-memory-state/Cargo.toml +++ b/examples/exex/in-memory-state/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "exex-in-memory-state" +name = "example-exex-in-memory-state" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/exex/minimal/Cargo.toml b/examples/exex/minimal/Cargo.toml index b4a3b4af0ae2..5759b41e514d 100644 --- a/examples/exex/minimal/Cargo.toml +++ b/examples/exex/minimal/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "exex-minimal" +name = "example-exex-minimal" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/exex/op-bridge/Cargo.toml b/examples/exex/op-bridge/Cargo.toml index ce1e39db16f0..a1c8d98c5b2b 100644 --- a/examples/exex/op-bridge/Cargo.toml +++ b/examples/exex/op-bridge/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "exex-op-bridge" +name = "example-exex-op-bridge" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/exex/rollup/Cargo.toml b/examples/exex/rollup/Cargo.toml index 738c90360a0d..f22a8eeca07d 100644 --- a/examples/exex/rollup/Cargo.toml +++ b/examples/exex/rollup/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "exex-rollup" +name = "example-exex-rollup" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/manual-p2p/Cargo.toml b/examples/manual-p2p/Cargo.toml index 3876c490852c..ca792a364bf4 100644 --- a/examples/manual-p2p/Cargo.toml +++ b/examples/manual-p2p/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "manual-p2p" +name = "example-manual-p2p" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/network-txpool/Cargo.toml b/examples/network-txpool/Cargo.toml index 12544a8f30df..7d4817263b75 100644 --- a/examples/network-txpool/Cargo.toml +++ b/examples/network-txpool/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "network-txpool" +name = "example-network-txpool" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/network/Cargo.toml b/examples/network/Cargo.toml index b3b740dd8ff1..fe7fc381b526 100644 --- a/examples/network/Cargo.toml +++ b/examples/network/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "network" +name = "example-network" version = "0.0.0" publish = false edition.workspace = true @@ -10,4 +10,4 @@ reth-network.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } futures.workspace = true tokio.workspace = true -eyre.workspace = true \ No newline at end of file +eyre.workspace = true diff --git a/examples/node-custom-rpc/Cargo.toml b/examples/node-custom-rpc/Cargo.toml index 473e9acaf9fa..e82254757ec1 100644 --- a/examples/node-custom-rpc/Cargo.toml +++ b/examples/node-custom-rpc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "node-custom-rpc" +name = "example-node-custom-rpc" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/node-event-hooks/Cargo.toml b/examples/node-event-hooks/Cargo.toml index eb36722aadee..450f6f006b28 100644 --- a/examples/node-event-hooks/Cargo.toml +++ b/examples/node-event-hooks/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "node-event-hooks" +name = "example-node-event-hooks" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/polygon-p2p/Cargo.toml b/examples/polygon-p2p/Cargo.toml index d0813467c662..b3a7af7506b4 100644 --- a/examples/polygon-p2p/Cargo.toml +++ b/examples/polygon-p2p/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "polygon-p2p" +name = "example-polygon-p2p" version = "0.0.0" publish = false edition.workspace = true @@ -17,4 +17,4 @@ serde_json.workspace = true reth-tracing.workspace = true tokio-stream.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } -reth-discv4 = { workspace = true, features = ["test-utils"] } \ No newline at end of file +reth-discv4 = { workspace = true, features = ["test-utils"] } diff --git a/examples/rpc-db/Cargo.toml b/examples/rpc-db/Cargo.toml index 0237ff7fb615..8bcab0e2be52 100644 --- a/examples/rpc-db/Cargo.toml +++ b/examples/rpc-db/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rpc-db" +name = "example-rpc-db" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/stateful-precompile/Cargo.toml b/examples/stateful-precompile/Cargo.toml index 2a248fbefb00..332b07e614df 100644 --- a/examples/stateful-precompile/Cargo.toml +++ b/examples/stateful-precompile/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "stateful-precompile" +name = "example-stateful-precompile" version = "0.0.0" publish = false edition.workspace = true diff --git a/examples/txpool-tracing/Cargo.toml b/examples/txpool-tracing/Cargo.toml index 220e5d8d523e..0a3dd2b9b996 100644 --- a/examples/txpool-tracing/Cargo.toml +++ b/examples/txpool-tracing/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "txpool-tracing" +name = "example-txpool-tracing" version = "0.0.0" publish = false edition.workspace = true @@ -9,4 +9,4 @@ license.workspace = true reth.workspace = true reth-node-ethereum.workspace = true clap = { workspace = true, features = ["derive"] } -futures-util.workspace = true \ No newline at end of file +futures-util.workspace = true From d0b27f063557e0eb51c00c7e2df4da80ae640f8c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 19 Jun 2024 18:48:53 +0200 Subject: [PATCH 133/405] fix: enable autoseal in op correctly (#8961) --- Cargo.lock | 2 ++ crates/optimism/node/Cargo.toml | 3 +++ crates/optimism/node/src/node.rs | 9 +++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ad71595e519..dc9c4e930529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7641,9 +7641,11 @@ dependencies = [ "parking_lot 0.12.3", "reqwest", "reth", + "reth-auto-seal-consensus", "reth-basic-payload-builder", "reth-beacon-consensus", "reth-chainspec", + "reth-consensus", "reth-db", "reth-discv5", "reth-e2e-test-utils", diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index 8b9ecf0a03cc..31eed026337a 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -15,7 +15,9 @@ workspace = true reth-chainspec.workspace = true reth-primitives.workspace = true reth-payload-builder.workspace = true +reth-auto-seal-consensus.workspace = true reth-basic-payload-builder.workspace = true +reth-consensus.workspace = true reth-optimism-payload-builder.workspace = true reth-rpc-types.workspace = true reth-rpc.workspace = true @@ -67,4 +69,5 @@ optimism = [ "reth-optimism-payload-builder/optimism", "reth-beacon-consensus/optimism", "reth-revm/optimism", + "reth-auto-seal-consensus/optimism", ] diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index aabd7b815de6..1b64dcce691d 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -25,6 +25,7 @@ use reth_transaction_pool::{ blobstore::DiskFileBlobStore, CoinbaseTipOrdering, TransactionPool, TransactionValidationTaskExecutor, }; +use std::sync::Arc; /// Type configuration for a regular Optimism node. #[derive(Debug, Default, Clone)] @@ -317,9 +318,13 @@ impl ConsensusBuilder for OptimismConsensusBuilder where Node: FullNodeTypes, { - type Consensus = OptimismBeaconConsensus; + type Consensus = Arc; async fn build_consensus(self, ctx: &BuilderContext) -> eyre::Result { - Ok(OptimismBeaconConsensus::new(ctx.chain_spec())) + if ctx.is_dev() { + Ok(Arc::new(reth_auto_seal_consensus::AutoSealConsensus::new(ctx.chain_spec()))) + } else { + Ok(Arc::new(OptimismBeaconConsensus::new(ctx.chain_spec()))) + } } } From 5a1c99034df98e10a35bde3f15001dc2895e33e4 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:08:47 +0200 Subject: [PATCH 134/405] chore: move `GotExpected`, `log` and `gas_units` to `reth-primitives-traits` (#8958) --- .../src/constants/gas_units.rs | 0 .../src/{constants.rs => constants/mod.rs} | 3 +++ .../{primitives => primitives-traits}/src/error.rs | 0 crates/primitives-traits/src/lib.rs | 6 ++++++ crates/{primitives => primitives-traits}/src/log.rs | 3 +-- crates/primitives/src/constants/mod.rs | 13 ------------- crates/primitives/src/lib.rs | 7 ++----- 7 files changed, 12 insertions(+), 20 deletions(-) rename crates/{primitives => primitives-traits}/src/constants/gas_units.rs (100%) rename crates/primitives-traits/src/{constants.rs => constants/mod.rs} (99%) rename crates/{primitives => primitives-traits}/src/error.rs (100%) rename crates/{primitives => primitives-traits}/src/log.rs (98%) diff --git a/crates/primitives/src/constants/gas_units.rs b/crates/primitives-traits/src/constants/gas_units.rs similarity index 100% rename from crates/primitives/src/constants/gas_units.rs rename to crates/primitives-traits/src/constants/gas_units.rs diff --git a/crates/primitives-traits/src/constants.rs b/crates/primitives-traits/src/constants/mod.rs similarity index 99% rename from crates/primitives-traits/src/constants.rs rename to crates/primitives-traits/src/constants/mod.rs index f8b427389b22..32d41ddcd8dd 100644 --- a/crates/primitives-traits/src/constants.rs +++ b/crates/primitives-traits/src/constants/mod.rs @@ -3,6 +3,9 @@ use alloy_primitives::{address, b256, Address, B256, U256}; use core::time::Duration; +/// Gas units, for example [`GIGAGAS`](gas_units::GIGAGAS). +pub mod gas_units; + /// The client version: `reth/v{major}.{minor}.{patch}` pub const RETH_CLIENT_VERSION: &str = concat!("reth/v", env!("CARGO_PKG_VERSION")); diff --git a/crates/primitives/src/error.rs b/crates/primitives-traits/src/error.rs similarity index 100% rename from crates/primitives/src/error.rs rename to crates/primitives-traits/src/error.rs diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index b4b03d0dab05..e936bbdf1861 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -30,6 +30,12 @@ pub use request::{Request, Requests}; mod withdrawal; pub use withdrawal::{Withdrawal, Withdrawals}; +mod error; +pub use error::{GotExpected, GotExpectedBoxed}; + +mod log; +pub use log::{logs_bloom, Log}; + mod storage; pub use storage::StorageEntry; diff --git a/crates/primitives/src/log.rs b/crates/primitives-traits/src/log.rs similarity index 98% rename from crates/primitives/src/log.rs rename to crates/primitives-traits/src/log.rs index b2b6b8a4852c..cb732b47b681 100644 --- a/crates/primitives/src/log.rs +++ b/crates/primitives-traits/src/log.rs @@ -1,5 +1,4 @@ -use crate::Bloom; - +use alloy_primitives::Bloom; pub use alloy_primitives::Log; /// Calculate receipt logs bloom. diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs index d03303edc328..fd1dc1586248 100644 --- a/crates/primitives/src/constants/mod.rs +++ b/crates/primitives/src/constants/mod.rs @@ -2,18 +2,5 @@ pub use reth_primitives_traits::constants::*; -/// Gas units, for example [`GIGAGAS`](gas_units::GIGAGAS). -pub mod gas_units; - /// [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#parameters) constants. pub mod eip4844; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn min_protocol_sanity() { - assert_eq!(MIN_PROTOCOL_BASE_FEE_U256.to::(), MIN_PROTOCOL_BASE_FEE); - } -} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index d7f663d560eb..eff4b7dcfacd 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -30,10 +30,8 @@ mod block; mod compression; pub mod constants; pub mod eip4844; -mod error; pub mod genesis; pub mod header; -mod log; pub mod proofs; mod receipt; /// Helpers for working with revm @@ -52,15 +50,14 @@ pub use constants::{ DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH, KECCAK_EMPTY, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, }; -pub use error::{GotExpected, GotExpectedBoxed}; pub use genesis::{ChainConfig, Genesis, GenesisAccount}; pub use header::{Header, HeadersDirection, SealedHeader}; -pub use log::{logs_bloom, Log}; pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; pub use reth_primitives_traits::{ - Account, Bytecode, Request, Requests, StorageEntry, Withdrawal, Withdrawals, + logs_bloom, Account, Bytecode, GotExpected, GotExpectedBoxed, Log, Request, Requests, + StorageEntry, Withdrawal, Withdrawals, }; pub use static_file::StaticFileSegment; From 152b2765bdbe3674abd6a4af626916fd55086e36 Mon Sep 17 00:00:00 2001 From: Darshan Kathiriya <8559992+lakshya-sky@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:09:25 -0400 Subject: [PATCH 135/405] use watch channels instead of rwlock (#8950) --- .../provider/src/providers/chain_info.rs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/storage/provider/src/providers/chain_info.rs b/crates/storage/provider/src/providers/chain_info.rs index 2e53a78acd78..c696fefea9e3 100644 --- a/crates/storage/provider/src/providers/chain_info.rs +++ b/crates/storage/provider/src/providers/chain_info.rs @@ -8,6 +8,7 @@ use std::{ }, time::Instant, }; +use tokio::sync::watch; /// Tracks the chain info: canonical head, safe block, finalized block. #[derive(Debug, Clone)] @@ -18,14 +19,16 @@ pub(crate) struct ChainInfoTracker { impl ChainInfoTracker { /// Create a new chain info container for the given canonical head. pub(crate) fn new(head: SealedHeader) -> Self { + let (finalized_block, _) = watch::channel(None); + let (safe_block, _) = watch::channel(None); Self { inner: Arc::new(ChainInfoInner { last_forkchoice_update: RwLock::new(None), last_transition_configuration_exchange: RwLock::new(None), canonical_head_number: AtomicU64::new(head.number), canonical_head: RwLock::new(head), - safe_block: RwLock::new(None), - finalized_block: RwLock::new(None), + safe_block, + finalized_block, }), } } @@ -63,12 +66,12 @@ impl ChainInfoTracker { /// Returns the safe header of the chain. pub(crate) fn get_safe_header(&self) -> Option { - self.inner.safe_block.read().clone() + self.inner.safe_block.borrow().clone() } /// Returns the finalized header of the chain. pub(crate) fn get_finalized_header(&self) -> Option { - self.inner.finalized_block.read().clone() + self.inner.finalized_block.borrow().clone() } /// Returns the canonical head of the chain. @@ -85,14 +88,14 @@ impl ChainInfoTracker { /// Returns the safe header of the chain. #[allow(dead_code)] pub(crate) fn get_safe_num_hash(&self) -> Option { - let h = self.inner.safe_block.read(); + let h = self.inner.safe_block.borrow(); h.as_ref().map(|h| h.num_hash()) } /// Returns the finalized header of the chain. #[allow(dead_code)] pub(crate) fn get_finalized_num_hash(&self) -> Option { - let h = self.inner.finalized_block.read(); + let h = self.inner.finalized_block.borrow(); h.as_ref().map(|h| h.num_hash()) } @@ -107,12 +110,16 @@ impl ChainInfoTracker { /// Sets the safe header of the chain. pub(crate) fn set_safe(&self, header: SealedHeader) { - self.inner.safe_block.write().replace(header); + self.inner.safe_block.send_modify(|h| { + let _ = h.replace(header); + }); } /// Sets the finalized header of the chain. pub(crate) fn set_finalized(&self, header: SealedHeader) { - self.inner.finalized_block.write().replace(header); + self.inner.finalized_block.send_modify(|h| { + let _ = h.replace(header); + }); } } @@ -132,7 +139,7 @@ struct ChainInfoInner { /// The canonical head of the chain. canonical_head: RwLock, /// The block that the beacon node considers safe. - safe_block: RwLock>, + safe_block: watch::Sender>, /// The block that the beacon node considers finalized. - finalized_block: RwLock>, + finalized_block: watch::Sender>, } From 8e452016effa893a33396f033d9e5e834481d19b Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:48:32 +0200 Subject: [PATCH 136/405] implement `From` for `ChainSplitTarget` (#8962) --- crates/blockchain-tree/src/blockchain_tree.rs | 4 +-- crates/evm/execution-types/src/chain.rs | 26 ++++++++++++------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index a3a16e954afe..bafeb6ffcba9 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1063,9 +1063,7 @@ where }; // we are splitting chain at the block hash that we want to make canonical - let Some(canonical) = - self.remove_and_split_chain(chain_id, ChainSplitTarget::Hash(block_hash)) - else { + let Some(canonical) = self.remove_and_split_chain(chain_id, block_hash.into()) else { debug!(target: "blockchain_tree", ?block_hash, ?chain_id, "Chain not present"); return Err(CanonicalError::from(BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into(), diff --git a/crates/evm/execution-types/src/chain.rs b/crates/evm/execution-types/src/chain.rs index 97bd6fcf30ce..47c3aef2e35f 100644 --- a/crates/evm/execution-types/src/chain.rs +++ b/crates/evm/execution-types/src/chain.rs @@ -460,6 +460,18 @@ pub enum ChainSplitTarget { Hash(BlockHash), } +impl From for ChainSplitTarget { + fn from(number: BlockNumber) -> Self { + Self::Number(number) + } +} + +impl From for ChainSplitTarget { + fn from(hash: BlockHash) -> Self { + Self::Hash(hash) + } +} + /// Result of a split chain. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ChainSplit { @@ -603,27 +615,21 @@ mod tests { // split in two assert_eq!( - chain.clone().split(ChainSplitTarget::Hash(block1_hash)), + chain.clone().split(block1_hash.into()), ChainSplit::Split { canonical: chain_split1, pending: chain_split2 } ); // split at unknown block hash assert_eq!( - chain.clone().split(ChainSplitTarget::Hash(B256::new([100; 32]))), + chain.clone().split(B256::new([100; 32]).into()), ChainSplit::NoSplitPending(chain.clone()) ); // split at higher number - assert_eq!( - chain.clone().split(ChainSplitTarget::Number(10)), - ChainSplit::NoSplitPending(chain.clone()) - ); + assert_eq!(chain.clone().split(10u64.into()), ChainSplit::NoSplitPending(chain.clone())); // split at lower number - assert_eq!( - chain.clone().split(ChainSplitTarget::Number(0)), - ChainSplit::NoSplitPending(chain) - ); + assert_eq!(chain.clone().split(0u64.into()), ChainSplit::NoSplitPending(chain)); } #[test] From 5293a2f58da507abf432d69ff24d165ec49ee224 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 19 Jun 2024 14:09:14 -0400 Subject: [PATCH 137/405] chore: add stateful precompile example to readme (#8965) --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index 6605fd2972b3..c5f20f21c881 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,6 +16,7 @@ to make a PR! | [Custom event hooks](./node-event-hooks) | Illustrates how to hook to various node lifecycle events | | [Custom dev node](./custom-dev-node) | Illustrates how to run a custom dev node programmatically and submit a transaction to it via RPC | | [Custom EVM](./custom-evm) | Illustrates how to implement a node with a custom EVM | +| [Custom Stateful Precompile](./stateful-precompile)| Illustrates how to implement a node with a stateful precompile | | [Custom inspector](./custom-inspector) | Illustrates how to use a custom EVM inspector to trace new transactions | | [Custom engine types](./custom-engine-types) | Illustrates how to create a node with custom engine types | | [Custom node components](./custom-node-components) | Illustrates how to configure custom node components | From 88dbb9bd3103e0ef54c3d2b6b74904e1ac8e00bb Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 19 Jun 2024 20:29:57 +0200 Subject: [PATCH 138/405] feat: make FullNodeComponents Clone (#8966) --- crates/exex/exex/src/context.rs | 45 +++++++++++++-------------------- crates/node/api/src/node.rs | 2 +- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/crates/exex/exex/src/context.rs b/crates/exex/exex/src/context.rs index 6edb8f558324..86939e3ba767 100644 --- a/crates/exex/exex/src/context.rs +++ b/crates/exex/exex/src/context.rs @@ -1,13 +1,11 @@ -use std::fmt::Debug; - -use reth_node_api::{FullNodeComponents, FullNodeTypes, NodeTypes}; +use crate::{ExExEvent, ExExNotification}; +use reth_node_api::FullNodeComponents; use reth_node_core::node_config::NodeConfig; use reth_primitives::Head; use reth_tasks::TaskExecutor; +use std::fmt::Debug; use tokio::sync::mpsc::{Receiver, UnboundedSender}; -use crate::{ExExEvent, ExExNotification}; - /// Captures the context that an `ExEx` has access to. pub struct ExExContext { /// The current head of the blockchain at launch. @@ -49,46 +47,39 @@ impl Debug for ExExContext { } } -impl NodeTypes for ExExContext { - type Primitives = Node::Primitives; - type Engine = Node::Engine; -} - -impl FullNodeTypes for ExExContext { - type DB = Node::DB; - type Provider = Node::Provider; -} - -impl FullNodeComponents for ExExContext { - type Pool = Node::Pool; - type Evm = Node::Evm; - type Executor = Node::Executor; - - fn pool(&self) -> &Self::Pool { +impl ExExContext { + /// Returns the transaction pool of the node. + pub fn pool(&self) -> &Node::Pool { self.components.pool() } - fn evm_config(&self) -> &Self::Evm { + /// Returns the node's evm config. + pub fn evm_config(&self) -> &Node::Evm { self.components.evm_config() } - fn block_executor(&self) -> &Self::Executor { + /// Returns the node's executor type. + pub fn block_executor(&self) -> &Node::Executor { self.components.block_executor() } - fn provider(&self) -> &Self::Provider { + /// Returns the provider of the node. + pub fn provider(&self) -> &Node::Provider { self.components.provider() } - fn network(&self) -> &reth_network::NetworkHandle { + /// Returns the handle to the network + pub fn network(&self) -> &reth_network::NetworkHandle { self.components.network() } - fn payload_builder(&self) -> &reth_payload_builder::PayloadBuilderHandle { + /// Returns the handle to the payload builder service. + pub fn payload_builder(&self) -> &reth_payload_builder::PayloadBuilderHandle { self.components.payload_builder() } - fn task_executor(&self) -> &TaskExecutor { + /// Returns the task executor. + pub fn task_executor(&self) -> &TaskExecutor { self.components.task_executor() } } diff --git a/crates/node/api/src/node.rs b/crates/node/api/src/node.rs index b8649d858c92..b58df68c44cf 100644 --- a/crates/node/api/src/node.rs +++ b/crates/node/api/src/node.rs @@ -88,7 +88,7 @@ where } /// Encapsulates all types and components of the node. -pub trait FullNodeComponents: FullNodeTypes + 'static { +pub trait FullNodeComponents: FullNodeTypes + Clone + 'static { /// The transaction pool of the node. type Pool: TransactionPool + Unpin; From aed612052462b16b48b4f9e8825f1fbd806f8b5b Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:17:45 -0400 Subject: [PATCH 139/405] chore: update CODEOWNERS with updated layout (#8970) --- CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 6ee0cf25a839..1fd984ec1291 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -2,6 +2,7 @@ bin/ @onbjerg crates/blockchain-tree/ @rakita @rkrasiuk @mattsse @Rjected crates/blockchain-tree-api/ @rakita @rkrasiuk @mattsse @Rjected +crates/chainspec/ @Rjected @joshieDo @mattsse crates/cli/ @onbjerg @mattsse crates/config/ @onbjerg crates/consensus/ @rkrasiuk @mattsse @Rjected @@ -22,6 +23,7 @@ crates/node-core/ @mattsse @Rjected @onbjerg crates/optimism/ @mattsse @Rjected @fgimenez crates/payload/ @mattsse @Rjected crates/primitives/ @DaniPopes @Rjected +crates/primitives-traits/ @DaniPopes @Rjected @joshieDo crates/prune/ @shekhirin @joshieDo crates/revm/ @mattsse @rakita crates/rpc/ @mattsse @Rjected From da49358483dbbdb7971fb18f09b4044f9bd67b1e Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:18:23 -0400 Subject: [PATCH 140/405] chore: remove cursed comments from broken proptest impl (#8969) --- crates/primitives-traits/src/header/mod.rs | 28 ++++------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/crates/primitives-traits/src/header/mod.rs b/crates/primitives-traits/src/header/mod.rs index 2b112954c267..cb85a3363ef7 100644 --- a/crates/primitives-traits/src/header/mod.rs +++ b/crates/primitives-traits/src/header/mod.rs @@ -380,26 +380,22 @@ impl Encodable for Header { self.mix_hash.encode(out); // Encode mix hash. B64::new(self.nonce.to_be_bytes()).encode(out); // Encode nonce. - // Encode base fee. Put empty list if base fee is missing, - // but withdrawals root is present. + // Encode base fee. if let Some(ref base_fee) = self.base_fee_per_gas { U256::from(*base_fee).encode(out); } - // Encode withdrawals root. Put empty string if withdrawals root is missing, - // but blob gas used is present. + // Encode withdrawals root. if let Some(ref root) = self.withdrawals_root { root.encode(out); } - // Encode blob gas used. Put empty list if blob gas used is missing, - // but excess blob gas is present. + // Encode blob gas used. if let Some(ref blob_gas_used) = self.blob_gas_used { U256::from(*blob_gas_used).encode(out); } - // Encode excess blob gas. Put empty list if excess blob gas is missing, - // but parent beacon block root is present. + // Encode excess blob gas. if let Some(ref excess_blob_gas) = self.excess_blob_gas { U256::from(*excess_blob_gas).encode(out); } @@ -410,14 +406,6 @@ impl Encodable for Header { } // 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 requests_root) = self.requests_root { requests_root.encode(out); } @@ -485,14 +473,6 @@ impl Decodable for Header { } // 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: - // * 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 started_len - buf.len() < rlp_head.payload_length { this.requests_root = Some(B256::decode(buf)?); } From e2b8254a3d8eb4ec1748f83cde50f55a05aa6526 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:42:25 -0400 Subject: [PATCH 141/405] chore: use execution-types instead of provider where possible (#8971) --- Cargo.lock | 16 ++++++++++++++-- bin/reth/Cargo.toml | 1 + bin/reth/src/commands/debug_cmd/build_block.rs | 3 ++- .../src/commands/debug_cmd/in_memory_merkle.rs | 7 ++++--- bin/reth/src/commands/import_receipts_op.rs | 3 ++- crates/blockchain-tree/Cargo.toml | 1 + crates/blockchain-tree/src/block_indices.rs | 2 +- crates/blockchain-tree/src/blockchain_tree.rs | 6 +++--- crates/blockchain-tree/src/chain.rs | 3 ++- crates/consensus/auto-seal/Cargo.toml | 1 + crates/consensus/auto-seal/src/lib.rs | 3 ++- crates/ethereum/payload/Cargo.toml | 1 + crates/ethereum/payload/src/lib.rs | 3 ++- crates/exex/test-utils/Cargo.toml | 1 + crates/exex/test-utils/src/lib.rs | 3 ++- crates/optimism/evm/Cargo.toml | 4 ++-- crates/optimism/evm/src/execute.rs | 2 +- crates/optimism/payload/Cargo.toml | 1 + crates/optimism/payload/src/builder.rs | 3 ++- crates/rpc/rpc/Cargo.toml | 1 + crates/rpc/rpc/src/eth/api/pending_block.rs | 3 ++- crates/rpc/rpc/src/eth/cache/mod.rs | 4 ++-- crates/stages/stages/Cargo.toml | 1 + crates/stages/stages/src/stages/execution.rs | 6 +++--- crates/stages/stages/src/stages/headers.rs | 5 ++--- crates/storage/provider/src/bundle_state/mod.rs | 2 +- .../provider/src/providers/database/provider.rs | 7 ++++--- crates/storage/provider/src/traits/block.rs | 2 +- crates/transaction-pool/Cargo.toml | 1 + crates/transaction-pool/src/maintain.rs | 3 ++- examples/exex/in-memory-state/Cargo.toml | 1 + examples/exex/in-memory-state/src/main.rs | 11 ++++------- examples/exex/minimal/Cargo.toml | 1 + examples/exex/minimal/src/main.rs | 2 +- examples/exex/op-bridge/Cargo.toml | 2 +- examples/exex/op-bridge/src/main.rs | 4 ++-- examples/exex/rollup/Cargo.toml | 1 + examples/exex/rollup/src/main.rs | 2 +- 38 files changed, 77 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc9c4e930529..5db2c22312a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2878,6 +2878,7 @@ version = "0.0.0" dependencies = [ "eyre", "reth", + "reth-execution-types", "reth-exex", "reth-exex-test-utils", "reth-node-api", @@ -2894,6 +2895,7 @@ dependencies = [ "eyre", "futures", "reth", + "reth-execution-types", "reth-exex", "reth-exex-test-utils", "reth-node-api", @@ -2911,12 +2913,12 @@ dependencies = [ "futures", "rand 0.8.5", "reth", + "reth-execution-types", "reth-exex", "reth-exex-test-utils", "reth-node-api", "reth-node-ethereum", "reth-primitives", - "reth-provider", "reth-testing-utils", "reth-tracing", "rusqlite", @@ -2937,6 +2939,7 @@ dependencies = [ "reth", "reth-chainspec", "reth-execution-errors", + "reth-execution-types", "reth-exex", "reth-node-api", "reth-node-ethereum", @@ -6323,6 +6326,7 @@ dependencies = [ "reth-errors", "reth-ethereum-payload-builder", "reth-evm", + "reth-execution-types", "reth-exex", "reth-fs-util", "reth-net-common", @@ -6377,6 +6381,7 @@ dependencies = [ "reth-engine-primitives", "reth-evm", "reth-execution-errors", + "reth-execution-types", "reth-network-p2p", "reth-network-peers", "reth-primitives", @@ -6521,6 +6526,7 @@ dependencies = [ "reth-evm", "reth-evm-ethereum", "reth-execution-errors", + "reth-execution-types", "reth-metrics", "reth-network", "reth-primitives", @@ -7061,6 +7067,7 @@ dependencies = [ "reth-errors", "reth-evm", "reth-evm-ethereum", + "reth-execution-types", "reth-payload-builder", "reth-primitives", "reth-provider", @@ -7124,9 +7131,9 @@ dependencies = [ "reth-consensus-common", "reth-evm", "reth-execution-errors", + "reth-execution-types", "reth-optimism-consensus", "reth-primitives", - "reth-provider", "reth-prune-types", "reth-revm", "revm", @@ -7196,6 +7203,7 @@ dependencies = [ "reth-db", "reth-db-common", "reth-evm", + "reth-execution-types", "reth-exex", "reth-network", "reth-node-api", @@ -7693,6 +7701,7 @@ dependencies = [ "reth-chainspec", "reth-evm", "reth-evm-optimism", + "reth-execution-types", "reth-payload-builder", "reth-payload-primitives", "reth-primitives", @@ -7963,6 +7972,7 @@ dependencies = [ "reth-evm", "reth-evm-ethereum", "reth-evm-optimism", + "reth-execution-types", "reth-metrics", "reth-network-api", "reth-network-peers", @@ -8183,6 +8193,7 @@ dependencies = [ "reth-evm", "reth-evm-ethereum", "reth-execution-errors", + "reth-execution-types", "reth-exex", "reth-network-p2p", "reth-network-peers", @@ -8375,6 +8386,7 @@ dependencies = [ "rand 0.8.5", "reth-chainspec", "reth-eth-wire-types", + "reth-execution-types", "reth-fs-util", "reth-metrics", "reth-primitives", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 5e0e7d2de091..882c1afed705 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -25,6 +25,7 @@ reth-provider = { workspace = true } reth-evm.workspace = true reth-revm.workspace = true reth-stages.workspace = true +reth-execution-types.workspace = true reth-errors.workspace = true reth-transaction-pool.workspace = true reth-beacon-consensus.workspace = true diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 119a6d98f642..40b9722ae4b5 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -19,6 +19,7 @@ use reth_consensus::Consensus; use reth_db::DatabaseEnv; use reth_errors::RethResult; use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; +use reth_execution_types::ExecutionOutcome; use reth_fs_util as fs; use reth_node_api::PayloadBuilderAttributes; use reth_payload_builder::database::CachedReads; @@ -30,7 +31,7 @@ use reth_primitives::{ }; use reth_provider::{ providers::BlockchainProvider, BlockHashReader, BlockReader, BlockWriter, ChainSpecProvider, - ExecutionOutcome, ProviderFactory, StageCheckpointReader, StateProviderFactory, + ProviderFactory, StageCheckpointReader, StateProviderFactory, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_types::engine::{BlobsBundleV1, PayloadAttributes}; diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index aef62326a1a5..8f78d6711073 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -13,13 +13,14 @@ use reth_config::Config; use reth_db::DatabaseEnv; use reth_errors::BlockValidationError; use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; +use reth_execution_types::ExecutionOutcome; use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::BlockHashOrNumber; use reth_provider::{ - AccountExtReader, ChainSpecProvider, ExecutionOutcome, HashingWriter, HeaderProvider, - LatestStateProviderRef, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, - StateWriter, StaticFileProviderFactory, StorageReader, + AccountExtReader, ChainSpecProvider, HashingWriter, HeaderProvider, LatestStateProviderRef, + OriginalValuesKnown, ProviderFactory, StageCheckpointReader, StateWriter, + StaticFileProviderFactory, StorageReader, }; use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; diff --git a/bin/reth/src/commands/import_receipts_op.rs b/bin/reth/src/commands/import_receipts_op.rs index 9d84cdd01fc0..62cff7017744 100644 --- a/bin/reth/src/commands/import_receipts_op.rs +++ b/bin/reth/src/commands/import_receipts_op.rs @@ -9,11 +9,12 @@ use reth_downloaders::{ file_client::{ChunkedFileReader, DEFAULT_BYTE_LEN_CHUNK_CHAIN_FILE}, receipt_file_client::ReceiptFileClient, }; +use reth_execution_types::ExecutionOutcome; use reth_node_core::version::SHORT_VERSION; use reth_optimism_primitives::bedrock_import::is_dup_tx; use reth_primitives::{Receipts, StaticFileSegment}; use reth_provider::{ - ExecutionOutcome, OriginalValuesKnown, ProviderFactory, StageCheckpointReader, StateWriter, + OriginalValuesKnown, ProviderFactory, StageCheckpointReader, StateWriter, StaticFileProviderFactory, StaticFileWriter, StatsReader, }; use reth_stages::StageId; diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index 033b20225997..a90f36add053 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -21,6 +21,7 @@ reth-db-api.workspace = true reth-evm.workspace = true reth-revm.workspace = true reth-provider.workspace = true +reth-execution-types.workspace = true reth-prune-types.workspace = true reth-stages-api.workspace = true reth-trie = { workspace = true, features = ["metrics"] } diff --git a/crates/blockchain-tree/src/block_indices.rs b/crates/blockchain-tree/src/block_indices.rs index 494b6fc98d62..b080f26bda33 100644 --- a/crates/blockchain-tree/src/block_indices.rs +++ b/crates/blockchain-tree/src/block_indices.rs @@ -3,8 +3,8 @@ use super::state::BlockchainId; use crate::canonical_chain::CanonicalChain; use linked_hash_set::LinkedHashSet; +use reth_execution_types::Chain; use reth_primitives::{BlockHash, BlockNumHash, BlockNumber, SealedBlockWithSenders}; -use reth_provider::Chain; use std::collections::{btree_map, hash_map, BTreeMap, BTreeSet, HashMap, HashSet}; /// Internal indices of the blocks and chains. diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index bafeb6ffcba9..4c92e1fd8bf1 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -13,15 +13,15 @@ use reth_consensus::{Consensus, ConsensusError}; use reth_db_api::database::Database; use reth_evm::execute::BlockExecutorProvider; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; +use reth_execution_types::{Chain, ExecutionOutcome}; use reth_primitives::{ BlockHash, BlockNumHash, BlockNumber, ForkBlock, GotExpected, Hardfork, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, B256, U256, }; use reth_provider::{ BlockExecutionWriter, BlockNumReader, BlockWriter, CanonStateNotification, - CanonStateNotificationSender, CanonStateNotifications, Chain, ChainSpecProvider, ChainSplit, - ChainSplitTarget, DisplayBlocksChain, ExecutionOutcome, HeaderProvider, ProviderError, - StaticFileProviderFactory, + CanonStateNotificationSender, CanonStateNotifications, ChainSpecProvider, ChainSplit, + ChainSplitTarget, DisplayBlocksChain, HeaderProvider, ProviderError, StaticFileProviderFactory, }; use reth_prune_types::PruneModes; use reth_stages_api::{MetricEvent, MetricEventsSender}; diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs index 613cb59cdd34..dbc0c1d04b5f 100644 --- a/crates/blockchain-tree/src/chain.rs +++ b/crates/blockchain-tree/src/chain.rs @@ -13,12 +13,13 @@ use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_db_api::database::Database; use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; use reth_execution_errors::BlockExecutionError; +use reth_execution_types::{Chain, ExecutionOutcome}; use reth_primitives::{ BlockHash, BlockNumber, ForkBlock, GotExpected, SealedBlockWithSenders, SealedHeader, U256, }; use reth_provider::{ providers::{BundleStateProvider, ConsistentDbView}, - Chain, ExecutionOutcome, FullExecutionDataProvider, ProviderError, StateRootProvider, + FullExecutionDataProvider, ProviderError, StateRootProvider, }; use reth_revm::database::StateProviderDatabase; use reth_trie::updates::TrieUpdates; diff --git a/crates/consensus/auto-seal/Cargo.toml b/crates/consensus/auto-seal/Cargo.toml index f5f29b9051ac..edf9f84389dc 100644 --- a/crates/consensus/auto-seal/Cargo.toml +++ b/crates/consensus/auto-seal/Cargo.toml @@ -17,6 +17,7 @@ reth-chainspec.workspace = true reth-beacon-consensus.workspace = true reth-primitives.workspace = true reth-execution-errors.workspace = true +reth-execution-types.workspace = true reth-network-p2p.workspace = true reth-provider.workspace = true reth-stages-api.workspace = true diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index cd4c09e10f51..ba6c67487e20 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -20,12 +20,13 @@ use reth_chainspec::ChainSpec; use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_engine_primitives::EngineTypes; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; +use reth_execution_types::ExecutionOutcome; use reth_primitives::{ constants::ETHEREUM_BLOCK_GAS_LIMIT, eip4844::calculate_excess_blob_gas, proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, Bloom, Header, Requests, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, U256, }; -use reth_provider::{BlockReaderIdExt, ExecutionOutcome, StateProviderFactory, StateRootProvider}; +use reth_provider::{BlockReaderIdExt, StateProviderFactory, StateRootProvider}; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::TransactionPool; use std::{ diff --git a/crates/ethereum/payload/Cargo.toml b/crates/ethereum/payload/Cargo.toml index 883752a9681c..e41c8a407c33 100644 --- a/crates/ethereum/payload/Cargo.toml +++ b/crates/ethereum/payload/Cargo.toml @@ -18,6 +18,7 @@ reth-revm.workspace = true reth-transaction-pool.workspace = true reth-provider.workspace = true reth-payload-builder.workspace = true +reth-execution-types.workspace = true reth-basic-payload-builder.workspace = true reth-evm.workspace = true reth-evm-ethereum.workspace = true diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index c979397d7553..1e53180d99bb 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -17,6 +17,7 @@ use reth_basic_payload_builder::{ use reth_errors::RethError; use reth_evm::ConfigureEvm; use reth_evm_ethereum::{eip6110::parse_deposits_from_receipts, EthEvmConfig}; +use reth_execution_types::ExecutionOutcome; use reth_payload_builder::{ error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes, }; @@ -29,7 +30,7 @@ use reth_primitives::{ revm::env::tx_env_with_recovered, Block, Header, IntoRecoveredTransaction, Receipt, EMPTY_OMMER_ROOT_HASH, U256, }; -use reth_provider::{ExecutionOutcome, StateProviderFactory}; +use reth_provider::StateProviderFactory; use reth_revm::{database::StateProviderDatabase, state_change::apply_blockhashes_update}; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; use revm::{ diff --git a/crates/exex/test-utils/Cargo.toml b/crates/exex/test-utils/Cargo.toml index 7c49c43612db..b7db9a98f02c 100644 --- a/crates/exex/test-utils/Cargo.toml +++ b/crates/exex/test-utils/Cargo.toml @@ -19,6 +19,7 @@ reth-consensus = { workspace = true, features = ["test-utils"] } reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-evm = { workspace = true, features = ["test-utils"] } +reth-execution-types.workspace = true reth-exex.workspace = true reth-network.workspace = true reth-node-api.workspace = true diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index b8ca75c9e4d4..cba6e01246e6 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -15,6 +15,7 @@ use reth_consensus::test_utils::TestConsensus; use reth_db::{test_utils::TempDatabase, DatabaseEnv}; use reth_db_common::init::init_genesis; use reth_evm::test_utils::MockExecutorProvider; +use reth_execution_types::Chain; use reth_exex::{ExExContext, ExExEvent, ExExNotification}; use reth_network::{config::SecretKey, NetworkConfigBuilder, NetworkManager}; use reth_node_api::{FullNodeTypes, FullNodeTypesAdapter, NodeTypes}; @@ -34,7 +35,7 @@ use reth_payload_builder::noop::NoopPayloadBuilderService; use reth_primitives::{Head, SealedBlockWithSenders}; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, - BlockReader, Chain, ProviderFactory, + BlockReader, ProviderFactory, }; use reth_tasks::TaskManager; use reth_transaction_pool::test_utils::{testing_pool, TestPool}; diff --git a/crates/optimism/evm/Cargo.toml b/crates/optimism/evm/Cargo.toml index c7886ebf2845..47c8cedee294 100644 --- a/crates/optimism/evm/Cargo.toml +++ b/crates/optimism/evm/Cargo.toml @@ -17,7 +17,7 @@ reth-evm.workspace = true reth-primitives.workspace = true reth-revm.workspace = true reth-execution-errors.workspace = true -reth-provider.workspace = true +reth-execution-types.workspace = true reth-prune-types.workspace = true reth-consensus-common.workspace = true @@ -38,7 +38,7 @@ reth-revm = { workspace = true, features = ["test-utils"] } [features] optimism = [ "reth-primitives/optimism", - "reth-provider/optimism", + "reth-execution-types/optimism", "reth-optimism-consensus/optimism", "reth-revm/optimism", ] diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index a3062b89aae8..e6130295f455 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -9,11 +9,11 @@ use reth_evm::{ }, ConfigureEvm, }; +use reth_execution_types::ExecutionOutcome; use reth_optimism_consensus::validate_block_post_execution; use reth_primitives::{ BlockNumber, BlockWithSenders, Header, Receipt, Receipts, TxType, Withdrawals, U256, }; -use reth_provider::ExecutionOutcome; use reth_prune_types::PruneModes; use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index 357f13956e1a..6aaec1076779 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -22,6 +22,7 @@ reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true reth-evm.workspace = true reth-evm-optimism.workspace = true +reth-execution-types.workspace = true reth-payload-builder.workspace = true reth-payload-primitives.workspace = true reth-basic-payload-builder.workspace = true diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index d0289efc8bd6..fa17982cb8c6 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -7,6 +7,7 @@ use crate::{ use reth_basic_payload_builder::*; use reth_chainspec::ChainSpec; use reth_evm::ConfigureEvm; +use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; use reth_primitives::{ constants::{BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS}, @@ -16,7 +17,7 @@ use reth_primitives::{ Block, Hardfork, Header, IntoRecoveredTransaction, Receipt, TxType, EMPTY_OMMER_ROOT_HASH, U256, }; -use reth_provider::{ExecutionOutcome, StateProviderFactory}; +use reth_provider::StateProviderFactory; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; use revm::{ diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 096409ab251b..2c08a0fd6a7e 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -30,6 +30,7 @@ reth-rpc-types-compat.workspace = true revm-inspectors = { workspace = true, features = ["js-tracer"] } reth-evm.workspace = true reth-network-peers.workspace = true +reth-execution-types.workspace = true reth-evm-optimism = { workspace = true, optional = true } diff --git a/crates/rpc/rpc/src/eth/api/pending_block.rs b/crates/rpc/rpc/src/eth/api/pending_block.rs index c3645b3c85a1..4f9a616dece8 100644 --- a/crates/rpc/rpc/src/eth/api/pending_block.rs +++ b/crates/rpc/rpc/src/eth/api/pending_block.rs @@ -3,6 +3,7 @@ use crate::eth::error::{EthApiError, EthResult}; use reth_chainspec::ChainSpec; use reth_errors::ProviderError; +use reth_execution_types::ExecutionOutcome; use reth_primitives::{ constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH}, proofs, @@ -13,7 +14,7 @@ use reth_primitives::{ Block, BlockId, BlockNumberOrTag, Header, IntoRecoveredTransaction, Receipt, Requests, SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256, }; -use reth_provider::{ChainSpecProvider, ExecutionOutcome, StateProviderFactory}; +use reth_provider::{ChainSpecProvider, StateProviderFactory}; use reth_revm::{ database::StateProviderDatabase, state_change::{ diff --git a/crates/rpc/rpc/src/eth/cache/mod.rs b/crates/rpc/rpc/src/eth/cache/mod.rs index 5d6ae6508485..cfbe68311ee3 100644 --- a/crates/rpc/rpc/src/eth/cache/mod.rs +++ b/crates/rpc/rpc/src/eth/cache/mod.rs @@ -3,13 +3,13 @@ use futures::{future::Either, Stream, StreamExt}; use reth_errors::{ProviderError, ProviderResult}; use reth_evm::ConfigureEvm; +use reth_execution_types::Chain; use reth_primitives::{ Block, BlockHashOrNumber, BlockWithSenders, Receipt, SealedBlock, SealedBlockWithSenders, TransactionSigned, TransactionSignedEcRecovered, B256, }; use reth_provider::{ - BlockReader, CanonStateNotification, Chain, EvmEnvProvider, StateProviderFactory, - TransactionVariant, + BlockReader, CanonStateNotification, EvmEnvProvider, StateProviderFactory, TransactionVariant, }; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}; diff --git a/crates/stages/stages/Cargo.toml b/crates/stages/stages/Cargo.toml index be203b43a18a..ab0dd6c689a7 100644 --- a/crates/stages/stages/Cargo.toml +++ b/crates/stages/stages/Cargo.toml @@ -25,6 +25,7 @@ reth-exex.workspace = true reth-network-p2p.workspace = true reth-primitives.workspace = true reth-provider.workspace = true +reth-execution-types.workspace = true reth-prune-types.workspace = true reth-storage-errors.workspace = true reth-revm.workspace = true diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 9f3b229268ed..a37c081b4da7 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -4,6 +4,7 @@ use reth_config::config::ExecutionConfig; use reth_db::{static_file::HeaderMask, tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; +use reth_execution_types::{Chain, ExecutionOutcome}; use reth_exex::{ExExManagerHandle, ExExNotification}; use reth_primitives::{ constants::gas_units::{GIGAGAS, KILOGAS, MEGAGAS}, @@ -11,9 +12,8 @@ use reth_primitives::{ }; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, - BlockReader, Chain, DatabaseProviderRW, ExecutionOutcome, HeaderProvider, - LatestStateProviderRef, OriginalValuesKnown, ProviderError, StateWriter, StatsReader, - TransactionVariant, + BlockReader, DatabaseProviderRW, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, + ProviderError, StateWriter, StatsReader, TransactionVariant, }; use reth_prune_types::PruneModes; use reth_revm::database::StateProviderDatabase; diff --git a/crates/stages/stages/src/stages/headers.rs b/crates/stages/stages/src/stages/headers.rs index 58c578e34d99..3d05ea52d960 100644 --- a/crates/stages/stages/src/stages/headers.rs +++ b/crates/stages/stages/src/stages/headers.rs @@ -376,10 +376,9 @@ mod tests { stage_test_suite, ExecuteStageTestRunner, StageTestRunner, UnwindStageTestRunner, }; use assert_matches::assert_matches; + use reth_execution_types::ExecutionOutcome; use reth_primitives::{BlockBody, SealedBlock, SealedBlockWithSenders, B256}; - use reth_provider::{ - BlockWriter, ExecutionOutcome, ProviderFactory, StaticFileProviderFactory, - }; + use reth_provider::{BlockWriter, ProviderFactory, StaticFileProviderFactory}; use reth_stages_api::StageUnitCheckpoint; use reth_testing_utils::generators::{self, random_header, random_header_range}; use reth_trie::{updates::TrieUpdates, HashedPostState}; diff --git a/crates/storage/provider/src/bundle_state/mod.rs b/crates/storage/provider/src/bundle_state/mod.rs index 1b3965a14a45..d1a9e9b2a14d 100644 --- a/crates/storage/provider/src/bundle_state/mod.rs +++ b/crates/storage/provider/src/bundle_state/mod.rs @@ -6,7 +6,7 @@ mod state_changes; mod state_reverts; pub use bundle_state_with_receipts::{ - AccountRevertInit, BundleStateInit, ExecutionOutcome, OriginalValuesKnown, RevertsInit, + AccountRevertInit, BundleStateInit, OriginalValuesKnown, RevertsInit, }; pub use hashed_state_changes::HashedStateChanges; pub use state_changes::StateChanges; diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index f0932f45dbf4..02287b96cad0 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1,13 +1,13 @@ use crate::{ - bundle_state::{BundleStateInit, ExecutionOutcome, HashedStateChanges, RevertsInit}, + bundle_state::{BundleStateInit, HashedStateChanges, RevertsInit}, providers::{database::metrics, static_file::StaticFileWriter, StaticFileProvider}, to_range, traits::{ AccountExtReader, BlockSource, ChangeSetReader, ReceiptProvider, StageCheckpointWriter, }, AccountReader, BlockExecutionWriter, BlockHashReader, BlockNumReader, BlockReader, BlockWriter, - Chain, EvmEnvProvider, FinalizedBlockReader, FinalizedBlockWriter, HashingWriter, - HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HistoricalStateProvider, HistoryWriter, + EvmEnvProvider, FinalizedBlockReader, FinalizedBlockWriter, HashingWriter, HeaderProvider, + HeaderSyncGap, HeaderSyncGapProvider, HistoricalStateProvider, HistoryWriter, LatestStateProvider, OriginalValuesKnown, ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RequestsProvider, StageCheckpointReader, StateProviderBox, StateWriter, StatsReader, StorageReader, TransactionVariant, TransactionsProvider, TransactionsProviderExt, @@ -29,6 +29,7 @@ use reth_db_api::{ DatabaseError, }; use reth_evm::ConfigureEvmEnv; +use reth_execution_types::{Chain, ExecutionOutcome}; use reth_network_p2p::headers::downloader::SyncTarget; use reth_primitives::{ keccak256, diff --git a/crates/storage/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index 56255969e97b..7211cb691fb6 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -1,5 +1,5 @@ -use crate::{Chain, ExecutionOutcome}; use reth_db_api::models::StoredBlockBodyIndices; +use reth_execution_types::{Chain, ExecutionOutcome}; use reth_primitives::{BlockNumber, SealedBlockWithSenders}; use reth_prune_types::PruneModes; use reth_storage_api::BlockReader; diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index a90b16083f42..eee2a083357c 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -16,6 +16,7 @@ workspace = true reth-chainspec.workspace = true reth-eth-wire-types.workspace = true reth-primitives.workspace = true +reth-execution-types.workspace = true reth-fs-util.workspace = true reth-provider.workspace = true reth-tasks.workspace = true diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 99b29ed01289..10f2ea2e59f9 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -11,6 +11,7 @@ use futures_util::{ future::{BoxFuture, Fuse, FusedFuture}, FutureExt, Stream, StreamExt, }; +use reth_execution_types::ExecutionOutcome; use reth_fs_util::FsPathError; use reth_primitives::{ Address, BlockHash, BlockNumber, BlockNumberOrTag, FromRecoveredPooledTransaction, @@ -18,7 +19,7 @@ use reth_primitives::{ TryFromRecoveredTransaction, }; use reth_provider::{ - BlockReaderIdExt, CanonStateNotification, ChainSpecProvider, ExecutionOutcome, ProviderError, + BlockReaderIdExt, CanonStateNotification, ChainSpecProvider, ProviderError, StateProviderFactory, }; use reth_tasks::TaskSpawner; diff --git a/examples/exex/in-memory-state/Cargo.toml b/examples/exex/in-memory-state/Cargo.toml index 1ea633b071eb..b79808cb1392 100644 --- a/examples/exex/in-memory-state/Cargo.toml +++ b/examples/exex/in-memory-state/Cargo.toml @@ -11,6 +11,7 @@ reth-exex.workspace = true reth-node-api.workspace = true reth-node-ethereum.workspace = true reth-tracing.workspace = true +reth-execution-types.workspace = true eyre.workspace = true diff --git a/examples/exex/in-memory-state/src/main.rs b/examples/exex/in-memory-state/src/main.rs index cd683147ff05..c56cdcf5044b 100644 --- a/examples/exex/in-memory-state/src/main.rs +++ b/examples/exex/in-memory-state/src/main.rs @@ -1,6 +1,6 @@ #![warn(unused_crate_dependencies)] -use reth::providers::ExecutionOutcome; +use reth_execution_types::ExecutionOutcome; use reth_exex::{ExExContext, ExExEvent, ExExNotification}; use reth_node_api::FullNodeComponents; use reth_node_ethereum::EthereumNode; @@ -73,14 +73,11 @@ fn main() -> eyre::Result<()> { #[cfg(test)] mod tests { - use std::pin::pin; - - use reth::{ - providers::{Chain, ExecutionOutcome}, - revm::db::BundleState, - }; + use reth::revm::db::BundleState; + use reth_execution_types::{Chain, ExecutionOutcome}; use reth_exex_test_utils::{test_exex_context, PollOnce}; use reth_testing_utils::generators::{self, random_block, random_receipt}; + use std::pin::pin; #[tokio::test] async fn test_exex() -> eyre::Result<()> { diff --git a/examples/exex/minimal/Cargo.toml b/examples/exex/minimal/Cargo.toml index 5759b41e514d..6cf958904129 100644 --- a/examples/exex/minimal/Cargo.toml +++ b/examples/exex/minimal/Cargo.toml @@ -11,6 +11,7 @@ reth-exex.workspace = true reth-node-api.workspace = true reth-node-ethereum.workspace = true reth-tracing.workspace = true +reth-execution-types.workspace = true eyre.workspace = true futures.workspace = true diff --git a/examples/exex/minimal/src/main.rs b/examples/exex/minimal/src/main.rs index 7f6d4585559e..cb7af242e65d 100644 --- a/examples/exex/minimal/src/main.rs +++ b/examples/exex/minimal/src/main.rs @@ -54,7 +54,7 @@ fn main() -> eyre::Result<()> { #[cfg(test)] mod tests { - use reth::providers::{Chain, ExecutionOutcome}; + use reth_execution_types::{Chain, ExecutionOutcome}; use reth_exex_test_utils::{test_exex_context, PollOnce}; use std::pin::pin; diff --git a/examples/exex/op-bridge/Cargo.toml b/examples/exex/op-bridge/Cargo.toml index a1c8d98c5b2b..38693a2c57d8 100644 --- a/examples/exex/op-bridge/Cargo.toml +++ b/examples/exex/op-bridge/Cargo.toml @@ -11,7 +11,7 @@ reth-exex.workspace = true reth-node-api.workspace = true reth-node-ethereum.workspace = true reth-primitives.workspace = true -reth-provider.workspace = true +reth-execution-types.workspace = true reth-tracing.workspace = true eyre.workspace = true diff --git a/examples/exex/op-bridge/src/main.rs b/examples/exex/op-bridge/src/main.rs index aebe90e193c5..3c69572f24ae 100644 --- a/examples/exex/op-bridge/src/main.rs +++ b/examples/exex/op-bridge/src/main.rs @@ -1,10 +1,10 @@ use alloy_sol_types::{sol, SolEventInterface}; use futures::Future; +use reth_execution_types::Chain; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::FullNodeComponents; use reth_node_ethereum::EthereumNode; use reth_primitives::{address, Address, Log, SealedBlockWithSenders, TransactionSigned}; -use reth_provider::Chain; use reth_tracing::tracing::info; use rusqlite::Connection; @@ -259,12 +259,12 @@ mod tests { use alloy_sol_types::SolEvent; use reth::revm::db::BundleState; + use reth_execution_types::{Chain, ExecutionOutcome}; use reth_exex_test_utils::{test_exex_context, PollOnce}; use reth_primitives::{ Address, Block, Header, Log, Receipt, Transaction, TransactionSigned, TxKind, TxLegacy, TxType, U256, }; - use reth_provider::{Chain, ExecutionOutcome}; use reth_testing_utils::generators::sign_tx_with_random_key_pair; use rusqlite::Connection; diff --git a/examples/exex/rollup/Cargo.toml b/examples/exex/rollup/Cargo.toml index f22a8eeca07d..c9623ce2206d 100644 --- a/examples/exex/rollup/Cargo.toml +++ b/examples/exex/rollup/Cargo.toml @@ -14,6 +14,7 @@ reth-node-api.workspace = true reth-node-ethereum.workspace = true reth-primitives.workspace = true reth-execution-errors.workspace = true +reth-execution-types.workspace = true reth-provider.workspace = true reth-revm.workspace = true reth-tracing.workspace = true diff --git a/examples/exex/rollup/src/main.rs b/examples/exex/rollup/src/main.rs index f8a2ffc401f6..a07b3fa3301f 100644 --- a/examples/exex/rollup/src/main.rs +++ b/examples/exex/rollup/src/main.rs @@ -9,11 +9,11 @@ use db::Database; use execution::execute_block; use once_cell::sync::Lazy; use reth_chainspec::{ChainSpec, ChainSpecBuilder}; +use reth_execution_types::Chain; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::FullNodeComponents; use reth_node_ethereum::EthereumNode; use reth_primitives::{address, Address, Genesis, SealedBlockWithSenders, TransactionSigned, U256}; -use reth_provider::Chain; use reth_tracing::tracing::{error, info}; use rusqlite::Connection; use std::sync::Arc; From 50cf64bfbb5ddd6664db1738b061182976b52122 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 20 Jun 2024 00:55:24 +0200 Subject: [PATCH 142/405] fix: make sure to commit static file provider on `stage run` (#8972) --- bin/reth/src/commands/stage/run.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bin/reth/src/commands/stage/run.rs b/bin/reth/src/commands/stage/run.rs index 01a57fd52f4d..b8dcc7c9194d 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/bin/reth/src/commands/stage/run.rs @@ -16,6 +16,7 @@ use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_exex::ExExManagerHandle; use reth_provider::{ ChainSpecProvider, StageCheckpointReader, StageCheckpointWriter, StaticFileProviderFactory, + StaticFileWriter, }; use reth_stages::{ stages::{ @@ -253,7 +254,12 @@ impl Command { } if self.commit { + // For unwinding it makes more sense to commit the database first, since if + // this function is interrupted before the static files commit, we can just + // truncate the static files according to the + // checkpoints on the next start-up. provider_rw.commit()?; + provider_factory.static_file_provider().commit()?; provider_rw = provider_factory.provider_rw()?; } } @@ -276,6 +282,7 @@ impl Command { provider_rw.save_stage_checkpoint(exec_stage.id(), checkpoint)?; } if self.commit { + provider_factory.static_file_provider().commit()?; provider_rw.commit()?; provider_rw = provider_factory.provider_rw()?; } From bf9cac7571f018fec581fe3647862dab527aeafb Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:58:58 -0400 Subject: [PATCH 143/405] feat: add stage unwind to sync 100k CI flow (#8974) --- .github/workflows/integration.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 8a0bb0948c59..8bd42c8bb31d 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -77,6 +77,10 @@ jobs: cargo run --release --bin reth \ -- db get static-file headers 100000 \ | grep 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 + - name: Run stage unwind for 100 blocks + run: | + cargo run --release --bin reth \ + -- stage unwind num-blocks 100 integration-success: name: integration success From 8b6ca877d699c66f448d9c72073fe1a77538e701 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:14:25 -0400 Subject: [PATCH 144/405] chore: use alloy-genesis imports for Genesis (#8973) --- Cargo.lock | 11 +++++++++++ crates/blockchain-tree/Cargo.toml | 1 + crates/blockchain-tree/src/blockchain_tree.rs | 5 +++-- crates/consensus/beacon/Cargo.toml | 1 + crates/consensus/beacon/src/engine/mod.rs | 3 ++- crates/net/eth-wire-types/Cargo.toml | 1 + crates/net/eth-wire-types/src/status.rs | 6 ++++-- crates/node-core/Cargo.toml | 1 + crates/node-core/src/args/utils.rs | 3 ++- crates/optimism/node/Cargo.toml | 1 + crates/optimism/node/tests/e2e/utils.rs | 3 ++- crates/primitives/src/proofs.rs | 6 ++---- crates/rpc/rpc/Cargo.toml | 1 + crates/rpc/rpc/src/admin.rs | 2 +- crates/storage/db-common/Cargo.toml | 3 +++ crates/storage/db-common/src/init.rs | 9 ++++----- examples/custom-engine-types/Cargo.toml | 1 + examples/custom-engine-types/src/main.rs | 3 ++- examples/custom-evm/Cargo.toml | 1 + examples/custom-evm/src/main.rs | 3 ++- examples/exex/rollup/Cargo.toml | 1 + examples/exex/rollup/src/main.rs | 3 ++- examples/stateful-precompile/Cargo.toml | 1 + examples/stateful-precompile/src/main.rs | 3 ++- 24 files changed, 52 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5db2c22312a6..efd35ee3f808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2789,6 +2789,7 @@ dependencies = [ name = "example-custom-engine-types" version = "0.0.0" dependencies = [ + "alloy-genesis", "eyre", "reth", "reth-basic-payload-builder", @@ -2810,6 +2811,7 @@ dependencies = [ name = "example-custom-evm" version = "0.0.0" dependencies = [ + "alloy-genesis", "eyre", "reth", "reth-chainspec", @@ -2931,6 +2933,7 @@ name = "example-exex-rollup" version = "0.0.0" dependencies = [ "alloy-consensus", + "alloy-genesis", "alloy-rlp", "alloy-sol-types", "eyre", @@ -3049,6 +3052,7 @@ dependencies = [ name = "example-stateful-precompile" version = "0.0.0" dependencies = [ + "alloy-genesis", "eyre", "parking_lot 0.12.3", "reth", @@ -6422,6 +6426,7 @@ dependencies = [ name = "reth-beacon-consensus" version = "1.0.0-rc.2" dependencies = [ + "alloy-genesis", "assert_matches", "futures", "itertools 0.12.1", @@ -6513,6 +6518,7 @@ dependencies = [ name = "reth-blockchain-tree" version = "1.0.0-rc.2" dependencies = [ + "alloy-genesis", "aquamarine", "assert_matches", "linked_hash_set", @@ -6744,6 +6750,7 @@ dependencies = [ name = "reth-db-common" version = "1.0.0-rc.2" dependencies = [ + "alloy-genesis", "eyre", "reth-chainspec", "reth-codecs", @@ -6997,6 +7004,7 @@ dependencies = [ name = "reth-eth-wire-types" version = "1.0.0-rc.2" dependencies = [ + "alloy-genesis", "alloy-rlp", "arbitrary", "async-stream", @@ -7523,6 +7531,7 @@ dependencies = [ name = "reth-node-core" version = "1.0.0-rc.2" dependencies = [ + "alloy-genesis", "alloy-rpc-types-engine", "clap", "const-str", @@ -7641,6 +7650,7 @@ dependencies = [ name = "reth-node-optimism" version = "1.0.0-rc.2" dependencies = [ + "alloy-genesis", "alloy-primitives", "async-trait", "clap", @@ -7949,6 +7959,7 @@ name = "reth-rpc" version = "1.0.0-rc.2" dependencies = [ "alloy-dyn-abi", + "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-sol-types", diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index a90f36add053..b3679677a13c 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -54,6 +54,7 @@ reth-revm.workspace = true reth-evm-ethereum.workspace = true parking_lot.workspace = true assert_matches.workspace = true +alloy-genesis.workspace = true [features] test-utils = [] diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 4c92e1fd8bf1..ea73fdcb8702 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1367,6 +1367,7 @@ where #[cfg(test)] mod tests { use super::*; + use alloy_genesis::{Genesis, GenesisAccount}; use assert_matches::assert_matches; use linked_hash_set::LinkedHashSet; use reth_chainspec::{ChainSpecBuilder, MAINNET}; @@ -1384,8 +1385,8 @@ mod tests { keccak256, proofs::calculate_transaction_root, revm_primitives::AccountInfo, - Account, Address, Genesis, GenesisAccount, Header, Signature, Transaction, - TransactionSigned, TransactionSignedEcRecovered, TxEip1559, Withdrawals, B256, + Account, Address, Header, Signature, Transaction, TransactionSigned, + TransactionSignedEcRecovered, TxEip1559, Withdrawals, B256, }; use reth_provider::{ test_utils::{blocks::BlockchainTestData, create_test_provider_factory_with_chain_spec}, diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index df88348411a5..bf74df0f7598 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -69,6 +69,7 @@ reth-config.workspace = true reth-testing-utils.workspace = true reth-exex-types.workspace = true reth-prune-types.workspace = true +alloy-genesis.workspace = true assert_matches.workspace = true diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 29c5c6a1c124..72f71b2861ac 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -2490,8 +2490,9 @@ mod tests { mod new_payload { use super::*; + use alloy_genesis::Genesis; use reth_db::test_utils::create_test_static_files_dir; - use reth_primitives::{genesis::Genesis, Hardfork, U256}; + use reth_primitives::{Hardfork, U256}; use reth_provider::{ providers::StaticFileProvider, test_utils::blocks::BlockchainTestData, }; diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index 5c5509c8c913..09b28e5237ca 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -17,6 +17,7 @@ reth-chainspec.workspace = true reth-codecs-derive.workspace = true reth-primitives.workspace = true alloy-rlp = { workspace = true, features = ["derive"] } +alloy-genesis.workspace = true bytes.workspace = true derive_more.workspace = true diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 5f16a623ccb5..95e5aa84adf5 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -1,8 +1,9 @@ use crate::EthVersion; +use alloy_genesis::Genesis; use alloy_rlp::{RlpDecodable, RlpEncodable}; use reth_chainspec::{Chain, ChainSpec, NamedChain, MAINNET}; use reth_codecs_derive::derive_arbitrary; -use reth_primitives::{hex, ForkId, Genesis, Hardfork, Head, B256, U256}; +use reth_primitives::{hex, ForkId, Hardfork, Head, B256, U256}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; @@ -228,10 +229,11 @@ impl StatusBuilder { #[cfg(test)] mod tests { use crate::{EthVersion, Status}; + use alloy_genesis::Genesis; use alloy_rlp::{Decodable, Encodable}; use rand::Rng; use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain}; - use reth_primitives::{hex, ForkHash, ForkId, Genesis, Hardfork, Head, B256, U256}; + use reth_primitives::{hex, ForkHash, ForkId, Hardfork, Head, B256, U256}; use std::str::FromStr; #[test] diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index ed6aedfc8654..75f03559bb9c 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -98,6 +98,7 @@ procfs = "0.16.0" [dev-dependencies] # test vectors generation proptest.workspace = true +alloy-genesis.workspace = true [features] optimism = [ diff --git a/crates/node-core/src/args/utils.rs b/crates/node-core/src/args/utils.rs index 527e1ac228ac..38c0b01b6034 100644 --- a/crates/node-core/src/args/utils.rs +++ b/crates/node-core/src/args/utils.rs @@ -168,9 +168,10 @@ pub fn parse_socket_address(value: &str) -> eyre::Result B256 { #[cfg(test)] mod tests { use super::*; - use crate::{ - bloom, constants::EMPTY_ROOT_HASH, hex_literal::hex, Block, GenesisAccount, Log, TxType, - U256, - }; + use crate::{bloom, constants::EMPTY_ROOT_HASH, hex_literal::hex, Block, Log, TxType, U256}; + use alloy_genesis::GenesisAccount; use alloy_primitives::{b256, Address, LogData}; use alloy_rlp::Decodable; use reth_chainspec::{GOERLI, HOLESKY, MAINNET, SEPOLIA}; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 2c08a0fd6a7e..87539eab4ae5 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -39,6 +39,7 @@ alloy-rlp.workspace = true alloy-dyn-abi = { workspace = true, features = ["eip712"] } alloy-primitives.workspace = true alloy-sol-types.workspace = true +alloy-genesis.workspace = true revm = { workspace = true, features = [ "optional_block_gas_limit", "optional_eip3607", diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 2ee093aed28e..71f95fedec9a 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -1,11 +1,11 @@ use crate::result::ToRpcResult; +use alloy_genesis::ChainConfig; use alloy_primitives::B256; use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_chainspec::ChainSpec; use reth_network_api::{NetworkInfo, PeerKind, Peers}; use reth_network_peers::{AnyNode, NodeRecord}; -use reth_primitives::ChainConfig; use reth_rpc_api::AdminApiServer; use reth_rpc_types::{ admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo}, diff --git a/crates/storage/db-common/Cargo.toml b/crates/storage/db-common/Cargo.toml index 3299bcc12560..c2760f672793 100644 --- a/crates/storage/db-common/Cargo.toml +++ b/crates/storage/db-common/Cargo.toml @@ -20,6 +20,9 @@ reth-etl.workspace = true reth-codecs.workspace = true reth-stages-types.workspace = true +# eth +alloy-genesis.workspace = true + # misc eyre.workspace = true thiserror.workspace = true diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 3907efd58e57..c6b4802b7829 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -1,5 +1,6 @@ //! Reth genesis initialization utility functions. +use alloy_genesis::GenesisAccount; use reth_chainspec::ChainSpec; use reth_codecs::Compact; use reth_config::config::EtlConfig; @@ -7,8 +8,7 @@ use reth_db::tables; use reth_db_api::{database::Database, transaction::DbTxMut, DatabaseError}; use reth_etl::Collector; use reth_primitives::{ - Account, Address, Bytecode, GenesisAccount, Receipts, StaticFileSegment, StorageEntry, B256, - U256, + Account, Address, Bytecode, Receipts, StaticFileSegment, StorageEntry, B256, U256, }; use reth_provider::{ bundle_state::{BundleStateInit, RevertsInit}, @@ -525,6 +525,7 @@ struct GenesisAccountWithAddress { #[cfg(test)] mod tests { use super::*; + use alloy_genesis::Genesis; use reth_chainspec::{Chain, GOERLI, MAINNET, SEPOLIA}; use reth_db::DatabaseEnv; use reth_db_api::{ @@ -533,9 +534,7 @@ mod tests { table::{Table, TableRow}, transaction::DbTx, }; - use reth_primitives::{ - Genesis, GOERLI_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, - }; + use reth_primitives::{GOERLI_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH}; use reth_primitives_traits::IntegerList; use reth_provider::test_utils::create_test_provider_factory_with_chain_spec; diff --git a/examples/custom-engine-types/Cargo.toml b/examples/custom-engine-types/Cargo.toml index 6728888336c9..3b6a796ba0ad 100644 --- a/examples/custom-engine-types/Cargo.toml +++ b/examples/custom-engine-types/Cargo.toml @@ -17,6 +17,7 @@ reth-basic-payload-builder.workspace = true reth-ethereum-payload-builder.workspace = true reth-node-ethereum.workspace = true reth-tracing.workspace = true +alloy-genesis.workspace = true eyre.workspace = true tokio.workspace = true diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index 07fd78c5ffc9..c006e396e2c7 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -22,6 +22,7 @@ use std::convert::Infallible; use serde::{Deserialize, Serialize}; use thiserror::Error; +use alloy_genesis::Genesis; use reth::{ api::PayloadTypes, builder::{ @@ -51,7 +52,7 @@ use reth_payload_builder::{ error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes, PayloadBuilderHandle, PayloadBuilderService, }; -use reth_primitives::{Address, Genesis, Header, Withdrawals, B256}; +use reth_primitives::{Address, Header, Withdrawals, B256}; use reth_rpc_types::{ engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, diff --git a/examples/custom-evm/Cargo.toml b/examples/custom-evm/Cargo.toml index 3177f5932043..5822bcb22790 100644 --- a/examples/custom-evm/Cargo.toml +++ b/examples/custom-evm/Cargo.toml @@ -13,6 +13,7 @@ reth-node-core.workspace = true reth-primitives.workspace = true reth-node-ethereum.workspace = true reth-tracing.workspace = true +alloy-genesis.workspace = true eyre.workspace = true tokio.workspace = true diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index f5751a3c63a1..9d394126fde2 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] +use alloy_genesis::Genesis; use reth::{ builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, primitives::{ @@ -21,7 +22,7 @@ use reth_chainspec::{Chain, ChainSpec}; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider, EthereumNode}; -use reth_primitives::{Genesis, Header, TransactionSigned}; +use reth_primitives::{Header, TransactionSigned}; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; diff --git a/examples/exex/rollup/Cargo.toml b/examples/exex/rollup/Cargo.toml index c9623ce2206d..665d23f225be 100644 --- a/examples/exex/rollup/Cargo.toml +++ b/examples/exex/rollup/Cargo.toml @@ -24,6 +24,7 @@ tokio.workspace = true # misc alloy-consensus = { workspace = true, features = ["kzg"] } +alloy-genesis.workspace = true alloy-rlp.workspace = true alloy-sol-types = { workspace = true, features = ["json"] } eyre.workspace = true diff --git a/examples/exex/rollup/src/main.rs b/examples/exex/rollup/src/main.rs index a07b3fa3301f..0bac98cd7745 100644 --- a/examples/exex/rollup/src/main.rs +++ b/examples/exex/rollup/src/main.rs @@ -4,6 +4,7 @@ //! The rollup contract accepts blocks of transactions and deposits of ETH and is deployed on //! Holesky at [ROLLUP_CONTRACT_ADDRESS], see . +use alloy_genesis::Genesis; use alloy_sol_types::{sol, SolEventInterface, SolInterface}; use db::Database; use execution::execute_block; @@ -13,7 +14,7 @@ use reth_execution_types::Chain; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::FullNodeComponents; use reth_node_ethereum::EthereumNode; -use reth_primitives::{address, Address, Genesis, SealedBlockWithSenders, TransactionSigned, U256}; +use reth_primitives::{address, Address, SealedBlockWithSenders, TransactionSigned, U256}; use reth_tracing::tracing::{error, info}; use rusqlite::Connection; use std::sync::Arc; diff --git a/examples/stateful-precompile/Cargo.toml b/examples/stateful-precompile/Cargo.toml index 332b07e614df..c983ef80d95e 100644 --- a/examples/stateful-precompile/Cargo.toml +++ b/examples/stateful-precompile/Cargo.toml @@ -13,6 +13,7 @@ reth-node-core.workspace = true reth-primitives.workspace = true reth-node-ethereum.workspace = true reth-tracing.workspace = true +alloy-genesis.workspace = true eyre.workspace = true parking_lot.workspace = true diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index 0cd495e85a53..8eaecb29ac73 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] +use alloy_genesis::Genesis; use parking_lot::RwLock; use reth::{ builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, @@ -23,7 +24,7 @@ use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider, EthereumNode}; use reth_primitives::{ revm_primitives::{SpecId, StatefulPrecompileMut}, - Genesis, Header, TransactionSigned, + Header, TransactionSigned, }; use reth_tracing::{RethTracer, Tracer}; use schnellru::{ByLength, LruMap}; From 554e8b191382718974ec72f78fd597c034c35af3 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 20 Jun 2024 11:33:53 +0100 Subject: [PATCH 145/405] feat(exex): derive serde ser/deser for `ExExNotification` (#8963) --- Cargo.lock | 3 +++ crates/ethereum-forks/Cargo.toml | 4 ++-- crates/evm/execution-types/Cargo.toml | 5 ++++- crates/evm/execution-types/src/chain.rs | 1 + .../evm/execution-types/src/execution_outcome.rs | 1 + crates/exex/exex/Cargo.toml | 5 +++++ crates/exex/exex/src/notification.rs | 1 + crates/net/discv4/Cargo.toml | 2 +- crates/net/eth-wire-types/Cargo.toml | 3 ++- crates/net/eth-wire/Cargo.toml | 2 +- crates/net/network/Cargo.toml | 2 +- crates/primitives-traits/src/lib.rs | 2 +- crates/primitives-traits/src/log.rs | 2 +- crates/primitives/src/receipt.rs | 15 ++++++++++++++- crates/storage/provider/Cargo.toml | 3 ++- crates/transaction-pool/Cargo.toml | 2 +- crates/trie/trie/Cargo.toml | 4 ++++ crates/trie/trie/src/updates.rs | 3 +++ 18 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efd35ee3f808..d8642eb7f132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7174,6 +7174,7 @@ dependencies = [ "reth-primitives", "reth-trie", "revm", + "serde", ] [[package]] @@ -7193,6 +7194,7 @@ dependencies = [ "reth-provider", "reth-tasks", "reth-tracing", + "serde", "tokio", "tokio-util", ] @@ -8440,6 +8442,7 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "revm", + "serde", "serde_json", "similar-asserts", "tokio", diff --git a/crates/ethereum-forks/Cargo.toml b/crates/ethereum-forks/Cargo.toml index ac9836c5438a..bcdbc6551a41 100644 --- a/crates/ethereum-forks/Cargo.toml +++ b/crates/ethereum-forks/Cargo.toml @@ -36,7 +36,7 @@ proptest-derive.workspace = true [features] default = ["std", "serde"] -std = ["thiserror-no-std/std"] -serde = ["dep:serde"] arbitrary = ["dep:arbitrary", "dep:proptest", "dep:proptest-derive"] optimism = [] +serde = ["dep:serde"] +std = ["thiserror-no-std/std"] diff --git a/crates/evm/execution-types/Cargo.toml b/crates/evm/execution-types/Cargo.toml index 0f0ba75479ef..57181537c542 100644 --- a/crates/evm/execution-types/Cargo.toml +++ b/crates/evm/execution-types/Cargo.toml @@ -18,10 +18,13 @@ reth-trie.workspace = true revm.workspace = true +serde = { workspace = true, optional = true } + [dev-dependencies] reth-primitives = { workspace = true, features = ["test-utils"] } alloy-primitives.workspace = true alloy-eips.workspace = true [features] -optimism = ["dep:reth-chainspec"] \ No newline at end of file +optimism = ["dep:reth-chainspec"] +serde = ["dep:serde", "reth-trie/serde", "revm/serde"] diff --git a/crates/evm/execution-types/src/chain.rs b/crates/evm/execution-types/src/chain.rs index 47c3aef2e35f..8833615bbc38 100644 --- a/crates/evm/execution-types/src/chain.rs +++ b/crates/evm/execution-types/src/chain.rs @@ -21,6 +21,7 @@ use std::{borrow::Cow, collections::BTreeMap, fmt, ops::RangeInclusive}; /// /// A chain of blocks should not be empty. #[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Chain { /// All blocks in this chain. blocks: BTreeMap, diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs index e7e861cf1b93..322e397a6be7 100644 --- a/crates/evm/execution-types/src/execution_outcome.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -14,6 +14,7 @@ use std::collections::HashMap; /// The `ExecutionOutcome` structure aggregates the state changes over an arbitrary number of /// blocks, capturing the resulting state, receipts, and requests following the execution. #[derive(Default, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ExecutionOutcome { /// Bundle state with reverts. pub bundle: BundleState, diff --git a/crates/exex/exex/Cargo.toml b/crates/exex/exex/Cargo.toml index 4df59b3fc8c8..5bbf177d098b 100644 --- a/crates/exex/exex/Cargo.toml +++ b/crates/exex/exex/Cargo.toml @@ -32,3 +32,8 @@ tokio-util.workspace = true ## misc eyre.workspace = true metrics.workspace = true +serde = { workspace = true, optional = true } + +[features] +default = [] +serde = ["dep:serde", "reth-provider/serde"] diff --git a/crates/exex/exex/src/notification.rs b/crates/exex/exex/src/notification.rs index 9f1beec414d3..390d9dc665a7 100644 --- a/crates/exex/exex/src/notification.rs +++ b/crates/exex/exex/src/notification.rs @@ -4,6 +4,7 @@ use reth_provider::{CanonStateNotification, Chain}; /// Notifications sent to an `ExEx`. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ExExNotification { /// Chain got committed without a reorg, and only the new chain is returned. ChainCommitted { diff --git a/crates/net/discv4/Cargo.toml b/crates/net/discv4/Cargo.toml index 8c823d5209ec..2418f4d63ccc 100644 --- a/crates/net/discv4/Cargo.toml +++ b/crates/net/discv4/Cargo.toml @@ -52,5 +52,5 @@ reth-tracing.workspace = true [features] default = ["serde"] -test-utils = ["dep:rand"] serde = ["dep:serde"] +test-utils = ["dep:rand"] diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index 09b28e5237ca..d7766043e974 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -45,10 +45,11 @@ async-stream.workspace = true [features] default = ["serde"] -serde = ["dep:serde"] arbitrary = [ "reth-primitives/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive", ] +serde = ["dep:serde"] + diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index c4015cc9bd7c..0add7b8a59ca 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -63,7 +63,6 @@ async-stream.workspace = true [features] default = ["serde"] -serde = ["dep:serde"] arbitrary = [ "reth-primitives/arbitrary", "dep:arbitrary", @@ -71,6 +70,7 @@ arbitrary = [ "dep:proptest-derive", ] optimism = ["reth-primitives/optimism"] +serde = ["dep:serde"] [[test]] name = "fuzz_roundtrip" diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index a99dfea97822..7d56243f66ff 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -95,9 +95,9 @@ criterion = { workspace = true, features = ["async_tokio", "html_reports"] } [features] default = ["serde"] +geth-tests = [] serde = ["dep:serde", "dep:humantime-serde", "secp256k1/serde", "enr/serde", "dep:serde_json"] test-utils = ["reth-provider/test-utils", "dep:tempfile", "reth-transaction-pool/test-utils"] -geth-tests = [] [[bench]] name = "bench" diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index e936bbdf1861..22d4c86a0fda 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -34,7 +34,7 @@ mod error; pub use error::{GotExpected, GotExpectedBoxed}; mod log; -pub use log::{logs_bloom, Log}; +pub use log::{logs_bloom, Log, LogData}; mod storage; pub use storage::StorageEntry; diff --git a/crates/primitives-traits/src/log.rs b/crates/primitives-traits/src/log.rs index cb732b47b681..8233859782b1 100644 --- a/crates/primitives-traits/src/log.rs +++ b/crates/primitives-traits/src/log.rs @@ -1,5 +1,5 @@ use alloy_primitives::Bloom; -pub use alloy_primitives::Log; +pub use alloy_primitives::{Log, LogData}; /// Calculate receipt logs bloom. pub fn logs_bloom<'a>(logs: impl IntoIterator) -> Bloom { diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 32d2a4cd80fe..da84dda8b4a7 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -11,6 +11,7 @@ use proptest::strategy::Strategy; #[cfg(feature = "zstd-codec")] use reth_codecs::CompactZstd; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; +use serde::{Deserialize, Serialize}; #[cfg(not(feature = "std"))] use alloc::{vec, vec::Vec}; @@ -66,7 +67,19 @@ impl Receipt { } /// A collection of receipts organized as a two-dimensional vector. -#[derive(Clone, Debug, PartialEq, Eq, Default, From, Deref, DerefMut, IntoIterator)] +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Default, + Serialize, + Deserialize, + From, + Deref, + DerefMut, + IntoIterator, +)] pub struct Receipts { /// A two-dimensional vector of optional `Receipt` instances. pub receipt_vec: Vec>>, diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index c1f87891ce4d..6cf456665bdf 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -73,5 +73,6 @@ assert_matches.workspace = true rand.workspace = true [features] -test-utils = ["alloy-rlp", "reth-db/test-utils", "reth-nippy-jar/test-utils"] optimism = ["reth-primitives/optimism", "reth-execution-types/optimism"] +serde = ["reth-execution-types/serde"] +test-utils = ["alloy-rlp", "reth-db/test-utils", "reth-nippy-jar/test-utils"] diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index eee2a083357c..d66da4e191de 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -67,9 +67,9 @@ serde_json.workspace = true [features] default = ["serde"] +arbitrary = ["proptest", "reth-primitives/arbitrary"] serde = ["dep:serde"] test-utils = ["rand", "paste", "serde"] -arbitrary = ["proptest", "reth-primitives/arbitrary"] [[bench]] name = "truncate" diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index d4e9b856bed5..ea28d132969e 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -40,6 +40,9 @@ metrics = { workspace = true, optional = true } # `test-utils` feature triehash = { version = "0.8", optional = true } +# `serde` feature +serde = { workspace = true, optional = true } + [dev-dependencies] # reth reth-chainspec.workspace = true @@ -67,6 +70,7 @@ criterion.workspace = true [features] metrics = ["reth-metrics", "dep:metrics"] +serde = ["dep:serde"] test-utils = ["triehash", "reth-trie-common/test-utils"] [[bench]] diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 457266eeabab..39628e6d5272 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -13,6 +13,7 @@ use std::collections::{hash_map::IntoIter, HashMap, HashSet}; /// The key of a trie node. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TrieKey { /// A node in the account trie. AccountNode(StoredNibbles), @@ -24,6 +25,7 @@ pub enum TrieKey { /// The operation to perform on the trie. #[derive(PartialEq, Eq, Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TrieOp { /// Delete the node entry. Delete, @@ -40,6 +42,7 @@ impl TrieOp { /// The aggregation of trie updates. #[derive(Debug, Default, Clone, PartialEq, Eq, Deref)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TrieUpdates { trie_operations: HashMap, } From abbc0e633822a3d431cb13a8351868c26954d501 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 20 Jun 2024 11:53:51 +0100 Subject: [PATCH 146/405] feat(stages): clarify Merkle error (#8981) --- crates/stages/stages/src/stages/merkle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 885fc86c579b..5a3d31a40436 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -23,13 +23,13 @@ use tracing::*; /// they should include in a bug report, since true state root errors can be impossible to debug /// with just basic logs. pub const INVALID_STATE_ROOT_ERROR_MESSAGE: &str = r#" -Invalid state root error on new payload! +Invalid state root error on stage verification! This is an error that likely requires a report to the reth team with additional information. Please include the following information in your report: * This error message * The state root of the block that was rejected * The output of `reth db stats --checksum` from the database that was being used. This will take a long time to run! - * 50-100 lines of logs before and after the first occurrence of this log message. Please search your log output for the first observed occurrence of MAGIC_STATE_ROOT. + * 50-100 lines of logs before and after the first occurrence of the log message with the state root of the block that was rejected. * The debug logs from __the same time period__. To find the default location for these logs, run: `reth --help | grep -A 4 'log.file.directory'` From 93b82469b01ee5b6116106e0cf213d1ae2ac6732 Mon Sep 17 00:00:00 2001 From: Omid Chenane <155813094+ochenane@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:30:53 +0330 Subject: [PATCH 147/405] Make ReceiptFileClient generic (#8955) --- bin/reth/src/commands/import_receipts_op.rs | 13 +- .../downloaders/src/file_codec_ovm_receipt.rs | 2 +- .../downloaders/src/receipt_file_client.rs | 120 ++++++++++++++---- 3 files changed, 105 insertions(+), 30 deletions(-) diff --git a/bin/reth/src/commands/import_receipts_op.rs b/bin/reth/src/commands/import_receipts_op.rs index 62cff7017744..d77332f86be2 100644 --- a/bin/reth/src/commands/import_receipts_op.rs +++ b/bin/reth/src/commands/import_receipts_op.rs @@ -7,6 +7,7 @@ use reth_db::tables; use reth_db_api::{database::Database, transaction::DbTx}; use reth_downloaders::{ file_client::{ChunkedFileReader, DEFAULT_BYTE_LEN_CHUNK_CHAIN_FILE}, + file_codec_ovm_receipt::HackReceiptFileCodec, receipt_file_client::ReceiptFileClient, }; use reth_execution_types::ExecutionOutcome; @@ -115,10 +116,16 @@ where // open file let mut reader = ChunkedFileReader::new(path, chunk_len).await?; - while let Some(file_client) = reader.next_chunk::().await? { + while let Some(file_client) = + reader.next_chunk::>().await? + { // create a new file client from chunk read from file - let ReceiptFileClient { mut receipts, first_block, total_receipts: total_receipts_chunk } = - file_client; + let ReceiptFileClient { + mut receipts, + first_block, + total_receipts: total_receipts_chunk, + .. + } = file_client; // mark these as decoded total_decoded_receipts += total_receipts_chunk; diff --git a/crates/net/downloaders/src/file_codec_ovm_receipt.rs b/crates/net/downloaders/src/file_codec_ovm_receipt.rs index 5b3c81a9233a..74911c7e3f1e 100644 --- a/crates/net/downloaders/src/file_codec_ovm_receipt.rs +++ b/crates/net/downloaders/src/file_codec_ovm_receipt.rs @@ -22,7 +22,7 @@ use crate::{file_client::FileClientError, receipt_file_client::ReceiptWithBlockN /// /// It's recommended to use [`with_capacity`](tokio_util::codec::FramedRead::with_capacity) to set /// the capacity of the framed reader to the size of the file. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct HackReceiptFileCodec; impl Decoder for HackReceiptFileCodec { diff --git a/crates/net/downloaders/src/receipt_file_client.rs b/crates/net/downloaders/src/receipt_file_client.rs index 7b05543894ec..9dd23489d314 100644 --- a/crates/net/downloaders/src/receipt_file_client.rs +++ b/crates/net/downloaders/src/receipt_file_client.rs @@ -1,34 +1,83 @@ +use std::marker::PhantomData; + use futures::Future; use reth_primitives::{Receipt, Receipts}; use tokio::io::AsyncReadExt; use tokio_stream::StreamExt; -use tokio_util::codec::FramedRead; +use tokio_util::codec::{Decoder, FramedRead}; use tracing::trace; -use crate::{ - file_client::{FileClientError, FromReader}, - file_codec_ovm_receipt::HackReceiptFileCodec, -}; +use crate::file_client::{FileClientError, FromReader}; /// File client for reading RLP encoded receipts from file. Receipts in file must be in sequential /// order w.r.t. block number. #[derive(Debug)] -pub struct ReceiptFileClient { +pub struct ReceiptFileClient { /// The buffered receipts, read from file, as nested lists. One list per block number. pub receipts: Receipts, /// First (lowest) block number read from file. pub first_block: u64, /// Total number of receipts. Count of elements in [`Receipts`] flattened. pub total_receipts: usize, + /// marker + _marker: PhantomData, +} + +/// Constructs a file client from a reader and decoder. +pub trait FromReceiptReader { + /// Error returned by file client type. + type Error: From; + + /// Returns a decoder instance + fn decoder() -> D; + + /// Returns a file client + fn from_receipt_reader( + reader: B, + decoder: D, + num_bytes: u64, + ) -> impl Future), Self::Error>> + where + Self: Sized, + B: AsyncReadExt + Unpin; } -impl FromReader for ReceiptFileClient { - type Error = FileClientError; +impl FromReader for ReceiptFileClient +where + D: Decoder, Error = FileClientError> + + std::fmt::Debug + + Default, +{ + type Error = D::Error; + + fn from_reader( + reader: B, + num_bytes: u64, + ) -> impl Future), Self::Error>> + where + B: AsyncReadExt + Unpin, + { + Self::from_receipt_reader(reader, Self::decoder(), num_bytes) + } +} + +impl FromReceiptReader for ReceiptFileClient +where + D: Decoder, Error = FileClientError> + + std::fmt::Debug + + Default, +{ + type Error = D::Error; + + fn decoder() -> D { + Default::default() + } /// Initialize the [`ReceiptFileClient`] from bytes that have been read from file. Caution! If /// first block has no transactions, it's assumed to be the genesis block. - fn from_reader( + fn from_receipt_reader( reader: B, + decoder: D, num_bytes: u64, ) -> impl Future), Self::Error>> where @@ -37,13 +86,12 @@ impl FromReader for ReceiptFileClient { let mut receipts = Receipts::default(); // use with_capacity to make sure the internal buffer contains the entire chunk - let mut stream = - FramedRead::with_capacity(reader, HackReceiptFileCodec, num_bytes as usize); + let mut stream = FramedRead::with_capacity(reader, decoder, num_bytes as usize); trace!(target: "downloaders::file", target_num_bytes=num_bytes, capacity=stream.read_buffer().capacity(), - codec=?HackReceiptFileCodec, + codec=?Self::decoder(), "init decode stream" ); @@ -149,7 +197,12 @@ impl FromReader for ReceiptFileClient { ); Ok(( - Self { receipts, first_block: first_block.unwrap_or_default(), total_receipts }, + Self { + receipts, + first_block: first_block.unwrap_or_default(), + total_receipts, + _marker: Default::default(), + }, remaining_bytes, )) } @@ -170,13 +223,16 @@ mod test { use reth_primitives::hex; use reth_tracing::init_test_tracing; - use crate::file_codec_ovm_receipt::test::{ - receipt_block_1 as op_mainnet_receipt_block_1, - receipt_block_2 as op_mainnet_receipt_block_2, - receipt_block_3 as op_mainnet_receipt_block_3, - HACK_RECEIPT_ENCODED_BLOCK_1 as HACK_RECEIPT_ENCODED_BLOCK_1_OP_MAINNET, - HACK_RECEIPT_ENCODED_BLOCK_2 as HACK_RECEIPT_ENCODED_BLOCK_2_OP_MAINNET, - HACK_RECEIPT_ENCODED_BLOCK_3 as HACK_RECEIPT_ENCODED_BLOCK_3_OP_MAINNET, + use crate::file_codec_ovm_receipt::{ + test::{ + receipt_block_1 as op_mainnet_receipt_block_1, + receipt_block_2 as op_mainnet_receipt_block_2, + receipt_block_3 as op_mainnet_receipt_block_3, + HACK_RECEIPT_ENCODED_BLOCK_1 as HACK_RECEIPT_ENCODED_BLOCK_1_OP_MAINNET, + HACK_RECEIPT_ENCODED_BLOCK_2 as HACK_RECEIPT_ENCODED_BLOCK_2_OP_MAINNET, + HACK_RECEIPT_ENCODED_BLOCK_3 as HACK_RECEIPT_ENCODED_BLOCK_3_OP_MAINNET, + }, + HackReceiptFileCodec, }; use super::*; @@ -199,8 +255,12 @@ mod test { let encoded_byte_len = encoded_receipts.len() as u64; let reader = &mut &encoded_receipts[..]; - let (ReceiptFileClient { receipts, first_block, total_receipts }, _remaining_bytes) = - ReceiptFileClient::from_reader(reader, encoded_byte_len).await.unwrap(); + let ( + ReceiptFileClient { receipts, first_block, total_receipts, _marker }, + _remaining_bytes, + ) = ReceiptFileClient::::from_reader(reader, encoded_byte_len) + .await + .unwrap(); // 2 non-empty receipt objects assert_eq!(2, total_receipts); @@ -227,8 +287,12 @@ mod test { let encoded_byte_len = encoded_receipts.len() as u64; let reader = &mut &encoded_receipts[..]; - let (ReceiptFileClient { receipts, first_block, total_receipts }, _remaining_bytes) = - ReceiptFileClient::from_reader(reader, encoded_byte_len).await.unwrap(); + let ( + ReceiptFileClient { receipts, first_block, total_receipts, _marker }, + _remaining_bytes, + ) = ReceiptFileClient::::from_reader(reader, encoded_byte_len) + .await + .unwrap(); // 2 non-empty receipt objects assert_eq!(2, total_receipts); @@ -256,8 +320,12 @@ mod test { let encoded_byte_len = encoded_receipts.len() as u64; let reader = &mut &encoded_receipts[..]; - let (ReceiptFileClient { receipts, first_block, total_receipts }, _remaining_bytes) = - ReceiptFileClient::from_reader(reader, encoded_byte_len).await.unwrap(); + let ( + ReceiptFileClient { receipts, first_block, total_receipts, _marker }, + _remaining_bytes, + ) = ReceiptFileClient::::from_reader(reader, encoded_byte_len) + .await + .unwrap(); // 4 non-empty receipt objects assert_eq!(4, total_receipts); From e90b526a6668fae51179541221666894b3ade14b Mon Sep 17 00:00:00 2001 From: Marquis Shanahan <29431502+9547@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:35:13 +0800 Subject: [PATCH 148/405] Update docs db (#8979) Signed-off-by: 9547 <29431502+9547@users.noreply.github.com> --- docs/crates/db.md | 243 ++++++++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 115 deletions(-) diff --git a/docs/crates/db.md b/docs/crates/db.md index b08383b7a6d9..2c2977b0674a 100644 --- a/docs/crates/db.md +++ b/docs/crates/db.md @@ -8,7 +8,7 @@ The database is a central component to Reth, enabling persistent storage for dat Within Reth, the database is organized via "tables". A table is any struct that implements the `Table` trait. -[File: crates/storage/db/src/abstraction/table.rs](https://github.com/paradigmxyz/reth/blob/1563506aea09049a85e5cc72c2894f3f7a371581/crates/storage/db/src/abstraction/table.rs#L55-L82) +[File: crates/storage/db-api/src/table.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/table.rs#L64-L93) ```rust ignore pub trait Table: Send + Sync + Debug + 'static { @@ -32,7 +32,7 @@ pub trait Value: Compress + Decompress + Serialize {} The `Table` trait has two generic values, `Key` and `Value`, which need to implement the `Key` and `Value` traits, respectively. The `Encode` trait is responsible for transforming data into bytes so it can be stored in the database, while the `Decode` trait transforms the bytes back into its original form. Similarly, the `Compress` and `Decompress` traits transform the data to and from a compressed format when storing or reading data from the database. -There are many tables within the node, all used to store different types of data from `Headers` to `Transactions` and more. Below is a list of all of the tables. You can follow [this link](https://github.com/paradigmxyz/reth/blob/1563506aea09049a85e5cc72c2894f3f7a371581/crates/storage/db/src/tables/mod.rs#L161-L188) if you would like to see the table definitions for any of the tables below. +There are many tables within the node, all used to store different types of data from `Headers` to `Transactions` and more. Below is a list of all of the tables. You can follow [this link](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db/src/tables/mod.rs#L274-L414) if you would like to see the table definitions for any of the tables below. - CanonicalHeaders - HeaderTerminalDifficulties @@ -41,18 +41,18 @@ There are many tables within the node, all used to store different types of data - BlockBodyIndices - BlockOmmers - BlockWithdrawals -- TransactionBlocks - Transactions - TransactionHashNumbers +- TransactionBlocks - Receipts +- Bytecodes - PlainAccountState - PlainStorageState -- Bytecodes - AccountsHistory - StoragesHistory - AccountChangeSets - StorageChangeSets -- HashedAccount +- HashedAccounts - HashedStorages - AccountsTrie - StoragesTrie @@ -60,28 +60,41 @@ There are many tables within the node, all used to store different types of data - StageCheckpoints - StageCheckpointProgresses - PruneCheckpoints +- VersionHistory +- BlockRequests +- ChainState
## Database -Reth's database design revolves around it's main [Database trait](https://github.com/paradigmxyz/reth/blob/eaca2a4a7fbbdc2f5cd15eab9a8a18ede1891bda/crates/storage/db/src/abstraction/database.rs#L21), which implements the database's functionality across many types. Let's take a quick look at the `Database` trait and how it works. +Reth's database design revolves around it's main [Database trait](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/database.rs#L8-L52), which implements the database's functionality across many types. Let's take a quick look at the `Database` trait and how it works. -[File: crates/storage/db/src/abstraction/database.rs](https://github.com/paradigmxyz/reth/blob/eaca2a4a7fbbdc2f5cd15eab9a8a18ede1891bda/crates/storage/db/src/abstraction/database.rs#L21) +[File: crates/storage/db-api/src/database.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/database.rs#L8-L52) ```rust ignore -/// Main Database trait that spawns transactions to be executed. -pub trait Database { - /// RO database transaction - type TX: DbTx + Send + Sync + Debug; - /// RW database transaction - type TXMut: DbTxMut + DbTx + TableImporter + Send + Sync + Debug; +/// Main Database trait that can open read-only and read-write transactions. +/// +/// Sealed trait which cannot be implemented by 3rd parties, exposed only for consumption. +pub trait Database: Send + Sync { + /// Read-Only database transaction + type TX: DbTx + Send + Sync + Debug + 'static; + /// Read-Write database transaction + type TXMut: DbTxMut + DbTx + TableImporter + Send + Sync + Debug + 'static; + + /// Create read only transaction. + #[track_caller] + fn tx(&self) -> Result; + + /// Create read write transaction only possible if database is open with write access. + #[track_caller] + fn tx_mut(&self) -> Result; /// Takes a function and passes a read-only transaction into it, making sure it's closed in the /// end of the execution. - fn view(&self, f: F) -> Result + fn view(&self, f: F) -> Result where - F: Fn(&::TX) -> T, + F: FnOnce(&Self::TX) -> T, { let tx = self.tx()?; @@ -93,9 +106,9 @@ pub trait Database { /// Takes a function and passes a write-read transaction into it, making sure it's committed in /// the end of the execution. - fn update(&self, f: F) -> Result + fn update(&self, f: F) -> Result where - F: Fn(&::TXMut) -> T, + F: FnOnce(&Self::TXMut) -> T, { let tx = self.tx_mut()?; @@ -135,183 +148,183 @@ where The `Database` defines two associated types `TX` and `TXMut`. -[File: crates/storage/db/src/abstraction/database.rs](https://github.com/paradigmxyz/reth/blob/main/crates/storage/db/src/abstraction/database.rs#L11) +[File: crates/storage/db-api/src/database.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/database.rs#L54-L78) The `TX` type can be any type that implements the `DbTx` trait, which provides a set of functions to interact with read only transactions. -[File: crates/storage/db/src/abstraction/transaction.rs](https://github.com/paradigmxyz/reth/blob/main/crates/storage/db/src/abstraction/transaction.rs#L36) +[File: crates/storage/db-api/src/transaction.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/transaction.rs#L7-L29) ```rust ignore /// Read only transaction pub trait DbTx: Send + Sync { /// Cursor type for this read-only transaction type Cursor: DbCursorRO + Send + Sync; - /// DupCursor type for this read-only transaction + /// `DupCursor` type for this read-only transaction type DupCursor: DbDupCursorRO + DbCursorRO + Send + Sync; /// Get value - fn get(&self, key: T::Key) -> Result, Error>; + fn get(&self, key: T::Key) -> Result, DatabaseError>; /// Commit for read only transaction will consume and free transaction and allows /// freeing of memory pages - fn commit(self) -> Result; + fn commit(self) -> Result; + /// Aborts transaction + fn abort(self); /// Iterate over read only values in table. - fn cursor(&self) -> Result, Error>; + fn cursor_read(&self) -> Result, DatabaseError>; /// Iterate over read only values in dup sorted table. - fn cursor_dup(&self) -> Result, Error>; + fn cursor_dup_read(&self) -> Result, DatabaseError>; + /// Returns number of entries in the table. + fn entries(&self) -> Result; + /// Disables long-lived read transaction safety guarantees. + fn disable_long_read_transaction_safety(&mut self); } ``` The `TXMut` type can be any type that implements the `DbTxMut` trait, which provides a set of functions to interact with read/write transactions and the associated cursor types. -[File: crates/storage/db/src/abstraction/transaction.rs](https://github.com/paradigmxyz/reth/blob/main/crates/storage/db/src/abstraction/transaction.rs#L49) +[File: crates/storage/db-api/src/transaction.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/transaction.rs#L31-L54) ```rust ignore /// Read write transaction that allows writing to database pub trait DbTxMut: Send + Sync { /// Read-Write Cursor type type CursorMut: DbCursorRW + DbCursorRO + Send + Sync; - /// Read-Write DupCursor type + /// Read-Write `DupCursor` type type DupCursorMut: DbDupCursorRW + DbCursorRW + DbDupCursorRO + DbCursorRO + Send + Sync; + /// Put value to database - fn put(&self, key: T::Key, value: T::Value) -> Result<(), Error>; + fn put(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError>; /// Delete value from database - fn delete(&self, key: T::Key, value: Option) -> Result; + fn delete(&self, key: T::Key, value: Option) + -> Result; /// Clears database. - fn clear(&self) -> Result<(), Error>; - /// Cursor for writing - fn cursor_write(&self) -> Result, Error>; - /// DupCursor for writing - fn cursor_dup_write( - &self, - ) -> Result, Error>; + fn clear(&self) -> Result<(), DatabaseError>; + /// Cursor mut + fn cursor_write(&self) -> Result, DatabaseError>; + /// `DupCursor` mut. + fn cursor_dup_write(&self) -> Result, DatabaseError>; } ``` -Lets take a look at the `DbTx` and `DbTxMut` traits in action. Revisiting the `Transaction` struct as an example, the `Transaction::get_block_hash()` method uses the `DbTx::get()` function to get a block header hash in the form of `self.get::(number)`. +Let's take a look at the `DbTx` and `DbTxMut` traits in action. -[File: crates/storage/provider/src/transaction.rs](https://github.com/paradigmxyz/reth/blob/main/crates/storage/provider/src/transaction.rs#L106) +Revisiting the `DatabaseProvider` struct as an exampl, the `DatabaseProvider::header_by_number()` function uses the `DbTx::get()` function to get a header from the `Headers` table. -```rust ignore +[File: crates/storage/provider/src/providers/database/provider.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/provider/src/providers/database/provider.rs#L1319-L1336) -impl<'this, DB> Transaction<'this, DB> -where - DB: Database, -{ +```rust ignore +impl HeaderProvider for DatabaseProvider { //--snip-- - /// Query [tables::CanonicalHeaders] table for block hash by block number - pub(crate) fn get_block_hash(&self, number: BlockNumber) -> Result { - let hash = self - .get::(number)? - .ok_or(ProviderError::CanonicalHash { number })?; - Ok(hash) + fn header_by_number(&self, num: BlockNumber) -> ProviderResult> { + self.static_file_provider.get_with_static_file_or_database( + StaticFileSegment::Headers, + num, + |static_file| static_file.header_by_number(num), + || Ok(self.tx.get::(num)?), + ) } - //--snip-- -} -//--snip-- -impl<'a, DB: Database> Deref for Transaction<'a, DB> { - type Target = ::TXMut; - fn deref(&self) -> &Self::Target { - self.tx.as_ref().expect("Tried getting a reference to a non-existent transaction") - } + //--snip-- } ``` -The `Transaction` struct implements the `Deref` trait, which returns a reference to its `tx` field, which is a `TxMut`. Recall that `TxMut` is a generic type on the `Database` trait, which is defined as `type TXMut: DbTxMut + DbTx + Send + Sync;`, giving it access to all of the functions available to `DbTx`, including the `DbTx::get()` function. - Notice that the function uses a [turbofish](https://techblog.tonsser.com/posts/what-is-rusts-turbofish) to define which table to use when passing in the `key` to the `DbTx::get()` function. Taking a quick look at the function definition, a generic `T` is defined that implements the `Table` trait mentioned at the beginning of this chapter. -[File: crates/storage/db/src/abstraction/transaction.rs](https://github.com/paradigmxyz/reth/blob/main/crates/storage/db/src/abstraction/transaction.rs#L38) +[File: crates/storage/db-api/src/transaction.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/transaction.rs#L15) ```rust ignore -fn get(&self, key: T::Key) -> Result, Error>; +fn get(&self, key: T::Key) -> Result, DatabaseError>; ``` This design pattern is very powerful and allows Reth to use the methods available to the `DbTx` and `DbTxMut` traits without having to define implementation blocks for each table within the database. -Lets take a look at a couple examples before moving on. In the snippet below, the `DbTxMut::put()` method is used to insert values into the `CanonicalHeaders`, `Headers` and `HeaderNumbers` tables. +Let's take a look at a couple examples before moving on. In the snippet below, the `DbTxMut::put()` method is used to insert values into the `CanonicalHeaders`, `Headers` and `HeaderNumbers` tables. -[File: crates/storage/provider/src/block.rs](https://github.com/paradigmxyz/reth/blob/main/crates/storage/provider/src/block.rs#L121-L125) +[File: crates/storage/provider/src/providers/database/provider.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/provider/src/providers/database/provider.rs#L2606-L2745) ```rust ignore - tx.put::(block.number, block.hash())?; - // Put header with canonical hashes. - tx.put::(block.number, block.header.as_ref().clone())?; - tx.put::(block.hash(), block.number)?; +self.tx.put::(block_number, block.hash())?; +self.tx.put::(block_number, block.header.as_ref().clone())?; +self.tx.put::(block.hash(), block_number)?; ``` +Let's take a look at the `DatabaseProviderRW` struct, which is used to create a mutable transaction to interact with the database. +The `DatabaseProviderRW` struct implements the `Deref` and `DerefMut` trait, which returns a reference to its first field, which is a `TxMut`. Recall that `TxMut` is a generic type on the `Database` trait, which is defined as `type TXMut: DbTxMut + DbTx + Send + Sync;`, giving it access to all of the functions available to `DbTx`, including the `DbTx::get()` function. + This next example uses the `DbTx::cursor()` method to get a `Cursor`. The `Cursor` type provides a way to traverse through rows in a database table, one row at a time. A cursor enables the program to perform an operation (updating, deleting, etc) on each row in the table individually. The following code snippet gets a cursor for a few different tables in the database. -[File: crates/stages/src/stages/execution.rs](https://github.com/paradigmxyz/reth/blob/main/crates/stages/src/stages/execution.rs#L93-L101) +[File: crates/static-file/static-file/src/segments/headers.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/static-file/static-file/src/segments/headers.rs#L22-L58) ```rust ignore -// Get next canonical block hashes to execute. - let mut canonicals = db_tx.cursor_read::()?; - // Get header with canonical hashes. - let mut headers = db_tx.cursor_read::()?; - // Get bodies (to get tx index) with canonical hashes. - let mut cumulative_tx_count = db_tx.cursor_read::()?; - // Get transaction of the block that we are executing. - let mut tx = db_tx.cursor_read::()?; - // Skip sender recovery and load signer from database. - let mut tx_sender = db_tx.cursor_read::()?; - +# Get a cursor for the Headers table +let mut headers_cursor = provider.tx_ref().cursor_read::()?; +# Then we can walk the cursor to get the headers for a specific block range +let headers_walker = headers_cursor.walk_range(block_range.clone())?; ``` Lets look at an examples of how cursors are used. The code snippet below contains the `unwind` method from the `BodyStage` defined in the `stages` crate. This function is responsible for unwinding any changes to the database if there is an error when executing the body stage within the Reth pipeline. -[File: crates/stages/src/stages/bodies.rs](https://github.com/paradigmxyz/reth/blob/main/crates/stages/src/stages/bodies.rs#L205-L238) +[File: crates/stages/stages/src/stages/bodies.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/stages/stages/src/stages/bodies.rs#L267-L345) ```rust ignore - /// Unwind the stage. - async fn unwind( - &mut self, - db: &mut Transaction<'_, DB>, - input: UnwindInput, - ) -> Result> { - let mut tx_count_cursor = db.cursor_write::()?; - let mut block_ommers_cursor = db.cursor_write::()?; - let mut transaction_cursor = db.cursor_write::()?; - - let mut entry = tx_count_cursor.last()?; - while let Some((key, count)) = entry { - if key.number() <= input.unwind_to { - break - } - - tx_count_cursor.delete_current()?; - entry = tx_count_cursor.prev()?; - - if block_ommers_cursor.seek_exact(key)?.is_some() { - block_ommers_cursor.delete_current()?; - } - - let prev_count = entry.map(|(_, v)| v).unwrap_or_default(); - for tx_id in prev_count..count { - if transaction_cursor.seek_exact(tx_id)?.is_some() { - transaction_cursor.delete_current()?; - } - } +/// Unwind the stage. +fn unwind(&mut self, provider: &DatabaseProviderRW, input: UnwindInput) { + self.buffer.take(); + + let static_file_provider = provider.static_file_provider(); + let tx = provider.tx_ref(); + // Cursors to unwind bodies, ommers + 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::()?; + + let mut rev_walker = body_cursor.walk_back(None)?; + while let Some((number, block_meta)) = rev_walker.next().transpose()? { + if number <= input.unwind_to { + break } - //--snip-- - } + // Delete the ommers entry if any + if ommers_cursor.seek_exact(number)?.is_some() { + ommers_cursor.delete_current()?; + } -``` + // Delete the withdrawals entry if any + if withdrawals_cursor.seek_exact(number)?.is_some() { + withdrawals_cursor.delete_current()?; + } -This function first grabs a mutable cursor for the `CumulativeTxCount`, `BlockOmmers` and `Transactions` tables. + // 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() + { + tx_block_cursor.delete_current()?; + } -The `tx_count_cursor` is used to get the last key value pair written to the `CumulativeTxCount` table and delete key value pair where the cursor is currently pointing. + // Delete the current body value + rev_walker.delete_current()?; + } + //--snip-- +} +``` -The `block_ommers_cursor` is used to get the block ommers from the `BlockOmmers` table at the specified key, and delete the entry where the cursor is currently pointing. +This function first grabs a mutable cursor for the `BlockBodyIndices`, `BlockOmmers`, `BlockWithdrawals`, `BlockRequests`, `TransactionBlocks` tables. -Finally, the `transaction_cursor` is used to get delete each transaction from the last `TXNumber` written to the database, to the current tx count. +Then it gets a walker of the block body cursor, and then walk backwards through the cursor to delete the block body entries from the last block number to the block number specified in the `UnwindInput` struct. While this is a brief look at how cursors work in the context of database tables, the chapter on the `libmdbx` crate will go into further detail on how cursors communicate with the database and what is actually happening under the hood. From b3cb1fde754811366924a9a63c7667a2bd6ef3c2 Mon Sep 17 00:00:00 2001 From: Marquis Shanahan <29431502+9547@users.noreply.github.com> Date: Thu, 20 Jun 2024 22:49:26 +0800 Subject: [PATCH 149/405] docs(book): add sigma_prime_audit_v1.pdf (#8942) Signed-off-by: 9547 <29431502+9547@users.noreply.github.com> Co-authored-by: Oliver --- book/intro.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/book/intro.md b/book/intro.md index 1a334fbb170e..9b57849eb53e 100644 --- a/book/intro.md +++ b/book/intro.md @@ -1,9 +1,9 @@ # Reth Book -_Documentation for Reth users and developers._ +_Documentation for Reth users and developers._ [![Telegram Chat][tg-badge]][tg-url] -Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is an **Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient.** +Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is an **Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient.** Reth is production ready, and suitable for usage in mission-critical environments such as staking or high-uptime servi ces. We also actively recommend professional node operators to switch to Reth in production for performance and cost reasons in use cases where high performance with great margins is required such as RPC, MEV, Indexing, Simulations, and P2P activities. @@ -56,12 +56,12 @@ We want to solve for node operators that care about fast historical queries, but We also want to support teams and individuals who want both sync from genesis and via "fast sync". -We envision that Reth will be configurable enough for the tradeoffs that each team faces. +We envision that Reth will be configurable enough for the tradeoffs that each team faces. ## Who is this for? Reth is a new Ethereum full node that allows users to sync and interact with the entire blockchain, including its historical state if in archive mode. -- Full node: It can be used as a full node, which stores and processes the entire blockchain, validates blocks and transactions, and participates in the consensus process. +- Full node: It can be used as a full node, which stores and processes the entire blockchain, validates blocks and transactions, and participates in the consensus process. - Archive node: It can also be used as an archive node, which stores the entire history of the blockchain and is useful for applications that need access to historical data. As a data engineer/analyst, or as a data indexer, you'll want to use Archive mode. For all other use cases where historical access is not needed, you can use Full mode. @@ -76,7 +76,10 @@ Reth implements the specification of Ethereum as defined in the [ethereum/execut 1. We operate multiple nodes at the tip of Ethereum mainnet and various testnets. 1. We extensively unit test, fuzz test and document all our code, while also restricting PRs with aggressive lint rules. -We intend to also audit / fuzz the EVM & parts of the codebase. Please reach out if you're interested in collaborating on securing this codebase. +We have completed an audit of the [Reth v1.0.0-rc.2](https://github.com/paradigmxyz/reth/releases/tag/v1.0.0-rc.2) with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/audit/sigma_prime_audit_v1.pdf). + +[Revm](https://github.com/bluealloy/revm) (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. + ## Sections From f571183d08c056dffd202415d78d9dbb10a5e598 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:06:20 +0200 Subject: [PATCH 150/405] perf: memoize address pruning filter as a hashset (#8989) --- crates/revm/src/batch.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/revm/src/batch.rs b/crates/revm/src/batch.rs index a9a44bff1bce..f2903a4f47cf 100644 --- a/crates/revm/src/batch.rs +++ b/crates/revm/src/batch.rs @@ -6,6 +6,7 @@ use reth_execution_errors::BlockExecutionError; use reth_primitives::{Receipt, Receipts, Request, Requests}; use reth_prune_types::{PruneMode, PruneModes, PruneSegmentError, MINIMUM_PRUNING_DISTANCE}; use revm::db::states::bundle_state::BundleRetention; +use std::collections::HashSet; use tracing::debug; #[cfg(not(feature = "std"))] @@ -33,9 +34,10 @@ pub struct BlockBatchRecord { /// 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. - pruning_address_filter: Option<(u64, Vec
)>, + pruning_address_filter: Option<(u64, HashSet
)>, /// First block will be initialized to `None` /// and be set to the block number of first block executed. first_block: Option, @@ -127,10 +129,7 @@ impl BlockBatchRecord { &mut self, receipts: &mut Vec>, ) -> Result<(), PruneSegmentError> { - let (first_block, tip) = match self.first_block.zip(self.tip) { - Some((block, tip)) => (block, tip), - _ => return Ok(()), - }; + let (Some(first_block), Some(tip)) = (self.first_block, self.tip) else { return Ok(()) }; let block_number = first_block + self.receipts.len() as u64; @@ -154,18 +153,18 @@ impl BlockBatchRecord { let contract_log_pruner = self.prune_modes.receipts_log_filter.group_by_block(tip, None)?; if !contract_log_pruner.is_empty() { - let (prev_block, filter) = self.pruning_address_filter.get_or_insert((0, Vec::new())); + let (prev_block, filter) = + self.pruning_address_filter.get_or_insert_with(|| (0, HashSet::new())); for (_, addresses) in contract_log_pruner.range(*prev_block..=block_number) { filter.extend(addresses.iter().copied()); } } - for receipt in receipts.iter_mut() { - let inner_receipt = receipt.as_ref().expect("receipts have not been pruned"); - - // If there is an address_filter, and it does not contain any of the - // contract addresses, then remove this receipts - if let Some((_, filter)) = &self.pruning_address_filter { + if let Some((_, filter)) = &self.pruning_address_filter { + for receipt in receipts.iter_mut() { + // If there is an address_filter, it does not contain any of the + // contract addresses, then remove this receipt. + let inner_receipt = receipt.as_ref().expect("receipts have not been pruned"); if !inner_receipt.logs.iter().any(|log| filter.contains(&log.address)) { receipt.take(); } From 55831500cf8a1a0ff36646f89cec0a1fa8a8cb73 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:37:35 +0200 Subject: [PATCH 151/405] chore(deps): bump interprocess and others (#8982) --- Cargo.lock | 198 ++++++---------------------- Cargo.toml | 5 +- crates/rpc/ipc/Cargo.toml | 2 +- crates/rpc/ipc/src/client/mod.rs | 55 ++++---- crates/rpc/ipc/src/server/mod.rs | 125 +++++++++--------- crates/rpc/rpc-builder/src/auth.rs | 2 +- crates/storage/db-api/Cargo.toml | 2 +- crates/storage/db/Cargo.toml | 2 +- crates/storage/db/src/metrics.rs | 14 +- crates/storage/nippy-jar/Cargo.toml | 2 +- crates/trie/common/Cargo.toml | 6 +- 11 files changed, 150 insertions(+), 263 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8642eb7f132..91ba40344caa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -653,7 +653,7 @@ dependencies = [ "alloy-transport", "bytes", "futures", - "interprocess 2.2.0", + "interprocess", "pin-project", "serde_json", "tokio", @@ -953,22 +953,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener 2.5.3", + "event-listener", "futures-core", ] -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - [[package]] name = "async-compression" version = "0.4.11" @@ -991,8 +979,8 @@ version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e6fa871e4334a622afd6bb2f611635e8083a6f5e2936c0f90f37c7ef9856298" dependencies = [ - "async-channel 1.9.0", - "futures-lite 1.13.0", + "async-channel", + "futures-lite", "http-types", "log", "memchr", @@ -1021,12 +1009,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" version = "0.1.80" @@ -1192,7 +1174,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.66", "which", @@ -1284,19 +1266,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel 2.3.1", - "async-task", - "futures-io", - "futures-lite 2.3.0", - "piper", -] - [[package]] name = "blst" version = "0.3.12" @@ -1320,7 +1289,7 @@ dependencies = [ "boa_macros", "indexmap 2.2.6", "num-bigint", - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -1356,7 +1325,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "regress", - "rustc-hash", + "rustc-hash 1.1.0", "ryu-js", "serde", "serde_json", @@ -1392,7 +1361,7 @@ dependencies = [ "indexmap 2.2.6", "once_cell", "phf", - "rustc-hash", + "rustc-hash 1.1.0", "static_assertions", ] @@ -1424,7 +1393,7 @@ dependencies = [ "num-bigint", "num-traits", "regress", - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -2703,27 +2672,6 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener 5.3.1", - "pin-project-lite", -] - [[package]] name = "example-beacon-api-sidecar-fetcher" version = "0.1.0" @@ -3298,16 +3246,6 @@ dependencies = [ "waker-fn", ] -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.30" @@ -3715,9 +3653,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" dependencies = [ "anyhow", - "async-channel 1.9.0", + "async-channel", "base64 0.13.1", - "futures-lite 1.13.0", + "futures-lite", "infer", "pin-project-lite", "rand 0.7.3", @@ -3825,9 +3763,9 @@ dependencies = [ [[package]] name = "iai-callgrind" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99bf26f496b13ac6273014f40afda46a233fbfb0289ce50fb4daaad2f2ffc80" +checksum = "7b780c98c212412a6d54b5d3d7cf62fb20d88cd32c0653d6df2a03d63e52a903" dependencies = [ "bincode", "bindgen", @@ -3851,9 +3789,9 @@ dependencies = [ [[package]] name = "iai-callgrind-runner" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c23a951b9eccaa1e38556d27473d1462a9c247a27961812edcaac156af861282" +checksum = "fa8d015de54e6431004efede625ee79e3b4105dcb2100cd574de914e06fd4f7c" dependencies = [ "serde", ] @@ -4155,27 +4093,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "interprocess" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb" -dependencies = [ - "blocking", - "cfg-if", - "futures-core", - "futures-io", - "intmap", - "libc", - "once_cell", - "rustc_version 0.4.0", - "spinning", - "thiserror", - "to_method", - "tokio", - "winapi", -] - [[package]] name = "interprocess" version = "2.2.0" @@ -4191,12 +4108,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "intmap" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9" - [[package]] name = "intrusive-collections" version = "0.9.6" @@ -4384,7 +4295,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "rand 0.8.5", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "serde_json", "thiserror", @@ -4876,15 +4787,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" -dependencies = [ - "libc", -] - [[package]] name = "memmap2" version = "0.9.4" @@ -5623,17 +5525,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" -dependencies = [ - "atomic-waker", - "fastrand 2.1.0", - "futures-io", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -5951,7 +5842,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 1.1.0", "rustls", "thiserror", "tokio", @@ -5967,7 +5858,7 @@ dependencies = [ "bytes", "rand 0.8.5", "ring", - "rustc-hash", + "rustc-hash 1.1.0", "rustls", "slab", "thiserror", @@ -6305,7 +6196,7 @@ dependencies = [ "fdlimit", "futures", "human_bytes", - "itertools 0.12.1", + "itertools 0.13.0", "jsonrpsee", "libc", "metrics-process", @@ -6429,7 +6320,7 @@ dependencies = [ "alloy-genesis", "assert_matches", "futures", - "itertools 0.12.1", + "itertools 0.13.0", "metrics", "reth-blockchain-tree", "reth-blockchain-tree-api", @@ -6706,7 +6597,7 @@ dependencies = [ "reth-storage-errors", "reth-tracing", "reth-trie-common", - "rustc-hash", + "rustc-hash 2.0.0", "serde", "serde_json", "strum", @@ -6806,7 +6697,7 @@ dependencies = [ "discv5", "enr", "futures", - "itertools 0.12.1", + "itertools 0.13.0", "libp2p-identity", "metrics", "multiaddr", @@ -6858,7 +6749,7 @@ dependencies = [ "assert_matches", "futures", "futures-util", - "itertools 0.12.1", + "itertools 0.13.0", "metrics", "pin-project", "rand 0.8.5", @@ -7252,7 +7143,7 @@ dependencies = [ "bytes", "futures", "futures-util", - "interprocess 1.2.1", + "interprocess", "jsonrpsee", "pin-project", "rand 0.8.5", @@ -7355,7 +7246,7 @@ dependencies = [ "fnv", "futures", "humantime-serde", - "itertools 0.12.1", + "itertools 0.13.0", "metrics", "parking_lot 0.12.3", "pin-project", @@ -7452,7 +7343,7 @@ dependencies = [ "cuckoofilter", "derive_more", "lz4_flex", - "memmap2 0.7.1", + "memmap2", "ph", "rand 0.8.5", "reth-fs-util", @@ -7857,7 +7748,7 @@ dependencies = [ "assert_matches", "auto_impl", "dashmap", - "itertools 0.12.1", + "itertools 0.13.0", "metrics", "parking_lot 0.12.3", "pin-project", @@ -7896,7 +7787,7 @@ version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", "assert_matches", - "itertools 0.12.1", + "itertools 0.13.0", "metrics", "rayon", "reth-chainspec", @@ -8189,7 +8080,7 @@ dependencies = [ "assert_matches", "criterion", "futures-util", - "itertools 0.12.1", + "itertools 0.13.0", "num-traits", "paste", "pprof", @@ -8390,7 +8281,7 @@ dependencies = [ "bitflags 2.5.0", "criterion", "futures-util", - "itertools 0.12.1", + "itertools 0.13.0", "metrics", "parking_lot 0.12.3", "paste", @@ -8407,7 +8298,7 @@ dependencies = [ "reth-tasks", "reth-tracing", "revm", - "rustc-hash", + "rustc-hash 2.0.0", "schnellru", "serde", "serde_json", @@ -8465,7 +8356,7 @@ dependencies = [ "bytes", "derive_more", "hash-db", - "itertools 0.12.1", + "itertools 0.13.0", "nybbles", "plain_hasher", "proptest", @@ -8486,7 +8377,7 @@ dependencies = [ "alloy-rlp", "criterion", "derive_more", - "itertools 0.12.1", + "itertools 0.13.0", "metrics", "proptest", "rand 0.8.5", @@ -8732,6 +8623,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -9403,15 +9300,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "spinning" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" version = "0.7.3" @@ -9520,7 +9408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71297dc3e250f7dbdf8adb99e235da783d690f5819fdeb4cce39d9cfb0aca9f1" dependencies = [ "debugid", - "memmap2 0.9.4", + "memmap2", "stable_deref_trait", "uuid", ] @@ -9859,12 +9747,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "to_method" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" - [[package]] name = "tokio" version = "1.38.0" diff --git a/Cargo.toml b/Cargo.toml index 4cf1778781be..69a06061cc38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -409,11 +409,11 @@ serde_with = "3.3.0" humantime = "2.1" humantime-serde = "1.1" rand = "0.8.5" -rustc-hash = "1.1.0" +rustc-hash = "2.0" schnellru = "0.2" strum = "0.26" rayon = "1.7" -itertools = "0.12" +itertools = "0.13" parking_lot = "0.12" modular-bitfield = "0.11.2" once_cell = "1.17" @@ -494,6 +494,7 @@ proptest-derive = "0.4" serial_test = "3" similar-asserts = "1.5.0" test-fuzz = "5" +iai-callgrind = "0.11" [patch.crates-io] revm = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } diff --git a/crates/rpc/ipc/Cargo.toml b/crates/rpc/ipc/Cargo.toml index af6e64db19c3..42bb64d53c7a 100644 --- a/crates/rpc/ipc/Cargo.toml +++ b/crates/rpc/ipc/Cargo.toml @@ -29,7 +29,7 @@ tracing.workspace = true bytes.workspace = true thiserror.workspace = true futures-util = "0.3.30" -interprocess = { version = "1.2.1", features = ["tokio_support"] } +interprocess = { version = "2.2.0", features = ["tokio"] } [dev-dependencies] tokio-stream = { workspace = true, features = ["sync"] } diff --git a/crates/rpc/ipc/src/client/mod.rs b/crates/rpc/ipc/src/client/mod.rs index 05ea7ed589d5..e8eff9c8f454 100644 --- a/crates/rpc/ipc/src/client/mod.rs +++ b/crates/rpc/ipc/src/client/mod.rs @@ -1,23 +1,23 @@ //! [`jsonrpsee`] transport adapter implementation for IPC. use crate::stream_codec::StreamCodec; -use futures::StreamExt; -use interprocess::local_socket::tokio::{LocalSocketStream, OwnedReadHalf, OwnedWriteHalf}; +use futures::{StreamExt, TryFutureExt}; +use interprocess::local_socket::{ + tokio::{prelude::*, RecvHalf, SendHalf}, + GenericFilePath, +}; use jsonrpsee::{ async_client::{Client, ClientBuilder}, core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}, }; use std::io; use tokio::io::AsyncWriteExt; -use tokio_util::{ - codec::FramedRead, - compat::{Compat, FuturesAsyncReadCompatExt, FuturesAsyncWriteCompatExt}, -}; +use tokio_util::codec::FramedRead; /// Sending end of IPC transport. #[derive(Debug)] pub(crate) struct Sender { - inner: Compat, + inner: SendHalf, } #[async_trait::async_trait] @@ -44,7 +44,7 @@ impl TransportSenderT for Sender { /// Receiving end of IPC transport. #[derive(Debug)] pub(crate) struct Receiver { - pub(crate) inner: FramedRead, StreamCodec>, + pub(crate) inner: FramedRead, } #[async_trait::async_trait] @@ -63,20 +63,17 @@ impl TransportReceiverT for Receiver { pub(crate) struct IpcTransportClientBuilder; impl IpcTransportClientBuilder { - pub(crate) async fn build( - self, - endpoint: impl AsRef, - ) -> Result<(Sender, Receiver), IpcError> { - let endpoint = endpoint.as_ref().to_string(); - let conn = LocalSocketStream::connect(endpoint.clone()) + pub(crate) async fn build(self, path: &str) -> Result<(Sender, Receiver), IpcError> { + let conn = async { path.to_fs_name::() } + .and_then(LocalSocketStream::connect) .await - .map_err(|err| IpcError::FailedToConnect { path: endpoint, err })?; + .map_err(|err| IpcError::FailedToConnect { path: path.to_string(), err })?; - let (rhlf, whlf) = conn.into_split(); + let (recv, send) = conn.split(); Ok(( - Sender { inner: whlf.compat_write() }, - Receiver { inner: FramedRead::new(rhlf.compat(), StreamCodec::stream_incoming()) }, + Sender { inner: send }, + Receiver { inner: FramedRead::new(recv, StreamCodec::stream_incoming()) }, )) } } @@ -92,14 +89,14 @@ impl IpcClientBuilder { /// ``` /// use jsonrpsee::{core::client::ClientT, rpc_params}; /// use reth_ipc::client::IpcClientBuilder; + /// /// # async fn run_client() -> Result<(), Box> { /// let client = IpcClientBuilder::default().build("/tmp/my-uds").await?; /// let response: String = client.request("say_hello", rpc_params![]).await?; - /// # Ok(()) - /// # } + /// # Ok(()) } /// ``` - pub async fn build(self, path: impl AsRef) -> Result { - let (tx, rx) = IpcTransportClientBuilder::default().build(path).await?; + pub async fn build(self, name: &str) -> Result { + let (tx, rx) = IpcTransportClientBuilder::default().build(name).await?; Ok(self.build_with_tokio(tx, rx)) } @@ -139,20 +136,24 @@ pub enum IpcError { #[cfg(test)] mod tests { - use crate::server::dummy_endpoint; - use interprocess::local_socket::tokio::LocalSocketListener; + use interprocess::local_socket::ListenerOptions; use super::*; + use crate::server::dummy_name; #[tokio::test] async fn test_connect() { - let endpoint = dummy_endpoint(); - let binding = LocalSocketListener::bind(endpoint.clone()).unwrap(); + let name = &dummy_name(); + + let binding = ListenerOptions::new() + .name(name.as_str().to_fs_name::().unwrap()) + .create_tokio() + .unwrap(); tokio::spawn(async move { let _x = binding.accept().await; }); - let (tx, rx) = IpcTransportClientBuilder::default().build(endpoint).await.unwrap(); + let (tx, rx) = IpcTransportClientBuilder::default().build(name).await.unwrap(); let _ = IpcClientBuilder::default().build_with_tokio(tx, rx); } } diff --git a/crates/rpc/ipc/src/server/mod.rs b/crates/rpc/ipc/src/server/mod.rs index d001909d37f6..c38b1629e667 100644 --- a/crates/rpc/ipc/src/server/mod.rs +++ b/crates/rpc/ipc/src/server/mod.rs @@ -5,8 +5,12 @@ use crate::server::{ future::StopHandle, }; use futures::StreamExt; -use futures_util::{future::Either, AsyncWriteExt}; -use interprocess::local_socket::tokio::{LocalSocketListener, LocalSocketStream}; +use futures_util::future::Either; +use interprocess::local_socket::{ + tokio::prelude::{LocalSocketListener, LocalSocketStream}, + traits::tokio::{Listener, Stream}, + GenericFilePath, ListenerOptions, ToFsName, +}; use jsonrpsee::{ core::TEN_MB_SIZE_BYTES, server::{ @@ -24,7 +28,7 @@ use std::{ task::{Context, Poll}, }; use tokio::{ - io::{AsyncRead, AsyncWrite}, + io::{AsyncRead, AsyncWrite, AsyncWriteExt}, sync::{oneshot, watch}, }; use tower::{layer::util::Identity, Layer, Service}; @@ -39,7 +43,6 @@ use crate::{ }; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; -use tokio_util::compat::FuturesAsyncReadCompatExt; use tower::layer::{util::Stack, LayerFn}; mod connection; @@ -68,17 +71,17 @@ impl IpcServer { impl IpcServer where - RpcMiddleware: Layer + Clone + Send + 'static, - for<'a> >::Service: RpcServiceT<'a>, - HttpMiddleware: Layer> + Send + 'static, - >>::Service: Send - + Service< - String, - Response = Option, - Error = Box, - >, - <>>::Service as Service>::Future: - Send + Unpin, + RpcMiddleware: for<'a> Layer> + Clone + Send + 'static, + HttpMiddleware: Layer< + TowerServiceNoHttp, + Service: Service< + String, + Response = Option, + Error = Box, + Future: Send + Unpin, + > + Send, + > + Send + + 'static, { /// Start responding to connections requests. /// @@ -89,7 +92,7 @@ where /// use jsonrpsee::RpcModule; /// use reth_ipc::server::Builder; /// async fn run_server() -> Result<(), Box> { - /// let server = Builder::default().build("/tmp/my-uds"); + /// let server = Builder::default().build("/tmp/my-uds".into()); /// let mut module = RpcModule::new(()); /// module.register_method("say_hello", |_, _, _| "lo")?; /// let handle = server.start(module).await?; @@ -137,15 +140,19 @@ where } } - let listener = match LocalSocketListener::bind(self.endpoint.clone()) { + let listener = match self + .endpoint + .as_str() + .to_fs_name::() + .and_then(|name| ListenerOptions::new().name(name).create_tokio()) + { + Ok(listener) => listener, Err(err) => { on_ready .send(Err(IpcServerStartError { endpoint: self.endpoint.clone(), source: err })) .ok(); return; } - - Ok(listener) => listener, }; // signal that we're ready to accept connections @@ -164,9 +171,10 @@ where match try_accept_conn(&listener, stopped).await { AcceptConnection::Established { local_socket_stream, stop } => { let Some(conn_permit) = connection_guard.try_acquire() else { - let (mut _reader, mut writer) = local_socket_stream.into_split(); - let _ = writer.write_all(b"Too many connections. Please try again later.").await; - drop((_reader, writer)); + let (_reader, mut writer) = local_socket_stream.split(); + let _ = writer + .write_all(b"Too many connections. Please try again later.") + .await; stopped = stop; continue; }; @@ -177,7 +185,7 @@ where let conn_permit = Arc::new(conn_permit); - process_connection(ProcessConnection{ + process_connection(ProcessConnection { http_middleware: &self.http_middleware, rpc_middleware: self.rpc_middleware.clone(), conn_permit, @@ -193,9 +201,11 @@ where id = id.wrapping_add(1); stopped = stop; } - AcceptConnection::Shutdown => { break; } - AcceptConnection::Err((e, stop)) => { - tracing::error!("Error while awaiting a new IPC connection: {:?}", e); + AcceptConnection::Shutdown => { + break; + } + AcceptConnection::Err((err, stop)) => { + tracing::error!(%err, "Failed accepting a new IPC connection"); stopped = stop; } } @@ -204,7 +214,8 @@ where // Drop the last Sender drop(drop_on_completion); - // Once this channel is closed it is safe to assume that all connections have been gracefully shutdown + // Once this channel is closed it is safe to assume that all connections have been + // gracefully shutdown while process_connection_awaiter.recv().await.is_some() { // Generally, messages should not be sent across this channel, // but we'll loop here to wait for `None` just to be on the safe side @@ -222,10 +233,7 @@ async fn try_accept_conn(listener: &LocalSocketListener, stopped: S) -> Accep where S: Future + Unpin, { - let accept = listener.accept(); - let accept = pin!(accept); - - match futures_util::future::select(accept, stopped).await { + match futures_util::future::select(pin!(listener.accept()), stopped).await { Either::Left((res, stop)) => match res { Ok(local_socket_stream) => AcceptConnection::Established { local_socket_stream, stop }, Err(e) => AcceptConnection::Err((e, stop)), @@ -459,7 +467,7 @@ fn process_connection<'b, RpcMiddleware, HttpMiddleware>( let ipc = IpcConn(tokio_util::codec::Decoder::framed( StreamCodec::stream_incoming(), - local_socket_stream.compat(), + local_socket_stream, )); let (tx, rx) = mpsc::channel::(server_cfg.message_buffer_capacity as usize); @@ -682,9 +690,9 @@ impl Builder { /// #[tokio::main] /// async fn main() { /// let builder = tower::ServiceBuilder::new(); - /// - /// let server = - /// reth_ipc::server::Builder::default().set_http_middleware(builder).build("/tmp/my-uds"); + /// let server = reth_ipc::server::Builder::default() + /// .set_http_middleware(builder) + /// .build("/tmp/my-uds".into()); /// } /// ``` pub fn set_http_middleware( @@ -776,9 +784,9 @@ impl Builder { } /// Finalize the configuration of the server. Consumes the [`Builder`]. - pub fn build(self, endpoint: impl AsRef) -> IpcServer { + pub fn build(self, endpoint: String) -> IpcServer { IpcServer { - endpoint: endpoint.as_ref().to_string(), + endpoint, cfg: self.settings, id_provider: self.id_provider, http_middleware: self.http_middleware, @@ -816,9 +824,8 @@ impl ServerHandle { } } -/// For testing/examples #[cfg(test)] -pub fn dummy_endpoint() -> String { +pub fn dummy_name() -> String { let num: u64 = rand::Rng::gen(&mut rand::thread_rng()); if cfg!(windows) { format!(r"\\.\pipe\my-pipe-{}", num) @@ -893,8 +900,8 @@ mod tests { #[tokio::test] async fn can_set_the_max_response_body_size() { // init_test_tracing(); - let endpoint = dummy_endpoint(); - let server = Builder::default().max_response_body_size(100).build(&endpoint); + let endpoint = &dummy_name(); + let server = Builder::default().max_response_body_size(100).build(endpoint.clone()); let mut module = RpcModule::new(()); module.register_method("anything", |_, _, _| "a".repeat(101)).unwrap(); let handle = server.start(module).await.unwrap(); @@ -908,8 +915,8 @@ mod tests { #[tokio::test] async fn can_set_the_max_request_body_size() { init_test_tracing(); - let endpoint = dummy_endpoint(); - let server = Builder::default().max_request_body_size(100).build(&endpoint); + let endpoint = &dummy_name(); + let server = Builder::default().max_request_body_size(100).build(endpoint.clone()); let mut module = RpcModule::new(()); module.register_method("anything", |_, _, _| "succeed").unwrap(); let handle = server.start(module).await.unwrap(); @@ -936,16 +943,16 @@ mod tests { async fn can_set_max_connections() { init_test_tracing(); - let endpoint = dummy_endpoint(); - let server = Builder::default().max_connections(2).build(&endpoint); + let endpoint = &dummy_name(); + let server = Builder::default().max_connections(2).build(endpoint.clone()); let mut module = RpcModule::new(()); module.register_method("anything", |_, _, _| "succeed").unwrap(); let handle = server.start(module).await.unwrap(); tokio::spawn(handle.stopped()); - let client1 = IpcClientBuilder::default().build(endpoint.clone()).await.unwrap(); - let client2 = IpcClientBuilder::default().build(endpoint.clone()).await.unwrap(); - let client3 = IpcClientBuilder::default().build(endpoint.clone()).await.unwrap(); + let client1 = IpcClientBuilder::default().build(endpoint).await.unwrap(); + let client2 = IpcClientBuilder::default().build(endpoint).await.unwrap(); + let client3 = IpcClientBuilder::default().build(endpoint).await.unwrap(); let response1: Result = client1.request("anything", rpc_params![]).await; let response2: Result = client2.request("anything", rpc_params![]).await; @@ -961,7 +968,7 @@ mod tests { tokio::time::sleep(std::time::Duration::from_millis(100)).await; // Can connect again - let client4 = IpcClientBuilder::default().build(endpoint.clone()).await.unwrap(); + let client4 = IpcClientBuilder::default().build(endpoint).await.unwrap(); let response4: Result = client4.request("anything", rpc_params![]).await; assert!(response4.is_ok()); } @@ -969,8 +976,8 @@ mod tests { #[tokio::test] async fn test_rpc_request() { init_test_tracing(); - let endpoint = dummy_endpoint(); - let server = Builder::default().build(&endpoint); + let endpoint = &dummy_name(); + let server = Builder::default().build(endpoint.clone()); let mut module = RpcModule::new(()); let msg = r#"{"jsonrpc":"2.0","id":83,"result":"0x7a69"}"#; module.register_method("eth_chainId", move |_, _, _| msg).unwrap(); @@ -984,8 +991,8 @@ mod tests { #[tokio::test] async fn test_batch_request() { - let endpoint = dummy_endpoint(); - let server = Builder::default().build(&endpoint); + let endpoint = &dummy_name(); + let server = Builder::default().build(endpoint.clone()); let mut module = RpcModule::new(()); module.register_method("anything", |_, _, _| "ok").unwrap(); let handle = server.start(module).await.unwrap(); @@ -1009,8 +1016,8 @@ mod tests { #[tokio::test] async fn test_ipc_modules() { reth_tracing::init_test_tracing(); - let endpoint = dummy_endpoint(); - let server = Builder::default().build(&endpoint); + let endpoint = &dummy_name(); + let server = Builder::default().build(endpoint.clone()); let mut module = RpcModule::new(()); let msg = r#"{"admin":"1.0","debug":"1.0","engine":"1.0","eth":"1.0","ethash":"1.0","miner":"1.0","net":"1.0","rpc":"1.0","txpool":"1.0","web3":"1.0"}"#; module.register_method("rpc_modules", move |_, _, _| msg).unwrap(); @@ -1024,8 +1031,8 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_rpc_subscription() { - let endpoint = dummy_endpoint(); - let server = Builder::default().build(&endpoint); + let endpoint = &dummy_name(); + let server = Builder::default().build(endpoint.clone()); let (tx, _rx) = broadcast::channel::(16); let mut module = RpcModule::new(tx.clone()); @@ -1080,10 +1087,10 @@ mod tests { } reth_tracing::init_test_tracing(); - let endpoint = dummy_endpoint(); + let endpoint = &dummy_name(); let rpc_middleware = RpcServiceBuilder::new().layer_fn(ModifyRequestIf); - let server = Builder::default().set_rpc_middleware(rpc_middleware).build(&endpoint); + let server = Builder::default().set_rpc_middleware(rpc_middleware).build(endpoint.clone()); let mut module = RpcModule::new(()); let goodbye_msg = r#"{"jsonrpc":"2.0","id":1,"result":"goodbye"}"#; diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 62a676eaa946..3f016c330e78 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -298,7 +298,7 @@ impl AuthServerHandle { pub async fn ipc_client(&self) -> Option { use reth_ipc::client::IpcClientBuilder; - if let Some(ipc_endpoint) = self.ipc_endpoint.clone() { + if let Some(ipc_endpoint) = &self.ipc_endpoint { return Some( IpcClientBuilder::default() .build(ipc_endpoint) diff --git a/crates/storage/db-api/Cargo.toml b/crates/storage/db-api/Cargo.toml index 1f55e6d7fa7d..a3b33abfd72d 100644 --- a/crates/storage/db-api/Cargo.toml +++ b/crates/storage/db-api/Cargo.toml @@ -54,7 +54,7 @@ pprof = { workspace = true, features = [ "criterion", ] } criterion.workspace = true -iai-callgrind = "0.10.2" +iai-callgrind.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index b563e1d787e2..336cc75d2700 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -64,7 +64,7 @@ pprof = { workspace = true, features = [ "criterion", ] } criterion.workspace = true -iai-callgrind = "0.10.2" +iai-callgrind.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true diff --git a/crates/storage/db/src/metrics.rs b/crates/storage/db/src/metrics.rs index 0d0b68722e64..fecd691ee5d7 100644 --- a/crates/storage/db/src/metrics.rs +++ b/crates/storage/db/src/metrics.rs @@ -1,12 +1,8 @@ use crate::Tables; use metrics::{Gauge, Histogram}; use reth_metrics::{metrics::Counter, Metrics}; -use rustc_hash::{FxHashMap, FxHasher}; -use std::{ - collections::HashMap, - hash::BuildHasherDefault, - time::{Duration, Instant}, -}; +use rustc_hash::FxHashMap; +use std::time::{Duration, Instant}; use strum::{EnumCount, EnumIter, IntoEnumIterator}; const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096; @@ -45,7 +41,7 @@ impl DatabaseEnvMetrics { fn generate_operation_handles() -> FxHashMap<(&'static str, Operation), OperationMetrics> { let mut operations = FxHashMap::with_capacity_and_hasher( Tables::COUNT * Operation::COUNT, - BuildHasherDefault::::default(), + Default::default(), ); for table in Tables::ALL { for operation in Operation::iter() { @@ -81,9 +77,9 @@ impl DatabaseEnvMetrics { /// Used for tracking various stats for finished transactions (e.g. commit duration). fn generate_transaction_outcome_handles( ) -> FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics> { - let mut transaction_outcomes = HashMap::with_capacity_and_hasher( + let mut transaction_outcomes = FxHashMap::with_capacity_and_hasher( TransactionMode::COUNT * TransactionOutcome::COUNT, - BuildHasherDefault::::default(), + Default::default(), ); for mode in TransactionMode::iter() { for outcome in TransactionOutcome::iter() { diff --git a/crates/storage/nippy-jar/Cargo.toml b/crates/storage/nippy-jar/Cargo.toml index fb485e32ac9c..dcc916e783f8 100644 --- a/crates/storage/nippy-jar/Cargo.toml +++ b/crates/storage/nippy-jar/Cargo.toml @@ -32,7 +32,7 @@ lz4_flex = { version = "0.11", default-features = false } # offsets sucds = "~0.8" -memmap2 = "0.7.1" +memmap2 = "0.9.4" bincode = "1.3" serde = { workspace = true, features = ["derive"] } tracing.workspace = true diff --git a/crates/trie/common/Cargo.toml b/crates/trie/common/Cargo.toml index 241a3518f741..f9267c2b8ebc 100644 --- a/crates/trie/common/Cargo.toml +++ b/crates/trie/common/Cargo.toml @@ -29,7 +29,7 @@ itertools.workspace = true nybbles = { workspace = true, features = ["serde", "rlp"] } # `test-utils` feature -hash-db = { version = "~0.15", optional = true } +hash-db = { version = "=0.15.2", optional = true } plain_hasher = { version = "0.2", optional = true } arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } @@ -43,7 +43,7 @@ proptest-derive.workspace = true serde_json.workspace = true test-fuzz.workspace = true toml.workspace = true -hash-db = "~0.15" +hash-db = "=0.15.2" plain_hasher = "0.2" [features] @@ -53,4 +53,4 @@ arbitrary = [ "dep:arbitrary", "dep:proptest", "dep:proptest-derive", -] \ No newline at end of file +] From ef2088d57c233821400641ff39133616e84cc22f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 20 Jun 2024 19:10:03 +0200 Subject: [PATCH 152/405] chore: weaken engine type requirements (#8976) --- crates/ethereum/engine-primitives/src/lib.rs | 3 +-- crates/ethereum/node/src/node.rs | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index c1ad788f9770..fe4a050fa41b 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -10,14 +10,13 @@ mod payload; pub use payload::{EthBuiltPayload, EthPayloadBuilderAttributes}; - use reth_chainspec::ChainSpec; use reth_engine_primitives::EngineTypes; use reth_payload_primitives::{ validate_version_specific_fields, EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes, PayloadTypes, }; -use reth_rpc_types::{ +pub use reth_rpc_types::{ engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, PayloadAttributes as EthPayloadAttributes, diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 55592762304a..c3d8a9af55f4 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -4,6 +4,9 @@ use crate::{EthEngineTypes, EthEvmConfig}; use reth_auto_seal_consensus::AutoSealConsensus; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; use reth_beacon_consensus::EthBeaconConsensus; +use reth_ethereum_engine_primitives::{ + EthBuiltPayload, EthPayloadAttributes, EthPayloadBuilderAttributes, +}; use reth_evm_ethereum::execute::EthExecutorProvider; use reth_network::NetworkHandle; use reth_node_builder::{ @@ -12,7 +15,7 @@ use reth_node_builder::{ PayloadServiceBuilder, PoolBuilder, }, node::{FullNodeTypes, NodeTypes}, - BuilderContext, Node, PayloadBuilderConfig, + BuilderContext, Node, PayloadBuilderConfig, PayloadTypes, }; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::CanonStateSubscriptions; @@ -39,7 +42,12 @@ impl EthereumNode { EthereumConsensusBuilder, > where - Node: FullNodeTypes, + Node: FullNodeTypes, + ::Engine: PayloadTypes< + BuiltPayload = EthBuiltPayload, + PayloadAttributes = EthPayloadAttributes, + PayloadBuilderAttributes = EthPayloadBuilderAttributes, + >, { ComponentsBuilder::default() .node_types::() @@ -178,8 +186,13 @@ pub struct EthereumPayloadBuilder; impl PayloadServiceBuilder for EthereumPayloadBuilder where - Node: FullNodeTypes, Pool: TransactionPool + Unpin + 'static, + Node: FullNodeTypes, + ::Engine: PayloadTypes< + BuiltPayload = EthBuiltPayload, + PayloadAttributes = EthPayloadAttributes, + PayloadBuilderAttributes = EthPayloadBuilderAttributes, + >, { async fn spawn_payload_service( self, From 542bcf054e5159ae252d996a1b17f515e2ef4a11 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 20 Jun 2024 19:18:35 +0100 Subject: [PATCH 153/405] fix(pruner): prune history segments first (#8996) --- crates/prune/prune/src/segments/set.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/prune/prune/src/segments/set.rs b/crates/prune/prune/src/segments/set.rs index 7448e03ae88f..f5b60f2695aa 100644 --- a/crates/prune/prune/src/segments/set.rs +++ b/crates/prune/prune/src/segments/set.rs @@ -48,6 +48,10 @@ impl SegmentSet { } = prune_modes; Self::default() + // Account history + .segment_opt(account_history.map(AccountHistory::new)) + // Storage history + .segment_opt(storage_history.map(StorageHistory::new)) // Receipts .segment_opt(receipts.map(Receipts::new)) // Receipts by logs @@ -59,10 +63,6 @@ impl SegmentSet { .segment_opt(transaction_lookup.map(TransactionLookup::new)) // Sender recovery .segment_opt(sender_recovery.map(SenderRecovery::new)) - // Account history - .segment_opt(account_history.map(AccountHistory::new)) - // Storage history - .segment_opt(storage_history.map(StorageHistory::new)) } } From d213c1eadf92ac415d2974ffea2daa74b3dac46f Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 20 Jun 2024 20:22:18 +0200 Subject: [PATCH 154/405] chore: bump alloy to 0.1.2 (#8990) --- Cargo.lock | 158 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 8 +-- 2 files changed, 82 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91ba40344caa..7acedaadfbe4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc7579e4fb5558af44810f542c90d1145dba8b92c08211c215196160c48d2ea" +checksum = "a016bfa21193744d4c38b3f3ab845462284d129e5e23c7cc0fafca7e92d9db37" dependencies = [ "alloy-eips", "alloy-primitives", @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdbc8d98cc36ebe17bb5b42d0873137bc76628a4ee0f7e7acad5b8fc59d3597" +checksum = "32d6d8118b83b0489cfb7e6435106948add2b35217f4a5004ef895f613f60299" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -177,14 +177,13 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e10a047066076b32d52b3228e95a4f7793db7a204f648aa1a1ea675085bffd8" +checksum = "894f33a7822abb018db56b10ab90398e63273ce1b5a33282afd186c132d764a6" dependencies = [ "alloy-primitives", "alloy-serde", "serde", - "serde_json", ] [[package]] @@ -201,9 +200,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06d33b79246313c4103ef9596c721674a926f1ddc8b605aa2bac4d8ba94ee34" +checksum = "61f0ae6e93b885cc70fe8dae449e7fd629751dbee8f59767eaaa7285333c5727" dependencies = [ "alloy-primitives", "serde", @@ -214,9 +213,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef742b478a2db5c27063cde82128dfbecffcd38237d7f682a91d3ecf6aa1836c" +checksum = "dc122cbee2b8523854cc11d87bcd5773741602c553d2d2d106d82eeb9c16924a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -234,9 +233,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4890cdebc750890b1cd3b5a61f72df992e262c5bfb5423b2999f0d81449f04e" +checksum = "df0e005ecc1b41f0b3bf90f68df5a446971e7eb34e1ea051da401e7e8eeef8fd" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -277,9 +276,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b786259a17acf318b9c423afe9669bec24ce9cdf59de153ff9a4009914bb6" +checksum = "3d5af289798fe8783acd0c5f10644d9d26f54a12bc52a083e4f3b31718e9bf92" dependencies = [ "alloy-chains", "alloy-consensus", @@ -313,9 +312,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e6e6c1eab938a18a8e88d430cc9d548edf54c850a550873888285c85428eca" +checksum = "702f330b7da123a71465ab9d39616292f8344a2811c28f2cc8d8438a69d79e35" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -354,9 +353,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328a6a14aba6152ddf6d01bac5e17a70dbe9d6f343bf402b995c30bac63a1fbf" +checksum = "b40fcb53b2a9d0a78a4968b2eca8805a4b7011b9ee3fdfa2acaf137c5128f36b" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -378,9 +377,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3164e7d8a718a22ede70b2c1d2bb554a8b4bd8e56c07ab630b75c74c06c53752" +checksum = "50f2fbe956a3e0f0975c798f488dc6be96b669544df3737e18f4a325b42f4c86" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -390,9 +389,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b61b27c1a30ee71c751fe8a1b926d897e6795527bba2832f458acd432408df20" +checksum = "334a8c00cde17a48e073031f1534e71a75b529dbf25552178c43c2337632e0ab" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -402,9 +401,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21cdf2617cfb0ae95ef462a0144abfef5ff56d25b56915d7a08f8a5a7b92926" +checksum = "d87f724e6170f558b809a520e37bdb34d99123092b78118bff31fb5b21dc2a2e" dependencies = [ "alloy-primitives", "alloy-serde", @@ -413,9 +412,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a3c7fd58772e4eadec5f08153573aa0ce8d998bd03f239ca74cd2927022d4c" +checksum = "fb383cd3981cee0031aeacbd394c4e726e907f3a0180fe36d5fc76d37c41cd82" dependencies = [ "alloy-eips", "alloy-primitives", @@ -427,9 +426,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c3de574f90d9b939e3ee666a74bea29fb1a2ae66f1569b111bb6a922b1c762" +checksum = "cd473d98ec552f8229cd6d566bd2b0bbfc5bb4efcefbb5288c834aa8fd832020" dependencies = [ "alloy-consensus", "alloy-eips", @@ -446,9 +445,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bce0676f144be1eae71122d1d417885a3b063add0353b35e46cdf1440d6b33b1" +checksum = "083f443a83b9313373817236a8f4bea09cca862618e9177d822aee579640a5d6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -468,22 +467,23 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a39c52613dc4d9995ff284b496158594ae63f9bfc58b5ef04e48ec5da2e3d747" +checksum = "4c7a838f9a34aae7022c6cb53ecf21bc0a5a30c82f8d9eb0afed701ab5fd88de" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", "serde_json", + "thiserror", ] [[package]] name = "alloy-rpc-types-txpool" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aeb7995e8859f3931b6199e13a533c9fde89affa900addb7218db2f15f9687d" +checksum = "1572267dbc660843d87c02994029d1654c2c32867e186b266d1c03644b43af97" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -493,9 +493,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c224916316519558d8c2b6a60dc7626688c08f1b8951774702562dbcb8666ee" +checksum = "d94da1c0c4e27cc344b05626fe22a89dc6b8b531b9475f3b7691dbf6913e4109" dependencies = [ "alloy-primitives", "arbitrary", @@ -507,9 +507,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227c5fd0ed6e06e1ccc30593f8ff6d9fb907ac5f03a709a6d687f0943494a229" +checksum = "58d876be3afd8b78979540084ff63995292a26aa527ad0d44276405780aa0ffd" dependencies = [ "alloy-primitives", "async-trait", @@ -521,9 +521,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c44057ac1e8707f8c6a983db9f83ac1265c9e05be81d432acf2aad2880e1c0" +checksum = "d40a37dc216c269b8a7244047cb1c18a9c69f7a0332ab2c4c2aa4cbb1a31468b" dependencies = [ "alloy-consensus", "alloy-network", @@ -611,9 +611,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3628d81530263fe837a09cd527022f5728202a669973f04270942f4d390b5f5" +checksum = "245af9541f0a0dbd5258669c80dfe3af118164cacec978a520041fc130550deb" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f35d34e7a51503c9ff267404a5850bd58f991b7ab524b892f364901e3576376" +checksum = "5619c017e1fdaa1db87f9182f4f0ed97c53d674957f4902fba655e972d359c6c" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d2f106151a583f7d258fe8cc846c5196da90a9f502d4b3516c56d63e1f25a2" +checksum = "173cefa110afac7a53cf2e75519327761f2344d305eea2993f3af1b2c1fc1c44" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a80da44d3709c4ceaf47745ad820eae8f121404b9ffd8e285522ac4eb06681" +checksum = "9c0aff8af5be5e58856c5cdd1e46db2c67c7ecd3a652d9100b4822c96c899947" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1467,9 +1467,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" dependencies = [ "bytemuck_derive", ] @@ -3955,14 +3955,12 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -4807,9 +4805,9 @@ dependencies = [ [[package]] name = "metrics" -version = "0.22.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be3cbd384d4e955b231c895ce10685e3d8260c5ccffae898c96c723b0772835" +checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" dependencies = [ "ahash", "portable-atomic", @@ -4817,9 +4815,9 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d58e362dc7206e9456ddbcdbd53c71ba441020e62104703075a69151e38d85f" +checksum = "26eb45aff37b45cff885538e1dcbd6c2b462c04fe84ce0155ea469f325672c98" dependencies = [ "base64 0.22.1", "indexmap 2.2.6", @@ -4831,9 +4829,9 @@ dependencies = [ [[package]] name = "metrics-process" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d8f5027620bf43b86e2c8144beea1e4323aec39241f5eae59dee54f79c6a29" +checksum = "cb524e5438255eaa8aa74214d5a62713b77b2c3c6e3c0bbeee65cfd9a58948ba" dependencies = [ "libproc", "mach2", @@ -4841,14 +4839,14 @@ dependencies = [ "once_cell", "procfs", "rlimit", - "windows 0.56.0", + "windows 0.57.0", ] [[package]] name = "metrics-util" -version = "0.16.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b07a5eb561b8cbc16be2d216faf7757f9baf3bfb94dbb0fae3df8387a5bb47f" +checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" dependencies = [ "aho-corasick", "crossbeam-epoch", @@ -9387,9 +9385,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" [[package]] name = "sucds" @@ -10269,12 +10267,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna 1.0.0", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -10555,11 +10553,11 @@ dependencies = [ [[package]] name = "windows" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "windows-core 0.56.0", + "windows-core 0.57.0", "windows-targets 0.52.5", ] @@ -10574,9 +10572,9 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ "windows-implement", "windows-interface", @@ -10586,9 +10584,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", @@ -10597,9 +10595,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 69a06061cc38..528d213f3753 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -427,10 +427,10 @@ url = "2.3" backon = "0.4" # metrics -metrics = "0.22.0" -metrics-exporter-prometheus = { version = "0.14.0", default-features = false } -metrics-util = "0.16.0" -metrics-process = "2.0.0" +metrics = "0.23.0" +metrics-exporter-prometheus = { version = "0.15.0", default-features = false } +metrics-util = "0.17.0" +metrics-process = "2.1.0" # proc-macros proc-macro2 = "1.0" From 27d26ac10dc8e3d20eed6fe90c66239eeb88adf9 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 20 Jun 2024 20:44:31 +0200 Subject: [PATCH 155/405] chore: rm custom chainspec parser (#8998) --- bin/reth/src/commands/p2p/mod.rs | 4 ++-- crates/node-core/src/args/utils.rs | 30 ------------------------------ 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/bin/reth/src/commands/p2p/mod.rs b/bin/reth/src/commands/p2p/mod.rs index 2de5a9aa5cff..b40207c80579 100644 --- a/bin/reth/src/commands/p2p/mod.rs +++ b/bin/reth/src/commands/p2p/mod.rs @@ -3,7 +3,7 @@ use crate::{ args::{ get_secret_key, - utils::{chain_help, chain_spec_value_parser, hash_or_num_value_parser, SUPPORTED_CHAINS}, + utils::{chain_help, genesis_value_parser, hash_or_num_value_parser, SUPPORTED_CHAINS}, DatabaseArgs, DiscoveryArgs, NetworkArgs, }, utils::get_single_header, @@ -40,7 +40,7 @@ pub struct Command { value_name = "CHAIN_OR_PATH", long_help = chain_help(), default_value = SUPPORTED_CHAINS[0], - value_parser = chain_spec_value_parser + value_parser = genesis_value_parser )] chain: Arc, diff --git a/crates/node-core/src/args/utils.rs b/crates/node-core/src/args/utils.rs index 38c0b01b6034..784a3f8187db 100644 --- a/crates/node-core/src/args/utils.rs +++ b/crates/node-core/src/args/utils.rs @@ -32,35 +32,6 @@ pub fn parse_duration_from_secs(arg: &str) -> eyre::Result eyre::Result, eyre::Error> { - Ok(match s { - #[cfg(not(feature = "optimism"))] - "mainnet" => MAINNET.clone(), - #[cfg(not(feature = "optimism"))] - "goerli" => GOERLI.clone(), - #[cfg(not(feature = "optimism"))] - "sepolia" => SEPOLIA.clone(), - #[cfg(not(feature = "optimism"))] - "holesky" => HOLESKY.clone(), - #[cfg(not(feature = "optimism"))] - "dev" => DEV.clone(), - #[cfg(feature = "optimism")] - "optimism" => OP_MAINNET.clone(), - #[cfg(feature = "optimism")] - "optimism_sepolia" | "optimism-sepolia" => OP_SEPOLIA.clone(), - #[cfg(feature = "optimism")] - "base" => BASE_MAINNET.clone(), - #[cfg(feature = "optimism")] - "base_sepolia" | "base-sepolia" => BASE_SEPOLIA.clone(), - _ => { - let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?; - serde_json::from_str(&raw)? - } - }) -} - /// The help info for the --chain flag pub fn chain_help() -> String { format!("The chain this node is running.\nPossible values are either a built-in chain or the path to a chain specification file.\n\nBuilt-in chains:\n {}", SUPPORTED_CHAINS.join(", ")) @@ -178,7 +149,6 @@ mod tests { #[test] fn parse_known_chain_spec() { for chain in SUPPORTED_CHAINS { - chain_spec_value_parser(chain).unwrap(); genesis_value_parser(chain).unwrap(); } } From 4fcf26c5aeb4e95493f189d330865d961a0a53b1 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:45:12 +0200 Subject: [PATCH 156/405] chore(deps): bump revm 10.0, un-git revm-inspectors (#8997) --- Cargo.lock | 23 ++++++++++++++--------- Cargo.toml | 12 +++--------- crates/primitives/src/revm/env.rs | 23 ++++++++++------------- crates/rpc/rpc/src/eth/api/call.rs | 8 +++----- crates/rpc/rpc/src/eth/revm_utils.rs | 9 ++------- examples/exex/rollup/src/execution.rs | 6 +++--- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7acedaadfbe4..ab66b3b568c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8395,8 +8395,9 @@ dependencies = [ [[package]] name = "revm" -version = "9.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355bde4e21578c241f9379fbb344a73d254969b5007239115e094dda1511cd34" dependencies = [ "auto_impl", "cfg-if", @@ -8410,7 +8411,8 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=4fe17f0#4fe17f08797450d9d5df315e724d14c9f3749b3f" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba2e187811b160463663fd71881b4e5d653720ba00be0f1e85962d4db60341c" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -8426,8 +8428,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "5.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dfd24faa3cbbd96e0976103d1e174d6559b8036730f70415488ee21870d578" dependencies = [ "revm-primitives", "serde", @@ -8435,8 +8438,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "7.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c669c9b105dbb41133c17bf7f34d29368e358a7fee8fcc289e90dbfb024dfc4" dependencies = [ "aurora-engine-modexp", "blst", @@ -8453,8 +8457,9 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "4.0.0" -source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "902184a7a781550858d4b96707098da357429f1e4545806fd5b589f455555cf2" dependencies = [ "alloy-primitives", "auto_impl", diff --git a/Cargo.toml b/Cargo.toml index 528d213f3753..db4613a04b46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -339,15 +339,15 @@ reth-trie-common = { path = "crates/trie/common" } reth-trie-parallel = { path = "crates/trie/parallel" } # revm -revm = { version = "9.0.0", features = [ +revm = { version = "10.0.0", features = [ "std", "secp256k1", "blst", ], default-features = false } -revm-primitives = { version = "4.0.0", features = [ +revm-primitives = { version = "5.0.0", features = [ "std", ], default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "4fe17f0" } +revm-inspectors = "0.1" # eth alloy-chains = "0.1.15" @@ -495,9 +495,3 @@ serial_test = "3" similar-asserts = "1.5.0" test-fuzz = "5" iai-callgrind = "0.11" - -[patch.crates-io] -revm = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } -revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } -revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } -revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index bfd5a3a69a84..bbd4cffd35fb 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -1,6 +1,6 @@ use crate::{ recover_signer_unchecked, - revm_primitives::{BlockEnv, Env, TransactTo, TxEnv}, + revm_primitives::{BlockEnv, Env, TxEnv}, Address, Bytes, Header, Transaction, TransactionSignedEcRecovered, TxKind, B256, U256, }; use reth_chainspec::{Chain, ChainSpec}; @@ -182,7 +182,7 @@ fn fill_tx_env_with_system_contract_call( ) { env.tx = TxEnv { caller, - transact_to: TransactTo::Call(contract), + transact_to: TxKind::Call(contract), // Explicitly set nonce to None so revm does not do any nonce checks nonce: None, gas_limit: 30_000_000, @@ -246,8 +246,8 @@ where tx_env.gas_price = U256::from(tx.gas_price); tx_env.gas_priority_fee = None; tx_env.transact_to = match tx.to { - TxKind::Call(to) => TransactTo::Call(to), - TxKind::Create => TransactTo::create(), + TxKind::Call(to) => TxKind::Call(to), + TxKind::Create => TxKind::Create, }; tx_env.value = tx.value; tx_env.data = tx.input.clone(); @@ -262,8 +262,8 @@ where tx_env.gas_price = U256::from(tx.gas_price); tx_env.gas_priority_fee = None; tx_env.transact_to = match tx.to { - TxKind::Call(to) => TransactTo::Call(to), - TxKind::Create => TransactTo::create(), + TxKind::Call(to) => TxKind::Call(to), + TxKind::Create => TxKind::Create, }; tx_env.value = tx.value; tx_env.data = tx.input.clone(); @@ -285,8 +285,8 @@ where tx_env.gas_price = U256::from(tx.max_fee_per_gas); tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); tx_env.transact_to = match tx.to { - TxKind::Call(to) => TransactTo::Call(to), - TxKind::Create => TransactTo::create(), + TxKind::Call(to) => TxKind::Call(to), + TxKind::Create => TxKind::Create, }; tx_env.value = tx.value; tx_env.data = tx.input.clone(); @@ -307,7 +307,7 @@ where tx_env.gas_limit = tx.gas_limit; tx_env.gas_price = U256::from(tx.max_fee_per_gas); tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); - tx_env.transact_to = TransactTo::Call(tx.to); + tx_env.transact_to = TxKind::Call(tx.to); tx_env.value = tx.value; tx_env.data = tx.input.clone(); tx_env.chain_id = Some(tx.chain_id); @@ -329,10 +329,7 @@ where tx_env.gas_limit = tx.gas_limit; tx_env.gas_price = U256::ZERO; tx_env.gas_priority_fee = None; - match tx.to { - TxKind::Call(to) => tx_env.transact_to = TransactTo::Call(to), - TxKind::Create => tx_env.transact_to = TransactTo::create(), - } + tx_env.transact_to = tx.to; tx_env.value = tx.value; tx_env.data = tx.input.clone(); tx_env.chain_id = None; diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index d0fb52b25246..907065e476d4 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -25,9 +25,7 @@ use reth_rpc_types::{ use reth_transaction_pool::TransactionPool; use revm::{ db::{CacheDB, DatabaseRef}, - primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason, TransactTo, - }, + primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason}, DatabaseCommit, }; use revm_inspectors::access_list::AccessListInspector; @@ -219,7 +217,7 @@ where // Optimize for simple transfer transactions, potentially reducing the gas estimate. if env.tx.data.is_empty() { - if let TransactTo::Call(to) = env.tx.transact_to { + if let TxKind::Call(to) = env.tx.transact_to { if let Ok(code) = db.db.account_code(to) { let no_code_callee = code.map(|code| code.is_empty()).unwrap_or(true); if no_code_callee { @@ -509,7 +507,7 @@ fn update_estimated_gas_range( } ExecutionResult::Halt { reason, .. } => { match reason { - HaltReason::OutOfGas(_) | HaltReason::InvalidFEOpcode => { + HaltReason::OutOfGas(_) | HaltReason::InvalidEFOpcode => { // Both `OutOfGas` and `InvalidFEOpcode` can occur dynamically if the gas left // is too low. Treat this as an out of gas condition, // knowing that the call succeeds with a higher gas limit. diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 560c3eeb63d6..4f4b9d7f02e4 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -19,8 +19,7 @@ use revm::{ db::CacheDB, precompile::{PrecompileSpecId, Precompiles}, primitives::{ - db::DatabaseRef, BlockEnv, Bytecode, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, - TransactTo, TxEnv, + db::DatabaseRef, BlockEnv, Bytecode, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv, }, Database, }; @@ -211,17 +210,13 @@ pub fn create_txn_env(block_env: &BlockEnv, request: TransactionRequest) -> EthR )?; let gas_limit = gas.unwrap_or_else(|| block_env.gas_limit.min(U256::from(u64::MAX)).to()); - let transact_to = match to { - Some(TxKind::Call(to)) => TransactTo::call(to), - _ => TransactTo::create(), - }; let env = TxEnv { gas_limit: gas_limit.try_into().map_err(|_| RpcInvalidTransactionError::GasUintOverflow)?, nonce, caller: from.unwrap_or_default(), gas_price, gas_priority_fee: max_priority_fee_per_gas, - transact_to, + transact_to: to.unwrap_or(TxKind::Create), value: value.unwrap_or_default(), data: input.try_into_unique_input()?.unwrap_or_default(), chain_id, diff --git a/examples/exex/rollup/src/execution.rs b/examples/exex/rollup/src/execution.rs index e1fcfb5c485b..cc5b716bdc09 100644 --- a/examples/exex/rollup/src/execution.rs +++ b/examples/exex/rollup/src/execution.rs @@ -277,7 +277,7 @@ mod tests { bytes, constants::ETH_TO_WEI, keccak256, public_key_to_address, - revm_primitives::{AccountInfo, ExecutionResult, Output, TransactTo, TxEnv}, + revm_primitives::{AccountInfo, ExecutionResult, Output, TxEnv}, BlockNumber, Receipt, SealedBlockWithSenders, Transaction, TxEip2930, TxKind, U256, }; use reth_revm::Evm; @@ -383,7 +383,7 @@ mod tests { .with_tx_env(TxEnv { caller: sender_address, gas_limit: 50_000_000, - transact_to: TransactTo::Call(weth_address), + transact_to: TxKind::Call(weth_address), data: WETH::balanceOfCall::new((sender_address,)).abi_encode().into(), ..Default::default() }) @@ -408,7 +408,7 @@ mod tests { .with_tx_env(TxEnv { caller: sender_address, gas_limit: 50_000_000, - transact_to: TransactTo::Call(weth_address), + transact_to: TxKind::Call(weth_address), data: WETH::balanceOfCall::new((sender_address,)).abi_encode().into(), ..Default::default() }) From 13b581914264cbce75c9b244ed7424fa30ac2822 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:02:10 -0400 Subject: [PATCH 157/405] feat: add _with_senders_unchecked methods to SealedBlock (#9002) --- crates/primitives/src/block.rs | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 3bd41e2cdacf..4980ebe73d5a 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -380,6 +380,43 @@ impl SealedBlock { } } + /// Transform into a [`SealedBlockWithSenders`]. + /// + /// # Panics + /// + /// If the number of senders does not match the number of transactions in the block + /// and the signer recovery for one of the transactions fails. + #[track_caller] + pub fn with_senders_unchecked(self, senders: Vec
) -> SealedBlockWithSenders { + self.try_with_senders_unchecked(senders).expect("stored block is valid") + } + + /// Transform into a [`SealedBlockWithSenders`] using the given senders. + /// + /// If the number of senders does not match the number of transactions in the block, this falls + /// back to manually recovery, but _without ensuring that the signature has a low `s` value_. + /// See also [`TransactionSigned::recover_signer_unchecked`] + /// + /// Returns an error if a signature is invalid. + #[track_caller] + pub fn try_with_senders_unchecked( + self, + senders: Vec
, + ) -> Result { + let senders = if self.body.len() == senders.len() { + senders + } else { + let Some(senders) = + TransactionSigned::recover_signers_unchecked(&self.body, self.body.len()) + else { + return Err(self) + }; + senders + }; + + Ok(SealedBlockWithSenders { block: self, senders }) + } + /// Unseal the block pub fn unseal(self) -> Block { Block { From 135e11b19b352b5c0f12c988dabfc7ad71ab7ccc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 20 Jun 2024 23:12:20 +0200 Subject: [PATCH 158/405] fix: skip failed new payload submission (#9003) --- crates/consensus/debug-client/src/client.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/consensus/debug-client/src/client.rs b/crates/consensus/debug-client/src/client.rs index a63b7a85d704..e72f58855eeb 100644 --- a/crates/consensus/debug-client/src/client.rs +++ b/crates/consensus/debug-client/src/client.rs @@ -92,17 +92,19 @@ impl DebugConsensusClient

{ let block_hash = payload.block_hash(); let block_number = payload.block_number(); + previous_block_hashes.push(block_hash); + // Send new events to execution client - reth_rpc_api::EngineApiClient::::new_payload_v3( + let _ = reth_rpc_api::EngineApiClient::::new_payload_v3( &execution_client, payload.execution_payload_v3, payload.versioned_hashes, payload.parent_beacon_block_root, ) .await - .unwrap(); - - previous_block_hashes.push(block_hash); + .inspect_err(|err| { + warn!(target: "consensus::debug-client", %err, %block_hash, %block_number, "failed to submit new payload to execution client"); + }); // Load previous block hashes. We're using (head - 32) and (head - 64) as the safe and // finalized block hashes. From 6b2c3af8b994a442f5efc8a81648ef8e51c1fe1d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 20 Jun 2024 23:40:21 +0200 Subject: [PATCH 159/405] test: disable dns discovery (#9004) --- crates/net/network/tests/it/startup.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/net/network/tests/it/startup.rs b/crates/net/network/tests/it/startup.rs index e924d322d172..9317bef0fd4a 100644 --- a/crates/net/network/tests/it/startup.rs +++ b/crates/net/network/tests/it/startup.rs @@ -91,6 +91,7 @@ async fn test_tcp_port_node_record_discovery() { let config = NetworkConfigBuilder::new(secret_key) .listener_port(0) .discovery_port(0) + .disable_dns_discovery() .build_with_noop_provider(); let network = NetworkManager::new(config).await.unwrap(); From 8492ab3d52486e25a0c53d371bbe438f62d19ba8 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 20 Jun 2024 19:02:07 -0400 Subject: [PATCH 160/405] chore: remove proptest arbitrary from codec derive and tests (#8968) --- Cargo.lock | 24 ++++++ Cargo.toml | 1 + bin/reth/Cargo.toml | 2 + bin/reth/src/commands/test_vectors/tables.rs | 37 +++----- crates/net/eth-wire-types/Cargo.toml | 5 +- crates/net/eth-wire-types/src/blocks.rs | 25 +----- crates/net/eth-wire-types/src/broadcast.rs | 4 +- crates/net/eth-wire-types/src/receipts.rs | 6 -- crates/net/eth-wire/Cargo.toml | 5 +- crates/net/eth-wire/src/capability.rs | 16 ---- crates/primitives-traits/Cargo.toml | 9 +- crates/primitives-traits/src/header/mod.rs | 44 +++++++++- crates/primitives-traits/src/header/sealed.rs | 22 +---- .../src/header/test_utils.rs | 5 +- crates/primitives-traits/src/log.rs | 9 +- crates/primitives-traits/src/withdrawal.rs | 3 +- crates/primitives/Cargo.toml | 4 +- crates/primitives/benches/validate_blob_tx.rs | 7 +- crates/primitives/src/block.rs | 86 +++++++++---------- crates/primitives/src/receipt.rs | 46 ---------- .../primitives/src/transaction/access_list.rs | 13 +-- crates/primitives/src/transaction/eip4844.rs | 14 ++- crates/primitives/src/transaction/mod.rs | 42 ++------- crates/primitives/src/transaction/pooled.rs | 27 ------ crates/prune/types/Cargo.toml | 3 +- crates/stages/types/Cargo.toml | 3 +- crates/storage/codecs/Cargo.toml | 3 +- crates/storage/codecs/derive/src/arbitrary.rs | 3 +- crates/storage/codecs/derive/src/lib.rs | 8 +- crates/storage/codecs/src/alloy/request.rs | 3 +- crates/storage/db-api/Cargo.toml | 3 +- crates/transaction-pool/Cargo.toml | 4 +- .../transaction-pool/src/test_utils/mock.rs | 5 +- crates/trie/common/Cargo.toml | 5 +- crates/trie/common/src/hash_builder/state.rs | 3 +- crates/trie/parallel/Cargo.toml | 1 + crates/trie/parallel/benches/root.rs | 3 +- crates/trie/trie/Cargo.toml | 1 + crates/trie/trie/benches/trie_root.rs | 3 +- .../trie/trie/src/hashed_cursor/post_state.rs | 3 +- crates/trie/trie/src/trie.rs | 7 +- 41 files changed, 205 insertions(+), 312 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab66b3b568c6..9fa1553c5cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5780,6 +5780,16 @@ dependencies = [ "unarray", ] +[[package]] +name = "proptest-arbitrary-interop" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1981e49bd2432249da8b0e11e5557099a8e74690d6b94e721f7dc0bb7f3555f" +dependencies = [ + "arbitrary", + "proptest", +] + [[package]] name = "proptest-derive" version = "0.4.0" @@ -6182,6 +6192,7 @@ dependencies = [ "ahash", "alloy-rlp", "aquamarine", + "arbitrary", "assert_matches", "backon", "boyer-moore-magiclen", @@ -6199,6 +6210,7 @@ dependencies = [ "libc", "metrics-process", "proptest", + "proptest-arbitrary-interop", "rand 0.8.5", "ratatui", "rayon", @@ -6492,6 +6504,7 @@ dependencies = [ "bytes", "modular-bitfield", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "reth-codecs-derive", "serde", @@ -6621,6 +6634,7 @@ dependencies = [ "paste", "pprof", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "rand 0.8.5", "reth-codecs", @@ -6866,6 +6880,7 @@ dependencies = [ "futures", "pin-project", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "rand 0.8.5", "reth-chainspec", @@ -6900,6 +6915,7 @@ dependencies = [ "bytes", "derive_more", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "rand 0.8.5", "reth-chainspec", @@ -7688,6 +7704,7 @@ dependencies = [ "once_cell", "pprof", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "rand 0.8.5", "rayon", @@ -7726,6 +7743,7 @@ dependencies = [ "derive_more", "modular-bitfield", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "rand 0.8.5", "reth-codecs", @@ -7818,6 +7836,7 @@ dependencies = [ "derive_more", "modular-bitfield", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "reth-codecs", "serde", @@ -8153,6 +8172,7 @@ dependencies = [ "bytes", "modular-bitfield", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "rand 0.8.5", "reth-codecs", @@ -8285,6 +8305,7 @@ dependencies = [ "paste", "pprof", "proptest", + "proptest-arbitrary-interop", "rand 0.8.5", "reth-chainspec", "reth-eth-wire-types", @@ -8319,6 +8340,7 @@ dependencies = [ "metrics", "once_cell", "proptest", + "proptest-arbitrary-interop", "rayon", "reth-chainspec", "reth-db", @@ -8358,6 +8380,7 @@ dependencies = [ "nybbles", "plain_hasher", "proptest", + "proptest-arbitrary-interop", "proptest-derive", "reth-codecs", "reth-primitives-traits", @@ -8378,6 +8401,7 @@ dependencies = [ "itertools 0.13.0", "metrics", "proptest", + "proptest-arbitrary-interop", "rand 0.8.5", "rayon", "reth-db", diff --git a/Cargo.toml b/Cargo.toml index db4613a04b46..069452a7f33e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -468,6 +468,7 @@ jsonrpsee-http-client = "0.23" http = "1.0" http-body = "1.0" jsonwebtoken = "9" +proptest-arbitrary-interop = "0.1.0" # crypto secp256k1 = { version = "0.29", default-features = false, features = [ diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 882c1afed705..46636913fdb1 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -86,6 +86,8 @@ metrics-process.workspace = true # test vectors generation proptest.workspace = true +arbitrary.workspace = true +proptest-arbitrary-interop.workspace = true rand.workspace = true # tui diff --git a/bin/reth/src/commands/test_vectors/tables.rs b/bin/reth/src/commands/test_vectors/tables.rs index 9442f52aef4f..d7138444f69a 100644 --- a/bin/reth/src/commands/test_vectors/tables.rs +++ b/bin/reth/src/commands/test_vectors/tables.rs @@ -1,15 +1,15 @@ -use std::collections::HashSet; - +use arbitrary::Arbitrary; use eyre::Result; use proptest::{ - arbitrary::Arbitrary, - prelude::{any_with, ProptestConfig}, + prelude::ProptestConfig, strategy::{Strategy, ValueTree}, test_runner::TestRunner, }; +use proptest_arbitrary_interop::arb; use reth_db::tables; use reth_db_api::table::{DupSort, Table, TableRow}; use reth_fs_util as fs; +use std::collections::HashSet; use tracing::error; const VECTORS_FOLDER: &str = "testdata/micro/db"; @@ -73,21 +73,14 @@ pub(crate) fn generate_vectors(mut tables: Vec) -> Result<()> { /// Generates test-vectors for normal tables. Keys are sorted and not repeated. fn generate_table_vector(runner: &mut TestRunner, per_table: usize) -> Result<()> where - T::Key: Arbitrary + serde::Serialize + Ord + std::hash::Hash, - T::Value: Arbitrary + serde::Serialize, T: Table, + T::Key: for<'a> Arbitrary<'a> + serde::Serialize + Ord + std::hash::Hash + Clone, + T::Value: for<'a> Arbitrary<'a> + serde::Serialize + Clone, { let mut rows = vec![]; let mut seen_keys = HashSet::new(); - let strategy = proptest::collection::vec( - any_with::>(( - ::Parameters::default(), - ::Parameters::default(), - )), - per_table - rows.len(), - ) - .no_shrink() - .boxed(); + let strategy = + proptest::collection::vec(arb::>(), per_table - rows.len()).no_shrink().boxed(); while rows.len() < per_table { // Generate all `per_table` rows: (Key, Value) @@ -111,23 +104,17 @@ where fn generate_dupsort_vector(runner: &mut TestRunner, per_table: usize) -> Result<()> where T: Table + DupSort, - T::Key: Arbitrary + serde::Serialize + Ord + std::hash::Hash, - T::Value: Arbitrary + serde::Serialize + Ord, + T::Key: for<'a> Arbitrary<'a> + serde::Serialize + Ord + std::hash::Hash + Clone, + T::Value: for<'a> Arbitrary<'a> + serde::Serialize + Ord + Clone, { let mut rows = vec![]; // We want to control our repeated keys let mut seen_keys = HashSet::new(); - let strat_values = proptest::collection::vec( - any_with::(::Parameters::default()), - 100..300, - ) - .no_shrink() - .boxed(); + let strat_values = proptest::collection::vec(arb::(), 100..300).no_shrink().boxed(); - let strat_keys = - any_with::(::Parameters::default()).no_shrink().boxed(); + let strat_keys = arb::().no_shrink().boxed(); while rows.len() < per_table { let key: T::Key = strat_keys.new_tree(runner).map_err(|e| eyre::eyre!("{e}"))?.current(); diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index d7766043e974..f695e926b126 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -27,7 +27,7 @@ serde = { workspace = true, optional = true } # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } -proptest-derive = { workspace = true, optional = true } +proptest-arbitrary-interop = { workspace = true, optional = true } [dev-dependencies] reth-net-common.workspace = true @@ -40,6 +40,7 @@ rand.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true +proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true async-stream.workspace = true @@ -49,7 +50,7 @@ arbitrary = [ "reth-primitives/arbitrary", "dep:arbitrary", "dep:proptest", - "dep:proptest-derive", + "dep:proptest-arbitrary-interop", ] serde = ["dep:serde"] diff --git a/crates/net/eth-wire-types/src/blocks.rs b/crates/net/eth-wire-types/src/blocks.rs index 97268ee642c6..c8668c251f19 100644 --- a/crates/net/eth-wire-types/src/blocks.rs +++ b/crates/net/eth-wire-types/src/blocks.rs @@ -3,12 +3,9 @@ use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use reth_codecs_derive::{add_arbitrary_tests, derive_arbitrary}; -use reth_primitives::{BlockBody, BlockHashOrNumber, Header, HeadersDirection, B256}; - -#[cfg(any(test, feature = "arbitrary"))] -use proptest::{collection::vec, prelude::*}; #[cfg(any(test, feature = "arbitrary"))] -use reth_primitives::{generate_valid_header, valid_header_strategy}; +use reth_primitives::generate_valid_header; +use reth_primitives::{BlockBody, BlockHashOrNumber, Header, HeadersDirection, B256}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -51,18 +48,6 @@ pub struct BlockHeaders( pub Vec

, ); -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for BlockHeaders { - type Parameters = (); - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - let headers_strategy = vec(valid_header_strategy(), 0..10); // Adjust the range as needed - - headers_strategy.prop_map(BlockHeaders).boxed() - } - - type Strategy = proptest::prelude::BoxedStrategy; -} - #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for BlockHeaders { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { @@ -111,12 +96,6 @@ impl From> for GetBlockBodies { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BlockBodies( /// The requested block bodies, each of which should correspond to a hash in the request. - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=20)" - ) - )] pub Vec, ); diff --git a/crates/net/eth-wire-types/src/broadcast.rs b/crates/net/eth-wire-types/src/broadcast.rs index e413ab9ee9fe..1b5a3b4115bd 100644 --- a/crates/net/eth-wire-types/src/broadcast.rs +++ b/crates/net/eth-wire-types/src/broadcast.rs @@ -19,6 +19,8 @@ use std::{ #[cfg(feature = "arbitrary")] use proptest::{collection::vec, prelude::*}; +#[cfg(feature = "arbitrary")] +use proptest_arbitrary_interop::arb; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -351,7 +353,7 @@ impl Arbitrary for NewPooledTransactionHashes68 { .prop_flat_map(|len| { // Use the generated length to create vectors of TxType, usize, and B256 let types_vec = - vec(any::().prop_map(|ty| ty as u8), len..=len); + vec(arb::().prop_map(|ty| ty as u8), len..=len); // Map the usize values to the range 0..131072(0x20000) let sizes_vec = vec(proptest::num::usize::ANY.prop_map(|x| x % 131072), len..=len); diff --git a/crates/net/eth-wire-types/src/receipts.rs b/crates/net/eth-wire-types/src/receipts.rs index 72424b0bd0a7..4816f85548ce 100644 --- a/crates/net/eth-wire-types/src/receipts.rs +++ b/crates/net/eth-wire-types/src/receipts.rs @@ -23,12 +23,6 @@ pub struct GetReceipts( #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Receipts( /// Each receipt hash should correspond to a block hash in the request. - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::collection::vec(proptest::arbitrary::any::(), 0..=50), 0..=5)" - ) - )] pub Vec>, ); diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 0add7b8a59ca..91bf9290f4c3 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -39,8 +39,6 @@ snap = "1.0.5" # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } -proptest = { workspace = true, optional = true } -proptest-derive = { workspace = true, optional = true } [dev-dependencies] reth-net-common.workspace = true @@ -58,6 +56,7 @@ secp256k1 = { workspace = true, features = [ arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true +proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true async-stream.workspace = true @@ -66,8 +65,6 @@ default = ["serde"] arbitrary = [ "reth-primitives/arbitrary", "dep:arbitrary", - "dep:proptest", - "dep:proptest-derive", ] optimism = ["reth-primitives/optimism"] serde = ["dep:serde"] diff --git a/crates/net/eth-wire/src/capability.rs b/crates/net/eth-wire/src/capability.rs index 2bf096287635..776cc375f9f9 100644 --- a/crates/net/eth-wire/src/capability.rs +++ b/crates/net/eth-wire/src/capability.rs @@ -129,22 +129,6 @@ impl<'a> arbitrary::Arbitrary<'a> for Capability { } } -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for Capability { - type Parameters = proptest::arbitrary::ParamsFor; - fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { - use proptest::strategy::Strategy; - proptest::arbitrary::any_with::(args) // TODO: what possible values? - .prop_flat_map(move |name| { - proptest::arbitrary::any_with::(()) // TODO: What's the max? - .prop_map(move |version| Self::new(name.clone(), version)) - }) - .boxed() - } - - type Strategy = proptest::strategy::BoxedStrategy; -} - /// Represents all capabilities of a node. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Capabilities { diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index 050f84c031c2..638cdccc6134 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -19,7 +19,7 @@ alloy-eips.workspace = true alloy-genesis.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true -alloy-rpc-types-eth = { workspace = true, optional = true } +alloy-rpc-types-eth = { workspace = true, optional = true } derive_more.workspace = true revm-primitives = { workspace = true, features = ["serde"] } @@ -37,11 +37,12 @@ serde.workspace = true # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } -proptest-derive = { workspace = true, optional = true } +proptest-arbitrary-interop = { workspace = true, optional = true } [dev-dependencies] arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true +proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true test-fuzz.workspace = true rand.workspace = true @@ -55,6 +56,6 @@ arbitrary = [ "alloy-consensus/arbitrary", "dep:arbitrary", "dep:proptest", - "dep:proptest-derive" + "dep:proptest-arbitrary-interop", ] -alloy-compat = ["alloy-rpc-types-eth"] \ No newline at end of file +alloy-compat = ["alloy-rpc-types-eth"] diff --git a/crates/primitives-traits/src/header/mod.rs b/crates/primitives-traits/src/header/mod.rs index cb85a3363ef7..e817965a6d52 100644 --- a/crates/primitives-traits/src/header/mod.rs +++ b/crates/primitives-traits/src/header/mod.rs @@ -16,11 +16,12 @@ use alloy_primitives::{keccak256, Address, BlockNumber, Bloom, Bytes, B256, B64, use alloy_rlp::{length_of_length, Decodable, Encodable}; use bytes::BufMut; use core::mem; -use reth_codecs::{main_codec, Compact}; +use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; use revm_primitives::{calc_blob_gasprice, calc_excess_blob_gas}; /// Block header -#[main_codec] +#[main_codec(no_arbitrary)] +#[add_arbitrary_tests(rlp, 25)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Header { /// The Keccak 256-bit hash of the parent @@ -487,3 +488,42 @@ impl Decodable for Header { Ok(this) } } + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Header { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // Generate an arbitrary header, passing it to the generate_valid_header function to make + // sure it is valid _with respect to hardforks only_. + let base = Self { + parent_hash: u.arbitrary()?, + ommers_hash: u.arbitrary()?, + beneficiary: u.arbitrary()?, + state_root: u.arbitrary()?, + transactions_root: u.arbitrary()?, + receipts_root: u.arbitrary()?, + logs_bloom: u.arbitrary()?, + difficulty: u.arbitrary()?, + number: u.arbitrary()?, + gas_limit: u.arbitrary()?, + gas_used: u.arbitrary()?, + timestamp: u.arbitrary()?, + extra_data: u.arbitrary()?, + mix_hash: u.arbitrary()?, + nonce: u.arbitrary()?, + base_fee_per_gas: u.arbitrary()?, + blob_gas_used: u.arbitrary()?, + excess_blob_gas: u.arbitrary()?, + parent_beacon_block_root: u.arbitrary()?, + requests_root: u.arbitrary()?, + withdrawals_root: u.arbitrary()?, + }; + + Ok(test_utils::generate_valid_header( + base, + u.arbitrary()?, + u.arbitrary()?, + u.arbitrary()?, + u.arbitrary()?, + )) + } +} diff --git a/crates/primitives-traits/src/header/sealed.rs b/crates/primitives-traits/src/header/sealed.rs index 894bd28aedae..355e4a9bc62a 100644 --- a/crates/primitives-traits/src/header/sealed.rs +++ b/crates/primitives-traits/src/header/sealed.rs @@ -7,8 +7,6 @@ use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; use core::mem; use derive_more::{AsRef, Deref}; -#[cfg(any(test, feature = "arbitrary"))] -use proptest::prelude::*; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; /// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want @@ -130,27 +128,9 @@ impl SealedHeader { } } -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for SealedHeader { - type Parameters = (); - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - // map valid header strategy by sealing - crate::test_utils::valid_header_strategy().prop_map(|header| header.seal_slow()).boxed() - } - type Strategy = proptest::strategy::BoxedStrategy; -} - #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for SealedHeader { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let sealed_header = crate::test_utils::generate_valid_header( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - ) - .seal_slow(); - Ok(sealed_header) + Ok(Header::arbitrary(u)?.seal_slow()) } } diff --git a/crates/primitives-traits/src/header/test_utils.rs b/crates/primitives-traits/src/header/test_utils.rs index 07d00f03bba2..982b779d4b5b 100644 --- a/crates/primitives-traits/src/header/test_utils.rs +++ b/crates/primitives-traits/src/header/test_utils.rs @@ -3,6 +3,7 @@ use crate::Header; use alloy_primitives::B256; use proptest::{arbitrary::any, prop_compose}; +use proptest_arbitrary_interop::arb; /// Generates a header which is valid __with respect to past and future forks__. This means, for /// example, that if the withdrawals root is present, the base fee per gas is also present. @@ -55,11 +56,11 @@ prop_compose! { /// /// See docs for [generate_valid_header] for more information. pub fn valid_header_strategy()( - header in any::
(), + header in arb::
(), eip_4844_active in any::(), blob_gas_used in any::(), excess_blob_gas in any::(), - parent_beacon_block_root in any::() + parent_beacon_block_root in arb::() ) -> Header { generate_valid_header(header, eip_4844_active, blob_gas_used, excess_blob_gas, parent_beacon_block_root) } diff --git a/crates/primitives-traits/src/log.rs b/crates/primitives-traits/src/log.rs index 8233859782b1..aa6bc26a97a9 100644 --- a/crates/primitives-traits/src/log.rs +++ b/crates/primitives-traits/src/log.rs @@ -18,6 +18,7 @@ mod tests { use alloy_primitives::{Address, Bytes, Log as AlloyLog, B256}; use alloy_rlp::{RlpDecodable, RlpEncodable}; use proptest::proptest; + use proptest_arbitrary_interop::arb; use reth_codecs::{main_codec, Compact}; /// This type is kept for compatibility tests after the codec support was added to @@ -28,12 +29,6 @@ mod tests { /// Contract that emitted this log. address: Address, /// Topics of the log. The number of logs depend on what `LOG` opcode is used. - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=5)" - ) - )] topics: Vec, /// Arbitrary length data. data: Bytes, @@ -57,7 +52,7 @@ mod tests { proptest! { #[test] - fn test_roundtrip_conversion_between_log_and_alloy_log(log: Log) { + fn test_roundtrip_conversion_between_log_and_alloy_log(log in arb::()) { // Convert log to buffer and then create alloy_log from buffer and compare let mut compacted_log = Vec::::new(); let len = log.clone().to_compact(&mut compacted_log); diff --git a/crates/primitives-traits/src/withdrawal.rs b/crates/primitives-traits/src/withdrawal.rs index d9285f6bc52b..679c80cab6a0 100644 --- a/crates/primitives-traits/src/withdrawal.rs +++ b/crates/primitives-traits/src/withdrawal.rs @@ -71,6 +71,7 @@ mod tests { use alloy_primitives::Address; use alloy_rlp::{RlpDecodable, RlpEncodable}; use proptest::proptest; + use proptest_arbitrary_interop::arb; /// This type is kept for compatibility tests after the codec support was added to alloy-eips /// Withdrawal type natively @@ -108,7 +109,7 @@ mod tests { proptest!( #[test] - fn test_roundtrip_withdrawal_compat(withdrawal: RethWithdrawal) { + fn test_roundtrip_withdrawal_compat(withdrawal in arb::()) { // Convert to buffer and then create alloy_access_list from buffer and // compare let mut compacted_reth_withdrawal = Vec::::new(); diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 29db6c993a39..1bd31171c1ed 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -51,7 +51,7 @@ zstd = { version = "0.13", features = ["experimental"], optional = true } # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } -proptest-derive = { workspace = true, optional = true } +# proptest-derive = { workspace = true, optional = true } [dev-dependencies] # eth @@ -64,6 +64,7 @@ alloy-eips = { workspace = true, features = ["arbitrary"] } assert_matches.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true +proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true rand.workspace = true serde_json.workspace = true @@ -94,7 +95,6 @@ arbitrary = [ "alloy-eips/arbitrary", "dep:arbitrary", "dep:proptest", - "dep:proptest-derive", "zstd-codec", ] c-kzg = ["dep:c-kzg", "revm-primitives/c-kzg", "dep:tempfile", "alloy-eips/kzg"] diff --git a/crates/primitives/benches/validate_blob_tx.rs b/crates/primitives/benches/validate_blob_tx.rs index ec62353fb688..40f8b90927cb 100644 --- a/crates/primitives/benches/validate_blob_tx.rs +++ b/crates/primitives/benches/validate_blob_tx.rs @@ -10,6 +10,7 @@ use proptest::{ strategy::ValueTree, test_runner::{RngAlgorithm, TestRng, TestRunner}, }; +use proptest_arbitrary_interop::arb; use reth_primitives::{ constants::eip4844::MAINNET_KZG_TRUSTED_SETUP, BlobTransactionSidecar, TxEip4844, }; @@ -42,13 +43,13 @@ fn validate_blob_tx( let mut runner = TestRunner::new_with_rng(config, rng); // generate tx and sidecar - let mut tx = any::().new_tree(&mut runner).unwrap().current(); + let mut tx = arb::().new_tree(&mut runner).unwrap().current(); let mut blob_sidecar = - any::().new_tree(&mut runner).unwrap().current(); + arb::().new_tree(&mut runner).unwrap().current(); while blob_sidecar.blobs.len() < num_blobs as usize { let blob_sidecar_ext = - any::().new_tree(&mut runner).unwrap().current(); + arb::().new_tree(&mut runner).unwrap().current(); // extend the sidecar with the new blobs blob_sidecar.blobs.extend(blob_sidecar_ext.blobs); diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 4980ebe73d5a..9b5e172e8fc2 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -9,7 +9,7 @@ use alloy_rlp::{RlpDecodable, RlpEncodable}; use derive_more::{Deref, DerefMut}; #[cfg(any(test, feature = "arbitrary"))] use proptest::prelude::prop_compose; -use reth_codecs::derive_arbitrary; +use reth_codecs::{add_arbitrary_tests, derive_arbitrary}; #[cfg(any(test, feature = "arbitrary"))] pub use reth_primitives_traits::test_utils::{generate_valid_header, valid_header_strategy}; use reth_primitives_traits::Requests; @@ -31,38 +31,22 @@ prop_compose! { /// Ethereum full block. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. -#[derive_arbitrary(rlp, 25)] +#[add_arbitrary_tests(rlp, 25)] #[derive( Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, Deref, RlpEncodable, RlpDecodable, )] #[rlp(trailing)] pub struct Block { /// Block header. - #[cfg_attr(any(test, feature = "arbitrary"), proptest(strategy = "valid_header_strategy()"))] #[deref] pub header: Header, /// Transactions in this block. - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=100)" - ) - )] pub body: Vec, /// Ommers/uncles header. - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest(strategy = "proptest::collection::vec(valid_header_strategy(), 0..=2)") - )] pub ommers: Vec
, /// Block withdrawals. - #[cfg_attr( - any(test, feature = "arbitrary"), - 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, } @@ -187,6 +171,28 @@ impl Block { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Block { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // first generate up to 100 txs + let transactions = (0..100) + .map(|_| TransactionSigned::arbitrary(u)) + .collect::>>()?; + + // then generate up to 2 ommers + let ommers = (0..2).map(|_| Header::arbitrary(u)).collect::>>()?; + + Ok(Self { + header: u.arbitrary()?, + body: transactions, + ommers, + // for now just generate empty requests, see HACK above + requests: u.arbitrary()?, + withdrawals: u.arbitrary()?, + }) + } +} + /// Sealed block with senders recovered from transactions. #[derive(Debug, Clone, PartialEq, Eq, Default, Deref, DerefMut)] pub struct BlockWithSenders { @@ -278,27 +284,12 @@ pub struct SealedBlock { #[deref_mut] pub header: SealedHeader, /// Transactions with signatures. - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=100)" - ) - )] pub body: Vec, /// Ommer/uncle headers - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest(strategy = "proptest::collection::vec(valid_header_strategy(), 0..=2)") - )] pub ommers: Vec
, /// Block withdrawals. - #[cfg_attr( - any(test, feature = "arbitrary"), - 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, } @@ -548,30 +539,19 @@ impl SealedBlockWithSenders { /// A response to `GetBlockBodies`, containing bodies if any bodies were found. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. -#[derive_arbitrary(rlp, 10)] +#[add_arbitrary_tests(rlp, 10)] #[derive( Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, RlpEncodable, RlpDecodable, )] #[rlp(trailing)] pub struct BlockBody { /// Transactions in the block - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=100)" - ) - )] pub transactions: Vec, /// Uncle headers for the given block - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest(strategy = "proptest::collection::vec(valid_header_strategy(), 0..=2)") - )] 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, } @@ -634,6 +614,22 @@ impl From for BlockBody { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for BlockBody { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // first generate up to 100 txs + let transactions = (0..100) + .map(|_| TransactionSigned::arbitrary(u)) + .collect::>>()?; + + // then generate up to 2 ommers + let ommers = (0..2).map(|_| Header::arbitrary(u)).collect::>>()?; + + // for now just generate empty requests, see HACK above + Ok(Self { transactions, ommers, requests: None, withdrawals: u.arbitrary()? }) + } +} + #[cfg(test)] mod tests { use super::{BlockNumberOrTag::*, *}; diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index da84dda8b4a7..1a9d917d62f7 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -6,8 +6,6 @@ use alloy_rlp::{length_of_length, Decodable, Encodable, RlpDecodable, RlpEncodab use bytes::{Buf, BufMut}; use core::{cmp::Ordering, ops::Deref}; use derive_more::{Deref, DerefMut, From, IntoIterator}; -#[cfg(any(test, feature = "arbitrary"))] -use proptest::strategy::Strategy; #[cfg(feature = "zstd-codec")] use reth_codecs::CompactZstd; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; @@ -186,50 +184,6 @@ pub fn gas_spent_by_transactions>( .collect() } -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for Receipt { - type Parameters = (); - - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - use proptest::prelude::{any, prop_compose}; - - prop_compose! { - fn arbitrary_receipt()(tx_type in any::(), - success in any::(), - cumulative_gas_used in any::(), - logs in proptest::collection::vec(proptest::arbitrary::any::(), 0..=20), - _deposit_nonce in any::>(), - _deposit_receipt_version in any::>()) -> Receipt - { - // Only receipts for deposit transactions may contain a deposit nonce - #[cfg(feature = "optimism")] - let (deposit_nonce, deposit_receipt_version) = if tx_type == TxType::Deposit { - // The deposit receipt version is only present if the deposit nonce is present - let deposit_receipt_version = _deposit_nonce.and(_deposit_receipt_version); - (_deposit_nonce, deposit_receipt_version) - } else { - (None, None) - }; - - Receipt { tx_type, - success, - cumulative_gas_used, - logs, - // Only receipts for deposit transactions may contain a deposit nonce - #[cfg(feature = "optimism")] - deposit_nonce, - // Only receipts for deposit transactions may contain a deposit nonce - #[cfg(feature = "optimism")] - deposit_receipt_version - } - } - } - arbitrary_receipt().boxed() - } - - type Strategy = proptest::strategy::BoxedStrategy; -} - #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for Receipt { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { diff --git a/crates/primitives/src/transaction/access_list.rs b/crates/primitives/src/transaction/access_list.rs index acaf132c479a..22e113fbdc11 100644 --- a/crates/primitives/src/transaction/access_list.rs +++ b/crates/primitives/src/transaction/access_list.rs @@ -10,6 +10,7 @@ mod tests { use crate::{Address, B256}; use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use proptest::proptest; + use proptest_arbitrary_interop::arb; use reth_codecs::{main_codec, Compact}; /// This type is kept for compatibility tests after the codec support was added to alloy-eips @@ -18,12 +19,7 @@ mod tests { #[derive( Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper, )] - struct RethAccessList( - #[proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=20)" - )] - Vec, - ); + struct RethAccessList(Vec); impl PartialEq for RethAccessList { fn eq(&self, other: &AccessList) -> bool { @@ -41,9 +37,6 @@ mod tests { /// The storage keys to be loaded at the start of execution. /// /// Each key is a 32-byte value representing a specific storage slot. - #[proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=20)" - )] storage_keys: Vec, } @@ -55,7 +48,7 @@ mod tests { proptest!( #[test] - fn test_roundtrip_accesslist_compat(access_list: RethAccessList) { + fn test_roundtrip_accesslist_compat(access_list in arb::()) { // Convert access_list to buffer and then create alloy_access_list from buffer and // compare let mut compacted_reth_access_list = Vec::::new(); diff --git a/crates/primitives/src/transaction/eip4844.rs b/crates/primitives/src/transaction/eip4844.rs index f4a2be8e2a84..f792d787afdd 100644 --- a/crates/primitives/src/transaction/eip4844.rs +++ b/crates/primitives/src/transaction/eip4844.rs @@ -148,20 +148,28 @@ impl TxEip4844 { /// - `max_fee_per_blob_gas` /// - `blob_versioned_hashes` pub fn decode_inner(buf: &mut &[u8]) -> alloy_rlp::Result { - Ok(Self { + let mut tx = Self { chain_id: Decodable::decode(buf)?, nonce: Decodable::decode(buf)?, max_priority_fee_per_gas: Decodable::decode(buf)?, max_fee_per_gas: Decodable::decode(buf)?, gas_limit: Decodable::decode(buf)?, - placeholder: Some(()), + placeholder: None, to: Decodable::decode(buf)?, value: Decodable::decode(buf)?, input: Decodable::decode(buf)?, access_list: Decodable::decode(buf)?, max_fee_per_blob_gas: Decodable::decode(buf)?, blob_versioned_hashes: Decodable::decode(buf)?, - }) + }; + + // HACK: our arbitrary implementation sets the placeholder this way for backwards + // compatibility, and should be removed once `placeholder` can be removed + if tx.to != Address::default() { + tx.placeholder = Some(()) + } + + Ok(tx) } /// Outputs the length of the transaction's fields, without a RLP header. diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 42e420a5e9f7..7a6d2a85b514 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1463,40 +1463,6 @@ impl Decodable for TransactionSigned { } } -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for TransactionSigned { - type Parameters = (); - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - use proptest::prelude::{any, Strategy}; - - any::<(Transaction, Signature)>() - .prop_map(move |(mut transaction, sig)| { - if let Some(chain_id) = transaction.chain_id() { - // Otherwise we might overflow when calculating `v` on `recalculate_hash` - transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36)); - } - - #[cfg(feature = "optimism")] - let sig = transaction - .is_deposit() - .then(Signature::optimism_deposit_tx_signature) - .unwrap_or(sig); - - if let Transaction::Eip4844(ref mut tx_eip_4844) = transaction { - tx_eip_4844.placeholder = - if tx_eip_4844.to != Address::default() { Some(()) } else { None }; - } - - let mut tx = Self { hash: Default::default(), signature: sig, transaction }; - tx.hash = tx.recalculate_hash(); - tx - }) - .boxed() - } - - type Strategy = proptest::strategy::BoxedStrategy; -} - #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { @@ -1506,6 +1472,11 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned { transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36)); } + if let Transaction::Eip4844(ref mut tx_eip_4844) = transaction { + tx_eip_4844.placeholder = + if tx_eip_4844.to != Address::default() { Some(()) } else { None }; + } + let signature = Signature::arbitrary(u)?; #[cfg(feature = "optimism")] @@ -1654,6 +1625,7 @@ mod tests { }; use alloy_primitives::{address, b256, bytes}; use alloy_rlp::{Decodable, Encodable, Error as RlpError}; + use proptest_arbitrary_interop::arb; use reth_codecs::Compact; use secp256k1::{Keypair, Secp256k1}; use std::str::FromStr; @@ -1929,7 +1901,7 @@ mod tests { #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1))] #[test] - fn test_parallel_recovery_order(txes in proptest::collection::vec(proptest::prelude::any::(), *PARALLEL_SENDER_RECOVERY_THRESHOLD * 5)) { + fn test_parallel_recovery_order(txes in proptest::collection::vec(arb::(), *PARALLEL_SENDER_RECOVERY_THRESHOLD * 5)) { let mut rng =rand::thread_rng(); let secp = Secp256k1::new(); let txes: Vec = txes.into_iter().map(|mut tx| { diff --git a/crates/primitives/src/transaction/pooled.rs b/crates/primitives/src/transaction/pooled.rs index 2ca58b179748..a72d22d9f907 100644 --- a/crates/primitives/src/transaction/pooled.rs +++ b/crates/primitives/src/transaction/pooled.rs @@ -615,33 +615,6 @@ impl<'a> arbitrary::Arbitrary<'a> for PooledTransactionsElement { } } -#[cfg(any(test, feature = "arbitrary"))] -impl proptest::arbitrary::Arbitrary for PooledTransactionsElement { - type Parameters = (); - fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - use proptest::prelude::{any, Strategy}; - - any::<(TransactionSigned, crate::BlobTransactionSidecar)>() - .prop_map(move |(transaction, sidecar)| { - match Self::try_from(transaction) { - Ok(Self::BlobTransaction(mut tx)) => { - tx.sidecar = sidecar; - Self::BlobTransaction(tx) - } - Ok(tx) => tx, - Err(_) => Self::Eip1559 { - transaction: Default::default(), - signature: Default::default(), - hash: Default::default(), - }, // Gen an Eip1559 as arbitrary for testing purpose - } - }) - .boxed() - } - - type Strategy = proptest::strategy::BoxedStrategy; -} - /// A signed pooled transaction with recovered signer. #[derive(Debug, Clone, PartialEq, Eq, AsRef, Deref)] pub struct PooledTransactionsElementEcRecovered { diff --git a/crates/prune/types/Cargo.toml b/crates/prune/types/Cargo.toml index 3895ebcd05c0..4fd5b9336812 100644 --- a/crates/prune/types/Cargo.toml +++ b/crates/prune/types/Cargo.toml @@ -26,6 +26,7 @@ arbitrary = { workspace = true, features = ["derive"] } assert_matches.workspace = true proptest.workspace = true proptest-derive.workspace = true +proptest-arbitrary-interop.workspace = true serde_json.workspace = true test-fuzz.workspace = true -toml.workspace = true \ No newline at end of file +toml.workspace = true diff --git a/crates/stages/types/Cargo.toml b/crates/stages/types/Cargo.toml index 973cd572ce0b..76bb9f4292c2 100644 --- a/crates/stages/types/Cargo.toml +++ b/crates/stages/types/Cargo.toml @@ -24,5 +24,6 @@ serde.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true proptest-derive.workspace = true +proptest-arbitrary-interop.workspace = true test-fuzz.workspace = true -rand.workspace = true \ No newline at end of file +rand.workspace = true diff --git a/crates/storage/codecs/Cargo.toml b/crates/storage/codecs/Cargo.toml index e370233d1c7d..6864a5cf64fa 100644 --- a/crates/storage/codecs/Cargo.toml +++ b/crates/storage/codecs/Cargo.toml @@ -15,7 +15,7 @@ workspace = true reth-codecs-derive = { path = "./derive", default-features = false } # eth -alloy-consensus = { workspace = true, optional = true } +alloy-consensus = { workspace = true, optional = true, features = ["arbitrary"] } alloy-eips = { workspace = true, optional = true } alloy-genesis = { workspace = true, optional = true } alloy-primitives.workspace = true @@ -37,6 +37,7 @@ serde_json.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true proptest-derive.workspace = true +proptest-arbitrary-interop.workspace = true [features] default = ["std", "alloy"] diff --git a/crates/storage/codecs/derive/src/arbitrary.rs b/crates/storage/codecs/derive/src/arbitrary.rs index 32271c5f0c5b..60bc9074a91a 100644 --- a/crates/storage/codecs/derive/src/arbitrary.rs +++ b/crates/storage/codecs/derive/src/arbitrary.rs @@ -87,12 +87,13 @@ pub fn maybe_generate_tests(args: TokenStream, ast: &DeriveInput) -> TokenStream #[cfg(test)] mod #mod_tests { #(#traits)* + use proptest_arbitrary_interop::arb; #[test] fn proptest() { let mut config = proptest::prelude::ProptestConfig::with_cases(#default_cases as u32); - proptest::proptest!(config, |(field: super::#type_ident)| { + proptest::proptest!(config, |(field in arb::())| { #(#roundtrips)* }); } diff --git a/crates/storage/codecs/derive/src/lib.rs b/crates/storage/codecs/derive/src/lib.rs index 7c668a6cb4aa..e0022edc4cdb 100644 --- a/crates/storage/codecs/derive/src/lib.rs +++ b/crates/storage/codecs/derive/src/lib.rs @@ -69,7 +69,7 @@ pub fn main_codec(args: TokenStream, input: TokenStream) -> TokenStream { derive_arbitrary(TokenStream::from_iter(args), compact) } -/// Adds `Arbitrary` and `proptest::Arbitrary` imports into scope and derives the struct/enum. +/// Adds `Arbitrary` imports into scope and derives the struct/enum. /// /// If `compact` or `rlp` is passed to `derive_arbitrary`, there will be proptest roundtrip tests /// generated. An integer value passed will limit the number of proptest cases generated (default: @@ -89,17 +89,13 @@ pub fn derive_arbitrary(args: TokenStream, input: TokenStream) -> TokenStream { let tests = arbitrary::maybe_generate_tests(args, &ast); // Avoid duplicate names - let prop_import = format_ident!("{}PropTestArbitrary", ast.ident); let arb_import = format_ident!("{}Arbitrary", ast.ident); quote! { - #[cfg(any(test, feature = "arbitrary"))] - use proptest_derive::Arbitrary as #prop_import; - #[cfg(any(test, feature = "arbitrary"))] use arbitrary::Arbitrary as #arb_import; - #[cfg_attr(any(test, feature = "arbitrary"), derive(#prop_import, #arb_import))] + #[cfg_attr(any(test, feature = "arbitrary"), derive(#arb_import))] #ast #tests diff --git a/crates/storage/codecs/src/alloy/request.rs b/crates/storage/codecs/src/alloy/request.rs index c732e30b2bba..388f2a2b5291 100644 --- a/crates/storage/codecs/src/alloy/request.rs +++ b/crates/storage/codecs/src/alloy/request.rs @@ -26,10 +26,11 @@ impl Compact for Request { mod tests { use super::*; use proptest::proptest; + use proptest_arbitrary_interop::arb; proptest! { #[test] - fn roundtrip(request: Request) { + fn roundtrip(request in arb::()) { let mut buf = Vec::::new(); request.to_compact(&mut buf); let (decoded, _) = Request::from_compact(&buf, buf.len()); diff --git a/crates/storage/db-api/Cargo.toml b/crates/storage/db-api/Cargo.toml index a3b33abfd72d..07d402d4afa8 100644 --- a/crates/storage/db-api/Cargo.toml +++ b/crates/storage/db-api/Cargo.toml @@ -36,7 +36,6 @@ bytes.workspace = true # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } -proptest-derive = { workspace = true, optional = true } [dev-dependencies] # reth libs with arbitrary @@ -58,6 +57,7 @@ iai-callgrind.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true +proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true paste.workspace = true @@ -70,6 +70,5 @@ arbitrary = [ "reth-primitives/arbitrary", "dep:arbitrary", "dep:proptest", - "dep:proptest-derive", ] optimism = [] diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index d66da4e191de..f45e198cb4af 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -51,6 +51,7 @@ itertools.workspace = true rand = { workspace = true, optional = true } paste = { workspace = true, optional = true } proptest = { workspace = true, optional = true } +proptest-arbitrary-interop = { workspace = true, optional = true } [dev-dependencies] reth-primitives = { workspace = true, features = ["arbitrary"] } @@ -59,6 +60,7 @@ reth-tracing.workspace = true paste.workspace = true rand.workspace = true proptest.workspace = true +proptest-arbitrary-interop.workspace = true criterion.workspace = true pprof = { workspace = true, features = ["criterion", "flamegraph"] } assert_matches.workspace = true @@ -67,9 +69,9 @@ serde_json.workspace = true [features] default = ["serde"] -arbitrary = ["proptest", "reth-primitives/arbitrary"] serde = ["dep:serde"] test-utils = ["rand", "paste", "serde"] +arbitrary = ["proptest", "reth-primitives/arbitrary", "proptest-arbitrary-interop"] [[bench]] name = "truncate" diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 9eb8d832ef22..72c7a4121730 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -987,9 +987,10 @@ impl From for Transaction { impl proptest::arbitrary::Arbitrary for MockTransaction { type Parameters = (); fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - use proptest::prelude::{any, Strategy}; + use proptest::prelude::Strategy; + use proptest_arbitrary_interop::arb; - any::<(Transaction, Address, B256)>() + arb::<(Transaction, Address, B256)>() .prop_map(|(tx, sender, tx_hash)| match &tx { Transaction::Legacy(TxLegacy { chain_id, diff --git a/crates/trie/common/Cargo.toml b/crates/trie/common/Cargo.toml index f9267c2b8ebc..da5d5a828cbf 100644 --- a/crates/trie/common/Cargo.toml +++ b/crates/trie/common/Cargo.toml @@ -32,13 +32,12 @@ nybbles = { workspace = true, features = ["serde", "rlp"] } hash-db = { version = "=0.15.2", optional = true } plain_hasher = { version = "0.2", optional = true } arbitrary = { workspace = true, features = ["derive"], optional = true } -proptest = { workspace = true, optional = true } -proptest-derive = { workspace = true, optional = true } [dev-dependencies] arbitrary = { workspace = true, features = ["derive"] } assert_matches.workspace = true proptest.workspace = true +proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true serde_json.workspace = true test-fuzz.workspace = true @@ -51,6 +50,4 @@ test-utils = ["dep:plain_hasher", "dep:hash-db", "arbitrary"] arbitrary = [ "alloy-trie/arbitrary", "dep:arbitrary", - "dep:proptest", - "dep:proptest-derive", ] diff --git a/crates/trie/common/src/hash_builder/state.rs b/crates/trie/common/src/hash_builder/state.rs index c1fa9640fca5..c70d7817e4c8 100644 --- a/crates/trie/common/src/hash_builder/state.rs +++ b/crates/trie/common/src/hash_builder/state.rs @@ -148,6 +148,7 @@ impl Compact for HashBuilderState { mod tests { use super::*; use proptest::prelude::*; + use proptest_arbitrary_interop::arb; #[test] fn hash_builder_state_regression() { @@ -161,7 +162,7 @@ mod tests { proptest! { #[test] - fn hash_builder_state_roundtrip(state: HashBuilderState) { + fn hash_builder_state_roundtrip(state in arb::()) { let mut buf = vec![]; let len = state.clone().to_compact(&mut buf); let (decoded, _) = HashBuilderState::from_compact(&buf, len); diff --git a/crates/trie/parallel/Cargo.toml b/crates/trie/parallel/Cargo.toml index 6a55091415d8..36b7cbdc4a28 100644 --- a/crates/trie/parallel/Cargo.toml +++ b/crates/trie/parallel/Cargo.toml @@ -54,6 +54,7 @@ tokio = { workspace = true, default-features = false, features = ["sync", "rt", rayon.workspace = true criterion = { workspace = true, features = ["async_tokio"] } proptest.workspace = true +proptest-arbitrary-interop.workspace = true [features] default = ["metrics", "async", "parallel"] diff --git a/crates/trie/parallel/benches/root.rs b/crates/trie/parallel/benches/root.rs index eba1cdd3daaf..d52702fbcc4a 100644 --- a/crates/trie/parallel/benches/root.rs +++ b/crates/trie/parallel/benches/root.rs @@ -1,6 +1,7 @@ #![allow(missing_docs, unreachable_pub)] use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner}; +use proptest_arbitrary_interop::arb; use rayon::ThreadPoolBuilder; use reth_primitives::{Account, B256, U256}; use reth_provider::{ @@ -82,7 +83,7 @@ fn generate_test_data(size: usize) -> (HashedPostState, HashedPostState) { let db_state = hash_map( any::(), ( - any::().prop_filter("non empty account", |a| !a.is_empty()), + arb::().prop_filter("non empty account", |a| !a.is_empty()), hash_map( any::(), any::().prop_filter("non zero value", |v| !v.is_zero()), diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index ea28d132969e..04b03014e33f 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -57,6 +57,7 @@ triehash = "0.8" # misc proptest.workspace = true +proptest-arbitrary-interop.workspace = true tokio = { workspace = true, default-features = false, features = [ "sync", "rt", diff --git a/crates/trie/trie/benches/trie_root.rs b/crates/trie/trie/benches/trie_root.rs index 8a149404abbb..3f7efecc3a1f 100644 --- a/crates/trie/trie/benches/trie_root.rs +++ b/crates/trie/trie/benches/trie_root.rs @@ -1,6 +1,7 @@ #![allow(missing_docs, unreachable_pub)] use criterion::{black_box, criterion_group, criterion_main, Criterion}; use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner}; +use proptest_arbitrary_interop::arb; use reth_primitives::{ReceiptWithBloom, B256}; use reth_trie::triehash::KeccakHasher; @@ -26,7 +27,7 @@ pub fn trie_root_benchmark(c: &mut Criterion) { } fn generate_test_data(size: usize) -> Vec { - prop::collection::vec(any::(), size) + prop::collection::vec(arb::(), size) .new_tree(&mut TestRunner::new(ProptestConfig::default())) .unwrap() .current() diff --git a/crates/trie/trie/src/hashed_cursor/post_state.rs b/crates/trie/trie/src/hashed_cursor/post_state.rs index df04d332d2c6..41b051b2a7a7 100644 --- a/crates/trie/trie/src/hashed_cursor/post_state.rs +++ b/crates/trie/trie/src/hashed_cursor/post_state.rs @@ -374,6 +374,7 @@ mod tests { use super::*; use crate::{HashedPostState, HashedStorage}; use proptest::prelude::*; + use proptest_arbitrary_interop::arb; use reth_db::{tables, test_utils::create_test_rw_db}; use reth_db_api::{database::Database, transaction::DbTxMut}; use reth_primitives::StorageEntry; @@ -537,7 +538,7 @@ mod tests { #[test] fn fuzz_hashed_account_cursor() { - proptest!(ProptestConfig::with_cases(10), |(db_accounts: BTreeMap, post_state_accounts: BTreeMap>)| { + proptest!(ProptestConfig::with_cases(10), |(db_accounts in arb::>(), post_state_accounts in arb::>>())| { let db = create_test_rw_db(); db.update(|tx| { for (key, account) in &db_accounts { diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 3fe97a6f788c..25ae616584d7 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -551,6 +551,7 @@ mod tests { BranchNodeCompact, TrieMask, }; use proptest::{prelude::ProptestConfig, proptest}; + use proptest_arbitrary_interop::arb; use reth_db::{tables, test_utils::TempDatabase, DatabaseEnv}; use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, @@ -649,7 +650,7 @@ mod tests { #[test] fn arbitrary_storage_root() { - proptest!(ProptestConfig::with_cases(10), |(item: (Address, std::collections::BTreeMap))| { + proptest!(ProptestConfig::with_cases(10), |(item in arb::<(Address, std::collections::BTreeMap)>())| { let (address, storage) = item; let hashed_address = keccak256(address); @@ -759,7 +760,7 @@ mod tests { #[test] fn arbitrary_state_root() { proptest!( - ProptestConfig::with_cases(10), | (state: State) | { + ProptestConfig::with_cases(10), | (state in arb::()) | { test_state_root_with_state(state); } ); @@ -768,7 +769,7 @@ mod tests { #[test] fn arbitrary_state_root_with_progress() { proptest!( - ProptestConfig::with_cases(10), | (state: State) | { + ProptestConfig::with_cases(10), | (state in arb::()) | { let hashed_entries_total = state.len() + state.values().map(|(_, slots)| slots.len()).sum::(); From a03306eb9442619cb9d316e11488154348f1d05d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 01:20:34 +0200 Subject: [PATCH 161/405] chore(deps): rm unused dev deps (#9005) --- Cargo.lock | 5 ----- crates/net/eth-wire-types/Cargo.toml | 8 +------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fa1553c5cb5..b284d347868c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6911,7 +6911,6 @@ dependencies = [ "alloy-genesis", "alloy-rlp", "arbitrary", - "async-stream", "bytes", "derive_more", "proptest", @@ -6920,13 +6919,9 @@ dependencies = [ "rand 0.8.5", "reth-chainspec", "reth-codecs-derive", - "reth-net-common", "reth-primitives", - "reth-tracing", "serde", - "test-fuzz", "thiserror", - "tokio-util", ] [[package]] diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index f695e926b126..242a7324367a 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -30,19 +30,13 @@ proptest = { workspace = true, optional = true } proptest-arbitrary-interop = { workspace = true, optional = true } [dev-dependencies] -reth-net-common.workspace = true reth-primitives = { workspace = true, features = ["arbitrary"] } -reth-tracing.workspace = true - -test-fuzz.workspace = true -tokio-util = { workspace = true, features = ["io", "codec"] } -rand.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true -async-stream.workspace = true +rand.workspace = true [features] default = ["serde"] From d6072e79f3b46d0cb545fd31e41a838a19917d26 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 01:29:10 +0200 Subject: [PATCH 162/405] chore(deps): rm provider dep (#9006) --- Cargo.lock | 1 + crates/net/downloaders/Cargo.toml | 8 ++++---- crates/net/downloaders/src/bodies/bodies.rs | 2 +- crates/net/downloaders/src/bodies/task.rs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b284d347868c..3ebaa9a26002 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6776,6 +6776,7 @@ dependencies = [ "reth-network-peers", "reth-primitives", "reth-provider", + "reth-storage-api", "reth-tasks", "reth-testing-utils", "reth-tracing", diff --git a/crates/net/downloaders/Cargo.toml b/crates/net/downloaders/Cargo.toml index 15ede0335d3a..4f009b44527a 100644 --- a/crates/net/downloaders/Cargo.toml +++ b/crates/net/downloaders/Cargo.toml @@ -13,13 +13,13 @@ workspace = true [dependencies] # reth -reth-primitives.workspace = true -reth-network-p2p.workspace = true -reth-tasks.workspace = true -reth-provider.workspace = true reth-config.workspace = true reth-consensus.workspace = true +reth-network-p2p.workspace = true reth-network-peers.workspace = true +reth-primitives.workspace = true +reth-storage-api.workspace = true +reth-tasks.workspace = true # optional deps for the test-utils feature reth-db = { workspace = true, optional = true } diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index 27a576e2f702..9d651eee13a2 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -13,7 +13,7 @@ use reth_network_p2p::{ error::{DownloadError, DownloadResult}, }; use reth_primitives::{BlockNumber, SealedHeader}; -use reth_provider::HeaderProvider; +use reth_storage_api::HeaderProvider; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use std::{ cmp::Ordering, diff --git a/crates/net/downloaders/src/bodies/task.rs b/crates/net/downloaders/src/bodies/task.rs index 42b21d5f1699..aa5bc27527e2 100644 --- a/crates/net/downloaders/src/bodies/task.rs +++ b/crates/net/downloaders/src/bodies/task.rs @@ -45,7 +45,7 @@ impl TaskDownloader { /// use reth_consensus::Consensus; /// use reth_downloaders::bodies::{bodies::BodiesDownloaderBuilder, task::TaskDownloader}; /// use reth_network_p2p::bodies::client::BodiesClient; - /// use reth_provider::HeaderProvider; + /// use reth_storage_api::HeaderProvider; /// use std::sync::Arc; /// /// fn t( From 50c1a8e48aef0fd577659db915275ac64bdf7cab Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 02:10:07 +0200 Subject: [PATCH 163/405] chore: move ratelimit type to tokio util (#9007) --- Cargo.lock | 3 +-- crates/net/common/Cargo.toml | 5 +---- crates/net/common/src/lib.rs | 2 -- crates/net/dns/Cargo.toml | 2 +- crates/net/dns/src/query.rs | 2 +- crates/tokio-util/Cargo.toml | 5 ++++- crates/tokio-util/src/lib.rs | 3 +++ crates/{net/common => tokio-util}/src/ratelimit.rs | 0 8 files changed, 11 insertions(+), 11 deletions(-) rename crates/{net/common => tokio-util}/src/ratelimit.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 3ebaa9a26002..bb62e8c4729d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6739,8 +6739,8 @@ dependencies = [ "rand 0.8.5", "reth-chainspec", "reth-ethereum-forks", - "reth-net-common", "reth-network-peers", + "reth-tokio-util", "reth-tracing", "schnellru", "secp256k1", @@ -7225,7 +7225,6 @@ name = "reth-net-common" version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", - "tokio", ] [[package]] diff --git a/crates/net/common/Cargo.toml b/crates/net/common/Cargo.toml index 975c2bdecc74..360c27c0aeb4 100644 --- a/crates/net/common/Cargo.toml +++ b/crates/net/common/Cargo.toml @@ -13,7 +13,4 @@ workspace = true [dependencies] # ethereum -alloy-primitives.workspace = true - -# async -tokio = { workspace = true, features = ["time"] } +alloy-primitives.workspace = true \ No newline at end of file diff --git a/crates/net/common/src/lib.rs b/crates/net/common/src/lib.rs index 3020abf26d66..b4fcc48d675f 100644 --- a/crates/net/common/src/lib.rs +++ b/crates/net/common/src/lib.rs @@ -9,5 +9,3 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod ban_list; - -pub mod ratelimit; diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index b20253a5bf3a..64419db96f28 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -14,8 +14,8 @@ workspace = true [dependencies] # reth reth-ethereum-forks.workspace = true -reth-net-common.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } +reth-tokio-util = { workspace = true, features = ["time"] } # ethereum alloy-primitives.workspace = true diff --git a/crates/net/dns/src/query.rs b/crates/net/dns/src/query.rs index a1c67740ed7a..6023f82dcf26 100644 --- a/crates/net/dns/src/query.rs +++ b/crates/net/dns/src/query.rs @@ -7,7 +7,7 @@ use crate::{ tree::{DnsEntry, LinkEntry, TreeRootEntry}, }; use enr::EnrKeyUnambiguous; -use reth_net_common::ratelimit::{Rate, RateLimit}; +use reth_tokio_util::ratelimit::{Rate, RateLimit}; use std::{ collections::VecDeque, future::Future, diff --git a/crates/tokio-util/Cargo.toml b/crates/tokio-util/Cargo.toml index ccace030c0f7..3a8ad768d59b 100644 --- a/crates/tokio-util/Cargo.toml +++ b/crates/tokio-util/Cargo.toml @@ -19,4 +19,7 @@ tokio = { workspace = true, features = ["sync"] } tokio-stream = { workspace = true, features = ["sync"] } [dev-dependencies] -tokio = { workspace = true, features = ["full", "macros"] } \ No newline at end of file +tokio = { workspace = true, features = ["full", "macros"] } + +[features] +time = ["tokio/time"] \ No newline at end of file diff --git a/crates/tokio-util/src/lib.rs b/crates/tokio-util/src/lib.rs index 2053bf60bc56..e476c4063d9e 100644 --- a/crates/tokio-util/src/lib.rs +++ b/crates/tokio-util/src/lib.rs @@ -12,3 +12,6 @@ mod event_sender; mod event_stream; pub use event_sender::EventSender; pub use event_stream::EventStream; + +#[cfg(feature = "time")] +pub mod ratelimit; diff --git a/crates/net/common/src/ratelimit.rs b/crates/tokio-util/src/ratelimit.rs similarity index 100% rename from crates/net/common/src/ratelimit.rs rename to crates/tokio-util/src/ratelimit.rs From b567f66fab1c747e32eeb63630904af07ecab3e0 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:40:43 +0200 Subject: [PATCH 164/405] chore: move different chain hardfork sets to `reth-ethereum-forks` (#8984) --- crates/chainspec/src/spec.rs | 202 +------------------ crates/ethereum-forks/src/chains/dev.rs | 23 +++ crates/ethereum-forks/src/chains/ethereum.rs | 94 +++++++++ crates/ethereum-forks/src/chains/mod.rs | 9 + crates/ethereum-forks/src/chains/optimism.rs | 105 ++++++++++ crates/ethereum-forks/src/lib.rs | 3 + 6 files changed, 245 insertions(+), 191 deletions(-) create mode 100644 crates/ethereum-forks/src/chains/dev.rs create mode 100644 crates/ethereum-forks/src/chains/ethereum.rs create mode 100644 crates/ethereum-forks/src/chains/mod.rs create mode 100644 crates/ethereum-forks/src/chains/optimism.rs diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index ff2407313c5d..10ec06c2dad4 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -14,6 +14,7 @@ use alloy_trie::EMPTY_ROOT_HASH; use derive_more::From; use once_cell::sync::Lazy; use reth_ethereum_forks::{ + chains::ethereum::{GOERLI_HARDFORKS, HOLESKY_HARDFORKS, MAINNET_HARDFORKS, SEPOLIA_HARDFORKS}, DisplayHardforks, ForkCondition, ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head, }; use reth_network_peers::NodeRecord; @@ -35,6 +36,8 @@ use crate::constants::optimism::{ OP_CANYON_BASE_FEE_PARAMS, OP_SEPOLIA_BASE_FEE_PARAMS, OP_SEPOLIA_CANYON_BASE_FEE_PARAMS, }; pub use alloy_eips::eip1559::BaseFeeParams; +#[cfg(feature = "optimism")] +use reth_ethereum_forks::chains::optimism::*; #[cfg(feature = "optimism")] use crate::net::{base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes}; @@ -54,31 +57,7 @@ pub static MAINNET: Lazy> = Lazy::new(|| { 15537394, U256::from(58_750_003_716_598_352_816_469u128), )), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(1150000)), - (Hardfork::Dao, ForkCondition::Block(1920000)), - (Hardfork::Tangerine, ForkCondition::Block(2463000)), - (Hardfork::SpuriousDragon, ForkCondition::Block(2675000)), - (Hardfork::Byzantium, ForkCondition::Block(4370000)), - (Hardfork::Constantinople, ForkCondition::Block(7280000)), - (Hardfork::Petersburg, ForkCondition::Block(7280000)), - (Hardfork::Istanbul, ForkCondition::Block(9069000)), - (Hardfork::MuirGlacier, ForkCondition::Block(9200000)), - (Hardfork::Berlin, ForkCondition::Block(12244000)), - (Hardfork::London, ForkCondition::Block(12965000)), - (Hardfork::ArrowGlacier, ForkCondition::Block(13773000)), - (Hardfork::GrayGlacier, ForkCondition::Block(15050000)), - ( - Hardfork::Paris, - ForkCondition::TTD { - fork_block: None, - total_difficulty: U256::from(58_750_000_000_000_000_000_000_u128), - }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1681338455)), - (Hardfork::Cancun, ForkCondition::Timestamp(1710338135)), - ]), + hardforks: MAINNET_HARDFORKS.into(), // https://etherscan.io/tx/0xe75fb554e433e03763a1560646ee22dcb74e5274b34c5ad644e7c0f619a7e1d0 deposit_contract: Some(DepositContract::new( address!("00000000219ab540356cbb839cbe05303d7705fa"), @@ -102,25 +81,7 @@ pub static GOERLI: Lazy> = Lazy::new(|| { )), // paris_block_and_final_difficulty: Some((7382818, U256::from(10_790_000))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(1561651)), - (Hardfork::Berlin, ForkCondition::Block(4460644)), - (Hardfork::London, ForkCondition::Block(5062605)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: None, total_difficulty: U256::from(10_790_000) }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1678832736)), - (Hardfork::Cancun, ForkCondition::Timestamp(1705473120)), - ]), + hardforks: GOERLI_HARDFORKS.into(), // https://goerli.etherscan.io/tx/0xa3c07dc59bfdb1bfc2d50920fed2ef2c1c4e0a09fe2325dbc14e07702f965a78 deposit_contract: Some(DepositContract::new( address!("ff50ed3d0ec03ac01d4c79aad74928bff48a7b2b"), @@ -144,29 +105,7 @@ pub static SEPOLIA: Lazy> = Lazy::new(|| { )), // paris_block_and_final_difficulty: Some((1450409, U256::from(17_000_018_015_853_232u128))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { - fork_block: Some(1735371), - total_difficulty: U256::from(17_000_000_000_000_000u64), - }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1677557088)), - (Hardfork::Cancun, ForkCondition::Timestamp(1706655072)), - ]), + hardforks: SEPOLIA_HARDFORKS.into(), // https://sepolia.etherscan.io/tx/0x025ecbf81a2f1220da6285d1701dc89fb5a956b62562ee922e1a9efd73eb4b14 deposit_contract: Some(DepositContract::new( address!("7f02c3e3c98b133055b8b348b2ac625669ed295d"), @@ -189,26 +128,7 @@ pub static HOLESKY: Lazy> = Lazy::new(|| { "b5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4" )), paris_block_and_final_difficulty: Some((0, U256::from(1))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1696000704)), - (Hardfork::Cancun, ForkCondition::Timestamp(1707305664)), - ]), + hardforks: HOLESKY_HARDFORKS.into(), deposit_contract: Some(DepositContract::new( address!("4242424242424242424242424242424242424242"), 0, @@ -279,32 +199,7 @@ pub static OP_MAINNET: Lazy> = Lazy::new(|| { "7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(3950000)), - (Hardfork::London, ForkCondition::Block(105235063)), - (Hardfork::ArrowGlacier, ForkCondition::Block(105235063)), - (Hardfork::GrayGlacier, ForkCondition::Block(105235063)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(105235063), total_difficulty: U256::from(0) }, - ), - (Hardfork::Bedrock, ForkCondition::Block(105235063)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1704992401)), - (Hardfork::Canyon, ForkCondition::Timestamp(1704992401)), - (Hardfork::Cancun, ForkCondition::Timestamp(1710374401)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1710374401)), - (Hardfork::Fjord, ForkCondition::Timestamp(1720627201)), - ]), + hardforks: OP_MAINNET_HARDFORKS.into(), base_fee_params: BaseFeeParamsKind::Variable( vec![ (Hardfork::London, OP_BASE_FEE_PARAMS), @@ -329,32 +224,7 @@ pub static OP_SEPOLIA: Lazy> = Lazy::new(|| { "102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - (Hardfork::ArrowGlacier, ForkCondition::Block(0)), - (Hardfork::GrayGlacier, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) }, - ), - (Hardfork::Bedrock, ForkCondition::Block(0)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), - (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), - (Hardfork::Cancun, ForkCondition::Timestamp(1708534800)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1708534800)), - (Hardfork::Fjord, ForkCondition::Timestamp(1716998400)), - ]), + hardforks: OP_SEPOLIA_HARDFORKS.into(), base_fee_params: BaseFeeParamsKind::Variable( vec![ (Hardfork::London, OP_SEPOLIA_BASE_FEE_PARAMS), @@ -379,32 +249,7 @@ pub static BASE_SEPOLIA: Lazy> = Lazy::new(|| { "0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - (Hardfork::ArrowGlacier, ForkCondition::Block(0)), - (Hardfork::GrayGlacier, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) }, - ), - (Hardfork::Bedrock, ForkCondition::Block(0)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), - (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), - (Hardfork::Cancun, ForkCondition::Timestamp(1708534800)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1708534800)), - (Hardfork::Fjord, ForkCondition::Timestamp(1716998400)), - ]), + hardforks: BASE_SEPOLIA_HARDFORKS.into(), base_fee_params: BaseFeeParamsKind::Variable( vec![ (Hardfork::London, BASE_SEPOLIA_BASE_FEE_PARAMS), @@ -429,32 +274,7 @@ pub static BASE_MAINNET: Lazy> = Lazy::new(|| { "f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - (Hardfork::ArrowGlacier, ForkCondition::Block(0)), - (Hardfork::GrayGlacier, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) }, - ), - (Hardfork::Bedrock, ForkCondition::Block(0)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1704992401)), - (Hardfork::Canyon, ForkCondition::Timestamp(1704992401)), - (Hardfork::Cancun, ForkCondition::Timestamp(1710374401)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1710374401)), - (Hardfork::Fjord, ForkCondition::Timestamp(1720627201)), - ]), + hardforks: BASE_MAINNET_HARDFORKS.into(), base_fee_params: BaseFeeParamsKind::Variable( vec![ (Hardfork::London, OP_BASE_FEE_PARAMS), diff --git a/crates/ethereum-forks/src/chains/dev.rs b/crates/ethereum-forks/src/chains/dev.rs new file mode 100644 index 000000000000..866be0dd4260 --- /dev/null +++ b/crates/ethereum-forks/src/chains/dev.rs @@ -0,0 +1,23 @@ +use crate::{ForkCondition, Hardfork}; +use alloy_primitives::uint; + +/// Dev hardforks +pub const DEV_HARDFORKS: [(Hardfork, ForkCondition); 14] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Dao, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(1561651)), + (Hardfork::Berlin, ForkCondition::Block(4460644)), + (Hardfork::London, ForkCondition::Block(5062605)), + ( + Hardfork::Paris, + ForkCondition::TTD { fork_block: None, total_difficulty: uint!(10_790_000_U256) }, + ), + (Hardfork::Shanghai, ForkCondition::Timestamp(1678832736)), + (Hardfork::Cancun, ForkCondition::Timestamp(1705473120)), +]; diff --git a/crates/ethereum-forks/src/chains/ethereum.rs b/crates/ethereum-forks/src/chains/ethereum.rs new file mode 100644 index 000000000000..6db4d95fcade --- /dev/null +++ b/crates/ethereum-forks/src/chains/ethereum.rs @@ -0,0 +1,94 @@ +use crate::{ForkCondition, Hardfork}; +use alloy_primitives::{uint, U256}; + +/// Ethereum mainnet hardforks +pub const MAINNET_HARDFORKS: [(Hardfork, ForkCondition); 17] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(1150000)), + (Hardfork::Dao, ForkCondition::Block(1920000)), + (Hardfork::Tangerine, ForkCondition::Block(2463000)), + (Hardfork::SpuriousDragon, ForkCondition::Block(2675000)), + (Hardfork::Byzantium, ForkCondition::Block(4370000)), + (Hardfork::Constantinople, ForkCondition::Block(7280000)), + (Hardfork::Petersburg, ForkCondition::Block(7280000)), + (Hardfork::Istanbul, ForkCondition::Block(9069000)), + (Hardfork::MuirGlacier, ForkCondition::Block(9200000)), + (Hardfork::Berlin, ForkCondition::Block(12244000)), + (Hardfork::London, ForkCondition::Block(12965000)), + (Hardfork::ArrowGlacier, ForkCondition::Block(13773000)), + (Hardfork::GrayGlacier, ForkCondition::Block(15050000)), + ( + Hardfork::Paris, + ForkCondition::TTD { + fork_block: None, + total_difficulty: uint!(58_750_000_000_000_000_000_000_U256), + }, + ), + (Hardfork::Shanghai, ForkCondition::Timestamp(1681338455)), + (Hardfork::Cancun, ForkCondition::Timestamp(1710338135)), +]; + +/// Ethereum Goerli hardforks +pub const GOERLI_HARDFORKS: [(Hardfork, ForkCondition); 14] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Dao, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(1561651)), + (Hardfork::Berlin, ForkCondition::Block(4460644)), + (Hardfork::London, ForkCondition::Block(5062605)), + ( + Hardfork::Paris, + ForkCondition::TTD { fork_block: None, total_difficulty: uint!(10_790_000_U256) }, + ), + (Hardfork::Shanghai, ForkCondition::Timestamp(1678832736)), + (Hardfork::Cancun, ForkCondition::Timestamp(1705473120)), +]; + +/// Ethereum Sepolia hardforks +pub const SEPOLIA_HARDFORKS: [(Hardfork, ForkCondition); 15] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Dao, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(0)), + (Hardfork::London, ForkCondition::Block(0)), + ( + Hardfork::Paris, + ForkCondition::TTD { + fork_block: Some(1735371), + total_difficulty: uint!(17_000_000_000_000_000_U256), + }, + ), + (Hardfork::Shanghai, ForkCondition::Timestamp(1677557088)), + (Hardfork::Cancun, ForkCondition::Timestamp(1706655072)), +]; + +/// Ethereum Holesky hardforks +pub const HOLESKY_HARDFORKS: [(Hardfork, ForkCondition); 15] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Dao, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(0)), + (Hardfork::London, ForkCondition::Block(0)), + (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), + (Hardfork::Shanghai, ForkCondition::Timestamp(1696000704)), + (Hardfork::Cancun, ForkCondition::Timestamp(1707305664)), +]; diff --git a/crates/ethereum-forks/src/chains/mod.rs b/crates/ethereum-forks/src/chains/mod.rs new file mode 100644 index 000000000000..ef775777f399 --- /dev/null +++ b/crates/ethereum-forks/src/chains/mod.rs @@ -0,0 +1,9 @@ +/// Ethereum chains +pub mod ethereum; + +/// Optimism chains +#[cfg(feature = "optimism")] +pub mod optimism; + +/// Dev chain +pub mod dev; diff --git a/crates/ethereum-forks/src/chains/optimism.rs b/crates/ethereum-forks/src/chains/optimism.rs new file mode 100644 index 000000000000..37af4a19ffe6 --- /dev/null +++ b/crates/ethereum-forks/src/chains/optimism.rs @@ -0,0 +1,105 @@ +use crate::{ForkCondition, Hardfork}; +use alloy_primitives::U256; + +/// Optimism mainnet hardforks +pub const OP_MAINNET_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(3950000)), + (Hardfork::London, ForkCondition::Block(105235063)), + (Hardfork::ArrowGlacier, ForkCondition::Block(105235063)), + (Hardfork::GrayGlacier, ForkCondition::Block(105235063)), + ( + Hardfork::Paris, + ForkCondition::TTD { fork_block: Some(105235063), total_difficulty: U256::ZERO }, + ), + (Hardfork::Bedrock, ForkCondition::Block(105235063)), + (Hardfork::Regolith, ForkCondition::Timestamp(0)), + (Hardfork::Shanghai, ForkCondition::Timestamp(1704992401)), + (Hardfork::Canyon, ForkCondition::Timestamp(1704992401)), + (Hardfork::Cancun, ForkCondition::Timestamp(1710374401)), + (Hardfork::Ecotone, ForkCondition::Timestamp(1710374401)), + (Hardfork::Fjord, ForkCondition::Timestamp(1720627201)), +]; + +/// Optimism Sepolia hardforks +pub const OP_SEPOLIA_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(0)), + (Hardfork::London, ForkCondition::Block(0)), + (Hardfork::ArrowGlacier, ForkCondition::Block(0)), + (Hardfork::GrayGlacier, ForkCondition::Block(0)), + (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), + (Hardfork::Bedrock, ForkCondition::Block(0)), + (Hardfork::Regolith, ForkCondition::Timestamp(0)), + (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), + (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), + (Hardfork::Cancun, ForkCondition::Timestamp(1708534800)), + (Hardfork::Ecotone, ForkCondition::Timestamp(1708534800)), + (Hardfork::Fjord, ForkCondition::Timestamp(1716998400)), +]; + +/// Base Sepolia hardforks +pub const BASE_SEPOLIA_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(0)), + (Hardfork::London, ForkCondition::Block(0)), + (Hardfork::ArrowGlacier, ForkCondition::Block(0)), + (Hardfork::GrayGlacier, ForkCondition::Block(0)), + (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), + (Hardfork::Bedrock, ForkCondition::Block(0)), + (Hardfork::Regolith, ForkCondition::Timestamp(0)), + (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), + (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), + (Hardfork::Cancun, ForkCondition::Timestamp(1708534800)), + (Hardfork::Ecotone, ForkCondition::Timestamp(1708534800)), + (Hardfork::Fjord, ForkCondition::Timestamp(1716998400)), +]; + +/// Base Mainnet hardforks +pub const BASE_MAINNET_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ + (Hardfork::Frontier, ForkCondition::Block(0)), + (Hardfork::Homestead, ForkCondition::Block(0)), + (Hardfork::Tangerine, ForkCondition::Block(0)), + (Hardfork::SpuriousDragon, ForkCondition::Block(0)), + (Hardfork::Byzantium, ForkCondition::Block(0)), + (Hardfork::Constantinople, ForkCondition::Block(0)), + (Hardfork::Petersburg, ForkCondition::Block(0)), + (Hardfork::Istanbul, ForkCondition::Block(0)), + (Hardfork::MuirGlacier, ForkCondition::Block(0)), + (Hardfork::Berlin, ForkCondition::Block(0)), + (Hardfork::London, ForkCondition::Block(0)), + (Hardfork::ArrowGlacier, ForkCondition::Block(0)), + (Hardfork::GrayGlacier, ForkCondition::Block(0)), + (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), + (Hardfork::Bedrock, ForkCondition::Block(0)), + (Hardfork::Regolith, ForkCondition::Timestamp(0)), + (Hardfork::Shanghai, ForkCondition::Timestamp(1704992401)), + (Hardfork::Canyon, ForkCondition::Timestamp(1704992401)), + (Hardfork::Cancun, ForkCondition::Timestamp(1710374401)), + (Hardfork::Ecotone, ForkCondition::Timestamp(1710374401)), + (Hardfork::Fjord, ForkCondition::Timestamp(1720627201)), +]; diff --git a/crates/ethereum-forks/src/lib.rs b/crates/ethereum-forks/src/lib.rs index c7831026905f..1a7e0f56e707 100644 --- a/crates/ethereum-forks/src/lib.rs +++ b/crates/ethereum-forks/src/lib.rs @@ -35,5 +35,8 @@ pub use head::Head; pub use display::DisplayHardforks; pub use forkcondition::ForkCondition; +/// Chains hardforks +pub mod chains; + #[cfg(any(test, feature = "arbitrary"))] pub use arbitrary; From a493b6270d1c61391b24c7d81e7723d3c8b89905 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:05:37 +0200 Subject: [PATCH 165/405] chore: remove `AllGenesisFormats` (#9013) --- crates/chainspec/src/lib.rs | 4 +- crates/chainspec/src/spec.rs | 75 ++-------------------- crates/node-core/Cargo.toml | 2 +- crates/node-core/src/args/utils.rs | 100 +---------------------------- 4 files changed, 12 insertions(+), 169 deletions(-) diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 51f30311a742..8dd598abdfb4 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -12,8 +12,8 @@ pub use alloy_chains::{Chain, ChainKind, NamedChain}; pub use info::ChainInfo; pub use spec::{ - AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, - DepositContract, ForkBaseFeeParams, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, + BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, + ForkBaseFeeParams, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, }; #[cfg(feature = "optimism")] pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 10ec06c2dad4..2d88f4508d51 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -922,43 +922,6 @@ impl From for ChainSpec { } } -/// A helper type for compatibility with geth's config -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(untagged)] -pub enum AllGenesisFormats { - /// The reth genesis format - Reth(ChainSpec), - /// The geth genesis format - Geth(Genesis), -} - -impl From for AllGenesisFormats { - fn from(genesis: Genesis) -> Self { - Self::Geth(genesis) - } -} - -impl From for AllGenesisFormats { - fn from(genesis: ChainSpec) -> Self { - Self::Reth(genesis) - } -} - -impl From> for AllGenesisFormats { - fn from(genesis: Arc) -> Self { - Arc::try_unwrap(genesis).unwrap_or_else(|arc| (*arc).clone()).into() - } -} - -impl From for ChainSpec { - fn from(genesis: AllGenesisFormats) -> Self { - match genesis { - AllGenesisFormats::Geth(genesis) => genesis.into(), - AllGenesisFormats::Reth(genesis) => genesis, - } - } -} - /// A helper to build custom chain specs #[derive(Debug, Default, Clone)] pub struct ChainSpecBuilder { @@ -2473,8 +2436,7 @@ Post-merge hard forks (timestamp based): } "#; - let _genesis = serde_json::from_str::(hive_json).unwrap(); - let genesis = serde_json::from_str::(hive_json).unwrap(); + let genesis = serde_json::from_str::(hive_json).unwrap(); let chainspec: ChainSpec = genesis.into(); assert_eq!(chainspec.genesis_hash, None); assert_eq!(chainspec.chain, Chain::from_named(NamedChain::Optimism)); @@ -2659,13 +2621,7 @@ Post-merge hard forks (timestamp based): #[test] fn test_parse_prague_genesis_all_formats() { let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661, "pragueTime": 4662},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#; - let genesis: AllGenesisFormats = serde_json::from_str(s).unwrap(); - - // this should be the genesis format - let genesis = match genesis { - AllGenesisFormats::Geth(genesis) => genesis, - _ => panic!("expected geth genesis format"), - }; + let genesis: Genesis = serde_json::from_str(s).unwrap(); // assert that the alloc was picked up let acc = genesis @@ -2682,13 +2638,7 @@ Post-merge hard forks (timestamp based): #[test] fn test_parse_cancun_genesis_all_formats() { let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0,"cancunTime":4661},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#; - let genesis: AllGenesisFormats = serde_json::from_str(s).unwrap(); - - // this should be the genesis format - let genesis = match genesis { - AllGenesisFormats::Geth(genesis) => genesis, - _ => panic!("expected geth genesis format"), - }; + let genesis: Genesis = serde_json::from_str(s).unwrap(); // assert that the alloc was picked up let acc = genesis @@ -2755,7 +2705,7 @@ Post-merge hard forks (timestamp based): } #[test] - fn test_all_genesis_formats_deserialization() { + fn test_genesis_format_deserialization() { // custom genesis with chain config let config = ChainConfig { chain_id: 2600, @@ -2793,22 +2743,9 @@ Post-merge hard forks (timestamp based): // ensure genesis is deserialized correctly let serialized_genesis = serde_json::to_string(&genesis).unwrap(); - let deserialized_genesis: AllGenesisFormats = - serde_json::from_str(&serialized_genesis).unwrap(); - assert!(matches!(deserialized_genesis, AllGenesisFormats::Geth(_))); - - // build chain - let chain_spec = ChainSpecBuilder::default() - .chain(2600.into()) - .genesis(genesis) - .cancun_activated() - .build(); + let deserialized_genesis: Genesis = serde_json::from_str(&serialized_genesis).unwrap(); - // ensure chain spec is deserialized correctly - let serialized_chain_spec = serde_json::to_string(&chain_spec).unwrap(); - let deserialized_chain_spec: AllGenesisFormats = - serde_json::from_str(&serialized_chain_spec).unwrap(); - assert!(matches!(deserialized_chain_spec, AllGenesisFormats::Reth(_))) + assert_eq!(genesis, deserialized_genesis); } #[test] diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index 75f03559bb9c..d9cae558b29a 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -41,6 +41,7 @@ reth-prune-types.workspace = true reth-stages-types.workspace = true # ethereum +alloy-genesis.workspace = true alloy-rpc-types-engine.workspace = true # async @@ -98,7 +99,6 @@ procfs = "0.16.0" [dev-dependencies] # test vectors generation proptest.workspace = true -alloy-genesis.workspace = true [features] optimism = [ diff --git a/crates/node-core/src/args/utils.rs b/crates/node-core/src/args/utils.rs index 784a3f8187db..fce8f68906ab 100644 --- a/crates/node-core/src/args/utils.rs +++ b/crates/node-core/src/args/utils.rs @@ -1,6 +1,7 @@ //! Clap parser utilities -use reth_chainspec::{AllGenesisFormats, ChainSpec}; +use alloy_genesis::Genesis; +use reth_chainspec::ChainSpec; use reth_fs_util as fs; use reth_primitives::{BlockHashOrNumber, B256}; use std::{ @@ -76,7 +77,7 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result, eyre::Error }; // both serialized Genesis and ChainSpec structs supported - let genesis: AllGenesisFormats = serde_json::from_str(&raw)?; + let genesis: Genesis = serde_json::from_str(&raw)?; Arc::new(genesis.into()) } @@ -139,12 +140,8 @@ pub fn parse_socket_address(value: &str) -> eyre::Result Date: Fri, 21 Jun 2024 16:05:38 +0200 Subject: [PATCH 166/405] chore: rename net-common to banlist (#9016) --- Cargo.lock | 9 ++++----- Cargo.toml | 4 ++-- bin/reth/Cargo.toml | 2 +- crates/net/{common => banlist}/Cargo.toml | 4 ++-- crates/net/banlist/src/ban_list.rs | 0 .../{common/src/ban_list.rs => banlist/src/lib.rs} | 8 ++++++++ crates/net/common/src/lib.rs | 11 ----------- crates/net/discv4/Cargo.toml | 2 +- crates/net/discv4/src/config.rs | 2 +- crates/net/eth-wire/Cargo.toml | 1 - crates/net/network/Cargo.toml | 2 +- crates/net/network/src/peers/manager.rs | 4 ++-- crates/net/network/tests/it/connect.rs | 2 +- docs/repo/layout.md | 2 +- 14 files changed, 24 insertions(+), 29 deletions(-) rename crates/net/{common => banlist}/Cargo.toml (77%) create mode 100644 crates/net/banlist/src/ban_list.rs rename crates/net/{common/src/ban_list.rs => banlist/src/lib.rs} (94%) delete mode 100644 crates/net/common/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bb62e8c4729d..3f85f1b76a62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6234,7 +6234,7 @@ dependencies = [ "reth-execution-types", "reth-exex", "reth-fs-util", - "reth-net-common", + "reth-net-banlist", "reth-network", "reth-network-api", "reth-network-p2p", @@ -6686,7 +6686,7 @@ dependencies = [ "rand 0.8.5", "reth-chainspec", "reth-ethereum-forks", - "reth-net-common", + "reth-net-banlist", "reth-net-nat", "reth-network-peers", "reth-tracing", @@ -6890,7 +6890,6 @@ dependencies = [ "reth-ecies", "reth-eth-wire-types", "reth-metrics", - "reth-net-common", "reth-network-peers", "reth-primitives", "reth-tracing", @@ -7221,7 +7220,7 @@ dependencies = [ ] [[package]] -name = "reth-net-common" +name = "reth-net-banlist" version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", @@ -7269,7 +7268,7 @@ dependencies = [ "reth-ecies", "reth-eth-wire", "reth-metrics", - "reth-net-common", + "reth-net-banlist", "reth-network", "reth-network-api", "reth-network-p2p", diff --git a/Cargo.toml b/Cargo.toml index 069452a7f33e..7c996352238b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ members = [ "crates/exex/types/", "crates/metrics/", "crates/metrics/metrics-derive/", - "crates/net/common/", + "crates/net/banlist/", "crates/net/discv4/", "crates/net/discv5/", "crates/net/dns/", @@ -288,7 +288,7 @@ reth-libmdbx = { path = "crates/storage/libmdbx-rs" } reth-mdbx-sys = { path = "crates/storage/libmdbx-rs/mdbx-sys" } reth-metrics = { path = "crates/metrics" } reth-metrics-derive = { path = "crates/metrics/metrics-derive" } -reth-net-common = { path = "crates/net/common" } +reth-net-banlist = { path = "crates/net/banlist" } reth-net-nat = { path = "crates/net/nat" } reth-network = { path = "crates/net/network" } reth-network-api = { path = "crates/net/network-api" } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 46636913fdb1..1440922ee225 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -39,7 +39,7 @@ reth-rpc-types-compat.workspace = true reth-rpc-api = { workspace = true, features = ["client"] } reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true -reth-net-common.workspace = true +reth-net-banlist.workspace = true reth-network-api.workspace = true reth-downloaders.workspace = true reth-tracing.workspace = true diff --git a/crates/net/common/Cargo.toml b/crates/net/banlist/Cargo.toml similarity index 77% rename from crates/net/common/Cargo.toml rename to crates/net/banlist/Cargo.toml index 360c27c0aeb4..a9fb9fcda609 100644 --- a/crates/net/common/Cargo.toml +++ b/crates/net/banlist/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "reth-net-common" +name = "reth-net-banlist" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true -description = "Types shared across network code" +description = "Banlist for peers and IPs" [lints] workspace = true diff --git a/crates/net/banlist/src/ban_list.rs b/crates/net/banlist/src/ban_list.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/crates/net/common/src/ban_list.rs b/crates/net/banlist/src/lib.rs similarity index 94% rename from crates/net/common/src/ban_list.rs rename to crates/net/banlist/src/lib.rs index 6586becae0ae..29cf8eb76a4a 100644 --- a/crates/net/common/src/ban_list.rs +++ b/crates/net/banlist/src/lib.rs @@ -1,5 +1,13 @@ //! Support for banning peers. +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + type PeerId = alloy_primitives::B512; use std::{collections::HashMap, net::IpAddr, time::Instant}; diff --git a/crates/net/common/src/lib.rs b/crates/net/common/src/lib.rs deleted file mode 100644 index b4fcc48d675f..000000000000 --- a/crates/net/common/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Shared types across `reth-net`. - -#![doc( - html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", - html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", - issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" -)] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -pub mod ban_list; diff --git a/crates/net/discv4/Cargo.toml b/crates/net/discv4/Cargo.toml index 2418f4d63ccc..2121b904c7fb 100644 --- a/crates/net/discv4/Cargo.toml +++ b/crates/net/discv4/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # reth -reth-net-common.workspace = true +reth-net-banlist.workspace = true reth-ethereum-forks.workspace = true reth-net-nat.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } diff --git a/crates/net/discv4/src/config.rs b/crates/net/discv4/src/config.rs index 174514d98c67..4fae31f585ae 100644 --- a/crates/net/discv4/src/config.rs +++ b/crates/net/discv4/src/config.rs @@ -5,7 +5,7 @@ use alloy_primitives::bytes::Bytes; use alloy_rlp::Encodable; -use reth_net_common::ban_list::BanList; +use reth_net_banlist::BanList; use reth_net_nat::{NatResolver, ResolveNatInterval}; use reth_network_peers::NodeRecord; #[cfg(feature = "serde")] diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 91bf9290f4c3..120512af1014 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -41,7 +41,6 @@ snap = "1.0.5" arbitrary = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] -reth-net-common.workspace = true reth-primitives = { workspace = true, features = ["arbitrary"] } reth-tracing.workspace = true diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 7d56243f66ff..f72ed6f6da29 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -15,7 +15,7 @@ workspace = true # reth reth-chainspec.workspace = true reth-primitives.workspace = true -reth-net-common.workspace = true +reth-net-banlist.workspace = true reth-network-api.workspace = true reth-network-p2p.workspace = true reth-discv4.workspace = true diff --git a/crates/net/network/src/peers/manager.rs b/crates/net/network/src/peers/manager.rs index 7468ba1a06ad..bde0bd066f07 100644 --- a/crates/net/network/src/peers/manager.rs +++ b/crates/net/network/src/peers/manager.rs @@ -12,7 +12,7 @@ use crate::{ }; use futures::StreamExt; use reth_eth_wire::{errors::EthStreamError, DisconnectReason}; -use reth_net_common::ban_list::BanList; +use reth_net_banlist::BanList; use reth_network_api::{PeerKind, ReputationChangeKind}; use reth_network_peers::{NodeRecord, PeerId}; use reth_primitives::ForkId; @@ -1555,7 +1555,7 @@ mod tests { errors::{EthHandshakeError, EthStreamError, P2PHandshakeError, P2PStreamError}, DisconnectReason, }; - use reth_net_common::ban_list::BanList; + use reth_net_banlist::BanList; use reth_network_api::{Direction, ReputationChangeKind}; use reth_network_peers::PeerId; use reth_primitives::B512; diff --git a/crates/net/network/tests/it/connect.rs b/crates/net/network/tests/it/connect.rs index bc61cd81befe..a191443ed8cb 100644 --- a/crates/net/network/tests/it/connect.rs +++ b/crates/net/network/tests/it/connect.rs @@ -6,7 +6,7 @@ use futures::StreamExt; use reth_chainspec::net::mainnet_nodes; use reth_discv4::Discv4Config; use reth_eth_wire::DisconnectReason; -use reth_net_common::ban_list::BanList; +use reth_net_banlist::BanList; use reth_network::{ test_utils::{enr_to_peer_id, NetworkEventStream, PeerConfig, Testnet, GETH_TIMEOUT}, NetworkConfigBuilder, NetworkEvent, NetworkEvents, NetworkManager, PeersConfig, diff --git a/docs/repo/layout.md b/docs/repo/layout.md index 552da3196b19..f7f0e93eab2e 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -56,7 +56,7 @@ The networking component mainly lives in [`net/network`](../../crates/net/networ #### Common -- [`net/common`](../../crates/net/common): Shared types used across multiple networking crates. +- [`net/banlist`](../../crates/net/banlist): A simple peer banlist that can be used to ban peers or IP addresses. - Contains: Peer banlist. - [`net/network-api`](../../crates/net/network-api): Contains traits that define the networking component as a whole. Other components that interface with the network stack only need to depend on this crate for the relevant types. - [`net/nat`](../../crates/net/nat): A small helper crate that resolves the external IP of the running node using various methods (such as a manually provided IP, using UPnP etc.) From 2473ed880b25f89fc7e14a6cc1fa4596dc25258b Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:29:37 +0200 Subject: [PATCH 167/405] chore: remove `serde` from `ChainSpec` (#9017) --- Cargo.lock | 1 - bin/reth/src/cli/mod.rs | 4 ++-- bin/reth/src/commands/common.rs | 4 ++-- bin/reth/src/commands/dump_genesis.rs | 4 ++-- bin/reth/src/commands/node/mod.rs | 4 ++-- bin/reth/src/commands/p2p/mod.rs | 4 ++-- crates/chainspec/Cargo.toml | 1 - crates/chainspec/src/spec.rs | 12 +++--------- crates/net/network/src/config.rs | 1 + crates/node-core/src/args/utils.rs | 7 +++---- 10 files changed, 17 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f85f1b76a62..2518f6ae726b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6479,7 +6479,6 @@ dependencies = [ "reth-primitives-traits", "reth-rpc-types", "reth-trie-common", - "serde", "serde_json", ] diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index 8750b84f543a..ff5c4add541c 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -2,7 +2,7 @@ use crate::{ args::{ - utils::{chain_help, genesis_value_parser, SUPPORTED_CHAINS}, + utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}, LogArgs, }, commands::{ @@ -46,7 +46,7 @@ pub struct Cli { value_name = "CHAIN_OR_PATH", long_help = chain_help(), default_value = SUPPORTED_CHAINS[0], - value_parser = genesis_value_parser, + value_parser = chain_value_parser, global = true, )] chain: Arc, diff --git a/bin/reth/src/commands/common.rs b/bin/reth/src/commands/common.rs index 329047cdddae..31c0329a5540 100644 --- a/bin/reth/src/commands/common.rs +++ b/bin/reth/src/commands/common.rs @@ -10,7 +10,7 @@ use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHe use reth_evm::noop::NoopBlockExecutorProvider; use reth_node_core::{ args::{ - utils::{chain_help, genesis_value_parser, SUPPORTED_CHAINS}, + utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}, DatabaseArgs, DatadirArgs, }, dirs::{ChainPath, DataDirPath}, @@ -42,7 +42,7 @@ pub struct EnvironmentArgs { value_name = "CHAIN_OR_PATH", long_help = chain_help(), default_value = SUPPORTED_CHAINS[0], - value_parser = genesis_value_parser + value_parser = chain_value_parser )] pub chain: Arc, diff --git a/bin/reth/src/commands/dump_genesis.rs b/bin/reth/src/commands/dump_genesis.rs index f4208584fbda..70b95e73639b 100644 --- a/bin/reth/src/commands/dump_genesis.rs +++ b/bin/reth/src/commands/dump_genesis.rs @@ -1,5 +1,5 @@ //! Command that dumps genesis block JSON configuration to stdout -use crate::args::utils::{chain_help, genesis_value_parser, SUPPORTED_CHAINS}; +use crate::args::utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}; use clap::Parser; use reth_chainspec::ChainSpec; use std::sync::Arc; @@ -15,7 +15,7 @@ pub struct DumpGenesisCommand { value_name = "CHAIN_OR_PATH", long_help = chain_help(), default_value = SUPPORTED_CHAINS[0], - value_parser = genesis_value_parser + value_parser = chain_value_parser )] chain: Arc, } diff --git a/bin/reth/src/commands/node/mod.rs b/bin/reth/src/commands/node/mod.rs index 606e0de42bb2..f4c355b1757f 100644 --- a/bin/reth/src/commands/node/mod.rs +++ b/bin/reth/src/commands/node/mod.rs @@ -1,7 +1,7 @@ //! Main node command for launching a node use crate::args::{ - utils::{chain_help, genesis_value_parser, parse_socket_address, SUPPORTED_CHAINS}, + utils::{chain_help, chain_value_parser, parse_socket_address, SUPPORTED_CHAINS}, DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, NetworkArgs, PayloadBuilderArgs, PruningArgs, RpcServerArgs, TxPoolArgs, }; @@ -29,7 +29,7 @@ pub struct NodeCommand { long_help = chain_help(), default_value = SUPPORTED_CHAINS[0], default_value_if("dev", "true", "dev"), - value_parser = genesis_value_parser, + value_parser = chain_value_parser, required = false, )] pub chain: Arc, diff --git a/bin/reth/src/commands/p2p/mod.rs b/bin/reth/src/commands/p2p/mod.rs index b40207c80579..290a0a0b08bb 100644 --- a/bin/reth/src/commands/p2p/mod.rs +++ b/bin/reth/src/commands/p2p/mod.rs @@ -3,7 +3,7 @@ use crate::{ args::{ get_secret_key, - utils::{chain_help, genesis_value_parser, hash_or_num_value_parser, SUPPORTED_CHAINS}, + utils::{chain_help, chain_value_parser, hash_or_num_value_parser, SUPPORTED_CHAINS}, DatabaseArgs, DiscoveryArgs, NetworkArgs, }, utils::get_single_header, @@ -40,7 +40,7 @@ pub struct Command { value_name = "CHAIN_OR_PATH", long_help = chain_help(), default_value = SUPPORTED_CHAINS[0], - value_parser = genesis_value_parser + value_parser = chain_value_parser )] chain: Arc, diff --git a/crates/chainspec/Cargo.toml b/crates/chainspec/Cargo.toml index 8c1d4d4dac4f..682289145aab 100644 --- a/crates/chainspec/Cargo.toml +++ b/crates/chainspec/Cargo.toml @@ -26,7 +26,6 @@ alloy-trie.workspace = true # misc once_cell.workspace = true -serde.workspace = true serde_json.workspace = true derive_more.workspace = true diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 2d88f4508d51..bf16a88f0913 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -26,7 +26,6 @@ use reth_primitives_traits::{ Header, SealedHeader, }; use reth_trie_common::root::state_root_ref_unhashed; -use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::{collections::BTreeMap, sync::Arc}; @@ -290,8 +289,7 @@ pub static BASE_MAINNET: Lazy> = Lazy::new(|| { /// A wrapper around [`BaseFeeParams`] that allows for specifying constant or dynamic EIP-1559 /// parameters based on the active [Hardfork]. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[serde(untagged)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum BaseFeeParamsKind { /// Constant [`BaseFeeParams`]; used for chains that don't have dynamic EIP-1559 parameters Constant(BaseFeeParams), @@ -314,7 +312,7 @@ impl From for BaseFeeParamsKind { /// A type alias to a vector of tuples of [Hardfork] and [`BaseFeeParams`], sorted by [Hardfork] /// activation order. This is used to specify dynamic EIP-1559 parameters for chains like Optimism. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, From)] +#[derive(Clone, Debug, PartialEq, Eq, From)] pub struct ForkBaseFeeParams(Vec<(Hardfork, BaseFeeParams)>); /// An Ethereum chain specification. @@ -324,7 +322,7 @@ pub struct ForkBaseFeeParams(Vec<(Hardfork, BaseFeeParams)>); /// - Meta-information about the chain (the chain ID) /// - The genesis block of the chain ([`Genesis`]) /// - What hardforks are activated, and under which conditions -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ChainSpec { /// The chain ID pub chain: Chain, @@ -333,7 +331,6 @@ pub struct ChainSpec { /// /// This acts as a small cache for known chains. If the chain is known, then the genesis hash /// is also known ahead of time, and this will be `Some`. - #[serde(skip, default)] pub genesis_hash: Option, /// The genesis block @@ -341,14 +338,12 @@ pub struct ChainSpec { /// The block at which [`Hardfork::Paris`] was activated and the final difficulty at this /// block. - #[serde(skip, default)] pub paris_block_and_final_difficulty: Option<(u64, U256)>, /// The active hard forks and their activation conditions pub hardforks: BTreeMap, /// The deposit contract deployed for `PoS` - #[serde(skip, default)] pub deposit_contract: Option, /// The parameters that configure how a block's base fee is computed @@ -357,7 +352,6 @@ pub struct ChainSpec { /// The delete limit for pruner, per block. In the actual pruner run it will be multiplied by /// the amount of blocks between pruner runs to account for the difference in amount of new /// data coming in. - #[serde(default)] pub prune_delete_limit: usize, } diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index ad865c55c816..993132f6c635 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -159,6 +159,7 @@ pub struct NetworkConfigBuilder { /// How to configure the sessions manager sessions_config: Option, /// The network's chain spec + #[serde(skip)] chain_spec: Arc, /// The default mode of the network. network_mode: NetworkMode, diff --git a/crates/node-core/src/args/utils.rs b/crates/node-core/src/args/utils.rs index fce8f68906ab..8a54a8942e1c 100644 --- a/crates/node-core/src/args/utils.rs +++ b/crates/node-core/src/args/utils.rs @@ -41,9 +41,8 @@ pub fn chain_help() -> String { /// Clap value parser for [`ChainSpec`]s. /// /// The value parser matches either a known chain, the path -/// to a json file, or a json formatted string in-memory. The json can be either -/// a serialized [`ChainSpec`] or Genesis struct. -pub fn genesis_value_parser(s: &str) -> eyre::Result, eyre::Error> { +/// to a json file, or a json formatted string in-memory. The json needs to be a Genesis struct. +pub fn chain_value_parser(s: &str) -> eyre::Result, eyre::Error> { Ok(match s { #[cfg(not(feature = "optimism"))] "mainnet" => MAINNET.clone(), @@ -146,7 +145,7 @@ mod tests { #[test] fn parse_known_chain_spec() { for chain in SUPPORTED_CHAINS { - genesis_value_parser(chain).unwrap(); + chain_value_parser(chain).unwrap(); } } From 16b10dc1a319b20db98f7706cbdda6610779ab56 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 18:21:53 +0200 Subject: [PATCH 168/405] chore: remove unused type (#9019) --- Cargo.lock | 1 - crates/transaction-pool/Cargo.toml | 1 - crates/transaction-pool/src/pool/txpool.rs | 30 ---------------------- 3 files changed, 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2518f6ae726b..2c8763d88c96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8292,7 +8292,6 @@ dependencies = [ "bitflags 2.5.0", "criterion", "futures-util", - "itertools 0.13.0", "metrics", "parking_lot 0.12.3", "paste", diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index f45e198cb4af..77edd6f3e541 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -45,7 +45,6 @@ serde = { workspace = true, features = ["derive", "rc"], optional = true } bitflags.workspace = true auto_impl.workspace = true smallvec.workspace = true -itertools.workspace = true # testing rand = { workspace = true, optional = true } diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 01e9f84c1715..48048412eba0 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -18,7 +18,6 @@ use crate::{ PoolConfig, PoolResult, PoolTransaction, PriceBumpConfig, TransactionOrdering, ValidPoolTransaction, U256, }; -use itertools::Itertools; use reth_primitives::{ constants::{ eip4844::BLOB_TX_MIN_BLOB_GASPRICE, ETHEREUM_BLOCK_GAS_LIMIT, MIN_PROTOCOL_BASE_FEE, @@ -1800,35 +1799,6 @@ impl Default for UpdateOutcome { } } -/// Represents the outcome of a prune -pub struct PruneResult { - /// A list of added transactions that a pruned marker satisfied - pub promoted: Vec>, - /// all transactions that failed to be promoted and now are discarded - pub failed: Vec, - /// all transactions that were pruned from the ready pool - pub pruned: Vec>>, -} - -impl fmt::Debug for PruneResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PruneResult") - .field( - "promoted", - &format_args!("[{}]", self.promoted.iter().map(|tx| tx.hash()).format(", ")), - ) - .field("failed", &self.failed) - .field( - "pruned", - &format_args!( - "[{}]", - self.pruned.iter().map(|tx| tx.transaction.hash()).format(", ") - ), - ) - .finish() - } -} - /// Stores relevant context about a sender. #[derive(Debug, Clone, Default)] pub(crate) struct SenderInfo { From 08cc16e4f3c40e552abd0e10926580673179f505 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 18:43:58 +0200 Subject: [PATCH 169/405] chore: rm serde for network builder (#9020) --- crates/net/network/src/config.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 993132f6c635..151421b4a5de 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -137,7 +137,6 @@ where /// Builder for [`NetworkConfig`](struct.NetworkConfig.html). #[derive(Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct NetworkConfigBuilder { /// The node's secret key, from which the node's identity is derived. secret_key: SecretKey, @@ -146,7 +145,6 @@ pub struct NetworkConfigBuilder { /// How to set up discovery version 4. discovery_v4_builder: Option, /// How to set up discovery version 5. - #[serde(skip)] discovery_v5_builder: Option, /// All boot nodes to start network discovery with. boot_nodes: HashSet, @@ -159,24 +157,20 @@ pub struct NetworkConfigBuilder { /// How to configure the sessions manager sessions_config: Option, /// The network's chain spec - #[serde(skip)] chain_spec: Arc, /// The default mode of the network. network_mode: NetworkMode, /// The executor to use for spawning tasks. - #[serde(skip)] executor: Option>, /// Sets the hello message for the p2p handshake in `RLPx` hello_message: Option, /// The executor to use for spawning tasks. - #[serde(skip)] extra_protocols: RlpxSubProtocols, /// Head used to start set for the fork filter and status. head: Option, /// Whether tx gossip is disabled tx_gossip_disabled: bool, /// The block importer type - #[serde(skip)] block_import: Option>, /// How to instantiate transactions manager. transactions_manager_config: TransactionsManagerConfig, From b0b2abb424c29bd1a4bcc93c03cabf910dfd6667 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 19:33:42 +0200 Subject: [PATCH 170/405] chore: rm default serde feature in reth-dns (#9021) --- crates/net/dns/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index 64419db96f28..2af72afcef65 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -48,5 +48,4 @@ reth-tracing.workspace = true rand.workspace = true [features] -default = ["serde"] serde = ["dep:serde", "dep:serde_with"] From 0712adc4c643265f01a5e2f487f41f965a669507 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 21 Jun 2024 19:48:36 +0200 Subject: [PATCH 171/405] chore(op): add link to op labs bedrock datadir download (#9014) --- book/run/sync-op-mainnet.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/book/run/sync-op-mainnet.md b/book/run/sync-op-mainnet.md index d9df4139bb10..19d57e6398c9 100644 --- a/book/run/sync-op-mainnet.md +++ b/book/run/sync-op-mainnet.md @@ -12,9 +12,14 @@ Importing OP mainnet Bedrock datadir requires exported data: ## Manual Export Steps -See . +The `op-geth` Bedrock datadir can be downloaded from . -Output from running the command to export state, can also be downloaded from . +To export the OVM chain from `op-geth`, clone the `testinprod-io/op-geth` repo and checkout +. Commands to export blocks, receipts and state dump can be +found in `op-geth/migrate.sh`. + +Output from running the command to export state, can also be downloaded from +. ## Manual Import Steps From 17c5121b50c9db58244591647dc2aab743d38400 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 23:30:06 +0200 Subject: [PATCH 172/405] chore(deps): replace fnv with fx (#9024) --- Cargo.lock | 2 +- crates/net/network/Cargo.toml | 2 +- crates/net/network/src/session/active.rs | 4 ++-- crates/net/network/src/session/mod.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c8763d88c96..e93162fbde5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7250,7 +7250,6 @@ dependencies = [ "derive_more", "discv5", "enr", - "fnv", "futures", "humantime-serde", "itertools 0.13.0", @@ -7279,6 +7278,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", + "rustc-hash 2.0.0", "schnellru", "secp256k1", "serde", diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index f72ed6f6da29..a40cd3126209 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -56,7 +56,7 @@ metrics.workspace = true auto_impl.workspace = true aquamarine.workspace = true tracing.workspace = true -fnv = "1.0" +rustc-hash.workspace = true thiserror.workspace = true parking_lot.workspace = true rand.workspace = true diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 9357cc325adb..7551721ea231 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -10,7 +10,6 @@ use crate::{ }, }; use core::sync::atomic::Ordering; -use fnv::FnvHashMap; use futures::{stream::Fuse, SinkExt, StreamExt}; use reth_eth_wire::{ capability::Capabilities, @@ -21,6 +20,7 @@ use reth_eth_wire::{ use reth_metrics::common::mpsc::MeteredPollSender; use reth_network_p2p::error::RequestError; use reth_network_peers::PeerId; +use rustc_hash::FxHashMap; use std::{ collections::VecDeque, future::Future, @@ -81,7 +81,7 @@ pub(crate) struct ActiveSession { /// Incoming internal requests which are delegated to the remote peer. pub(crate) internal_request_tx: Fuse>, /// All requests sent to the remote peer we're waiting on a response - pub(crate) inflight_requests: FnvHashMap, + pub(crate) inflight_requests: FxHashMap, /// All requests that were sent by the remote peer and we're waiting on an internal response pub(crate) received_requests_from_remote: Vec, /// Buffered messages that should be handled and sent to the peer. diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index 4f6be86317a0..6ce4e97aa519 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -5,7 +5,6 @@ use crate::{ metrics::SessionManagerMetrics, session::{active::ActiveSession, config::SessionCounter}, }; -use fnv::FnvHashMap; use futures::{future::Either, io, FutureExt, StreamExt}; use reth_ecies::{stream::ECIESStream, ECIESError}; use reth_eth_wire::{ @@ -18,6 +17,7 @@ use reth_metrics::common::mpsc::MeteredPollSender; use reth_network_peers::PeerId; use reth_primitives::{ForkFilter, ForkId, ForkTransition, Head}; use reth_tasks::TaskSpawner; +use rustc_hash::FxHashMap; use secp256k1::SecretKey; use std::{ collections::HashMap, @@ -86,7 +86,7 @@ pub struct SessionManager { /// /// Events produced during the authentication phase are reported to this manager. Once the /// session is authenticated, it can be moved to the `active_session` set. - pending_sessions: FnvHashMap, + pending_sessions: FxHashMap, /// All active sessions that are ready to exchange messages. active_sessions: HashMap, /// The original Sender half of the [`PendingSessionEvent`] channel. From a34e41c27527682d9e1343ecd653cb01e3a0343c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 21 Jun 2024 23:47:03 +0200 Subject: [PATCH 173/405] chore(deps): rm reth-rpc-types dep from reth-network (#9023) --- Cargo.lock | 3 +-- crates/net/network-api/Cargo.toml | 2 +- crates/net/network-api/src/lib.rs | 19 +++++++++++++++---- crates/net/network-api/src/noop.rs | 6 +++--- crates/net/network/Cargo.toml | 1 - crates/net/network/src/manager.rs | 3 +-- crates/net/network/src/network.rs | 3 +-- crates/net/network/src/protocol.rs | 3 +-- crates/net/network/tests/it/multiplex.rs | 3 +-- crates/rpc/rpc-types/src/lib.rs | 2 -- 10 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e93162fbde5c..63a6ddb756e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7273,7 +7273,6 @@ dependencies = [ "reth-network-peers", "reth-primitives", "reth-provider", - "reth-rpc-types", "reth-tasks", "reth-tokio-util", "reth-tracing", @@ -7298,10 +7297,10 @@ name = "reth-network-api" version = "1.0.0-rc.2" dependencies = [ "alloy-primitives", + "alloy-rpc-types-admin", "enr", "reth-eth-wire", "reth-network-peers", - "reth-rpc-types", "serde", "thiserror", "tokio", diff --git a/crates/net/network-api/Cargo.toml b/crates/net/network-api/Cargo.toml index 634c45a79c7b..bedaf2c297ce 100644 --- a/crates/net/network-api/Cargo.toml +++ b/crates/net/network-api/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] # reth reth-eth-wire.workspace = true -reth-rpc-types.workspace = true +alloy-rpc-types-admin.workspace = true reth-network-peers.workspace = true # ethereum diff --git a/crates/net/network-api/src/lib.rs b/crates/net/network-api/src/lib.rs index 97bd784065e8..6c6f8036daff 100644 --- a/crates/net/network-api/src/lib.rs +++ b/crates/net/network-api/src/lib.rs @@ -13,13 +13,13 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use reth_eth_wire::{capability::Capabilities, DisconnectReason, EthVersion, Status}; -use reth_rpc_types::NetworkStatus; -use std::{future::Future, net::SocketAddr, sync::Arc, time::Instant}; - +pub use alloy_rpc_types_admin::EthProtocolInfo; pub use error::NetworkError; pub use reputation::{Reputation, ReputationChangeKind}; +use reth_eth_wire::{capability::Capabilities, DisconnectReason, EthVersion, Status}; use reth_network_peers::NodeRecord; +use serde::{Deserialize, Serialize}; +use std::{future::Future, net::SocketAddr, sync::Arc, time::Instant}; /// The `PeerId` type. pub type PeerId = alloy_primitives::B512; @@ -215,3 +215,14 @@ impl std::fmt::Display for Direction { } } } + +/// The status of the network being ran by the local node. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NetworkStatus { + /// The local node client version. + pub client_version: String, + /// The current ethereum protocol version + pub protocol_version: u64, + /// Information about the Ethereum Wire Protocol. + pub eth_protocol_info: EthProtocolInfo, +} diff --git a/crates/net/network-api/src/noop.rs b/crates/net/network-api/src/noop.rs index 0678b928857d..745613f40655 100644 --- a/crates/net/network-api/src/noop.rs +++ b/crates/net/network-api/src/noop.rs @@ -4,13 +4,13 @@ //! generic over it. use crate::{ - NetworkError, NetworkInfo, PeerId, PeerInfo, PeerKind, Peers, PeersInfo, Reputation, - ReputationChangeKind, + NetworkError, NetworkInfo, NetworkStatus, PeerId, PeerInfo, PeerKind, Peers, PeersInfo, + Reputation, ReputationChangeKind, }; +use alloy_rpc_types_admin::EthProtocolInfo; use enr::{secp256k1::SecretKey, Enr}; use reth_eth_wire::{DisconnectReason, ProtocolVersion}; use reth_network_peers::NodeRecord; -use reth_rpc_types::{admin::EthProtocolInfo, NetworkStatus}; use std::net::{IpAddr, SocketAddr}; /// A type that implements all network trait that does nothing. diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index a40cd3126209..1dde28cfe2cf 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -26,7 +26,6 @@ reth-ecies.workspace = true reth-tasks.workspace = true reth-transaction-pool.workspace = true reth-provider.workspace = true -reth-rpc-types.workspace = true reth-tokio-util.workspace = true reth-consensus.workspace = true reth-network-peers.workspace = true diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index d07b9e02488f..17827444bc20 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -42,11 +42,10 @@ use reth_eth_wire::{ DisconnectReason, EthVersion, Status, }; use reth_metrics::common::mpsc::UnboundedMeteredSender; -use reth_network_api::ReputationChangeKind; +use reth_network_api::{EthProtocolInfo, NetworkStatus, ReputationChangeKind}; use reth_network_peers::{NodeRecord, PeerId}; use reth_primitives::ForkId; use reth_provider::{BlockNumReader, BlockReader}; -use reth_rpc_types::{admin::EthProtocolInfo, NetworkStatus}; use reth_tasks::shutdown::GracefulShutdown; use reth_tokio_util::EventSender; use secp256k1::SecretKey; diff --git a/crates/net/network/src/network.rs b/crates/net/network/src/network.rs index b26dd3e3700e..632a6028795a 100644 --- a/crates/net/network/src/network.rs +++ b/crates/net/network/src/network.rs @@ -8,13 +8,12 @@ use parking_lot::Mutex; use reth_discv4::Discv4; use reth_eth_wire::{DisconnectReason, NewBlock, NewPooledTransactionHashes, SharedTransactions}; use reth_network_api::{ - NetworkError, NetworkInfo, PeerInfo, PeerKind, Peers, PeersInfo, Reputation, + NetworkError, NetworkInfo, NetworkStatus, PeerInfo, PeerKind, Peers, PeersInfo, Reputation, ReputationChangeKind, }; use reth_network_p2p::sync::{NetworkSyncUpdater, SyncState, SyncStateProvider}; use reth_network_peers::{NodeRecord, PeerId}; use reth_primitives::{Head, TransactionSigned, B256}; -use reth_rpc_types::NetworkStatus; use reth_tokio_util::{EventSender, EventStream}; use secp256k1::SecretKey; use std::{ diff --git a/crates/net/network/src/protocol.rs b/crates/net/network/src/protocol.rs index 7be1c48a6a3a..2ae1b132df3f 100644 --- a/crates/net/network/src/protocol.rs +++ b/crates/net/network/src/protocol.rs @@ -6,9 +6,8 @@ use futures::Stream; use reth_eth_wire::{ capability::SharedCapabilities, multiplex::ProtocolConnection, protocol::Protocol, }; -use reth_network_api::Direction; +use reth_network_api::{Direction, PeerId}; use reth_primitives::BytesMut; -use reth_rpc_types::PeerId; use std::{ fmt, net::SocketAddr, diff --git a/crates/net/network/tests/it/multiplex.rs b/crates/net/network/tests/it/multiplex.rs index ae84f43aae3a..ea63ace952b7 100644 --- a/crates/net/network/tests/it/multiplex.rs +++ b/crates/net/network/tests/it/multiplex.rs @@ -10,10 +10,9 @@ use reth_network::{ protocol::{ConnectionHandler, OnNotSupported, ProtocolHandler}, test_utils::Testnet, }; -use reth_network_api::Direction; +use reth_network_api::{Direction, PeerId}; use reth_primitives::BytesMut; use reth_provider::test_utils::MockEthProvider; -use reth_rpc_types::PeerId; use std::{ net::SocketAddr, pin::Pin, diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index 3d239d1f945b..5df802da09fa 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -12,7 +12,6 @@ #[allow(hidden_glob_reexports)] mod eth; mod mev; -mod net; mod peer; mod rpc; @@ -53,6 +52,5 @@ pub use eth::{ }; pub use mev::*; -pub use net::*; pub use peer::*; pub use rpc::*; From f137ca8477976c75fb107f79c971312afcaae06f Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 22 Jun 2024 10:21:45 +0200 Subject: [PATCH 174/405] chore: remove some more usages of BytesMut (#9025) --- crates/net/downloaders/src/file_client.rs | 18 +++++++----------- crates/net/ecies/src/codec.rs | 4 ++-- crates/net/eth-wire/src/multiplex.rs | 13 +++++++------ crates/primitives/src/transaction/eip1559.rs | 3 +-- crates/primitives/src/transaction/eip2930.rs | 3 +-- crates/primitives/src/transaction/legacy.rs | 3 +-- crates/primitives/src/transaction/mod.rs | 3 ++- crates/rpc/rpc-engine-api/tests/it/payload.rs | 12 +++++------- crates/storage/codecs/src/alloy/access_list.rs | 14 ++++++-------- crates/storage/codecs/src/alloy/log.rs | 14 ++++++-------- 10 files changed, 38 insertions(+), 49 deletions(-) diff --git a/crates/net/downloaders/src/file_client.rs b/crates/net/downloaders/src/file_client.rs index 0ef12f1ba552..3464baf5d6ff 100644 --- a/crates/net/downloaders/src/file_client.rs +++ b/crates/net/downloaders/src/file_client.rs @@ -10,8 +10,8 @@ use reth_network_p2p::{ }; use reth_network_peers::PeerId; use reth_primitives::{ - BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BytesMut, Header, HeadersDirection, - SealedHeader, B256, + BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, Header, HeadersDirection, SealedHeader, + B256, }; use std::{collections::HashMap, io, path::Path}; use thiserror::Error; @@ -419,26 +419,22 @@ impl ChunkedFileReader { let new_read_bytes_target_len = chunk_target_len - old_bytes_len; // read new bytes from file - let mut reader = BytesMut::zeroed(new_read_bytes_target_len as usize); + let prev_read_bytes_len = self.chunk.len(); + self.chunk.extend(std::iter::repeat(0).take(new_read_bytes_target_len as usize)); + let reader = &mut self.chunk[prev_read_bytes_len..]; // actual bytes that have been read - let new_read_bytes_len = self.file.read_exact(&mut reader).await? as u64; + let new_read_bytes_len = self.file.read_exact(reader).await? as u64; + let next_chunk_byte_len = self.chunk.len(); // update remaining file length self.file_byte_len -= new_read_bytes_len; - let prev_read_bytes_len = self.chunk.len(); - - // read new bytes from file into chunk - self.chunk.extend_from_slice(&reader[..]); - let next_chunk_byte_len = self.chunk.len(); - debug!(target: "downloaders::file", max_chunk_byte_len=self.chunk_byte_len, prev_read_bytes_len, new_read_bytes_target_len, new_read_bytes_len, - reader_capacity=reader.capacity(), next_chunk_byte_len, remaining_file_byte_len=self.file_byte_len, "new bytes were read from file" diff --git a/crates/net/ecies/src/codec.rs b/crates/net/ecies/src/codec.rs index 7ad30d38d0d9..54250e10210e 100644 --- a/crates/net/ecies/src/codec.rs +++ b/crates/net/ecies/src/codec.rs @@ -43,7 +43,7 @@ impl Decoder for ECIESCodec { type Item = IngressECIESValue; type Error = ECIESError; - #[instrument(level = "trace", skip_all, fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] + #[instrument(level = "trace", skip_all, fields(peer=?self.ecies.remote_id, state=?self.state))] fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { loop { match self.state { @@ -115,7 +115,7 @@ impl Decoder for ECIESCodec { impl Encoder for ECIESCodec { type Error = io::Error; - #[instrument(level = "trace", skip(self, buf), fields(peer=&*format!("{:?}", self.ecies.remote_id.map(|s| s.to_string())), state=&*format!("{:?}", self.state)))] + #[instrument(level = "trace", skip(self, buf), fields(peer=?self.ecies.remote_id, state=?self.state))] fn encode(&mut self, item: EgressECIESValue, buf: &mut BytesMut) -> Result<(), Self::Error> { match item { EgressECIESValue::Auth => { diff --git a/crates/net/eth-wire/src/multiplex.rs b/crates/net/eth-wire/src/multiplex.rs index 262c2c193921..08e281f1a26c 100644 --- a/crates/net/eth-wire/src/multiplex.rs +++ b/crates/net/eth-wire/src/multiplex.rs @@ -312,13 +312,14 @@ impl ProtocolProxy { return Err(io::ErrorKind::InvalidInput.into()) } - let mut masked_bytes = BytesMut::zeroed(msg.len()); - masked_bytes[0] = msg[0] - .checked_add(self.shared_cap.relative_message_id_offset()) - .ok_or(io::ErrorKind::InvalidInput)?; + let offset = self.shared_cap.relative_message_id_offset(); + if offset == 0 { + return Ok(msg); + } - masked_bytes[1..].copy_from_slice(&msg[1..]); - Ok(masked_bytes.freeze()) + let mut masked = Vec::from(msg); + masked[0] = masked[0].checked_add(offset).ok_or(io::ErrorKind::InvalidInput)?; + Ok(masked.into()) } /// Unmasks the message ID of a message received from the wire. diff --git a/crates/primitives/src/transaction/eip1559.rs b/crates/primitives/src/transaction/eip1559.rs index 878efaa4f954..cce6f0ca22fc 100644 --- a/crates/primitives/src/transaction/eip1559.rs +++ b/crates/primitives/src/transaction/eip1559.rs @@ -1,7 +1,6 @@ use super::access_list::AccessList; use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256}; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; -use bytes::BytesMut; use core::mem; use reth_codecs::{main_codec, Compact}; @@ -216,7 +215,7 @@ impl TxEip1559 { /// Outputs the signature hash of the transaction by first encoding without a signature, then /// hashing. pub(crate) fn signature_hash(&self) -> B256 { - let mut buf = BytesMut::with_capacity(self.payload_len_for_signature()); + let mut buf = Vec::with_capacity(self.payload_len_for_signature()); self.encode_for_signing(&mut buf); keccak256(&buf) } diff --git a/crates/primitives/src/transaction/eip2930.rs b/crates/primitives/src/transaction/eip2930.rs index 45bc5e67a28e..ebaa12785c1d 100644 --- a/crates/primitives/src/transaction/eip2930.rs +++ b/crates/primitives/src/transaction/eip2930.rs @@ -1,7 +1,6 @@ use super::access_list::AccessList; use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256}; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; -use bytes::BytesMut; use core::mem; use reth_codecs::{main_codec, Compact}; @@ -179,7 +178,7 @@ impl TxEip2930 { /// Outputs the signature hash of the transaction by first encoding without a signature, then /// hashing. pub(crate) fn signature_hash(&self) -> B256 { - let mut buf = BytesMut::with_capacity(self.payload_len_for_signature()); + let mut buf = Vec::with_capacity(self.payload_len_for_signature()); self.encode_for_signing(&mut buf); keccak256(&buf) } diff --git a/crates/primitives/src/transaction/legacy.rs b/crates/primitives/src/transaction/legacy.rs index ebbe29a78c1e..09b661cf7995 100644 --- a/crates/primitives/src/transaction/legacy.rs +++ b/crates/primitives/src/transaction/legacy.rs @@ -1,6 +1,5 @@ use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256}; use alloy_rlp::{length_of_length, Encodable, Header}; -use bytes::BytesMut; use core::mem; use reth_codecs::{main_codec, Compact}; @@ -163,7 +162,7 @@ impl TxLegacy { /// /// See [`Self::encode_for_signing`] for more information on the encoding format. pub(crate) fn signature_hash(&self) -> B256 { - let mut buf = BytesMut::with_capacity(self.payload_len_for_signature()); + let mut buf = Vec::with_capacity(self.payload_len_for_signature()); self.encode_for_signing(&mut buf); keccak256(&buf) } diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 7a6d2a85b514..c23d454f868d 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -860,10 +860,11 @@ impl Compact for TransactionSignedNoHash { let tx_bits = if zstd_bit { TRANSACTION_COMPRESSOR.with(|compressor| { let mut compressor = compressor.borrow_mut(); - let mut tmp = bytes::BytesMut::with_capacity(200); + let mut tmp = Vec::with_capacity(256); let tx_bits = self.transaction.to_compact(&mut tmp); buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress")); + tx_bits as u8 }) } else { diff --git a/crates/rpc/rpc-engine-api/tests/it/payload.rs b/crates/rpc/rpc-engine-api/tests/it/payload.rs index 94f011c02db3..0f2853f1f022 100644 --- a/crates/rpc/rpc-engine-api/tests/it/payload.rs +++ b/crates/rpc/rpc-engine-api/tests/it/payload.rs @@ -3,8 +3,7 @@ use alloy_rlp::{Decodable, Error as RlpError}; use assert_matches::assert_matches; use reth_primitives::{ - bytes::{Bytes, BytesMut}, - proofs, Block, SealedBlock, TransactionSigned, Withdrawals, B256, U256, + proofs, Block, Bytes, SealedBlock, TransactionSigned, Withdrawals, B256, U256, }; use reth_rpc_types::engine::{ ExecutionPayload, ExecutionPayloadBodyV1, ExecutionPayloadV1, PayloadError, @@ -59,20 +58,19 @@ fn payload_validation() { // Valid extra data let block_with_valid_extra_data = transform_block(block.clone(), |mut b| { - b.header.extra_data = BytesMut::zeroed(32).freeze().into(); + b.header.extra_data = Bytes::from_static(&[0; 32]); b }); assert_matches!(try_into_sealed_block(block_with_valid_extra_data, None), Ok(_)); // Invalid extra data - let block_with_invalid_extra_data: Bytes = BytesMut::zeroed(33).freeze(); + let block_with_invalid_extra_data = Bytes::from_static(&[0; 33]); let invalid_extra_data_block = transform_block(block.clone(), |mut b| { - b.header.extra_data = block_with_invalid_extra_data.clone().into(); + b.header.extra_data = block_with_invalid_extra_data.clone(); b }); assert_matches!( - try_into_sealed_block(invalid_extra_data_block,None), Err(PayloadError::ExtraData(data)) if data == block_with_invalid_extra_data ); @@ -92,7 +90,7 @@ fn payload_validation() { let mut payload_with_invalid_txs: ExecutionPayloadV1 = block_to_payload_v1(block.clone()); payload_with_invalid_txs.transactions.iter_mut().for_each(|tx| { - *tx = Bytes::new().into(); + *tx = Bytes::new(); }); let payload_with_invalid_txs = try_payload_v1_to_block(payload_with_invalid_txs); assert_matches!(payload_with_invalid_txs, Err(PayloadError::Decode(RlpError::InputTooShort))); diff --git a/crates/storage/codecs/src/alloy/access_list.rs b/crates/storage/codecs/src/alloy/access_list.rs index d3f906318848..f5564e81601a 100644 --- a/crates/storage/codecs/src/alloy/access_list.rs +++ b/crates/storage/codecs/src/alloy/access_list.rs @@ -8,12 +8,11 @@ impl Compact for AccessListItem { where B: bytes::BufMut + AsMut<[u8]>, { - let mut buffer = bytes::BytesMut::new(); + let mut buffer = Vec::new(); self.address.to_compact(&mut buffer); self.storage_keys.specialized_to_compact(&mut buffer); - let total_length = buffer.len(); - buf.put(buffer); - total_length + buf.put(&buffer[..]); + buffer.len() } fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) { @@ -31,11 +30,10 @@ impl Compact for AccessList { where B: bytes::BufMut + AsMut<[u8]>, { - let mut buffer = bytes::BytesMut::new(); + let mut buffer = Vec::new(); self.0.to_compact(&mut buffer); - let total_length = buffer.len(); - buf.put(buffer); - total_length + buf.put(&buffer[..]); + buffer.len() } fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) { diff --git a/crates/storage/codecs/src/alloy/log.rs b/crates/storage/codecs/src/alloy/log.rs index 8d5c30e0a0b3..eadcb894f3f1 100644 --- a/crates/storage/codecs/src/alloy/log.rs +++ b/crates/storage/codecs/src/alloy/log.rs @@ -10,13 +10,12 @@ impl Compact for LogData { where B: BufMut + AsMut<[u8]>, { - let mut buffer = bytes::BytesMut::new(); + let mut buffer = Vec::new(); let (topics, data) = self.split(); topics.specialized_to_compact(&mut buffer); data.to_compact(&mut buffer); - let total_length = buffer.len(); - buf.put(buffer); - total_length + buf.put(&buffer[..]); + buffer.len() } fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) { @@ -33,12 +32,11 @@ impl Compact for Log { where B: BufMut + AsMut<[u8]>, { - let mut buffer = bytes::BytesMut::new(); + let mut buffer = Vec::new(); self.address.to_compact(&mut buffer); self.data.to_compact(&mut buffer); - let total_length = buffer.len(); - buf.put(buffer); - total_length + buf.put(&buffer[..]); + buffer.len() } fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8]) { From 572d4c182459c50358c87c34ebf5d2b644fcf973 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 23 Jun 2024 09:00:56 +0000 Subject: [PATCH 175/405] chore(deps): weekly `cargo update` (#9036) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- Cargo.lock | 144 +++++++++++++++++++++++++---------------------------- 1 file changed, 69 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63a6ddb756e5..1c1a59fd1fc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd47e5f8545bdf53beb545d3c039b4afa16040bdf69c50100581579b08776afd" +checksum = "04e9a1892803b02f53e25bea3e414ddd0501f12d97456c9d5ade4edf88f9516f" dependencies = [ "alloy-rlp", "arbitrary", @@ -348,7 +348,7 @@ checksum = "8037e03c7f462a063f28daec9fda285a9a89da003c552f8637a80b9c8fd96241" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -548,7 +548,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -565,7 +565,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", "syn-solidity", "tiny-keccak", ] @@ -583,7 +583,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.66", + "syn 2.0.67", "syn-solidity", ] @@ -786,7 +786,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -1006,7 +1006,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -1017,7 +1017,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -1055,7 +1055,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -1176,7 +1176,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.66", + "syn 2.0.67", "which", ] @@ -1219,9 +1219,9 @@ dependencies = [ [[package]] name = "bitm" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b9ea263f0faf826a1c9de0e8bf8f32f5986c05f5e3abcf6bcde74616009586" +checksum = "b06e8e5bec3490b9f6f3adbb78aa4f53e8396fd9994e8a62a346b44ea7c15f35" dependencies = [ "dyn_size_of", ] @@ -1373,7 +1373,7 @@ checksum = "6be9c93793b60dac381af475b98634d4b451e28336e72218cad9a20176218dbc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", "synstructure", ] @@ -1482,7 +1482,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -1702,7 +1702,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2173,7 +2173,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2197,7 +2197,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2208,7 +2208,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2314,7 +2314,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2327,7 +2327,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2435,13 +2435,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2598,7 +2598,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -2609,7 +2609,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -3254,7 +3254,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -3784,7 +3784,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -3934,7 +3934,7 @@ checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -4338,7 +4338,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -4456,11 +4456,11 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -4948,7 +4948,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -5200,7 +5200,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -5479,7 +5479,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -5508,7 +5508,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -5667,7 +5667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -5727,9 +5727,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -5762,9 +5762,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", @@ -6519,7 +6519,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -7214,7 +7214,7 @@ dependencies = [ "quote", "regex", "serial_test", - "syn 2.0.66", + "syn 2.0.67", "trybuild", ] @@ -8425,9 +8425,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba2e187811b160463663fd71881b4e5d653720ba00be0f1e85962d4db60341c" +checksum = "1b0971cad2f8f1ecb10e270d80646e63bf19daef0dc0a17a45680d24bb346b7c" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -8520,7 +8520,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.15", "libc", - "spin 0.9.8", + "spin", "untrusted", "windows-sys 0.52.0", ] @@ -8980,7 +8980,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9054,7 +9054,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9079,7 +9079,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9306,12 +9306,6 @@ dependencies = [ "sha1", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -9341,7 +9335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" dependencies = [ "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9387,7 +9381,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9455,9 +9449,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" dependencies = [ "proc-macro2", "quote", @@ -9473,7 +9467,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9490,7 +9484,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9575,7 +9569,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9614,7 +9608,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9792,7 +9786,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -9990,7 +9984,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -10435,7 +10429,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", "wasm-bindgen-shared", ] @@ -10469,7 +10463,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10610,7 +10604,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -10621,7 +10615,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -10879,7 +10873,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", "synstructure", ] @@ -10900,7 +10894,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -10920,7 +10914,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", "synstructure", ] @@ -10941,7 +10935,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] @@ -10963,7 +10957,7 @@ checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.67", ] [[package]] From 97de9d27bc6a928a72d7bb89413d3b36ed4aae79 Mon Sep 17 00:00:00 2001 From: leniram159 Date: Sun, 23 Jun 2024 12:30:16 +0200 Subject: [PATCH 176/405] Change the wrong 'Child' and 'Auxiliary' usage (#9033) --- crates/blockchain-tree/src/blockchain_tree.rs | 2 +- crates/blockchain-tree/src/metrics.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index ea73fdcb8702..5d73a1a78e06 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1198,7 +1198,7 @@ where } }); - durations_recorder.record_relative(MakeCanonicalAction::ClearTrieUpdatesForOtherChilds); + durations_recorder.record_relative(MakeCanonicalAction::ClearTrieUpdatesForOtherChildren); // Send notification about new canonical chain and return outcome of canonicalization. let outcome = CanonicalOutcome::Committed { head: chain_notification.tip().header.clone() }; diff --git a/crates/blockchain-tree/src/metrics.rs b/crates/blockchain-tree/src/metrics.rs index 735f1db96f1c..5d44a6391178 100644 --- a/crates/blockchain-tree/src/metrics.rs +++ b/crates/blockchain-tree/src/metrics.rs @@ -89,7 +89,7 @@ pub(crate) enum MakeCanonicalAction { /// Inserting an old canonical chain. InsertOldCanonicalChain, /// Clearing trie updates of other children chains after fork choice update. - ClearTrieUpdatesForOtherChilds, + ClearTrieUpdatesForOtherChildren, } /// Canonicalization metrics @@ -118,7 +118,7 @@ struct MakeCanonicalMetrics { insert_old_canonical_chain: Histogram, /// Duration of the clear trie updates of other children chains after fork choice update /// action. - clear_trie_updates_for_other_childs: Histogram, + clear_trie_updates_for_other_children: Histogram, } impl MakeCanonicalMetrics { @@ -145,8 +145,8 @@ impl MakeCanonicalMetrics { MakeCanonicalAction::InsertOldCanonicalChain => { self.insert_old_canonical_chain.record(duration) } - MakeCanonicalAction::ClearTrieUpdatesForOtherChilds => { - self.clear_trie_updates_for_other_childs.record(duration) + MakeCanonicalAction::ClearTrieUpdatesForOtherChildren => { + self.clear_trie_updates_for_other_children.record(duration) } } } From 8f2522eff9f4fd58776258e1e598de9e4fc503df Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:20:41 +0200 Subject: [PATCH 177/405] refactor(rpc): add builder pattern for `EthHandlers` (#9035) --- crates/rpc/rpc-builder/src/eth.rs | 212 +++++++++++++++++++++++++++++- crates/rpc/rpc-builder/src/lib.rs | 102 ++++---------- 2 files changed, 230 insertions(+), 84 deletions(-) diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 5d0d064247a9..224301966b23 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -1,16 +1,27 @@ +use crate::RpcModuleConfig; +use reth_evm::ConfigureEvm; +use reth_network_api::{NetworkInfo, Peers}; +use reth_provider::{ + AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, + EvmEnvProvider, StateProviderFactory, +}; use reth_rpc::{ eth::{ - cache::{EthStateCache, EthStateCacheConfig}, - gas_oracle::GasPriceOracleConfig, - EthFilterConfig, FeeHistoryCacheConfig, RPC_DEFAULT_GAS_CAP, + cache::{cache_new_blocks_task, EthStateCache, EthStateCacheConfig}, + fee_history_cache_new_blocks_task, + gas_oracle::{GasPriceOracle, GasPriceOracleConfig}, + traits::RawTransactionForwarder, + EthFilterConfig, FeeHistoryCache, FeeHistoryCacheConfig, RPC_DEFAULT_GAS_CAP, }, EthApi, EthFilter, EthPubSub, }; use reth_rpc_server_types::constants::{ default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, }; -use reth_tasks::pool::BlockingTaskPool; +use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; +use reth_transaction_pool::TransactionPool; use serde::{Deserialize, Serialize}; +use std::sync::Arc; /// All handlers for the `eth` namespace #[derive(Debug, Clone)] @@ -27,6 +38,199 @@ pub struct EthHandlers { pub blocking_task_pool: BlockingTaskPool, } +/// Configuration for `EthHandlersBuilder` +#[derive(Clone, Debug)] +pub(crate) struct EthHandlersConfig { + /// The provider for blockchain data, responsible for reading blocks, accounts, state, etc. + pub(crate) provider: Provider, + /// The transaction pool for managing pending transactions. + pub(crate) pool: Pool, + /// The network information, handling peer connections and network state. + pub(crate) network: Network, + /// The task executor for spawning asynchronous tasks. + pub(crate) executor: Tasks, + /// The event subscriptions for canonical state changes. + pub(crate) events: Events, + /// The EVM configuration for Ethereum Virtual Machine settings. + pub(crate) evm_config: EvmConfig, + /// An optional forwarder for raw transactions. + pub(crate) eth_raw_transaction_forwarder: Option>, +} + +/// Represents the builder for the `EthHandlers` struct, used to configure and create instances of +/// `EthHandlers`. +#[derive(Debug, Clone)] +pub(crate) struct EthHandlersBuilder { + eth_handlers_config: EthHandlersConfig, + /// Configuration for the RPC module + rpc_config: RpcModuleConfig, +} + +impl + EthHandlersBuilder +where + Provider: BlockReaderIdExt + + AccountReader + + StateProviderFactory + + EvmEnvProvider + + ChainSpecProvider + + ChangeSetReader + + Clone + + Unpin + + 'static, + Pool: TransactionPool + Clone + 'static, + Network: NetworkInfo + Peers + Clone + 'static, + Tasks: TaskSpawner + Clone + 'static, + Events: CanonStateSubscriptions + Clone + 'static, + EvmConfig: ConfigureEvm + 'static, +{ + /// Creates a new `EthHandlersBuilder` with the provided components. + pub(crate) const fn new( + eth_handlers_config: EthHandlersConfig, + rpc_config: RpcModuleConfig, + ) -> Self { + Self { eth_handlers_config, rpc_config } + } + + /// Builds and returns an `EthHandlers` instance. + pub(crate) fn build(self) -> EthHandlers { + // Initialize the cache + let cache = self.init_cache(); + + // Initialize the fee history cache + let fee_history_cache = self.init_fee_history_cache(&cache); + + // Spawn background tasks for cache + self.spawn_cache_tasks(&cache, &fee_history_cache); + + // Initialize the gas oracle + let gas_oracle = self.init_gas_oracle(&cache); + + // Initialize the blocking task pool + let blocking_task_pool = self.init_blocking_task_pool(); + + // Initialize the Eth API + let api = self.init_api(&cache, gas_oracle, &fee_history_cache, &blocking_task_pool); + + // Initialize the filter + let filter = self.init_filter(&cache); + + // Initialize the pubsub + let pubsub = self.init_pubsub(); + + EthHandlers { api, cache, filter, pubsub, blocking_task_pool } + } + + /// Initializes the `EthStateCache`. + fn init_cache(&self) -> EthStateCache { + EthStateCache::spawn_with( + self.eth_handlers_config.provider.clone(), + self.rpc_config.eth.cache.clone(), + self.eth_handlers_config.executor.clone(), + self.eth_handlers_config.evm_config.clone(), + ) + } + + /// Initializes the `FeeHistoryCache`. + fn init_fee_history_cache(&self, cache: &EthStateCache) -> FeeHistoryCache { + FeeHistoryCache::new(cache.clone(), self.rpc_config.eth.fee_history_cache.clone()) + } + + /// Spawns background tasks for updating caches. + fn spawn_cache_tasks(&self, cache: &EthStateCache, fee_history_cache: &FeeHistoryCache) { + // Get the stream of new canonical blocks + let new_canonical_blocks = self.eth_handlers_config.events.canonical_state_stream(); + + // Clone the cache for the task + let cache_clone = cache.clone(); + + // Spawn a critical task to update the cache with new blocks + self.eth_handlers_config.executor.spawn_critical( + "cache canonical blocks task", + Box::pin(async move { + cache_new_blocks_task(cache_clone, new_canonical_blocks).await; + }), + ); + + // Get another stream of new canonical blocks + let new_canonical_blocks = self.eth_handlers_config.events.canonical_state_stream(); + + // Clone the fee history cache for the task + let fhc_clone = fee_history_cache.clone(); + + // Clone the provider for the task + let provider_clone = self.eth_handlers_config.provider.clone(); + + // Spawn a critical task to update the fee history cache with new blocks + self.eth_handlers_config.executor.spawn_critical( + "cache canonical blocks for fee history task", + Box::pin(async move { + fee_history_cache_new_blocks_task(fhc_clone, new_canonical_blocks, provider_clone) + .await; + }), + ); + } + + /// Initializes the `GasPriceOracle`. + fn init_gas_oracle(&self, cache: &EthStateCache) -> GasPriceOracle { + GasPriceOracle::new( + self.eth_handlers_config.provider.clone(), + self.rpc_config.eth.gas_oracle.clone(), + cache.clone(), + ) + } + + /// Initializes the `BlockingTaskPool`. + fn init_blocking_task_pool(&self) -> BlockingTaskPool { + BlockingTaskPool::build().expect("failed to build tracing pool") + } + + /// Initializes the `EthApi`. + fn init_api( + &self, + cache: &EthStateCache, + gas_oracle: GasPriceOracle, + fee_history_cache: &FeeHistoryCache, + blocking_task_pool: &BlockingTaskPool, + ) -> EthApi { + EthApi::with_spawner( + self.eth_handlers_config.provider.clone(), + self.eth_handlers_config.pool.clone(), + self.eth_handlers_config.network.clone(), + cache.clone(), + gas_oracle, + self.rpc_config.eth.rpc_gas_cap, + Box::new(self.eth_handlers_config.executor.clone()), + blocking_task_pool.clone(), + fee_history_cache.clone(), + self.eth_handlers_config.evm_config.clone(), + self.eth_handlers_config.eth_raw_transaction_forwarder.clone(), + ) + } + + /// Initializes the `EthFilter`. + fn init_filter(&self, cache: &EthStateCache) -> EthFilter { + EthFilter::new( + self.eth_handlers_config.provider.clone(), + self.eth_handlers_config.pool.clone(), + cache.clone(), + self.rpc_config.eth.filter_config(), + Box::new(self.eth_handlers_config.executor.clone()), + ) + } + + /// Initializes the `EthPubSub`. + fn init_pubsub(&self) -> EthPubSub { + EthPubSub::with_spawner( + self.eth_handlers_config.provider.clone(), + self.eth_handlers_config.pool.clone(), + self.eth_handlers_config.events.clone(), + self.eth_handlers_config.network.clone(), + Box::new(self.eth_handlers_config.executor.clone()), + ) + } +} + /// Additional config values for the eth namespace. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct EthConfig { diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 93d4e6b264ee..b635a1351055 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -156,7 +156,10 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use crate::{ - auth::AuthRpcModule, cors::CorsDomainError, error::WsHttpSamePortError, + auth::AuthRpcModule, + cors::CorsDomainError, + error::WsHttpSamePortError, + eth::{EthHandlersBuilder, EthHandlersConfig}, metrics::RpcRequestMetrics, }; use error::{ConflictingModules, RpcError, ServerKind}; @@ -175,22 +178,13 @@ use reth_provider::{ ChangeSetReader, EvmEnvProvider, StateProviderFactory, }; use reth_rpc::{ - eth::{ - cache::{cache_new_blocks_task, EthStateCache}, - fee_history_cache_new_blocks_task, - gas_oracle::GasPriceOracle, - traits::RawTransactionForwarder, - EthBundle, FeeHistoryCache, - }, - AdminApi, DebugApi, EngineEthApi, EthApi, EthFilter, EthPubSub, EthSubscriptionIdProvider, - NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi, Web3Api, + eth::{cache::EthStateCache, traits::RawTransactionForwarder, EthBundle}, + AdminApi, DebugApi, EngineEthApi, EthApi, EthSubscriptionIdProvider, NetApi, OtterscanApi, + RPCApi, RethApi, TraceApi, TxPoolApi, Web3Api, }; use reth_rpc_api::servers::*; use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret}; -use reth_tasks::{ - pool::{BlockingTaskGuard, BlockingTaskPool}, - TaskSpawner, TokioTaskExecutor, -}; +use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, TransactionPool}; use serde::{Deserialize, Serialize}; use std::{ @@ -1001,7 +995,7 @@ where /// /// This will spawn the required service tasks for [`EthApi`] for: /// - [`EthStateCache`] - /// - [`FeeHistoryCache`] + /// - [`reth_rpc::eth::FeeHistoryCache`] fn with_eth(&mut self, f: F) -> R where F: FnOnce(&EthHandlers) -> R, @@ -1013,71 +1007,19 @@ where } fn init_eth(&self) -> EthHandlers { - let cache = EthStateCache::spawn_with( - self.provider.clone(), - self.config.eth.cache.clone(), - self.executor.clone(), - self.evm_config.clone(), - ); - let gas_oracle = GasPriceOracle::new( - self.provider.clone(), - self.config.eth.gas_oracle.clone(), - cache.clone(), - ); - let new_canonical_blocks = self.events.canonical_state_stream(); - let c = cache.clone(); - - self.executor.spawn_critical( - "cache canonical blocks task", - Box::pin(async move { - cache_new_blocks_task(c, new_canonical_blocks).await; - }), - ); - - let fee_history_cache = - FeeHistoryCache::new(cache.clone(), self.config.eth.fee_history_cache.clone()); - let new_canonical_blocks = self.events.canonical_state_stream(); - let fhc = fee_history_cache.clone(); - let provider_clone = self.provider.clone(); - self.executor.spawn_critical( - "cache canonical blocks for fee history task", - Box::pin(async move { - fee_history_cache_new_blocks_task(fhc, new_canonical_blocks, provider_clone).await; - }), - ); - - let executor = Box::new(self.executor.clone()); - let blocking_task_pool = BlockingTaskPool::build().expect("failed to build tracing pool"); - let api = EthApi::with_spawner( - self.provider.clone(), - self.pool.clone(), - self.network.clone(), - cache.clone(), - gas_oracle, - self.config.eth.rpc_gas_cap, - executor.clone(), - blocking_task_pool.clone(), - fee_history_cache, - self.evm_config.clone(), - self.eth_raw_transaction_forwarder.clone(), - ); - let filter = EthFilter::new( - self.provider.clone(), - self.pool.clone(), - cache.clone(), - self.config.eth.filter_config(), - executor.clone(), - ); - - let pubsub = EthPubSub::with_spawner( - self.provider.clone(), - self.pool.clone(), - self.events.clone(), - self.network.clone(), - executor, - ); - - EthHandlers { api, cache, filter, pubsub, blocking_task_pool } + EthHandlersBuilder::new( + EthHandlersConfig { + provider: self.provider.clone(), + pool: self.pool.clone(), + network: self.network.clone(), + executor: self.executor.clone(), + events: self.events.clone(), + evm_config: self.evm_config.clone(), + eth_raw_transaction_forwarder: self.eth_raw_transaction_forwarder.clone(), + }, + self.config.clone(), + ) + .build() } /// Returns the configured [`EthHandlers`] or creates it if it does not exist yet From 3c595f7d1d836e4992bc5e26407c9a5275ff84a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:34:43 +0200 Subject: [PATCH 178/405] feat: add `AnyNodeTypes` type (#9034) --- crates/node/api/src/node.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/node/api/src/node.rs b/crates/node/api/src/node.rs index b58df68c44cf..15345c49b6c0 100644 --- a/crates/node/api/src/node.rs +++ b/crates/node/api/src/node.rs @@ -26,6 +26,32 @@ pub trait NodeTypes: Send + Sync + Unpin + 'static { type Engine: EngineTypes; } +/// A [`NodeTypes`] type builder +#[derive(Default, Debug)] +pub struct AnyNodeTypes

(PhantomData

, PhantomData); + +impl AnyNodeTypes { + /// Sets the `Primitives` associated type. + pub const fn primitives(self) -> AnyNodeTypes { + AnyNodeTypes::(PhantomData::, PhantomData::) + } + + /// Sets the `Engine` associated type. + pub const fn engine(self) -> AnyNodeTypes { + AnyNodeTypes::(PhantomData::

, PhantomData::) + } +} + +impl NodeTypes for AnyNodeTypes +where + P: NodePrimitives + Send + Sync + Unpin + 'static, + E: EngineTypes + Send + Sync + Unpin + 'static, +{ + type Primitives = P; + + type Engine = E; +} + /// A helper trait that is downstream of the [`NodeTypes`] trait and adds stateful components to the /// node. /// From 8a5308672fac61c7199b0c8e26b7dc0f23a00985 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 24 Jun 2024 11:57:45 +0200 Subject: [PATCH 179/405] chore: release 1.0.0 (#9045) --- Cargo.lock | 194 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 98 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c1a59fd1fc5..b9923d34a8e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2515,7 +2515,7 @@ dependencies = [ [[package]] name = "ef-tests" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "rayon", @@ -6187,7 +6187,7 @@ dependencies = [ [[package]] name = "reth" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "ahash", "alloy-rlp", @@ -6277,7 +6277,7 @@ dependencies = [ [[package]] name = "reth-auto-seal-consensus" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "futures-util", "reth-beacon-consensus", @@ -6303,7 +6303,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "futures-core", @@ -6325,7 +6325,7 @@ dependencies = [ [[package]] name = "reth-beacon-consensus" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "assert_matches", @@ -6376,7 +6376,7 @@ dependencies = [ [[package]] name = "reth-bench" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6417,7 +6417,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "aquamarine", @@ -6451,7 +6451,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -6462,7 +6462,7 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-chains", "alloy-eips", @@ -6484,7 +6484,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-tasks", "tokio", @@ -6493,7 +6493,7 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6513,7 +6513,7 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "convert_case 0.6.0", "proc-macro2", @@ -6524,7 +6524,7 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "confy", "humantime-serde", @@ -6537,7 +6537,7 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "auto_impl", "reth-primitives", @@ -6546,7 +6546,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "mockall", "rand 0.8.5", @@ -6558,7 +6558,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6580,7 +6580,7 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "arbitrary", "assert_matches", @@ -6619,7 +6619,7 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "arbitrary", "assert_matches", @@ -6650,7 +6650,7 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "eyre", @@ -6673,7 +6673,7 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6700,7 +6700,7 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6726,7 +6726,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-chains", "alloy-primitives", @@ -6754,7 +6754,7 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "assert_matches", @@ -6789,7 +6789,7 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-consensus", "alloy-network", @@ -6819,7 +6819,7 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "aes 0.8.4", "alloy-primitives", @@ -6849,7 +6849,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-chainspec", "reth-payload-primitives", @@ -6858,7 +6858,7 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-blockchain-tree-api", "reth-consensus", @@ -6870,7 +6870,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "arbitrary", @@ -6905,7 +6905,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "alloy-rlp", @@ -6925,7 +6925,7 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-chainspec", "reth-consensus", @@ -6936,7 +6936,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "reth-chainspec", @@ -6953,7 +6953,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-chains", "alloy-primitives", @@ -6968,7 +6968,7 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-basic-payload-builder", "reth-errors", @@ -6986,7 +6986,7 @@ dependencies = [ [[package]] name = "reth-etl" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "rayon", @@ -6996,7 +6996,7 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "auto_impl", "futures-util", @@ -7013,7 +7013,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-eips", "alloy-sol-types", @@ -7032,7 +7032,7 @@ dependencies = [ [[package]] name = "reth-evm-optimism" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-chainspec", "reth-consensus-common", @@ -7051,7 +7051,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7064,7 +7064,7 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7078,7 +7078,7 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "eyre", "metrics", @@ -7100,7 +7100,7 @@ dependencies = [ [[package]] name = "reth-exex-test-utils" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "eyre", "futures-util", @@ -7130,14 +7130,14 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-fs-util" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "serde_json", "thiserror", @@ -7145,7 +7145,7 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "async-trait", "bytes", @@ -7167,7 +7167,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "bitflags 2.5.0", "byteorder", @@ -7187,7 +7187,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "bindgen", "cc", @@ -7195,7 +7195,7 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "futures", "metrics", @@ -7206,7 +7206,7 @@ dependencies = [ [[package]] name = "reth-metrics-derive" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "metrics", "once_cell", @@ -7220,14 +7220,14 @@ dependencies = [ [[package]] name = "reth-net-banlist" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-net-nat" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "futures-util", "reqwest", @@ -7239,7 +7239,7 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-node-bindings", "alloy-provider", @@ -7294,7 +7294,7 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "alloy-rpc-types-admin", @@ -7308,7 +7308,7 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "auto_impl", "futures", @@ -7326,7 +7326,7 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7342,7 +7342,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "anyhow", "bincode", @@ -7363,7 +7363,7 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-db-api", "reth-engine-primitives", @@ -7378,7 +7378,7 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "aquamarine", "backon", @@ -7428,7 +7428,7 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "alloy-rpc-types-engine", @@ -7493,7 +7493,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -7526,7 +7526,7 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rpc-types-engine", "futures", @@ -7547,7 +7547,7 @@ dependencies = [ [[package]] name = "reth-node-optimism" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -7592,7 +7592,7 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-chainspec", "reth-consensus", @@ -7603,7 +7603,7 @@ dependencies = [ [[package]] name = "reth-optimism-payload-builder" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "reth-basic-payload-builder", @@ -7627,11 +7627,11 @@ dependencies = [ [[package]] name = "reth-optimism-primitives" -version = "1.0.0-rc.2" +version = "1.0.0" [[package]] name = "reth-payload-builder" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "futures-util", "metrics", @@ -7653,7 +7653,7 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-chainspec", "reth-errors", @@ -7667,7 +7667,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-chainspec", "reth-primitives", @@ -7677,7 +7677,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-eips", "alloy-genesis", @@ -7721,7 +7721,7 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7749,7 +7749,7 @@ dependencies = [ [[package]] name = "reth-provider" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "alloy-rpc-types-engine", @@ -7791,7 +7791,7 @@ dependencies = [ [[package]] name = "reth-prune" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "assert_matches", @@ -7819,7 +7819,7 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "arbitrary", @@ -7840,7 +7840,7 @@ dependencies = [ [[package]] name = "reth-revm" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-eips", "alloy-rlp", @@ -7858,7 +7858,7 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-dyn-abi", "alloy-genesis", @@ -7918,7 +7918,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-dyn-abi", "jsonrpsee", @@ -7932,7 +7932,7 @@ dependencies = [ [[package]] name = "reth-rpc-api-testing-util" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "futures", "jsonrpsee", @@ -7946,7 +7946,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "clap", "http 1.1.0", @@ -7988,7 +7988,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "assert_matches", @@ -8021,7 +8021,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rpc-types-engine", "assert_matches", @@ -8038,7 +8038,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "serde", @@ -8047,7 +8047,7 @@ dependencies = [ [[package]] name = "reth-rpc-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -8071,7 +8071,7 @@ dependencies = [ [[package]] name = "reth-rpc-types-compat" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "alloy-rpc-types", @@ -8083,7 +8083,7 @@ dependencies = [ [[package]] name = "reth-stages" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "assert_matches", @@ -8128,7 +8128,7 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "aquamarine", @@ -8157,7 +8157,7 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "arbitrary", @@ -8175,7 +8175,7 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "assert_matches", @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-primitives", "clap", @@ -8208,7 +8208,7 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "auto_impl", "reth-chainspec", @@ -8224,7 +8224,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "reth-fs-util", "reth-primitives", @@ -8233,7 +8233,7 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "dyn-clone", "futures-util", @@ -8249,7 +8249,7 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-genesis", "rand 0.8.5", @@ -8259,7 +8259,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "tokio", "tokio-stream", @@ -8268,7 +8268,7 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "clap", "eyre", @@ -8282,7 +8282,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "aquamarine", @@ -8322,7 +8322,7 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "auto_impl", @@ -8355,7 +8355,7 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -8384,7 +8384,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "1.0.0-rc.2" +version = "1.0.0" dependencies = [ "alloy-rlp", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 7c996352238b..e0ed1793556b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.0.0-rc.2" +version = "1.0.0" edition = "2021" rust-version = "1.79" license = "MIT OR Apache-2.0" From 31e247086cc5fd765f1ce44385411e123e4cd110 Mon Sep 17 00:00:00 2001 From: Tien Nguyen <116023870+htiennv@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:00:02 +0700 Subject: [PATCH 180/405] feat(rpc): remove ipc future and now using ServerHandle and StopHandle from jsonrpsee (#9044) --- crates/rpc/ipc/src/server/future.rs | 60 ----------------------------- crates/rpc/ipc/src/server/mod.rs | 48 ++++------------------- crates/rpc/rpc-builder/src/auth.rs | 6 +-- crates/rpc/rpc-builder/src/lib.rs | 2 +- 4 files changed, 11 insertions(+), 105 deletions(-) delete mode 100644 crates/rpc/ipc/src/server/future.rs diff --git a/crates/rpc/ipc/src/server/future.rs b/crates/rpc/ipc/src/server/future.rs deleted file mode 100644 index d6aa675c98f9..000000000000 --- a/crates/rpc/ipc/src/server/future.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Utilities for handling async code. - -use std::sync::Arc; -use tokio::sync::watch; - -#[derive(Debug, Clone)] -pub(crate) struct StopHandle(watch::Receiver<()>); - -impl StopHandle { - pub(crate) const fn new(rx: watch::Receiver<()>) -> Self { - Self(rx) - } - - pub(crate) async fn shutdown(mut self) { - // Err(_) implies that the `sender` has been dropped. - // Ok(_) implies that `stop` has been called. - let _ = self.0.changed().await; - } -} - -/// Server handle. -/// -/// When all [`StopHandle`]'s have been `dropped` or `stop` has been called -/// the server will be stopped. -#[derive(Debug, Clone)] -pub(crate) struct ServerHandle(Arc>); - -impl ServerHandle { - /// Wait for the server to stop. - #[allow(dead_code)] - pub(crate) async fn stopped(self) { - self.0.closed().await - } -} diff --git a/crates/rpc/ipc/src/server/mod.rs b/crates/rpc/ipc/src/server/mod.rs index c38b1629e667..6dff8a8afae0 100644 --- a/crates/rpc/ipc/src/server/mod.rs +++ b/crates/rpc/ipc/src/server/mod.rs @@ -1,9 +1,6 @@ //! JSON-RPC IPC server implementation -use crate::server::{ - connection::{IpcConn, JsonRpcStream}, - future::StopHandle, -}; +use crate::server::connection::{IpcConn, JsonRpcStream}; use futures::StreamExt; use futures_util::future::Either; use interprocess::local_socket::{ @@ -15,8 +12,8 @@ use jsonrpsee::{ core::TEN_MB_SIZE_BYTES, server::{ middleware::rpc::{RpcLoggerLayer, RpcServiceT}, - AlreadyStoppedError, ConnectionGuard, ConnectionPermit, IdProvider, - RandomIntegerIdProvider, + stop_channel, ConnectionGuard, ConnectionPermit, IdProvider, RandomIntegerIdProvider, + ServerHandle, StopHandle, }, BoundedSubscriptions, MethodSink, Methods, }; @@ -29,7 +26,7 @@ use std::{ }; use tokio::{ io::{AsyncRead, AsyncWrite, AsyncWriteExt}, - sync::{oneshot, watch}, + sync::oneshot, }; use tower::{layer::util::Identity, Layer, Service}; use tracing::{debug, instrument, trace, warn, Instrument}; @@ -46,7 +43,6 @@ use tokio_stream::wrappers::ReceiverStream; use tower::layer::{util::Stack, LayerFn}; mod connection; -mod future; mod ipc; mod rpc_service; @@ -109,9 +105,8 @@ where methods: impl Into, ) -> Result { let methods = methods.into(); - let (stop_tx, stop_rx) = watch::channel(()); - let stop_handle = StopHandle::new(stop_rx); + let (stop_handle, server_handle) = stop_channel(); // use a signal channel to wait until we're ready to accept connections let (tx, rx) = oneshot::channel(); @@ -122,7 +117,7 @@ where }; rx.await.expect("channel is open")?; - Ok(ServerHandle::new(stop_tx)) + Ok(server_handle) } async fn start_inner( @@ -795,35 +790,6 @@ impl Builder { } } -/// Server handle. -/// -/// When all [`jsonrpsee::server::StopHandle`]'s have been `dropped` or `stop` has been called -/// the server will be stopped. -#[derive(Debug, Clone)] -pub struct ServerHandle(Arc>); - -impl ServerHandle { - /// Create a new server handle. - pub(crate) fn new(tx: watch::Sender<()>) -> Self { - Self(Arc::new(tx)) - } - - /// Tell the server to stop without waiting for the server to stop. - pub fn stop(&self) -> Result<(), AlreadyStoppedError> { - self.0.send(()).map_err(|_| AlreadyStoppedError) - } - - /// Wait for the server to stop. - pub async fn stopped(self) { - self.0.closed().await - } - - /// Check if the server has been stopped. - pub fn is_stopped(&self) -> bool { - self.0.is_closed() - } -} - #[cfg(test)] pub fn dummy_name() -> String { let num: u64 = rand::Rng::gen(&mut rand::thread_rng()); @@ -877,7 +843,7 @@ mod tests { // and you might want to do something smarter if it's // critical that "the most recent item" must be sent when it is produced. if sink.send(notif).await.is_err() { - break Ok(()) + break Ok(()); } closed = c; diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 3f016c330e78..1e8ef8f56a18 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -68,7 +68,7 @@ impl AuthServerConfig { .map_err(|err| RpcError::server_error(err, ServerKind::Auth(socket_addr)))?; let handle = server.start(module.inner.clone()); - let mut ipc_handle: Option = None; + let mut ipc_handle: Option = None; if let Some(ipc_server_config) = ipc_server_config { let ipc_endpoint_str = ipc_endpoint @@ -241,7 +241,7 @@ pub struct AuthServerHandle { handle: jsonrpsee::server::ServerHandle, secret: JwtSecret, ipc_endpoint: Option, - ipc_handle: Option, + ipc_handle: Option, } // === impl AuthServerHandle === @@ -310,7 +310,7 @@ impl AuthServerHandle { } /// Returns an ipc handle - pub fn ipc_handle(&self) -> Option { + pub fn ipc_handle(&self) -> Option { self.ipc_handle.clone() } diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index b635a1351055..7257b3be35ab 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1821,7 +1821,7 @@ pub struct RpcServerHandle { http: Option, ws: Option, ipc_endpoint: Option, - ipc: Option, + ipc: Option, jwt_secret: Option, } From bd0f676d06b33d84fc67f8b18963c876d2fb9626 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:21:16 +0200 Subject: [PATCH 181/405] feat(node): derive `Clone` for `FullNode` (#9046) --- crates/node/builder/src/node.rs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/crates/node/builder/src/node.rs b/crates/node/builder/src/node.rs index 0b82b2e9e1e0..76c2117552ee 100644 --- a/crates/node/builder/src/node.rs +++ b/crates/node/builder/src/node.rs @@ -31,7 +31,7 @@ pub trait Node: NodeTypes + Clone { /// The launched node with all components including RPC handlers. /// /// This can be used to interact with the launched node. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FullNode { /// The evm configuration. pub evm_config: Node::Evm, @@ -95,21 +95,3 @@ impl FullNode { self.auth_server_handle().ipc_client().await } } - -impl Clone for FullNode { - fn clone(&self) -> Self { - Self { - evm_config: self.evm_config.clone(), - block_executor: self.block_executor.clone(), - pool: self.pool.clone(), - network: self.network.clone(), - provider: self.provider.clone(), - payload_builder: self.payload_builder.clone(), - task_executor: self.task_executor.clone(), - rpc_server_handles: self.rpc_server_handles.clone(), - rpc_registry: self.rpc_registry.clone(), - config: self.config.clone(), - data_dir: self.data_dir.clone(), - } - } -} From 08b1e882727992a58adfa920df2c5a188ed43645 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Mon, 24 Jun 2024 12:21:52 +0200 Subject: [PATCH 182/405] feat: integrate Node traits into LaunchContextWith (#8993) --- crates/node/builder/src/launch/common.rs | 232 +++++++++++++++++++---- crates/node/builder/src/launch/mod.rs | 119 ++++-------- 2 files changed, 234 insertions(+), 117 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 45c5fe01cbe4..1c48fd763041 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -1,18 +1,28 @@ //! Helper types that can be used by launchers. +use crate::{ + components::{NodeComponents, NodeComponentsBuilder}, + hooks::OnComponentInitializedHook, + BuilderContext, NodeAdapter, +}; use backon::{ConstantBuilder, Retryable}; use eyre::Context; use rayon::ThreadPoolBuilder; use reth_auto_seal_consensus::MiningMode; use reth_beacon_consensus::EthBeaconConsensus; -use reth_blockchain_tree::{noop::NoopBlockchainTree, BlockchainTreeConfig}; +use reth_blockchain_tree::{ + noop::NoopBlockchainTree, BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, + TreeExternals, +}; use reth_chainspec::{Chain, ChainSpec}; use reth_config::{config::EtlConfig, PruneConfig}; +use reth_consensus::Consensus; use reth_db_api::{database::Database, database_metrics::DatabaseMetrics}; use reth_db_common::init::{init_genesis, InitDatabaseError}; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; use reth_evm::noop::NoopBlockExecutorProvider; use reth_network_p2p::headers::client::HeadersClient; +use reth_node_api::FullNodeTypes; use reth_node_core::{ dirs::{ChainPath, DataDirPath}, node_config::NodeConfig, @@ -29,7 +39,7 @@ use reth_stages::{sets::DefaultStages, MetricEvent, Pipeline, PipelineTarget}; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; use reth_tracing::tracing::{debug, error, info, warn}; -use std::{sync::Arc, thread::available_parallelism}; +use std::{marker::PhantomData, sync::Arc, thread::available_parallelism}; use tokio::sync::{ mpsc::{unbounded_channel, Receiver, UnboundedSender}, oneshot, watch, @@ -509,9 +519,12 @@ where } /// Creates a `BlockchainProvider` and attaches it to the launch context. - pub async fn with_blockchain_db( + pub async fn with_blockchain_db( self, - ) -> eyre::Result>>> { + ) -> eyre::Result>>> + where + T: FullNodeTypes::DB>>, + { let tree_config = BlockchainTreeConfig::default(); // NOTE: This is a temporary workaround to provide the canon state notification sender to the components builder because there's a cyclic dependency between the blockchain provider and the tree component. This will be removed once the Blockchain provider no longer depends on an instance of the tree: @@ -526,11 +539,15 @@ where )?; let metered_providers = WithMeteredProviders { - provider_factory: self.provider_factory().clone(), + db_provider_container: WithMeteredProvider { + provider_factory: self.provider_factory().clone(), + metrics_sender: self.sync_metrics_tx(), + }, blockchain_db, - metrics_sender: self.sync_metrics_tx(), tree_config, canon_state_notification_sender, + // we store here a reference to T. + phantom_data: PhantomData, }; let ctx = LaunchContextWith { @@ -542,9 +559,10 @@ where } } -impl LaunchContextWith>> +impl LaunchContextWith>> where DB: Database + DatabaseMetrics + Send + Sync + Clone + 'static, + T: FullNodeTypes>, { /// Returns access to the underlying database. pub fn database(&self) -> &DB { @@ -553,20 +571,122 @@ where /// Returns the configured `ProviderFactory`. pub const fn provider_factory(&self) -> &ProviderFactory { - &self.right().provider_factory + &self.right().db_provider_container.provider_factory } - /// Returns the static file provider to interact with the static files. - pub fn static_file_provider(&self) -> StaticFileProvider { - self.provider_factory().static_file_provider() + /// Fetches the head block from the database. + /// + /// If the database is empty, returns the genesis block. + pub fn lookup_head(&self) -> eyre::Result { + self.node_config() + .lookup_head(self.provider_factory().clone()) + .wrap_err("the head block is missing") } - /// Creates a new [`StaticFileProducer`] with the attached database. - pub fn static_file_producer(&self) -> StaticFileProducer { - StaticFileProducer::new( + /// Returns the metrics sender. + pub fn sync_metrics_tx(&self) -> UnboundedSender { + self.right().db_provider_container.metrics_sender.clone() + } + + /// Returns a reference to the `BlockchainProvider`. + pub const fn blockchain_db(&self) -> &BlockchainProvider { + &self.right().blockchain_db + } + + /// Returns a reference to the `BlockchainTreeConfig`. + pub const fn tree_config(&self) -> &BlockchainTreeConfig { + &self.right().tree_config + } + + /// Returns the `CanonStateNotificationSender`. + pub fn canon_state_notification_sender(&self) -> CanonStateNotificationSender { + self.right().canon_state_notification_sender.clone() + } + + /// Creates a `NodeAdapter` and attaches it to the launch context. + pub async fn with_components( + self, + components_builder: CB, + on_component_initialized: Box< + dyn OnComponentInitializedHook>, + >, + ) -> eyre::Result>>> + where + CB: NodeComponentsBuilder, + { + // fetch the head block from the database + let head = self.lookup_head()?; + + let builder_ctx = BuilderContext::new( + head, + self.blockchain_db().clone(), + self.task_executor().clone(), + self.configs().clone(), + ); + + debug!(target: "reth::cli", "creating components"); + let components = components_builder.build_components(&builder_ctx).await?; + + let consensus: Arc = Arc::new(components.consensus().clone()); + + let tree_externals = TreeExternals::new( self.provider_factory().clone(), - self.prune_modes().unwrap_or_default(), - ) + consensus.clone(), + components.block_executor().clone(), + ); + let tree = BlockchainTree::new(tree_externals, *self.tree_config(), self.prune_modes())? + .with_sync_metrics_tx(self.sync_metrics_tx()) + // Note: This is required because we need to ensure that both the components and the + // tree are using the same channel for canon state notifications. This will be removed + // once the Blockchain provider no longer depends on an instance of the tree + .with_canon_state_notification_sender(self.canon_state_notification_sender()); + + let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree)); + + // Replace the tree component with the actual tree + let blockchain_db = self.blockchain_db().clone().with_tree(blockchain_tree); + + debug!(target: "reth::cli", "configured blockchain tree"); + + let node_adapter = NodeAdapter { + components, + task_executor: self.task_executor().clone(), + provider: blockchain_db.clone(), + }; + + debug!(target: "reth::cli", "calling on_component_initialized hook"); + on_component_initialized.on_event(node_adapter.clone())?; + + let components_container = WithComponents { + db_provider_container: WithMeteredProvider { + provider_factory: self.provider_factory().clone(), + metrics_sender: self.sync_metrics_tx(), + }, + blockchain_db, + tree_config: self.right().tree_config, + node_adapter, + head, + consensus, + }; + + let ctx = LaunchContextWith { + inner: self.inner, + attachment: self.attachment.map_right(|_| components_container), + }; + + Ok(ctx) + } +} + +impl LaunchContextWith>> +where + DB: Database + DatabaseMetrics + Send + Sync + Clone + 'static, + T: FullNodeTypes>, + CB: NodeComponentsBuilder, +{ + /// Returns the configured `ProviderFactory`. + pub const fn provider_factory(&self) -> &ProviderFactory { + &self.right().db_provider_container.provider_factory } /// Returns the max block that the node should run to, looking it up from the network if @@ -578,18 +698,27 @@ where self.node_config().max_block(client, self.provider_factory().clone()).await } - /// Fetches the head block from the database. - /// - /// If the database is empty, returns the genesis block. - pub fn lookup_head(&self) -> eyre::Result { - self.node_config() - .lookup_head(self.provider_factory().clone()) - .wrap_err("the head block is missing") + /// Returns the static file provider to interact with the static files. + pub fn static_file_provider(&self) -> StaticFileProvider { + self.provider_factory().static_file_provider() } - /// Returns the metrics sender. - pub fn sync_metrics_tx(&self) -> UnboundedSender { - self.right().metrics_sender.clone() + /// Creates a new [`StaticFileProducer`] with the attached database. + pub fn static_file_producer(&self) -> StaticFileProducer { + StaticFileProducer::new( + self.provider_factory().clone(), + self.prune_modes().unwrap_or_default(), + ) + } + + /// Returns the current head block. + pub const fn head(&self) -> Head { + self.right().head + } + + /// Returns the configured `NodeAdapter`. + pub const fn node_adapter(&self) -> &NodeAdapter { + &self.right().node_adapter } /// Returns a reference to the `BlockchainProvider`. @@ -597,14 +726,24 @@ where &self.right().blockchain_db } + /// Returns the configured `Consensus`. + pub fn consensus(&self) -> Arc { + self.right().consensus.clone() + } + + /// Returns the metrics sender. + pub fn sync_metrics_tx(&self) -> UnboundedSender { + self.right().db_provider_container.metrics_sender.clone() + } + /// Returns a reference to the `BlockchainTreeConfig`. pub const fn tree_config(&self) -> &BlockchainTreeConfig { &self.right().tree_config } - /// Returns the `CanonStateNotificationSender`. - pub fn canon_state_notification_sender(&self) -> CanonStateNotificationSender { - self.right().canon_state_notification_sender.clone() + /// Returns the node adapter components. + pub const fn components(&self) -> &CB::Components { + &self.node_adapter().components } } @@ -668,23 +807,40 @@ pub struct WithConfigs { pub toml_config: reth_config::Config, } +/// Helper container type to bundle the [`ProviderFactory`] and the metrics +/// sender. +#[derive(Debug, Clone)] +pub struct WithMeteredProvider { + provider_factory: ProviderFactory, + metrics_sender: UnboundedSender, +} + /// Helper container to bundle the [`ProviderFactory`], [`BlockchainProvider`] /// and a metrics sender. #[allow(missing_debug_implementations)] -pub struct WithMeteredProviders { - provider_factory: ProviderFactory, +pub struct WithMeteredProviders { + db_provider_container: WithMeteredProvider, blockchain_db: BlockchainProvider, - metrics_sender: UnboundedSender, canon_state_notification_sender: CanonStateNotificationSender, tree_config: BlockchainTreeConfig, + // this field is used to store a reference to the FullNodeTypes so that we + // can build the components in `with_components` method. + phantom_data: PhantomData, } -/// Helper container type to bundle athe [`ProviderFactory`] and the metrics -/// sender. -#[derive(Debug)] -pub struct WithMeteredProvider { - provider_factory: ProviderFactory, - metrics_sender: UnboundedSender, +/// Helper container to bundle the metered providers container and [`NodeAdapter`]. +#[allow(missing_debug_implementations)] +pub struct WithComponents +where + T: FullNodeTypes>, + CB: NodeComponentsBuilder, +{ + db_provider_container: WithMeteredProvider, + tree_config: BlockchainTreeConfig, + blockchain_db: BlockchainProvider, + node_adapter: NodeAdapter, + head: Head, + consensus: Arc, } #[cfg(test)] diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 9326e3b14c04..9795cead4155 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -5,19 +5,17 @@ use crate::{ components::{NodeComponents, NodeComponentsBuilder}, hooks::NodeHooks, node::FullNode, - BuilderContext, NodeBuilderWithComponents, NodeHandle, + NodeBuilderWithComponents, NodeHandle, }; use futures::{future::Either, stream, stream_select, StreamExt}; use reth_beacon_consensus::{ hooks::{EngineHooks, PruneHook, StaticFileHook}, BeaconConsensusEngine, }; -use reth_blockchain_tree::{BlockchainTree, ShareableBlockchainTree, TreeExternals}; -use reth_consensus::Consensus; use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider}; use reth_exex::ExExManagerHandle; use reth_network::NetworkEvents; -use reth_node_api::{FullNodeComponents, FullNodeTypes}; +use reth_node_api::FullNodeTypes; use reth_node_core::{ dirs::{ChainPath, DataDirPath}, engine::EngineMessageStreamExt, @@ -101,6 +99,7 @@ where add_ons: NodeAddOns { hooks, rpc, exexs: installed_exex }, config, } = target; + let NodeHooks { on_component_initialized, on_node_started, .. } = hooks; // setup the launch context let ctx = ctx @@ -127,61 +126,23 @@ where info!(target: "reth::cli", "\n{}", this.chain_spec().display_hardforks()); }) .with_metrics() - .with_blockchain_db().await?; - - // fetch the head block from the database - let head = ctx.lookup_head()?; - - let builder_ctx = BuilderContext::new( - head, - ctx.blockchain_db().clone(), - ctx.task_executor().clone(), - ctx.configs().clone(), - ); - - debug!(target: "reth::cli", "creating components"); - let components = components_builder.build_components(&builder_ctx).await?; - - let consensus: Arc = Arc::new(components.consensus().clone()); - - let tree_externals = TreeExternals::new( - ctx.provider_factory().clone(), - consensus.clone(), - components.block_executor().clone(), - ); - let tree = BlockchainTree::new(tree_externals, *ctx.tree_config(), ctx.prune_modes())? - .with_sync_metrics_tx(ctx.sync_metrics_tx()) - // Note: This is required because we need to ensure that both the components and the - // tree are using the same channel for canon state notifications. This will be removed - // once the Blockchain provider no longer depends on an instance of the tree - .with_canon_state_notification_sender(ctx.canon_state_notification_sender()); - - let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree)); - - // Replace the tree component with the actual tree - let blockchain_db = ctx.blockchain_db().clone().with_tree(blockchain_tree); - - debug!(target: "reth::cli", "configured blockchain tree"); - - let NodeHooks { on_component_initialized, on_node_started, .. } = hooks; - - let node_adapter = NodeAdapter { - components, - task_executor: ctx.task_executor().clone(), - provider: blockchain_db.clone(), - }; - - debug!(target: "reth::cli", "calling on_component_initialized hook"); - on_component_initialized.on_event(node_adapter.clone())?; + // passing FullNodeTypes as type parameter here so that we can build + // later the components. + .with_blockchain_db::().await? + .with_components(components_builder, on_component_initialized).await?; // spawn exexs - let exex_manager_handle = - ExExLauncher::new(head, node_adapter.clone(), installed_exex, ctx.configs().clone()) - .launch() - .await; + let exex_manager_handle = ExExLauncher::new( + ctx.head(), + ctx.node_adapter().clone(), + installed_exex, + ctx.configs().clone(), + ) + .launch() + .await; // create pipeline - let network_client = node_adapter.network().fetch_client().await?; + let network_client = ctx.components().network().fetch_client().await?; let (consensus_engine_tx, consensus_engine_rx) = unbounded_channel(); let node_config = ctx.node_config(); @@ -216,30 +177,30 @@ where // install auto-seal let mining_mode = - ctx.dev_mining_mode(node_adapter.components.pool().pending_transactions_listener()); + ctx.dev_mining_mode(ctx.components().pool().pending_transactions_listener()); info!(target: "reth::cli", mode=%mining_mode, "configuring dev mining mode"); let (_, client, mut task) = reth_auto_seal_consensus::AutoSealBuilder::new( ctx.chain_spec(), - blockchain_db.clone(), - node_adapter.components.pool().clone(), + ctx.blockchain_db().clone(), + ctx.components().pool().clone(), consensus_engine_tx.clone(), mining_mode, - node_adapter.components.block_executor().clone(), + ctx.components().block_executor().clone(), ) .build(); let pipeline = crate::setup::build_networked_pipeline( &ctx.toml_config().stages, client.clone(), - consensus.clone(), + ctx.consensus(), ctx.provider_factory().clone(), ctx.task_executor(), ctx.sync_metrics_tx(), ctx.prune_config(), max_block, static_file_producer, - node_adapter.components.block_executor().clone(), + ctx.components().block_executor().clone(), pipeline_exex_handle, ) .await?; @@ -254,14 +215,14 @@ where let pipeline = crate::setup::build_networked_pipeline( &ctx.toml_config().stages, network_client.clone(), - consensus.clone(), + ctx.consensus(), ctx.provider_factory().clone(), ctx.task_executor(), ctx.sync_metrics_tx(), ctx.prune_config(), max_block, static_file_producer, - node_adapter.components.block_executor().clone(), + ctx.components().block_executor().clone(), pipeline_exex_handle, ) .await?; @@ -290,11 +251,11 @@ where let (beacon_consensus_engine, beacon_engine_handle) = BeaconConsensusEngine::with_channel( client, pipeline, - blockchain_db.clone(), + ctx.blockchain_db().clone(), Box::new(ctx.task_executor().clone()), - Box::new(node_adapter.components.network().clone()), + Box::new(ctx.components().network().clone()), max_block, - node_adapter.components.payload_builder().clone(), + ctx.components().payload_builder().clone(), initial_target, reth_beacon_consensus::MIN_BLOCKS_FOR_PIPELINE_RUN, consensus_engine_tx, @@ -304,12 +265,12 @@ where info!(target: "reth::cli", "Consensus engine initialized"); let events = stream_select!( - node_adapter.components.network().event_listener().map(Into::into), + ctx.components().network().event_listener().map(Into::into), beacon_engine_handle.event_listener().map(Into::into), pipeline_events.map(Into::into), if ctx.node_config().debug.tip.is_none() && !ctx.is_dev() { Either::Left( - ConsensusLayerHealthEvents::new(Box::new(blockchain_db.clone())) + ConsensusLayerHealthEvents::new(Box::new(ctx.blockchain_db().clone())) .map(Into::into), ) } else { @@ -321,8 +282,8 @@ where ctx.task_executor().spawn_critical( "events task", node::handle_events( - Some(node_adapter.components.network().clone()), - Some(head.number), + Some(ctx.components().network().clone()), + Some(ctx.head().number), events, database.clone(), ), @@ -335,10 +296,10 @@ where commit: VERGEN_GIT_SHA.to_string(), }; let engine_api = EngineApi::new( - blockchain_db.clone(), + ctx.blockchain_db().clone(), ctx.chain_spec(), beacon_engine_handle, - node_adapter.components.payload_builder().clone().into(), + ctx.components().payload_builder().clone().into(), Box::new(ctx.task_executor().clone()), client, ); @@ -349,7 +310,7 @@ where // Start RPC servers let (rpc_server_handles, mut rpc_registry) = crate::rpc::launch_rpc_servers( - node_adapter.clone(), + ctx.node_adapter().clone(), engine_api, ctx.node_config(), jwt_secret, @@ -413,12 +374,12 @@ where } let full_node = FullNode { - evm_config: node_adapter.components.evm_config().clone(), - block_executor: node_adapter.components.block_executor().clone(), - pool: node_adapter.components.pool().clone(), - network: node_adapter.components.network().clone(), - provider: node_adapter.provider.clone(), - payload_builder: node_adapter.components.payload_builder().clone(), + evm_config: ctx.components().evm_config().clone(), + block_executor: ctx.components().block_executor().clone(), + pool: ctx.components().pool().clone(), + network: ctx.components().network().clone(), + provider: ctx.node_adapter().provider.clone(), + payload_builder: ctx.components().payload_builder().clone(), task_executor: ctx.task_executor().clone(), rpc_server_handles, rpc_registry, From 42bbafff2c825a7c2a300e5c1156d08fed083162 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 24 Jun 2024 12:31:15 +0200 Subject: [PATCH 183/405] ci: use reth-prod.png for release notes (#9047) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91f65d2bcee7..c71b8c530990 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -132,7 +132,7 @@ jobs: # https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/.github/workflows/build.yml run: | body=$(cat <<- "ENDBODY" - ![image](https://github.com/paradigmxyz/reth/assets/17802178/d02595cf-7130-418f-81a3-ec91f614abf5) + ![image](https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-prod.png) ## Testing Checklist (DELETE ME) From 07def85cda048ef73b3a01a4eaecd3d615094d02 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:20:06 +0200 Subject: [PATCH 184/405] chore: tweak profiles, rename debug-fast to profiling (#9051) --- Cargo.toml | 16 ++++++++++------ bin/reth-bench/README.md | 3 +-- book/developers/profiling.md | 6 +++--- crates/consensus/beacon/src/engine/mod.rs | 2 +- crates/net/discv4/src/lib.rs | 2 +- crates/primitives/src/alloy_compat.rs | 2 +- crates/stages/stages/src/stages/bodies.rs | 2 +- crates/tasks/src/lib.rs | 4 ++-- 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0ed1793556b..6724c5318889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -224,20 +224,24 @@ opt-level = 3 lto = "thin" [profile.release] +opt-level = 3 lto = "thin" -strip = "debuginfo" +debug = "line-tables-only" +strip = true +panic = "unwind" +codegen-units = 16 -# Like release, but with full debug symbols. Useful for e.g. `perf`. -[profile.debug-fast] +# Use the `--profile profiling` flag to show symbols in release mode. +# e.g. `cargo build --profile profiling` +[profile.profiling] inherits = "release" -strip = "none" -debug = true +debug = 1 +strip = false [profile.maxperf] inherits = "release" lto = "fat" codegen-units = 1 -incremental = false [workspace.dependencies] # reth diff --git a/bin/reth-bench/README.md b/bin/reth-bench/README.md index 97c9572a1c6d..fa58d467f3d3 100644 --- a/bin/reth-bench/README.md +++ b/bin/reth-bench/README.md @@ -23,8 +23,7 @@ As long as the data is representative of real-world load, or closer to worst-cas ## Prerequisites -If you will be collecting CPU profiles, make sure `reth` is compiled with the `debug-fast` profile. -For collecting memory profiles, make sure `reth` is also compiled with the `--features profiling` flag. +If you will be collecting CPU profiles, make sure `reth` is compiled with the `profiling` profile. Otherwise, running `make maxperf` at the root of the repo should be sufficient for collecting accurate performance metrics. ## Command Usage diff --git a/book/developers/profiling.md b/book/developers/profiling.md index 884032b2ac88..f1fdf520eb2e 100644 --- a/book/developers/profiling.md +++ b/book/developers/profiling.md @@ -41,12 +41,12 @@ cargo build --features jemalloc-prof ``` When performing a longer-running or performance-sensitive task with reth, such as a sync test or load benchmark, it's usually recommended to use the `maxperf` profile. However, the `maxperf` -profile does not enable debug symbols, which are required for tools like `perf` and `jemalloc` to produce results that a human can interpret. Reth includes a performance profile with debug symbols called `debug-fast`. To compile reth with debug symbols, jemalloc, profiling, and a performance profile: +profile does not enable debug symbols, which are required for tools like `perf` and `jemalloc` to produce results that a human can interpret. Reth includes a performance profile with debug symbols called `profiling`. To compile reth with debug symbols, jemalloc, profiling, and a performance profile: ``` -cargo build --features jemalloc-prof --profile debug-fast +cargo build --features jemalloc-prof --profile profiling # May improve performance even more -RUSTFLAGS="-C target-cpu=native" cargo build --features jemalloc-prof --profile debug-fast +RUSTFLAGS="-C target-cpu=native" cargo build --features jemalloc-prof --profile profiling ``` ### Monitoring memory usage diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 72f71b2861ac..3391a1de95e5 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -2162,7 +2162,7 @@ mod tests { b.clone().try_seal_with_senders().expect("invalid tx signature in block"), None, ) - .map(|_| ()) + .map(drop) }) .expect("failed to insert"); provider.commit().unwrap(); diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 50311fd3c00f..6e18892fdd0c 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -1537,7 +1537,7 @@ impl Discv4Service { /// - timestamp is expired (lower than current local UNIX timestamp) fn ensure_not_expired(&self, timestamp: u64) -> Result<(), ()> { // ensure the timestamp is a valid UNIX timestamp - let _ = i64::try_from(timestamp).map_err(|_| ())?; + let _ = i64::try_from(timestamp).map_err(drop)?; let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); if self.config.enforce_expiration_timestamps && timestamp < now { diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index e53be884423b..f257ee8bb89a 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -168,7 +168,7 @@ impl TryFrom for Transaction { .gas .try_into() .map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?, - placeholder: tx.to.map(|_| ()), + placeholder: tx.to.map(drop), to: tx.to.unwrap_or_default(), value: tx.value, access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, diff --git a/crates/stages/stages/src/stages/bodies.rs b/crates/stages/stages/src/stages/bodies.rs index 454ee50bbe20..6776597a6065 100644 --- a/crates/stages/stages/src/stages/bodies.rs +++ b/crates/stages/stages/src/stages/bodies.rs @@ -741,7 +741,7 @@ mod tests { let transaction = random_signed_tx(&mut rng); static_file_producer .append_transaction(tx_num, transaction.into()) - .map(|_| ()) + .map(drop) })?; if body.tx_count != 0 { diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index ee5222e91312..2d3f5f41b2b6 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -464,7 +464,7 @@ impl TaskExecutor { error!("{task_error}"); let _ = panicked_tasks_tx.send(task_error); }) - .map(|_| ()) + .map(drop) .in_current_span(); self.handle.spawn(task) @@ -513,7 +513,7 @@ impl TaskExecutor { error!("{task_error}"); let _ = panicked_tasks_tx.send(task_error); }) - .map(|_| ()) + .map(drop) .in_current_span(); self.handle.spawn(task) From 81b5fbf57307b6eefe708c71d7d59e713dc70101 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 24 Jun 2024 14:40:35 +0100 Subject: [PATCH 185/405] feat(examples): remote exex (#8890) --- .github/workflows/integration.yml | 2 +- .github/workflows/lint.yml | 3 + .github/workflows/unit.yml | 3 +- Cargo.lock | 301 +++++++- crates/primitives/src/lib.rs | 2 +- examples/exex/remote/Cargo.toml | 42 ++ examples/exex/remote/bin/consumer.rs | 32 + examples/exex/remote/bin/exex.rs | 77 +++ examples/exex/remote/build.rs | 4 + examples/exex/remote/proto/exex.proto | 279 ++++++++ examples/exex/remote/src/codec.rs | 961 ++++++++++++++++++++++++++ examples/exex/remote/src/lib.rs | 4 + 12 files changed, 1690 insertions(+), 20 deletions(-) create mode 100644 examples/exex/remote/Cargo.toml create mode 100644 examples/exex/remote/bin/consumer.rs create mode 100644 examples/exex/remote/bin/exex.rs create mode 100644 examples/exex/remote/build.rs create mode 100644 examples/exex/remote/proto/exex.proto create mode 100644 examples/exex/remote/src/codec.rs create mode 100644 examples/exex/remote/src/lib.rs diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 8bd42c8bb31d..a57411744939 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -41,7 +41,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude ef-tests \ + --workspace --exclude example-exex-remote --exclude ef-tests \ -E "kind(test)" - if: matrix.network == 'optimism' name: Run tests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c07cee38830b..c758f6945a05 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -41,6 +41,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked env: RUSTFLAGS: -D warnings @@ -70,6 +71,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - run: cargo hack check msrv: @@ -105,6 +107,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - run: cargo docs --document-private-items env: # Keep in sync with ./book.yml:jobs.build diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index a6663aea8843..a5d42a85d208 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -39,7 +39,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude ef-tests \ + --workspace --exclude example-exex-remote --exclude ef-tests \ --partition hash:${{ matrix.partition }}/2 \ -E "!kind(test)" @@ -84,6 +84,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - uses: arduino/setup-protoc@v3 - name: Run doctests run: cargo test --doc --workspace --features "${{ matrix.network }}" diff --git a/Cargo.lock b/Cargo.lock index b9923d34a8e8..4e26b254f381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1064,6 +1064,51 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backon" version = "0.4.4" @@ -2876,6 +2921,25 @@ dependencies = [ "tokio", ] +[[package]] +name = "example-exex-remote" +version = "0.0.0" +dependencies = [ + "bincode", + "eyre", + "prost", + "reth", + "reth-exex", + "reth-exex-test-utils", + "reth-node-api", + "reth-node-ethereum", + "reth-tracing", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", +] + [[package]] name = "example-exex-rollup" version = "0.0.0" @@ -3129,6 +3193,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.30" @@ -3417,6 +3487,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.5" @@ -3617,6 +3706,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -3636,7 +3736,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "pin-project-lite", ] @@ -3700,6 +3800,30 @@ dependencies = [ "serde", ] +[[package]] +name = "hyper" +version = "0.14.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.3.1" @@ -3709,9 +3833,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", + "h2 0.4.5", "http 1.1.0", - "http-body", + "http-body 1.0.0", "httparse", "httpdate", "itoa", @@ -3729,7 +3853,7 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper", + "hyper 1.3.1", "hyper-util", "log", "rustls", @@ -3741,6 +3865,18 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.29", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-util" version = "0.1.5" @@ -3751,8 +3887,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -4287,7 +4423,7 @@ dependencies = [ "futures-timer", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", "jsonrpsee-types", "parking_lot 0.12.3", @@ -4311,8 +4447,8 @@ checksum = "fb25cab482c8512c4f3323a5c90b95a3b8f7c90681a87bf7a68b942d52f08933" dependencies = [ "async-trait", "base64 0.22.1", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.3.1", "hyper-rustls", "hyper-util", "jsonrpsee-core", @@ -4350,9 +4486,9 @@ dependencies = [ "anyhow", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.3.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -4779,6 +4915,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.4" @@ -5018,6 +5160,12 @@ dependencies = [ "unsigned-varint 0.7.2", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "multistream-select" version = "0.13.0" @@ -5426,6 +5574,16 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.2.6", +] + [[package]] name = "ph" version = "0.8.3" @@ -5801,6 +5959,59 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.67", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.67", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quanta" version = "0.12.3" @@ -6141,9 +6352,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.3.1", "hyper-rustls", "hyper-util", "ipnet", @@ -6161,7 +6372,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tokio-rustls", "tokio-util", @@ -7871,8 +8082,8 @@ dependencies = [ "dyn-clone", "futures", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.3.1", "jsonrpsee", "jsonwebtoken", "metrics", @@ -9470,6 +9681,12 @@ dependencies = [ "syn 2.0.67", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -9778,6 +9995,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.3.0" @@ -9888,6 +10115,46 @@ dependencies = [ "winnow 0.6.13", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.67", +] + [[package]] name = "tower" version = "0.4.13" @@ -9922,7 +10189,7 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", "http-range-header", "httpdate", diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index eff4b7dcfacd..59fec9702991 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -56,7 +56,7 @@ pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; pub use reth_primitives_traits::{ - logs_bloom, Account, Bytecode, GotExpected, GotExpectedBoxed, Log, Request, Requests, + logs_bloom, Account, Bytecode, GotExpected, GotExpectedBoxed, Log, LogData, Request, Requests, StorageEntry, Withdrawal, Withdrawals, }; pub use static_file::StaticFileSegment; diff --git a/examples/exex/remote/Cargo.toml b/examples/exex/remote/Cargo.toml new file mode 100644 index 000000000000..634b9a7fef7e --- /dev/null +++ b/examples/exex/remote/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "example-exex-remote" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth.workspace = true +reth-exex = { workspace = true, features = ["serde"] } +reth-node-api.workspace = true +reth-node-ethereum.workspace = true +reth-tracing.workspace = true + +eyre.workspace = true + +tonic = "0.11" +prost = "0.12" +tokio = { version = "1.0", features = ["full"] } +tokio-stream = "0.1" + +bincode = "1.3" + +[build-dependencies] +tonic-build = "0.11" + +[dev-dependencies] +reth-exex-test-utils.workspace = true + +tokio.workspace = true + +[features] +default = [] +optimism = ["reth/optimism"] + +[[bin]] +name = "exex" +path = "bin/exex.rs" + +[[bin]] +name = "consumer" +path = "bin/consumer.rs" diff --git a/examples/exex/remote/bin/consumer.rs b/examples/exex/remote/bin/consumer.rs new file mode 100644 index 000000000000..71d4ebbe6ed6 --- /dev/null +++ b/examples/exex/remote/bin/consumer.rs @@ -0,0 +1,32 @@ +use example_exex_remote::proto::{remote_ex_ex_client::RemoteExExClient, SubscribeRequest}; +use reth_exex::ExExNotification; +use reth_tracing::{tracing::info, RethTracer, Tracer}; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let _ = RethTracer::new().init()?; + + let mut client = RemoteExExClient::connect("http://[::1]:10000") + .await? + .max_encoding_message_size(usize::MAX) + .max_decoding_message_size(usize::MAX); + + let mut stream = client.subscribe(SubscribeRequest {}).await?.into_inner(); + while let Some(notification) = stream.message().await? { + let notification = ExExNotification::try_from(¬ification)?; + + match notification { + ExExNotification::ChainCommitted { new } => { + info!(committed_chain = ?new.range(), "Received commit"); + } + ExExNotification::ChainReorged { old, new } => { + info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg"); + } + ExExNotification::ChainReverted { old } => { + info!(reverted_chain = ?old.range(), "Received revert"); + } + }; + } + + Ok(()) +} diff --git a/examples/exex/remote/bin/exex.rs b/examples/exex/remote/bin/exex.rs new file mode 100644 index 000000000000..ed1e5ec1e8c4 --- /dev/null +++ b/examples/exex/remote/bin/exex.rs @@ -0,0 +1,77 @@ +use example_exex_remote::proto::{ + remote_ex_ex_server::{RemoteExEx, RemoteExExServer}, + ExExNotification as ProtoExExNotification, SubscribeRequest as ProtoSubscribeRequest, +}; +use reth_exex::{ExExContext, ExExEvent, ExExNotification}; +use reth_node_api::FullNodeComponents; +use reth_node_ethereum::EthereumNode; +use tokio::sync::{broadcast, mpsc}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{transport::Server, Request, Response, Status}; + +#[derive(Debug)] +struct ExExService { + notifications: broadcast::Sender, +} + +#[tonic::async_trait] +impl RemoteExEx for ExExService { + type SubscribeStream = ReceiverStream>; + + async fn subscribe( + &self, + _request: Request, + ) -> Result, Status> { + let (tx, rx) = mpsc::channel(1); + + let mut notifications = self.notifications.subscribe(); + tokio::spawn(async move { + while let Ok(notification) = notifications.recv().await { + tx.send(Ok((¬ification).try_into().expect("failed to encode"))) + .await + .expect("failed to send notification to client"); + } + }); + + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +async fn exex( + mut ctx: ExExContext, + notifications: broadcast::Sender, +) -> eyre::Result<()> { + while let Some(notification) = ctx.notifications.recv().await { + if let Some(committed_chain) = notification.committed_chain() { + ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + + let _ = notifications.send(notification); + } + + Ok(()) +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let notifications = broadcast::channel(1).0; + + let server = Server::builder() + .add_service(RemoteExExServer::new(ExExService { + notifications: notifications.clone(), + })) + .serve("[::1]:10000".parse().unwrap()); + + let handle = builder + .node(EthereumNode::default()) + .install_exex("Remote", |ctx| async move { Ok(exex(ctx, notifications)) }) + .launch() + .await?; + + handle.node.task_executor.spawn_critical("gRPC server", async move { + server.await.expect("gRPC server crashed") + }); + + handle.wait_for_node_exit().await + }) +} diff --git a/examples/exex/remote/build.rs b/examples/exex/remote/build.rs new file mode 100644 index 000000000000..9e70bbe9b31f --- /dev/null +++ b/examples/exex/remote/build.rs @@ -0,0 +1,4 @@ +fn main() { + tonic_build::compile_protos("proto/exex.proto") + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); +} diff --git a/examples/exex/remote/proto/exex.proto b/examples/exex/remote/proto/exex.proto new file mode 100644 index 000000000000..17620b6802ab --- /dev/null +++ b/examples/exex/remote/proto/exex.proto @@ -0,0 +1,279 @@ +syntax = "proto3"; + +package exex; + +import "google/protobuf/empty.proto"; + +service RemoteExEx { + rpc Subscribe(SubscribeRequest) returns (stream ExExNotification) {} +} + +message SubscribeRequest {} + +message ExExNotification { + oneof notification { + ChainCommitted chain_committed = 1; + ChainReorged chain_reorged = 2; + ChainReverted chain_reverted = 3; + } +} + +message ChainCommitted { + Chain new = 1; +} + +message ChainReorged { + Chain old = 1; + Chain new = 2; +} + +message ChainReverted { + Chain old = 1; +} + +message Chain { + repeated Block blocks = 1; + ExecutionOutcome execution_outcome = 2; +} + +message Block { + SealedHeader header = 1; + repeated Transaction body = 2; + repeated Header ommers = 3; + repeated bytes senders = 4; + // TODO: add withdrawals and requests +} + +message SealedHeader { + bytes hash = 1; + Header header = 2; +} + +message Header { + bytes parent_hash = 1; + bytes ommers_hash = 2; + bytes beneficiary = 3; + bytes state_root = 4; + bytes transactions_root = 5; + bytes receipts_root = 6; + optional bytes withdrawals_root = 7; + bytes logs_bloom = 8; + bytes difficulty = 9; + uint64 number = 10; + uint64 gas_limit = 11; + uint64 gas_used = 12; + uint64 timestamp = 13; + bytes mix_hash = 14; + uint64 nonce = 15; + optional uint64 base_fee_per_gas = 16; + optional uint64 blob_gas_used = 17; + optional uint64 excess_blob_gas = 18; + optional bytes parent_beacon_block_root = 19; + // TODO: add requests_root + bytes extra_data = 20; +} + +message Transaction { + bytes hash = 1; + Signature signature = 2; + oneof transaction { + TransactionLegacy legacy = 3; + TransactionEip2930 eip2930 = 4; + TransactionEip1559 eip1559 = 5; + TransactionEip4844 eip4844 = 6; + } +} + +message Signature { + bytes r = 1; + bytes s = 2; + bool odd_y_parity = 3; +} + +message TransactionLegacy { + optional uint64 chain_id = 1; + uint64 nonce = 2; + bytes gas_price = 3; + uint64 gas_limit = 4; + TxKind to = 5; + bytes value = 6; + bytes input = 7; +} + +message TransactionEip2930 { + uint64 chain_id = 1; + uint64 nonce = 2; + bytes gas_price = 3; + uint64 gas_limit = 4; + TxKind to = 5; + bytes value = 6; + repeated AccessListItem access_list = 7; + bytes input = 8; +} + +message TransactionEip1559 { + uint64 chain_id = 1; + uint64 nonce = 2; + uint64 gas_limit = 3; + bytes max_fee_per_gas = 4; + bytes max_priority_fee_per_gas = 5; + TxKind to = 6; + bytes value = 7; + repeated AccessListItem access_list = 8; + bytes input = 9; +} + +message TransactionEip4844 { + uint64 chain_id = 1; + uint64 nonce = 2; + uint64 gas_limit = 3; + bytes max_fee_per_gas = 4; + bytes max_priority_fee_per_gas = 5; + bytes to = 6; + bytes value = 7; + repeated AccessListItem access_list = 8; + repeated bytes blob_versioned_hashes = 9; + bytes max_fee_per_blob_gas = 10; + bytes input = 11; +} + +message TxKind { + oneof kind { + google.protobuf.Empty create = 1; + bytes call = 2; + } +} + +message AccessListItem { + bytes address = 1; + repeated bytes storage_keys = 2; +} + +message ExecutionOutcome { + BundleState bundle = 1; + repeated BlockReceipts receipts = 2; + uint64 first_block = 3; + // TODO: add requests +} + +message BundleState { + repeated BundleAccount state = 1; + repeated ContractBytecode contracts = 2; + repeated BlockReverts reverts = 3; + uint64 state_size = 4; + uint64 reverts_size = 5; +} + +message BundleAccount { + bytes address = 1; + AccountInfo info = 2; + AccountInfo original_info = 3; + repeated StorageSlot storage = 4; + AccountStatus status = 5; +} + +message AccountInfo { + bytes balance = 1; + uint64 nonce = 2; + bytes code_hash = 3; + Bytecode code = 4; +} + +message StorageSlot { + bytes key = 1; + bytes previous_or_original_value = 2; + bytes present_value = 3; +} + +enum AccountStatus { + LOADED_NOT_EXISTING = 0; + LOADED = 1; + LOADED_EMPTY_EIP161 = 2; + IN_MEMORY_CHANGE = 3; + CHANGED = 4; + DESTROYED = 5; + DESTROYED_CHANGED = 6; + DESTROYED_AGAIN = 7; +} + +message ContractBytecode { + bytes hash = 1; + Bytecode bytecode = 2; +} + +message Bytecode { + oneof bytecode { + bytes legacy_raw = 1; + LegacyAnalyzedBytecode legacy_analyzed = 2; + // TODO: add EOF + } +} + +message LegacyAnalyzedBytecode { + bytes bytecode = 1; + uint64 original_len = 2; + repeated uint32 jump_table = 3; +} + +message BlockReverts { + repeated Revert reverts = 1; +} + +message Revert { + bytes address = 1; + AccountInfoRevert account = 2; + repeated RevertToSlot storage = 3; + AccountStatus previous_status = 4; + bool wipe_storage = 5; +} + +message AccountInfoRevert { + oneof revert { + google.protobuf.Empty do_nothing = 1; + google.protobuf.Empty delete_it = 2; + AccountInfo revert_to = 3; + } +} + +message RevertToSlot { + bytes key = 1; + oneof revert { + bytes some = 2; + google.protobuf.Empty destroyed = 3; + } +} + +message BlockReceipts { + repeated Receipt receipts = 1; +} + +message Receipt { + oneof receipt { + google.protobuf.Empty empty = 1; + NonEmptyReceipt non_empty = 2; + } +} + +message NonEmptyReceipt { + TxType tx_type = 1; + bool success = 2; + uint64 cumulative_gas_used = 3; + repeated Log logs = 4; +} + +enum TxType { + LEGACY = 0; + EIP2930 = 1; + EIP1559 = 2; + EIP4844 = 3; +} + +message Log { + bytes address = 1; + LogData data = 2; +} + +message LogData { + repeated bytes topics = 1; + bytes data = 2; +} diff --git a/examples/exex/remote/src/codec.rs b/examples/exex/remote/src/codec.rs new file mode 100644 index 000000000000..d36b60b92a5c --- /dev/null +++ b/examples/exex/remote/src/codec.rs @@ -0,0 +1,961 @@ +use std::sync::Arc; + +use eyre::OptionExt; +use reth::primitives::{Address, BlockHash, Bloom, TxHash, B256, U256}; + +use crate::proto; + +impl TryFrom<&reth_exex::ExExNotification> for proto::ExExNotification { + type Error = eyre::Error; + + fn try_from(notification: &reth_exex::ExExNotification) -> Result { + let notification = match notification { + reth_exex::ExExNotification::ChainCommitted { new } => { + proto::ex_ex_notification::Notification::ChainCommitted(proto::ChainCommitted { + new: Some(new.as_ref().try_into()?), + }) + } + reth_exex::ExExNotification::ChainReorged { old, new } => { + proto::ex_ex_notification::Notification::ChainReorged(proto::ChainReorged { + old: Some(old.as_ref().try_into()?), + new: Some(new.as_ref().try_into()?), + }) + } + reth_exex::ExExNotification::ChainReverted { old } => { + proto::ex_ex_notification::Notification::ChainReverted(proto::ChainReverted { + old: Some(old.as_ref().try_into()?), + }) + } + }; + + Ok(proto::ExExNotification { notification: Some(notification) }) + } +} + +impl TryFrom<&reth::providers::Chain> for proto::Chain { + type Error = eyre::Error; + + fn try_from(chain: &reth::providers::Chain) -> Result { + let bundle_state = chain.execution_outcome().state(); + Ok(proto::Chain { + blocks: chain + .blocks_iter() + .map(|block| { + Ok(proto::Block { + header: Some(proto::SealedHeader { + hash: block.header.hash().to_vec(), + header: Some(block.header.header().into()), + }), + body: block + .transactions() + .map(TryInto::try_into) + .collect::>()?, + ommers: block.ommers.iter().map(Into::into).collect(), + senders: block.senders.iter().map(|sender| sender.to_vec()).collect(), + }) + }) + .collect::>()?, + execution_outcome: Some(proto::ExecutionOutcome { + bundle: Some(proto::BundleState { + state: bundle_state + .state + .iter() + .map(|(address, account)| (*address, account).try_into()) + .collect::>()?, + contracts: bundle_state + .contracts + .iter() + .map(|(hash, bytecode)| { + Ok(proto::ContractBytecode { + hash: hash.to_vec(), + bytecode: Some(bytecode.try_into()?), + }) + }) + .collect::>()?, + reverts: bundle_state + .reverts + .iter() + .map(|block_reverts| { + Ok(proto::BlockReverts { + reverts: block_reverts + .iter() + .map(|(address, revert)| (*address, revert).try_into()) + .collect::>()?, + }) + }) + .collect::>()?, + state_size: bundle_state.state_size as u64, + reverts_size: bundle_state.reverts_size as u64, + }), + receipts: chain + .execution_outcome() + .receipts() + .iter() + .map(|block_receipts| { + Ok(proto::BlockReceipts { + receipts: block_receipts + .iter() + .map(TryInto::try_into) + .collect::>()?, + }) + }) + .collect::>()?, + first_block: chain.execution_outcome().first_block, + }), + }) + } +} + +impl From<&reth::primitives::Header> for proto::Header { + fn from(header: &reth::primitives::Header) -> Self { + proto::Header { + parent_hash: header.parent_hash.to_vec(), + ommers_hash: header.ommers_hash.to_vec(), + beneficiary: header.beneficiary.to_vec(), + state_root: header.state_root.to_vec(), + transactions_root: header.transactions_root.to_vec(), + receipts_root: header.receipts_root.to_vec(), + withdrawals_root: header.withdrawals_root.map(|root| root.to_vec()), + logs_bloom: header.logs_bloom.to_vec(), + difficulty: header.difficulty.to_le_bytes_vec(), + number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + mix_hash: header.mix_hash.to_vec(), + nonce: header.nonce, + base_fee_per_gas: header.base_fee_per_gas, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + parent_beacon_block_root: header.parent_beacon_block_root.map(|root| root.to_vec()), + extra_data: header.extra_data.to_vec(), + } + } +} + +impl TryFrom<&reth::primitives::TransactionSigned> for proto::Transaction { + type Error = eyre::Error; + + fn try_from(transaction: &reth::primitives::TransactionSigned) -> Result { + let hash = transaction.hash().to_vec(); + let signature = proto::Signature { + r: transaction.signature.r.to_le_bytes_vec(), + s: transaction.signature.s.to_le_bytes_vec(), + odd_y_parity: transaction.signature.odd_y_parity, + }; + let transaction = match &transaction.transaction { + reth::primitives::Transaction::Legacy(reth::primitives::TxLegacy { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + input, + }) => proto::transaction::Transaction::Legacy(proto::TransactionLegacy { + chain_id: *chain_id, + nonce: *nonce, + gas_price: gas_price.to_le_bytes().to_vec(), + gas_limit: *gas_limit, + to: Some(to.into()), + value: value.to_le_bytes_vec(), + input: input.to_vec(), + }), + reth::primitives::Transaction::Eip2930(reth::primitives::TxEip2930 { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + access_list, + input, + }) => proto::transaction::Transaction::Eip2930(proto::TransactionEip2930 { + chain_id: *chain_id, + nonce: *nonce, + gas_price: gas_price.to_le_bytes().to_vec(), + gas_limit: *gas_limit, + to: Some(to.into()), + value: value.to_le_bytes_vec(), + access_list: access_list.iter().map(Into::into).collect(), + input: input.to_vec(), + }), + reth::primitives::Transaction::Eip1559(reth::primitives::TxEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + input, + }) => proto::transaction::Transaction::Eip1559(proto::TransactionEip1559 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: max_fee_per_gas.to_le_bytes().to_vec(), + max_priority_fee_per_gas: max_priority_fee_per_gas.to_le_bytes().to_vec(), + to: Some(to.into()), + value: value.to_le_bytes_vec(), + access_list: access_list.iter().map(Into::into).collect(), + input: input.to_vec(), + }), + reth::primitives::Transaction::Eip4844(reth::primitives::TxEip4844 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + placeholder: _, + to, + value, + access_list, + blob_versioned_hashes, + max_fee_per_blob_gas, + input, + }) => proto::transaction::Transaction::Eip4844(proto::TransactionEip4844 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: max_fee_per_gas.to_le_bytes().to_vec(), + max_priority_fee_per_gas: max_priority_fee_per_gas.to_le_bytes().to_vec(), + to: to.to_vec(), + value: value.to_le_bytes_vec(), + access_list: access_list.iter().map(Into::into).collect(), + blob_versioned_hashes: blob_versioned_hashes + .iter() + .map(|hash| hash.to_vec()) + .collect(), + max_fee_per_blob_gas: max_fee_per_blob_gas.to_le_bytes().to_vec(), + input: input.to_vec(), + }), + #[cfg(feature = "optimism")] + reth::primitives::Transaction::Deposit(_) => { + eyre::bail!("deposit transaction not supported") + } + }; + + Ok(proto::Transaction { hash, signature: Some(signature), transaction: Some(transaction) }) + } +} + +impl From<&reth::primitives::TxKind> for proto::TxKind { + fn from(kind: &reth::primitives::TxKind) -> Self { + proto::TxKind { + kind: match kind { + reth::primitives::TxKind::Create => Some(proto::tx_kind::Kind::Create(())), + reth::primitives::TxKind::Call(address) => { + Some(proto::tx_kind::Kind::Call(address.to_vec())) + } + }, + } + } +} + +impl From<&reth::primitives::AccessListItem> for proto::AccessListItem { + fn from(item: &reth::primitives::AccessListItem) -> Self { + proto::AccessListItem { + address: item.address.to_vec(), + storage_keys: item.storage_keys.iter().map(|key| key.to_vec()).collect(), + } + } +} + +impl TryFrom<(Address, &reth::revm::db::BundleAccount)> for proto::BundleAccount { + type Error = eyre::Error; + + fn try_from( + (address, account): (Address, &reth::revm::db::BundleAccount), + ) -> Result { + Ok(proto::BundleAccount { + address: address.to_vec(), + info: account.info.as_ref().map(TryInto::try_into).transpose()?, + original_info: account.original_info.as_ref().map(TryInto::try_into).transpose()?, + storage: account + .storage + .iter() + .map(|(key, slot)| proto::StorageSlot { + key: key.to_le_bytes_vec(), + previous_or_original_value: slot.previous_or_original_value.to_le_bytes_vec(), + present_value: slot.present_value.to_le_bytes_vec(), + }) + .collect(), + status: proto::AccountStatus::from(account.status) as i32, + }) + } +} + +impl TryFrom<&reth::revm::primitives::AccountInfo> for proto::AccountInfo { + type Error = eyre::Error; + + fn try_from(account_info: &reth::revm::primitives::AccountInfo) -> Result { + Ok(proto::AccountInfo { + balance: account_info.balance.to_le_bytes_vec(), + nonce: account_info.nonce, + code_hash: account_info.code_hash.to_vec(), + code: account_info.code.as_ref().map(TryInto::try_into).transpose()?, + }) + } +} + +impl TryFrom<&reth::revm::primitives::Bytecode> for proto::Bytecode { + type Error = eyre::Error; + + fn try_from(bytecode: &reth::revm::primitives::Bytecode) -> Result { + let bytecode = match bytecode { + reth::revm::primitives::Bytecode::LegacyRaw(code) => { + proto::bytecode::Bytecode::LegacyRaw(code.to_vec()) + } + reth::revm::primitives::Bytecode::LegacyAnalyzed(legacy_analyzed) => { + proto::bytecode::Bytecode::LegacyAnalyzed(proto::LegacyAnalyzedBytecode { + bytecode: legacy_analyzed.bytecode().to_vec(), + original_len: legacy_analyzed.original_len() as u64, + jump_table: legacy_analyzed + .jump_table() + .0 + .iter() + .by_vals() + .map(|x| x.into()) + .collect(), + }) + } + reth::revm::primitives::Bytecode::Eof(_) => { + eyre::bail!("EOF bytecode not supported"); + } + }; + Ok(proto::Bytecode { bytecode: Some(bytecode) }) + } +} + +impl From for proto::AccountStatus { + fn from(status: reth::revm::db::AccountStatus) -> Self { + match status { + reth::revm::db::AccountStatus::LoadedNotExisting => { + proto::AccountStatus::LoadedNotExisting + } + reth::revm::db::AccountStatus::Loaded => proto::AccountStatus::Loaded, + reth::revm::db::AccountStatus::LoadedEmptyEIP161 => { + proto::AccountStatus::LoadedEmptyEip161 + } + reth::revm::db::AccountStatus::InMemoryChange => proto::AccountStatus::InMemoryChange, + reth::revm::db::AccountStatus::Changed => proto::AccountStatus::Changed, + reth::revm::db::AccountStatus::Destroyed => proto::AccountStatus::Destroyed, + reth::revm::db::AccountStatus::DestroyedChanged => { + proto::AccountStatus::DestroyedChanged + } + reth::revm::db::AccountStatus::DestroyedAgain => proto::AccountStatus::DestroyedAgain, + } + } +} + +impl TryFrom<(Address, &reth::revm::db::states::reverts::AccountRevert)> for proto::Revert { + type Error = eyre::Error; + + fn try_from( + (address, revert): (Address, &reth::revm::db::states::reverts::AccountRevert), + ) -> Result { + Ok(proto::Revert { + address: address.to_vec(), + account: Some(proto::AccountInfoRevert { + revert: Some(match &revert.account { + reth::revm::db::states::reverts::AccountInfoRevert::DoNothing => { + proto::account_info_revert::Revert::DoNothing(()) + } + reth::revm::db::states::reverts::AccountInfoRevert::DeleteIt => { + proto::account_info_revert::Revert::DeleteIt(()) + } + reth::revm::db::states::reverts::AccountInfoRevert::RevertTo(account_info) => { + proto::account_info_revert::Revert::RevertTo(account_info.try_into()?) + } + }), + }), + storage: revert + .storage + .iter() + .map(|(key, slot)| { + Ok(proto::RevertToSlot { + key: key.to_le_bytes_vec(), + revert: Some(match slot { + reth::revm::db::RevertToSlot::Some(value) => { + proto::revert_to_slot::Revert::Some(value.to_le_bytes_vec()) + } + reth::revm::db::RevertToSlot::Destroyed => { + proto::revert_to_slot::Revert::Destroyed(()) + } + }), + }) + }) + .collect::>()?, + previous_status: proto::AccountStatus::from(revert.previous_status) as i32, + wipe_storage: revert.wipe_storage, + }) + } +} + +impl TryFrom<&Option> for proto::Receipt { + type Error = eyre::Error; + + fn try_from(receipt: &Option) -> Result { + Ok(proto::Receipt { + receipt: Some( + receipt + .as_ref() + .map_or(eyre::Ok(proto::receipt::Receipt::Empty(())), |receipt| { + Ok(proto::receipt::Receipt::NonEmpty(receipt.try_into()?)) + })?, + ), + }) + } +} + +impl TryFrom<&reth::primitives::Receipt> for proto::NonEmptyReceipt { + type Error = eyre::Error; + + fn try_from(receipt: &reth::primitives::Receipt) -> Result { + Ok(proto::NonEmptyReceipt { + tx_type: match receipt.tx_type { + reth::primitives::TxType::Legacy => proto::TxType::Legacy, + reth::primitives::TxType::Eip2930 => proto::TxType::Eip2930, + reth::primitives::TxType::Eip1559 => proto::TxType::Eip1559, + reth::primitives::TxType::Eip4844 => proto::TxType::Eip4844, + #[cfg(feature = "optimism")] + reth::primitives::TxType::Deposit => { + eyre::bail!("deposit transaction not supported") + } + } as i32, + success: receipt.success, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: receipt + .logs + .iter() + .map(|log| proto::Log { + address: log.address.to_vec(), + data: Some(proto::LogData { + topics: log.data.topics().iter().map(|topic| topic.to_vec()).collect(), + data: log.data.data.to_vec(), + }), + }) + .collect(), + }) + } +} + +impl TryFrom<&proto::ExExNotification> for reth_exex::ExExNotification { + type Error = eyre::Error; + + fn try_from(notification: &proto::ExExNotification) -> Result { + Ok(match notification.notification.as_ref().ok_or_eyre("no notification")? { + proto::ex_ex_notification::Notification::ChainCommitted(proto::ChainCommitted { + new, + }) => reth_exex::ExExNotification::ChainCommitted { + new: Arc::new(new.as_ref().ok_or_eyre("no new chain")?.try_into()?), + }, + proto::ex_ex_notification::Notification::ChainReorged(proto::ChainReorged { + old, + new, + }) => reth_exex::ExExNotification::ChainReorged { + old: Arc::new(old.as_ref().ok_or_eyre("no old chain")?.try_into()?), + new: Arc::new(new.as_ref().ok_or_eyre("no new chain")?.try_into()?), + }, + proto::ex_ex_notification::Notification::ChainReverted(proto::ChainReverted { + old, + }) => reth_exex::ExExNotification::ChainReverted { + old: Arc::new(old.as_ref().ok_or_eyre("no old chain")?.try_into()?), + }, + }) + } +} + +impl TryFrom<&proto::Chain> for reth::providers::Chain { + type Error = eyre::Error; + + fn try_from(chain: &proto::Chain) -> Result { + let execution_outcome = + chain.execution_outcome.as_ref().ok_or_eyre("no execution outcome")?; + let bundle = execution_outcome.bundle.as_ref().ok_or_eyre("no bundle")?; + Ok(reth::providers::Chain::new( + chain.blocks.iter().map(TryInto::try_into).collect::>>()?, + reth::providers::ExecutionOutcome { + bundle: reth::revm::db::BundleState { + state: bundle + .state + .iter() + .map(TryInto::try_into) + .collect::>()?, + contracts: bundle + .contracts + .iter() + .map(|contract| { + Ok(( + B256::try_from(contract.hash.as_slice())?, + contract.bytecode.as_ref().ok_or_eyre("no bytecode")?.try_into()?, + )) + }) + .collect::>()?, + reverts: reth::revm::db::states::reverts::Reverts::new( + bundle + .reverts + .iter() + .map(|block_reverts| { + block_reverts + .reverts + .iter() + .map(TryInto::try_into) + .collect::>() + }) + .collect::>()?, + ), + state_size: bundle.state_size as usize, + reverts_size: bundle.reverts_size as usize, + }, + receipts: reth::primitives::Receipts::from_iter( + execution_outcome + .receipts + .iter() + .map(|block_receipts| { + block_receipts + .receipts + .iter() + .map(TryInto::try_into) + .collect::>() + }) + .collect::>>()?, + ), + first_block: execution_outcome.first_block, + requests: Default::default(), + }, + None, + )) + } +} + +impl TryFrom<&proto::Block> for reth::primitives::SealedBlockWithSenders { + type Error = eyre::Error; + + fn try_from(block: &proto::Block) -> Result { + let sealed_header = block.header.as_ref().ok_or_eyre("no sealed header")?; + let header = sealed_header.header.as_ref().ok_or_eyre("no header")?.try_into()?; + let sealed_header = reth::primitives::SealedHeader::new( + header, + BlockHash::try_from(sealed_header.hash.as_slice())?, + ); + + let transactions = block.body.iter().map(TryInto::try_into).collect::>()?; + let ommers = block.ommers.iter().map(TryInto::try_into).collect::>()?; + let senders = block + .senders + .iter() + .map(|sender| Address::try_from(sender.as_slice())) + .collect::>()?; + + reth::primitives::SealedBlockWithSenders::new( + reth::primitives::SealedBlock::new( + sealed_header, + reth::primitives::BlockBody { + transactions, + ommers, + withdrawals: Default::default(), + requests: Default::default(), + }, + ), + senders, + ) + .ok_or_eyre("senders do not match transactions") + } +} + +impl TryFrom<&proto::Header> for reth::primitives::Header { + type Error = eyre::Error; + + fn try_from(header: &proto::Header) -> Result { + Ok(reth::primitives::Header { + parent_hash: B256::try_from(header.parent_hash.as_slice())?, + ommers_hash: B256::try_from(header.ommers_hash.as_slice())?, + beneficiary: Address::try_from(header.beneficiary.as_slice())?, + state_root: B256::try_from(header.state_root.as_slice())?, + transactions_root: B256::try_from(header.transactions_root.as_slice())?, + receipts_root: B256::try_from(header.receipts_root.as_slice())?, + withdrawals_root: header + .withdrawals_root + .as_ref() + .map(|root| B256::try_from(root.as_slice())) + .transpose()?, + logs_bloom: Bloom::try_from(header.logs_bloom.as_slice())?, + difficulty: U256::try_from_le_slice(&header.difficulty) + .ok_or_eyre("failed to parse difficulty")?, + number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + mix_hash: B256::try_from(header.mix_hash.as_slice())?, + nonce: header.nonce, + base_fee_per_gas: header.base_fee_per_gas, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + parent_beacon_block_root: header + .parent_beacon_block_root + .as_ref() + .map(|root| B256::try_from(root.as_slice())) + .transpose()?, + requests_root: None, + extra_data: header.extra_data.as_slice().to_vec().into(), + }) + } +} + +impl TryFrom<&proto::Transaction> for reth::primitives::TransactionSigned { + type Error = eyre::Error; + + fn try_from(transaction: &proto::Transaction) -> Result { + let hash = TxHash::try_from(transaction.hash.as_slice())?; + let signature = transaction.signature.as_ref().ok_or_eyre("no signature")?; + let signature = reth::primitives::Signature { + r: U256::try_from_le_slice(signature.r.as_slice()).ok_or_eyre("failed to parse r")?, + s: U256::try_from_le_slice(signature.s.as_slice()).ok_or_eyre("failed to parse s")?, + odd_y_parity: signature.odd_y_parity, + }; + let transaction = match transaction.transaction.as_ref().ok_or_eyre("no transaction")? { + proto::transaction::Transaction::Legacy(proto::TransactionLegacy { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + input, + }) => reth::primitives::Transaction::Legacy(reth::primitives::TxLegacy { + chain_id: *chain_id, + nonce: *nonce, + gas_price: u128::from_le_bytes(gas_price.as_slice().try_into()?), + gas_limit: *gas_limit, + to: to.as_ref().ok_or_eyre("no to")?.try_into()?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + input: input.to_vec().into(), + }), + proto::transaction::Transaction::Eip2930(proto::TransactionEip2930 { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + access_list, + input, + }) => reth::primitives::Transaction::Eip2930(reth::primitives::TxEip2930 { + chain_id: *chain_id, + nonce: *nonce, + gas_price: u128::from_le_bytes(gas_price.as_slice().try_into()?), + gas_limit: *gas_limit, + to: to.as_ref().ok_or_eyre("no to")?.try_into()?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + access_list: access_list + .iter() + .map(TryInto::try_into) + .collect::>>()? + .into(), + input: input.to_vec().into(), + }), + proto::transaction::Transaction::Eip1559(proto::TransactionEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + input, + }) => reth::primitives::Transaction::Eip1559(reth::primitives::TxEip1559 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: u128::from_le_bytes(max_fee_per_gas.as_slice().try_into()?), + max_priority_fee_per_gas: u128::from_le_bytes( + max_priority_fee_per_gas.as_slice().try_into()?, + ), + to: to.as_ref().ok_or_eyre("no to")?.try_into()?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + access_list: access_list + .iter() + .map(TryInto::try_into) + .collect::>>()? + .into(), + input: input.to_vec().into(), + }), + proto::transaction::Transaction::Eip4844(proto::TransactionEip4844 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value, + access_list, + blob_versioned_hashes, + max_fee_per_blob_gas, + input, + }) => reth::primitives::Transaction::Eip4844(reth::primitives::TxEip4844 { + chain_id: *chain_id, + nonce: *nonce, + gas_limit: *gas_limit, + max_fee_per_gas: u128::from_le_bytes(max_fee_per_gas.as_slice().try_into()?), + max_priority_fee_per_gas: u128::from_le_bytes( + max_priority_fee_per_gas.as_slice().try_into()?, + ), + placeholder: None, + to: Address::try_from(to.as_slice())?, + value: U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse value")?, + access_list: access_list + .iter() + .map(TryInto::try_into) + .collect::>>()? + .into(), + blob_versioned_hashes: blob_versioned_hashes + .iter() + .map(|hash| B256::try_from(hash.as_slice())) + .collect::>()?, + max_fee_per_blob_gas: u128::from_le_bytes( + max_fee_per_blob_gas.as_slice().try_into()?, + ), + input: input.to_vec().into(), + }), + }; + + Ok(reth::primitives::TransactionSigned { hash, signature, transaction }) + } +} + +impl TryFrom<&proto::TxKind> for reth::primitives::TxKind { + type Error = eyre::Error; + + fn try_from(tx_kind: &proto::TxKind) -> Result { + Ok(match tx_kind.kind.as_ref().ok_or_eyre("no kind")? { + proto::tx_kind::Kind::Create(()) => reth::primitives::TxKind::Create, + proto::tx_kind::Kind::Call(address) => { + reth::primitives::TxKind::Call(Address::try_from(address.as_slice())?) + } + }) + } +} + +impl TryFrom<&proto::AccessListItem> for reth::primitives::AccessListItem { + type Error = eyre::Error; + + fn try_from(item: &proto::AccessListItem) -> Result { + Ok(reth::primitives::AccessListItem { + address: Address::try_from(item.address.as_slice())?, + storage_keys: item + .storage_keys + .iter() + .map(|key| B256::try_from(key.as_slice())) + .collect::>()?, + }) + } +} + +impl TryFrom<&proto::AccountInfo> for reth::revm::primitives::AccountInfo { + type Error = eyre::Error; + + fn try_from(account_info: &proto::AccountInfo) -> Result { + Ok(reth::revm::primitives::AccountInfo { + balance: U256::try_from_le_slice(account_info.balance.as_slice()) + .ok_or_eyre("failed to parse balance")?, + nonce: account_info.nonce, + code_hash: B256::try_from(account_info.code_hash.as_slice())?, + code: account_info.code.as_ref().map(TryInto::try_into).transpose()?, + }) + } +} + +impl TryFrom<&proto::Bytecode> for reth::revm::primitives::Bytecode { + type Error = eyre::Error; + + fn try_from(bytecode: &proto::Bytecode) -> Result { + Ok(match bytecode.bytecode.as_ref().ok_or_eyre("no bytecode")? { + proto::bytecode::Bytecode::LegacyRaw(code) => { + reth::revm::primitives::Bytecode::LegacyRaw(code.clone().into()) + } + proto::bytecode::Bytecode::LegacyAnalyzed(legacy_analyzed) => { + reth::revm::primitives::Bytecode::LegacyAnalyzed( + reth::revm::primitives::LegacyAnalyzedBytecode::new( + legacy_analyzed.bytecode.clone().into(), + legacy_analyzed.original_len as usize, + reth::revm::primitives::JumpTable::from_slice( + legacy_analyzed + .jump_table + .iter() + .map(|dest| *dest as u8) + .collect::>() + .as_slice(), + ), + ), + ) + } + }) + } +} + +impl From for reth::revm::db::AccountStatus { + fn from(status: proto::AccountStatus) -> Self { + match status { + proto::AccountStatus::LoadedNotExisting => { + reth::revm::db::AccountStatus::LoadedNotExisting + } + proto::AccountStatus::Loaded => reth::revm::db::AccountStatus::Loaded, + proto::AccountStatus::LoadedEmptyEip161 => { + reth::revm::db::AccountStatus::LoadedEmptyEIP161 + } + proto::AccountStatus::InMemoryChange => reth::revm::db::AccountStatus::InMemoryChange, + proto::AccountStatus::Changed => reth::revm::db::AccountStatus::Changed, + proto::AccountStatus::Destroyed => reth::revm::db::AccountStatus::Destroyed, + proto::AccountStatus::DestroyedChanged => { + reth::revm::db::AccountStatus::DestroyedChanged + } + proto::AccountStatus::DestroyedAgain => reth::revm::db::AccountStatus::DestroyedAgain, + } + } +} + +impl TryFrom<&proto::BundleAccount> for (Address, reth::revm::db::BundleAccount) { + type Error = eyre::Error; + + fn try_from(account: &proto::BundleAccount) -> Result { + Ok(( + Address::try_from(account.address.as_slice())?, + reth::revm::db::BundleAccount { + info: account.info.as_ref().map(TryInto::try_into).transpose()?, + original_info: account.original_info.as_ref().map(TryInto::try_into).transpose()?, + storage: account + .storage + .iter() + .map(|slot| { + Ok(( + U256::try_from_le_slice(slot.key.as_slice()) + .ok_or_eyre("failed to parse key")?, + reth::revm::db::states::StorageSlot { + previous_or_original_value: U256::try_from_le_slice( + slot.previous_or_original_value.as_slice(), + ) + .ok_or_eyre("failed to parse previous or original value")?, + present_value: U256::try_from_le_slice( + slot.present_value.as_slice(), + ) + .ok_or_eyre("failed to parse present value")?, + }, + )) + }) + .collect::>()?, + status: proto::AccountStatus::try_from(account.status)?.into(), + }, + )) + } +} + +impl TryFrom<&proto::Revert> for (Address, reth::revm::db::states::reverts::AccountRevert) { + type Error = eyre::Error; + + fn try_from(revert: &proto::Revert) -> Result { + Ok(( + Address::try_from(revert.address.as_slice())?, + reth::revm::db::states::reverts::AccountRevert { + account: match revert + .account + .as_ref() + .ok_or_eyre("no revert account")? + .revert + .as_ref() + .ok_or_eyre("no revert account revert")? + { + proto::account_info_revert::Revert::DoNothing(()) => { + reth::revm::db::states::reverts::AccountInfoRevert::DoNothing + } + proto::account_info_revert::Revert::DeleteIt(()) => { + reth::revm::db::states::reverts::AccountInfoRevert::DeleteIt + } + proto::account_info_revert::Revert::RevertTo(account_info) => { + reth::revm::db::states::reverts::AccountInfoRevert::RevertTo( + account_info.try_into()?, + ) + } + }, + storage: revert + .storage + .iter() + .map(|slot| { + Ok(( + U256::try_from_le_slice(slot.key.as_slice()) + .ok_or_eyre("failed to parse slot key")?, + match slot.revert.as_ref().ok_or_eyre("no slot revert")? { + proto::revert_to_slot::Revert::Some(value) => { + reth::revm::db::states::reverts::RevertToSlot::Some( + U256::try_from_le_slice(value.as_slice()) + .ok_or_eyre("failed to parse slot revert")?, + ) + } + proto::revert_to_slot::Revert::Destroyed(()) => { + reth::revm::db::states::reverts::RevertToSlot::Destroyed + } + }, + )) + }) + .collect::>()?, + previous_status: proto::AccountStatus::try_from(revert.previous_status)?.into(), + wipe_storage: revert.wipe_storage, + }, + )) + } +} + +impl TryFrom<&proto::Receipt> for Option { + type Error = eyre::Error; + + fn try_from(receipt: &proto::Receipt) -> Result { + Ok(match receipt.receipt.as_ref().ok_or_eyre("no receipt")? { + proto::receipt::Receipt::Empty(()) => None, + proto::receipt::Receipt::NonEmpty(receipt) => Some(receipt.try_into()?), + }) + } +} + +impl TryFrom<&proto::NonEmptyReceipt> for reth::primitives::Receipt { + type Error = eyre::Error; + + fn try_from(receipt: &proto::NonEmptyReceipt) -> Result { + Ok(reth::primitives::Receipt { + tx_type: match proto::TxType::try_from(receipt.tx_type)? { + proto::TxType::Legacy => reth::primitives::TxType::Legacy, + proto::TxType::Eip2930 => reth::primitives::TxType::Eip2930, + proto::TxType::Eip1559 => reth::primitives::TxType::Eip1559, + proto::TxType::Eip4844 => reth::primitives::TxType::Eip4844, + }, + success: receipt.success, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: receipt + .logs + .iter() + .map(|log| { + let data = log.data.as_ref().ok_or_eyre("no log data")?; + Ok(reth::primitives::Log { + address: Address::try_from(log.address.as_slice())?, + data: reth::primitives::LogData::new_unchecked( + data.topics + .iter() + .map(|topic| Ok(B256::try_from(topic.as_slice())?)) + .collect::>()?, + data.data.clone().into(), + ), + }) + }) + .collect::>()?, + #[cfg(feature = "optimism")] + deposit_nonce: None, + #[cfg(feature = "optimism")] + deposit_receipt_version: None, + }) + } +} diff --git a/examples/exex/remote/src/lib.rs b/examples/exex/remote/src/lib.rs new file mode 100644 index 000000000000..9b8aa5781a8f --- /dev/null +++ b/examples/exex/remote/src/lib.rs @@ -0,0 +1,4 @@ +pub mod codec; +pub mod proto { + tonic::include_proto!("exex"); +} From c5aee02ff753db9cbda3823af46a67bb29e2d466 Mon Sep 17 00:00:00 2001 From: Omid Chenane <155813094+ochenane@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:26:53 +0330 Subject: [PATCH 186/405] fix: Change Arc to EnvKzgSettings (#9054) --- .../src/commands/debug_cmd/build_block.rs | 17 +++++++------ crates/e2e-test-utils/src/transaction.rs | 9 +++---- crates/node-core/src/node_config.rs | 9 ++++--- crates/node/builder/src/builder/mod.rs | 10 ++++---- crates/primitives/benches/validate_blob_tx.rs | 16 ++++++------- crates/primitives/src/constants/eip4844.rs | 24 +------------------ crates/primitives/src/transaction/sidecar.rs | 10 ++++---- crates/rpc/rpc/src/eth/bundle.rs | 5 ++-- crates/transaction-pool/src/validate/eth.rs | 23 +++++++++--------- 9 files changed, 49 insertions(+), 74 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 40b9722ae4b5..6c1125677815 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -24,16 +24,15 @@ use reth_fs_util as fs; use reth_node_api::PayloadBuilderAttributes; use reth_payload_builder::database::CachedReads; use reth_primitives::{ - constants::eip4844::{LoadKzgSettingsError, MAINNET_KZG_TRUSTED_SETUP}, - revm_primitives::KzgSettings, - Address, BlobTransaction, BlobTransactionSidecar, Bytes, PooledTransactionsElement, - SealedBlock, SealedBlockWithSenders, Transaction, TransactionSigned, TxEip4844, B256, U256, + constants::eip4844::LoadKzgSettingsError, revm_primitives::KzgSettings, Address, + BlobTransaction, BlobTransactionSidecar, Bytes, PooledTransactionsElement, SealedBlock, + SealedBlockWithSenders, Transaction, TransactionSigned, TxEip4844, B256, U256, }; use reth_provider::{ providers::BlockchainProvider, BlockHashReader, BlockReader, BlockWriter, ChainSpecProvider, ProviderFactory, StageCheckpointReader, StateProviderFactory, }; -use reth_revm::database::StateProviderDatabase; +use reth_revm::{database::StateProviderDatabase, primitives::EnvKzgSettings}; use reth_rpc_types::engine::{BlobsBundleV1, PayloadAttributes}; use reth_stages::StageId; use reth_transaction_pool::{ @@ -103,14 +102,14 @@ impl Command { } /// Loads the trusted setup params from a given file path or falls back to - /// `MAINNET_KZG_TRUSTED_SETUP`. - fn kzg_settings(&self) -> eyre::Result> { + /// `EnvKzgSettings::Default`. + fn kzg_settings(&self) -> eyre::Result { if let Some(ref trusted_setup_file) = self.trusted_setup_file { let trusted_setup = KzgSettings::load_trusted_setup_file(trusted_setup_file) .map_err(LoadKzgSettingsError::KzgError)?; - Ok(Arc::new(trusted_setup)) + Ok(EnvKzgSettings::Custom(Arc::new(trusted_setup))) } else { - Ok(Arc::clone(&MAINNET_KZG_TRUSTED_SETUP)) + Ok(EnvKzgSettings::Default) } } diff --git a/crates/e2e-test-utils/src/transaction.rs b/crates/e2e-test-utils/src/transaction.rs index b278c96365a5..0719c7733e6a 100644 --- a/crates/e2e-test-utils/src/transaction.rs +++ b/crates/e2e-test-utils/src/transaction.rs @@ -1,5 +1,6 @@ use alloy_consensus::{ - BlobTransactionSidecar, SidecarBuilder, SimpleCoder, TxEip4844Variant, TxEnvelope, + BlobTransactionSidecar, EnvKzgSettings, SidecarBuilder, SimpleCoder, TxEip4844Variant, + TxEnvelope, }; use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; use alloy_rpc_types::{TransactionInput, TransactionRequest}; @@ -7,7 +8,7 @@ use alloy_signer_local::PrivateKeySigner; use eyre::Ok; use reth_primitives::{hex, Address, Bytes, U256}; -use reth_primitives::{constants::eip4844::MAINNET_KZG_TRUSTED_SETUP, B256}; +use reth_primitives::B256; pub struct TransactionTestContext; @@ -71,12 +72,12 @@ impl TransactionTestContext { /// Validates the sidecar of a given tx envelope and returns the versioned hashes pub fn validate_sidecar(tx: TxEnvelope) -> Vec { - let proof_setting = MAINNET_KZG_TRUSTED_SETUP.clone(); + let proof_setting = EnvKzgSettings::Default; match tx { TxEnvelope::Eip4844(signed) => match signed.tx() { TxEip4844Variant::TxEip4844WithSidecar(tx) => { - tx.validate_blob(&proof_setting).unwrap(); + tx.validate_blob(proof_setting.get()).unwrap(); tx.sidecar.versioned_hashes().collect() } _ => panic!("Expected Eip4844 transaction with sidecar"), diff --git a/crates/node-core/src/node_config.rs b/crates/node-core/src/node_config.rs index 3a3b742cef02..932c12e70436 100644 --- a/crates/node-core/src/node_config.rs +++ b/crates/node-core/src/node_config.rs @@ -16,8 +16,7 @@ use reth_config::config::PruneConfig; use reth_db_api::{database::Database, database_metrics::DatabaseMetrics}; use reth_network_p2p::headers::client::HeadersClient; use reth_primitives::{ - constants::eip4844::MAINNET_KZG_TRUSTED_SETUP, kzg::KzgSettings, BlockHashOrNumber, - BlockNumber, Head, SealedHeader, B256, + revm_primitives::EnvKzgSettings, BlockHashOrNumber, BlockNumber, Head, SealedHeader, B256, }; use reth_provider::{ providers::StaticFileProvider, BlockHashReader, HeaderProvider, ProviderFactory, @@ -267,9 +266,9 @@ impl NodeConfig { Ok(max_block) } - /// Loads '`MAINNET_KZG_TRUSTED_SETUP`' - pub fn kzg_settings(&self) -> eyre::Result> { - Ok(Arc::clone(&MAINNET_KZG_TRUSTED_SETUP)) + /// Loads '`EnvKzgSettings::Default`' + pub const fn kzg_settings(&self) -> eyre::Result { + Ok(EnvKzgSettings::Default) } /// Installs the prometheus recorder. diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index c3255ceed212..f23fc873e885 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -30,10 +30,10 @@ use reth_node_core::{ cli::config::{PayloadBuilderConfig, RethTransactionPoolConfig}, dirs::{ChainPath, DataDirPath, MaybePlatformPath}, node_config::NodeConfig, - primitives::{kzg::KzgSettings, Head}, + primitives::Head, utils::write_peers_to_file, }; -use reth_primitives::constants::eip4844::MAINNET_KZG_TRUSTED_SETUP; +use reth_primitives::revm_primitives::EnvKzgSettings; use reth_provider::{providers::BlockchainProvider, ChainSpecProvider}; use reth_tasks::TaskExecutor; use reth_transaction_pool::{PoolConfig, TransactionPool}; @@ -469,9 +469,9 @@ impl BuilderContext { self.config().txpool.pool_config() } - /// Loads `MAINNET_KZG_TRUSTED_SETUP`. - pub fn kzg_settings(&self) -> eyre::Result> { - Ok(Arc::clone(&MAINNET_KZG_TRUSTED_SETUP)) + /// Loads `EnvKzgSettings::Default`. + pub const fn kzg_settings(&self) -> eyre::Result { + Ok(EnvKzgSettings::Default) } /// Returns the config for payload building. diff --git a/crates/primitives/benches/validate_blob_tx.rs b/crates/primitives/benches/validate_blob_tx.rs index 40f8b90927cb..622168bb35f8 100644 --- a/crates/primitives/benches/validate_blob_tx.rs +++ b/crates/primitives/benches/validate_blob_tx.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] +use alloy_eips::eip4844::env_settings::EnvKzgSettings; use alloy_primitives::hex; -use c_kzg::KzgSettings; use criterion::{ criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, }; @@ -11,11 +11,8 @@ use proptest::{ test_runner::{RngAlgorithm, TestRng, TestRunner}, }; use proptest_arbitrary_interop::arb; -use reth_primitives::{ - constants::eip4844::MAINNET_KZG_TRUSTED_SETUP, BlobTransactionSidecar, TxEip4844, -}; +use reth_primitives::{BlobTransactionSidecar, TxEip4844}; use revm_primitives::MAX_BLOB_NUMBER_PER_BLOCK; -use std::sync::Arc; // constant seed to use for the rng const SEED: [u8; 32] = hex!("1337133713371337133713371337133713371337133713371337133713371337"); @@ -23,11 +20,10 @@ const SEED: [u8; 32] = hex!("133713371337133713371337133713371337133713371337133 /// Benchmarks EIP-48444 blob validation. fn blob_validation(c: &mut Criterion) { let mut group = c.benchmark_group("Blob Transaction KZG validation"); - let kzg_settings = MAINNET_KZG_TRUSTED_SETUP.clone(); for num_blobs in 1..=MAX_BLOB_NUMBER_PER_BLOCK { println!("Benchmarking validation for tx with {num_blobs} blobs"); - validate_blob_tx(&mut group, "ValidateBlob", num_blobs, kzg_settings.clone()); + validate_blob_tx(&mut group, "ValidateBlob", num_blobs, EnvKzgSettings::Default); } } @@ -35,7 +31,7 @@ fn validate_blob_tx( group: &mut BenchmarkGroup<'_, WallTime>, description: &str, num_blobs: u64, - kzg_settings: Arc, + kzg_settings: EnvKzgSettings, ) { let setup = || { let config = ProptestConfig::default(); @@ -73,7 +69,9 @@ fn validate_blob_tx( // for now we just use the default SubPoolLimit group.bench_function(group_id, |b| { b.iter_with_setup(setup, |(tx, blob_sidecar)| { - if let Err(err) = std::hint::black_box(tx.validate_blob(&blob_sidecar, &kzg_settings)) { + if let Err(err) = + std::hint::black_box(tx.validate_blob(&blob_sidecar, kzg_settings.get())) + { println!("Validation failed: {err:?}"); } }); diff --git a/crates/primitives/src/constants/eip4844.rs b/crates/primitives/src/constants/eip4844.rs index c1748edf74b6..48a3aebb3c32 100644 --- a/crates/primitives/src/constants/eip4844.rs +++ b/crates/primitives/src/constants/eip4844.rs @@ -11,19 +11,7 @@ pub use alloy_eips::eip4844::{ #[cfg(feature = "c-kzg")] mod trusted_setup { use crate::kzg::KzgSettings; - use once_cell::sync::Lazy; - use std::{io::Write, sync::Arc}; - - /// KZG trusted setup - pub static MAINNET_KZG_TRUSTED_SETUP: Lazy> = Lazy::new(|| { - Arc::new( - c_kzg::KzgSettings::load_trusted_setup( - &revm_primitives::kzg::G1_POINTS.0, - &revm_primitives::kzg::G2_POINTS.0, - ) - .expect("failed to load trusted setup"), - ) - }); + use std::io::Write; /// Loads the trusted setup parameters from the given bytes and returns the [`KzgSettings`]. /// @@ -48,14 +36,4 @@ mod trusted_setup { #[error("KZG error: {0:?}")] KzgError(#[from] c_kzg::Error), } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn ensure_load_kzg_settings() { - let _settings = Arc::clone(&MAINNET_KZG_TRUSTED_SETUP); - } - } } diff --git a/crates/primitives/src/transaction/sidecar.rs b/crates/primitives/src/transaction/sidecar.rs index c45683ce7980..2c6f4598a513 100644 --- a/crates/primitives/src/transaction/sidecar.rs +++ b/crates/primitives/src/transaction/sidecar.rs @@ -281,14 +281,16 @@ impl BlobTransaction { /// Generates a [`BlobTransactionSidecar`] structure containing blobs, commitments, and proofs. #[cfg(all(feature = "c-kzg", any(test, feature = "arbitrary")))] pub fn generate_blob_sidecar(blobs: Vec) -> BlobTransactionSidecar { - use crate::constants::eip4844::MAINNET_KZG_TRUSTED_SETUP; + use alloy_eips::eip4844::env_settings::EnvKzgSettings; use c_kzg::{KzgCommitment, KzgProof}; - let kzg_settings = MAINNET_KZG_TRUSTED_SETUP.clone(); + let kzg_settings = EnvKzgSettings::Default; let commitments: Vec = blobs .iter() - .map(|blob| KzgCommitment::blob_to_kzg_commitment(&blob.clone(), &kzg_settings).unwrap()) + .map(|blob| { + KzgCommitment::blob_to_kzg_commitment(&blob.clone(), kzg_settings.get()).unwrap() + }) .map(|commitment| commitment.to_bytes()) .collect(); @@ -296,7 +298,7 @@ pub fn generate_blob_sidecar(blobs: Vec) -> BlobTransactionSidecar .iter() .zip(commitments.iter()) .map(|(blob, commitment)| { - KzgProof::compute_blob_kzg_proof(blob, commitment, &kzg_settings).unwrap() + KzgProof::compute_blob_kzg_proof(blob, commitment, kzg_settings.get()).unwrap() }) .map(|proof| proof.to_bytes()) .collect(); diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index a8e088278dcb..97ea4bb4c0fe 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -8,7 +8,6 @@ use crate::eth::{ }; use jsonrpsee::core::RpcResult; use reth_primitives::{ - constants::eip4844::MAINNET_KZG_TRUSTED_SETUP, keccak256, revm_primitives::db::{DatabaseCommit, DatabaseRef}, PooledTransactionsElement, U256, @@ -21,7 +20,7 @@ use revm::{ db::CacheDB, primitives::{ResultAndState, TxEnv}, }; -use revm_primitives::{EnvWithHandlerCfg, MAX_BLOB_GAS_PER_BLOCK}; +use revm_primitives::{EnvKzgSettings, EnvWithHandlerCfg, MAX_BLOB_GAS_PER_BLOCK}; use std::sync::Arc; /// `Eth` bundle implementation. @@ -126,7 +125,7 @@ where // Verify that the given blob data, commitments, and proofs are all valid for // this transaction. if let PooledTransactionsElement::BlobTransaction(ref tx) = tx { - tx.validate(MAINNET_KZG_TRUSTED_SETUP.as_ref()) + tx.validate(EnvKzgSettings::Default.get()) .map_err(|e| EthApiError::InvalidParams(e.to_string()))?; } diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 8bdd68d62154..79a93cf2e2a7 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -11,17 +11,16 @@ use crate::{ }; use reth_chainspec::ChainSpec; use reth_primitives::{ - constants::{ - eip4844::{MAINNET_KZG_TRUSTED_SETUP, MAX_BLOBS_PER_BLOCK}, - ETHEREUM_BLOCK_GAS_LIMIT, - }, - kzg::KzgSettings, + constants::{eip4844::MAX_BLOBS_PER_BLOCK, ETHEREUM_BLOCK_GAS_LIMIT}, Address, GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, }; use reth_provider::{AccountReader, BlockReaderIdExt, StateProviderFactory}; use reth_tasks::TaskSpawner; -use revm::{interpreter::gas::validate_initial_tx_gas, primitives::SpecId}; +use revm::{ + interpreter::gas::validate_initial_tx_gas, + primitives::{EnvKzgSettings, SpecId}, +}; use std::{ marker::PhantomData, sync::{atomic::AtomicBool, Arc}, @@ -125,7 +124,7 @@ pub(crate) struct EthTransactionValidatorInner { /// Minimum priority fee to enforce for acceptance into the pool. minimum_priority_fee: Option, /// Stores the setup and parameters needed for validating KZG proofs. - kzg_settings: Arc, + kzg_settings: EnvKzgSettings, /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions. local_transactions_config: LocalTransactionConfig, /// Maximum size in bytes a single transaction can have in order to be accepted into the pool. @@ -369,7 +368,7 @@ where } EthBlobTransactionSidecar::Present(blob) => { // validate the blob - if let Err(err) = transaction.validate_blob(&blob, &self.kzg_settings) { + if let Err(err) = transaction.validate_blob(&blob, self.kzg_settings.get()) { return TransactionValidationOutcome::Invalid( transaction, InvalidPoolTransactionError::Eip4844( @@ -435,7 +434,7 @@ pub struct EthTransactionValidatorBuilder { additional_tasks: usize, /// Stores the setup and parameters needed for validating KZG proofs. - kzg_settings: Arc, + kzg_settings: EnvKzgSettings, /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions. local_transactions_config: LocalTransactionConfig, /// Max size in bytes of a single transaction allowed @@ -457,7 +456,7 @@ impl EthTransactionValidatorBuilder { block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, minimum_priority_fee: None, additional_tasks: 1, - kzg_settings: Arc::clone(&MAINNET_KZG_TRUSTED_SETUP), + kzg_settings: EnvKzgSettings::Default, local_transactions_config: Default::default(), max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES, @@ -538,8 +537,8 @@ impl EthTransactionValidatorBuilder { self } - /// Sets the [`KzgSettings`] to use for validating KZG proofs. - pub fn kzg_settings(mut self, kzg_settings: Arc) -> Self { + /// Sets the [`EnvKzgSettings`] to use for validating KZG proofs. + pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self { self.kzg_settings = kzg_settings; self } From 83d412da70af678a46f368533b6df45a287a1ce6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 24 Jun 2024 18:01:42 +0200 Subject: [PATCH 187/405] fix(op): configure discv5 properly in op builder (#9058) --- crates/net/discv5/src/config.rs | 2 +- crates/node-core/src/args/network.rs | 2 +- crates/optimism/node/src/node.rs | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index da36d8f1c81f..669e7d04fe04 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -131,7 +131,7 @@ impl ConfigBuilder { } /// Adds boot nodes in the form a list of [`NodeRecord`]s, parsed enodes. - pub fn add_unsigned_boot_nodes(mut self, enodes: impl Iterator) -> Self { + pub fn add_unsigned_boot_nodes(mut self, enodes: impl IntoIterator) -> Self { for node in enodes { if let Ok(node) = BootNode::from_unsigned(node) { self.bootstrap_nodes.insert(node); diff --git a/crates/node-core/src/args/network.rs b/crates/node-core/src/args/network.rs index 28eb9f29bbb4..a5763495cd8d 100644 --- a/crates/node-core/src/args/network.rs +++ b/crates/node-core/src/args/network.rs @@ -185,7 +185,7 @@ impl NetworkArgs { } = self.discovery; builder - .add_unsigned_boot_nodes(chain_bootnodes.into_iter()) + .add_unsigned_boot_nodes(chain_bootnodes) .lookup_interval(discv5_lookup_interval) .bootstrap_lookup_interval(discv5_bootstrap_lookup_interval) .bootstrap_lookup_countdown(discv5_bootstrap_lookup_countdown) diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index 1b64dcce691d..d8628dc6bf85 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -292,6 +292,18 @@ where } builder + }) + // ensure we configure discv5 + .map_discv5_config_builder(|builder| { + builder + .add_unsigned_boot_nodes(ctx.chain_spec().bootnodes().unwrap_or_default()) + .lookup_interval(ctx.config().network.discovery.discv5_lookup_interval) + .bootstrap_lookup_interval( + ctx.config().network.discovery.discv5_bootstrap_lookup_interval, + ) + .bootstrap_lookup_countdown( + ctx.config().network.discovery.discv5_bootstrap_lookup_countdown, + ) }); let mut network_config = ctx.build_network_config(network_builder); From 37cb1194ad3bd3097fd7f101385ad1c6ada643a8 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:49:00 -0400 Subject: [PATCH 188/405] chore: move sync test to separate github action (#9061) --- .github/workflows/eth-sync.yml | 50 +++++++++++++++++++++++++++++++ .github/workflows/integration.yml | 33 -------------------- 2 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/eth-sync.yml diff --git a/.github/workflows/eth-sync.yml b/.github/workflows/eth-sync.yml new file mode 100644 index 000000000000..4a939c044703 --- /dev/null +++ b/.github/workflows/eth-sync.yml @@ -0,0 +1,50 @@ +# Runs an ethereum mainnet sync test. + +name: eth-sync-test + +on: + pull_request: + merge_group: + push: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + sync: + name: sync / 100k blocks + # Only run sync tests in merge groups + if: github.event_name == 'merge_group' + runs-on: + group: Reth + env: + RUST_LOG: info,sync=error + RUST_BACKTRACE: 1 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Run sync + run: | + cargo run --release --features asm-keccak,jemalloc,min-error-logs --bin reth \ + -- node \ + --debug.tip 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 \ + --debug.max-block 100000 \ + --debug.terminate + - name: Verify the target block hash + run: | + cargo run --release --bin reth \ + -- db get static-file headers 100000 \ + | grep 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 + - name: Run stage unwind for 100 blocks + run: | + cargo run --release --bin reth \ + -- stage unwind num-blocks 100 diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index a57411744939..a36cb1f194ef 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -49,39 +49,6 @@ jobs: cargo nextest run \ --locked -p reth-node-optimism --features "optimism" - sync: - name: sync / 100k blocks - # Only run sync tests in merge groups - if: github.event_name == 'merge_group' - runs-on: - group: Reth - env: - RUST_LOG: info,sync=error - RUST_BACKTRACE: 1 - timeout-minutes: 60 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Run sync - run: | - cargo run --release --features asm-keccak,jemalloc,min-error-logs --bin reth \ - -- node \ - --debug.tip 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 \ - --debug.max-block 100000 \ - --debug.terminate - - name: Verify the target block hash - run: | - cargo run --release --bin reth \ - -- db get static-file headers 100000 \ - | grep 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 - - name: Run stage unwind for 100 blocks - run: | - cargo run --release --bin reth \ - -- stage unwind num-blocks 100 - integration-success: name: integration success runs-on: ubuntu-latest From 31aad32541e4c43c21b822563fc84302be30b540 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:18:26 -0400 Subject: [PATCH 189/405] feat: add base mainnet 10k block sync test to CI (#9062) --- .github/workflows/op-sync.yml | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/op-sync.yml diff --git a/.github/workflows/op-sync.yml b/.github/workflows/op-sync.yml new file mode 100644 index 000000000000..cc213b207833 --- /dev/null +++ b/.github/workflows/op-sync.yml @@ -0,0 +1,51 @@ +# Runs a base mainnet sync test. + +name: op-sync-test + +on: + pull_request: + merge_group: + push: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + sync: + name: op sync / 100k blocks + # Only run sync tests in merge groups + if: github.event_name == 'merge_group' + runs-on: + group: Reth + env: + RUST_LOG: info,sync=error + RUST_BACKTRACE: 1 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Run sync + run: | + cargo run --release --features asm-keccak,jemalloc,min-error-logs,optimism --bin op-reth \ + -- node \ + --chain base \ + --debug.tip 0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7 \ # https://basescan.org/block/10000 + --debug.max-block 10000 \ + --debug.terminate + - name: Verify the target block hash + run: | + cargo run --release --bin reth \ + -- db get static-file headers 10000 \ + | grep 0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7 + - name: Run stage unwind for 100 blocks + run: | + cargo run --release --bin reth \ + -- stage unwind num-blocks 100 From 580711a93bb32237db483b65ddc6e9caaceb7369 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:50:22 -0400 Subject: [PATCH 190/405] fix: move base sync test comment (#9066) --- .github/workflows/op-sync.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/op-sync.yml b/.github/workflows/op-sync.yml index cc213b207833..5026cc616a82 100644 --- a/.github/workflows/op-sync.yml +++ b/.github/workflows/op-sync.yml @@ -17,7 +17,7 @@ concurrency: jobs: sync: - name: op sync / 100k blocks + name: op sync / 10k blocks # Only run sync tests in merge groups if: github.event_name == 'merge_group' runs-on: @@ -33,11 +33,12 @@ jobs: with: cache-on-failure: true - name: Run sync + # https://basescan.org/block/10000 run: | cargo run --release --features asm-keccak,jemalloc,min-error-logs,optimism --bin op-reth \ -- node \ --chain base \ - --debug.tip 0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7 \ # https://basescan.org/block/10000 + --debug.tip 0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7 \ --debug.max-block 10000 \ --debug.terminate - name: Verify the target block hash From f5403882d6fd22cb6a09b20fd46da901b27babc5 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 24 Jun 2024 21:58:05 +0100 Subject: [PATCH 191/405] docs(examples): add Remote ExEx to README.md (#9067) --- examples/README.md | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/examples/README.md b/examples/README.md index c5f20f21c881..ac9afb8fe583 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,26 +10,27 @@ to make a PR! ## Node Builder -| Example | Description | -| -------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| [Additional RPC namespace](./node-custom-rpc) | Illustrates how to add custom CLI parameters and set up a custom RPC namespace | -| [Custom event hooks](./node-event-hooks) | Illustrates how to hook to various node lifecycle events | -| [Custom dev node](./custom-dev-node) | Illustrates how to run a custom dev node programmatically and submit a transaction to it via RPC | -| [Custom EVM](./custom-evm) | Illustrates how to implement a node with a custom EVM | -| [Custom Stateful Precompile](./stateful-precompile)| Illustrates how to implement a node with a stateful precompile | -| [Custom inspector](./custom-inspector) | Illustrates how to use a custom EVM inspector to trace new transactions | -| [Custom engine types](./custom-engine-types) | Illustrates how to create a node with custom engine types | -| [Custom node components](./custom-node-components) | Illustrates how to configure custom node components | -| [Custom payload builder](./custom-payload-builder) | Illustrates how to use a custom payload builder | +| Example | Description | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| [Additional RPC namespace](./node-custom-rpc) | Illustrates how to add custom CLI parameters and set up a custom RPC namespace | +| [Custom event hooks](./node-event-hooks) | Illustrates how to hook to various node lifecycle events | +| [Custom dev node](./custom-dev-node) | Illustrates how to run a custom dev node programmatically and submit a transaction to it via RPC | +| [Custom EVM](./custom-evm) | Illustrates how to implement a node with a custom EVM | +| [Custom Stateful Precompile](./stateful-precompile) | Illustrates how to implement a node with a stateful precompile | +| [Custom inspector](./custom-inspector) | Illustrates how to use a custom EVM inspector to trace new transactions | +| [Custom engine types](./custom-engine-types) | Illustrates how to create a node with custom engine types | +| [Custom node components](./custom-node-components) | Illustrates how to configure custom node components | +| [Custom payload builder](./custom-payload-builder) | Illustrates how to use a custom payload builder | ## ExEx -| Example | Description | -|-------------------------------------------|-----------------------------------------------------------------------------------| -| [Minimal ExEx](./exex/minimal) | Illustrates how to build a simple ExEx | -| [OP Bridge ExEx](./exex/op-bridge) | Illustrates an ExEx that decodes Optimism deposit and withdrawal receipts from L1 | -| [Rollup](./exex/rollup) | Illustrates a rollup ExEx that derives the state from L1 | -| [In Memory State](./exex/in-memory-state) | Illustrates an ExEx that tracks the plain state in memory | +| Example | Description | +| ----------------------------------------- | --------------------------------------------------------------------------------------------------- | +| [In Memory State](./exex/in-memory-state) | Illustrates an ExEx that tracks the plain state in memory | +| [Minimal](./exex/minimal) | Illustrates how to build a simple ExEx | +| [OP Bridge](./exex/op-bridge) | Illustrates an ExEx that decodes Optimism deposit and withdrawal receipts from L1 | +| [Remote](./exex/remote) | Illustrates an ExEx that emits notifications using a gRPC server, and a consumer that receives them | +| [Rollup](./exex/rollup) | Illustrates a rollup ExEx that derives the state from L1 | ## RPC @@ -58,11 +59,11 @@ to make a PR! ## P2P -| Example | Description | -| --------------------------- | ----------------------------------------------------------------- | -| [Manual P2P](./manual-p2p) | Illustrates how to connect and communicate with a peer | -| [Polygon P2P](./polygon-p2p) | Illustrates how to connect and communicate with a peer on Polygon | -| [BSC P2P](./bsc-p2p) | Illustrates how to connect and communicate with a peer on Binance Smart Chain | +| Example | Description | +| ---------------------------- | ----------------------------------------------------------------------------- | +| [Manual P2P](./manual-p2p) | Illustrates how to connect and communicate with a peer | +| [Polygon P2P](./polygon-p2p) | Illustrates how to connect and communicate with a peer on Polygon | +| [BSC P2P](./bsc-p2p) | Illustrates how to connect and communicate with a peer on Binance Smart Chain | ## Misc From 0c1687f381466f32bfb007e69665d79c736263a6 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:20:38 -0400 Subject: [PATCH 192/405] feat: use a binary for sync tests (#9071) --- .github/workflows/eth-sync.yml | 12 ++++++------ .github/workflows/op-sync.yml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/eth-sync.yml b/.github/workflows/eth-sync.yml index 4a939c044703..54c0d96079b9 100644 --- a/.github/workflows/eth-sync.yml +++ b/.github/workflows/eth-sync.yml @@ -32,19 +32,19 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - name: Build reth + run: | + cargo install --features asm-keccak,jemalloc --path bin/reth - name: Run sync run: | - cargo run --release --features asm-keccak,jemalloc,min-error-logs --bin reth \ - -- node \ + reth node \ --debug.tip 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 \ --debug.max-block 100000 \ --debug.terminate - name: Verify the target block hash run: | - cargo run --release --bin reth \ - -- db get static-file headers 100000 \ + reth db get static-file headers 100000 \ | grep 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 - name: Run stage unwind for 100 blocks run: | - cargo run --release --bin reth \ - -- stage unwind num-blocks 100 + reth stage unwind num-blocks 100 diff --git a/.github/workflows/op-sync.yml b/.github/workflows/op-sync.yml index 5026cc616a82..adf927560790 100644 --- a/.github/workflows/op-sync.yml +++ b/.github/workflows/op-sync.yml @@ -32,21 +32,21 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - name: Build op-reth + run: | + cargo install --features asm-keccak,jemalloc,optimism --bin op-reth --path bin/reth - name: Run sync # https://basescan.org/block/10000 run: | - cargo run --release --features asm-keccak,jemalloc,min-error-logs,optimism --bin op-reth \ - -- node \ + op-reth node \ --chain base \ --debug.tip 0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7 \ --debug.max-block 10000 \ --debug.terminate - name: Verify the target block hash run: | - cargo run --release --bin reth \ - -- db get static-file headers 10000 \ + op-reth db --chain base get static-file headers 100000 \ | grep 0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7 - name: Run stage unwind for 100 blocks run: | - cargo run --release --bin reth \ - -- stage unwind num-blocks 100 + op-reth stage --chain base unwind num-blocks 100 From 4cb84a443abb38b490babde61420f1fa9fd666ea Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Mon, 24 Jun 2024 23:53:44 +0200 Subject: [PATCH 193/405] feat: add AnyNode type (#9056) Co-authored-by: Matthias Seitz --- crates/node/builder/src/node.rs | 40 ++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/node/builder/src/node.rs b/crates/node/builder/src/node.rs index 76c2117552ee..fe8d99ed6090 100644 --- a/crates/node/builder/src/node.rs +++ b/crates/node/builder/src/node.rs @@ -11,7 +11,7 @@ use reth_payload_builder::PayloadBuilderHandle; use reth_provider::ChainSpecProvider; use reth_rpc_builder::{auth::AuthServerHandle, RpcServerHandle}; use reth_tasks::TaskExecutor; -use std::sync::Arc; +use std::{marker::PhantomData, sync::Arc}; // re-export the node api types use crate::components::NodeComponentsBuilder; @@ -28,6 +28,44 @@ pub trait Node: NodeTypes + Clone { fn components_builder(self) -> Self::ComponentsBuilder; } +/// A [`Node`] type builder +#[derive(Clone, Default, Debug)] +pub struct AnyNode(PhantomData, C); + +impl AnyNode { + /// Configures the types of the node. + pub fn types(self) -> AnyNode { + AnyNode::(PhantomData::, self.1) + } + + /// Sets the node components builder. + pub fn components_builder(self, value: T) -> AnyNode { + AnyNode::(PhantomData::, value) + } +} + +impl NodeTypes for AnyNode +where + N: FullNodeTypes, + C: NodeComponentsBuilder + Sync + Unpin + 'static, +{ + type Primitives = N::Primitives; + + type Engine = N::Engine; +} + +impl Node for AnyNode +where + N: FullNodeTypes + Clone, + C: NodeComponentsBuilder + Clone + Sync + Unpin + 'static, +{ + type ComponentsBuilder = C; + + fn components_builder(self) -> Self::ComponentsBuilder { + self.1 + } +} + /// The launched node with all components including RPC handlers. /// /// This can be used to interact with the launched node. From 5585cb8cfac1faa8808fb54bf61644a1db515b10 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 25 Jun 2024 00:03:19 +0200 Subject: [PATCH 194/405] chore: add empty commands crate (#9039) --- Cargo.lock | 4 ++++ Cargo.toml | 2 ++ crates/cli/commands/Cargo.toml | 10 ++++++++++ crates/cli/commands/src/lib.rs | 9 +++++++++ 4 files changed, 25 insertions(+) create mode 100644 crates/cli/commands/Cargo.toml create mode 100644 crates/cli/commands/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4e26b254f381..7d76d53b80a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6693,6 +6693,10 @@ dependencies = [ "serde_json", ] +[[package]] +name = "reth-cli-commands" +version = "1.0.0" + [[package]] name = "reth-cli-runner" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 6724c5318889..ab99f90800ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/blockchain-tree/", "crates/blockchain-tree-api/", "crates/chainspec/", + "crates/cli/commands/", "crates/cli/runner/", "crates/config/", "crates/consensus/auto-seal/", @@ -253,6 +254,7 @@ reth-beacon-consensus = { path = "crates/consensus/beacon" } reth-blockchain-tree = { path = "crates/blockchain-tree" } reth-blockchain-tree-api = { path = "crates/blockchain-tree-api" } reth-chainspec = { path = "crates/chainspec" } +reth-cli-commands = { path = "crates/cli/commands" } reth-cli-runner = { path = "crates/cli/runner" } reth-codecs = { path = "crates/storage/codecs" } reth-codecs-derive = { path = "crates/storage/codecs/derive" } diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml new file mode 100644 index 000000000000..d12abefcd8b0 --- /dev/null +++ b/crates/cli/commands/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "reth-cli-commands" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] diff --git a/crates/cli/commands/src/lib.rs b/crates/cli/commands/src/lib.rs new file mode 100644 index 000000000000..33983bb856db --- /dev/null +++ b/crates/cli/commands/src/lib.rs @@ -0,0 +1,9 @@ +//! Commonly used reth CLI commands. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] From 667c38d3d688135275e160a20f6cb763e32d8a43 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:34:23 -0400 Subject: [PATCH 195/405] fix: check the correct block in op-sync (#9073) --- .github/workflows/op-sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/op-sync.yml b/.github/workflows/op-sync.yml index adf927560790..73303b032d05 100644 --- a/.github/workflows/op-sync.yml +++ b/.github/workflows/op-sync.yml @@ -45,7 +45,7 @@ jobs: --debug.terminate - name: Verify the target block hash run: | - op-reth db --chain base get static-file headers 100000 \ + op-reth db --chain base get static-file headers 10000 \ | grep 0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7 - name: Run stage unwind for 100 blocks run: | From de6332b51e9d4bdf9d3e75a1523ff1cdfb862686 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 24 Jun 2024 19:01:51 -0400 Subject: [PATCH 196/405] fix(ci): inherit profiling in bench profile (#9072) --- Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ab99f90800ab..5866925dd76a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -236,9 +236,13 @@ codegen-units = 16 # e.g. `cargo build --profile profiling` [profile.profiling] inherits = "release" -debug = 1 +debug = 2 strip = false +# Make sure debug symbols are in the bench profile +[profile.bench] +inherits = "profiling" + [profile.maxperf] inherits = "release" lto = "fat" From c885257e5200e568df9dfb155a2b329d45af377f Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:17:32 +0200 Subject: [PATCH 197/405] clippy: rm outdated clippy allow (#9070) --- Cargo.lock | 45 +++++++++++++-------- Cargo.toml | 2 +- crates/ethereum-forks/src/lib.rs | 2 - crates/net/eth-wire-types/src/lib.rs | 2 - crates/net/eth-wire/src/lib.rs | 2 - crates/net/eth-wire/tests/fuzz_roundtrip.rs | 3 -- crates/primitives-traits/src/lib.rs | 2 - crates/primitives/src/lib.rs | 2 - crates/prune/types/src/lib.rs | 2 - crates/stages/types/src/lib.rs | 2 - crates/storage/codecs/src/lib.rs | 2 - crates/storage/db-api/src/lib.rs | 2 - crates/storage/db/src/tables/mod.rs | 3 -- crates/trie/common/src/lib.rs | 2 - 14 files changed, 29 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d76d53b80a0..9699f5ebf503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,7 +134,7 @@ dependencies = [ "arbitrary", "c-kzg", "proptest", - "proptest-derive", + "proptest-derive 0.4.0", "serde", ] @@ -170,7 +170,7 @@ dependencies = [ "derive_more", "once_cell", "proptest", - "proptest-derive", + "proptest-derive 0.4.0", "serde", "sha2 0.10.8", ] @@ -267,7 +267,7 @@ dependencies = [ "k256", "keccak-asm", "proptest", - "proptest-derive", + "proptest-derive 0.4.0", "rand 0.8.5", "ruint", "serde", @@ -459,7 +459,7 @@ dependencies = [ "itertools 0.13.0", "jsonrpsee-types", "proptest", - "proptest-derive", + "proptest-derive 0.4.0", "serde", "serde_json", "thiserror", @@ -500,7 +500,7 @@ dependencies = [ "alloy-primitives", "arbitrary", "proptest", - "proptest-derive", + "proptest-derive 0.4.0", "serde", "serde_json", ] @@ -693,7 +693,7 @@ dependencies = [ "hashbrown 0.14.5", "nybbles", "proptest", - "proptest-derive", + "proptest-derive 0.4.0", "serde", "smallvec", "tracing", @@ -5959,6 +5959,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "proptest-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.67", +] + [[package]] name = "prost" version = "0.12.6" @@ -6719,7 +6730,7 @@ dependencies = [ "modular-bitfield", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "reth-codecs-derive", "serde", "serde_json", @@ -6849,7 +6860,7 @@ dependencies = [ "pprof", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "rand 0.8.5", "reth-codecs", "reth-primitives", @@ -7096,7 +7107,7 @@ dependencies = [ "pin-project", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "rand 0.8.5", "reth-chainspec", "reth-codecs", @@ -7129,7 +7140,7 @@ dependencies = [ "derive_more", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "rand 0.8.5", "reth-chainspec", "reth-codecs-derive", @@ -7176,7 +7187,7 @@ dependencies = [ "arbitrary", "crc", "proptest", - "proptest-derive", + "proptest-derive 0.5.0", "serde", "thiserror-no-std", ] @@ -7912,7 +7923,7 @@ dependencies = [ "pprof", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "rand 0.8.5", "rayon", "reth-chainspec", @@ -7951,7 +7962,7 @@ dependencies = [ "modular-bitfield", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "rand 0.8.5", "reth-codecs", "revm-primitives", @@ -8044,7 +8055,7 @@ dependencies = [ "modular-bitfield", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "reth-codecs", "serde", "serde_json", @@ -8277,7 +8288,7 @@ dependencies = [ "bytes", "jsonrpsee-types", "proptest", - "proptest-derive", + "proptest-derive 0.5.0", "rand 0.8.5", "serde", "serde_json", @@ -8380,7 +8391,7 @@ dependencies = [ "modular-bitfield", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "rand 0.8.5", "reth-codecs", "reth-trie-common", @@ -8587,7 +8598,7 @@ dependencies = [ "plain_hasher", "proptest", "proptest-arbitrary-interop", - "proptest-derive", + "proptest-derive 0.5.0", "reth-codecs", "reth-primitives-traits", "revm-primitives", diff --git a/Cargo.toml b/Cargo.toml index 5866925dd76a..62770b9deb4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -501,7 +501,7 @@ tempfile = "3.8" criterion = "0.5" pprof = "0.13" proptest = "1.4" -proptest-derive = "0.4" +proptest-derive = "0.5" serial_test = "3" similar-asserts = "1.5.0" test-fuzz = "5" diff --git a/crates/ethereum-forks/src/lib.rs b/crates/ethereum-forks/src/lib.rs index 1a7e0f56e707..37e9b8710823 100644 --- a/crates/ethereum-forks/src/lib.rs +++ b/crates/ethereum-forks/src/lib.rs @@ -12,8 +12,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/crates/net/eth-wire-types/src/lib.rs b/crates/net/eth-wire-types/src/lib.rs index a60fa4c8c1e9..e75898a1ff70 100644 --- a/crates/net/eth-wire-types/src/lib.rs +++ b/crates/net/eth-wire-types/src/lib.rs @@ -6,8 +6,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![allow(clippy::needless_lifetimes)] // side effect of optimism fields #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] diff --git a/crates/net/eth-wire/src/lib.rs b/crates/net/eth-wire/src/lib.rs index 3830baa1b7e5..e96a27077f8c 100644 --- a/crates/net/eth-wire/src/lib.rs +++ b/crates/net/eth-wire/src/lib.rs @@ -11,8 +11,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod capability; diff --git a/crates/net/eth-wire/tests/fuzz_roundtrip.rs b/crates/net/eth-wire/tests/fuzz_roundtrip.rs index f20d0397c2b6..ec55fc448ae0 100644 --- a/crates/net/eth-wire/tests/fuzz_roundtrip.rs +++ b/crates/net/eth-wire/tests/fuzz_roundtrip.rs @@ -1,8 +1,5 @@ //! Round-trip encoding fuzzing for the `eth-wire` crate. -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] - use alloy_rlp::{Decodable, Encodable}; use serde::Serialize; use std::fmt::Debug; diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 22d4c86a0fda..590e9573dc4c 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -6,8 +6,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 59fec9702991..35a3ee189308 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -14,8 +14,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/crates/prune/types/src/lib.rs b/crates/prune/types/src/lib.rs index 34d74614f2c1..82563010f165 100644 --- a/crates/prune/types/src/lib.rs +++ b/crates/prune/types/src/lib.rs @@ -6,8 +6,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod checkpoint; diff --git a/crates/stages/types/src/lib.rs b/crates/stages/types/src/lib.rs index 93106bd886d1..00355b023bc8 100644 --- a/crates/stages/types/src/lib.rs +++ b/crates/stages/types/src/lib.rs @@ -6,8 +6,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod id; diff --git a/crates/storage/codecs/src/lib.rs b/crates/storage/codecs/src/lib.rs index b0927a1481cb..bea26090deb1 100644 --- a/crates/storage/codecs/src/lib.rs +++ b/crates/storage/codecs/src/lib.rs @@ -14,8 +14,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/crates/storage/db-api/src/lib.rs b/crates/storage/db-api/src/lib.rs index 284321092320..dc7ab0eb4a65 100644 --- a/crates/storage/db-api/src/lib.rs +++ b/crates/storage/db-api/src/lib.rs @@ -58,8 +58,6 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] /// Common types used throughout the abstraction. pub mod common; diff --git a/crates/storage/db/src/tables/mod.rs b/crates/storage/db/src/tables/mod.rs index c968647a982d..af350b74ae82 100644 --- a/crates/storage/db/src/tables/mod.rs +++ b/crates/storage/db/src/tables/mod.rs @@ -11,9 +11,6 @@ //! //! TODO(onbjerg): Find appropriate format for this... -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] - pub mod codecs; mod raw; diff --git a/crates/trie/common/src/lib.rs b/crates/trie/common/src/lib.rs index bc3749e6f936..f845c9ca5cd6 100644 --- a/crates/trie/common/src/lib.rs +++ b/crates/trie/common/src/lib.rs @@ -6,8 +6,6 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// TODO: remove when https://github.com/proptest-rs/proptest/pull/427 is merged -#![allow(unknown_lints, non_local_definitions)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] /// The implementation of hash builder. From aac16ac6061c93fe77633d300cc42166f3d5ca34 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 25 Jun 2024 02:07:18 -0700 Subject: [PATCH 198/405] chore(trie): `TrieOp::as_update` (#9076) --- crates/trie/trie/src/updates.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 39628e6d5272..afad628cd74e 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -38,6 +38,15 @@ impl TrieOp { pub const fn is_update(&self) -> bool { matches!(self, Self::Update(..)) } + + /// Returns reference to updated branch node if operation is [`Self::Update`]. + pub const fn as_update(&self) -> Option<&BranchNodeCompact> { + if let Self::Update(node) = &self { + Some(node) + } else { + None + } + } } /// The aggregation of trie updates. From eb5217e2ac91e992516674a780e7be6e90cf2941 Mon Sep 17 00:00:00 2001 From: Vid Kersic <38610409+Vid201@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:36:54 +0200 Subject: [PATCH 199/405] chore: simplify OptimismGenesisInfo extraction (#9031) Co-authored-by: Matthias Seitz --- Cargo.lock | 1 + crates/chainspec/Cargo.toml | 4 +- crates/chainspec/src/spec.rs | 190 +++++++++++++++++++++++------------ 3 files changed, 128 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9699f5ebf503..9661d075a516 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6701,6 +6701,7 @@ dependencies = [ "reth-primitives-traits", "reth-rpc-types", "reth-trie-common", + "serde", "serde_json", ] diff --git a/crates/chainspec/Cargo.toml b/crates/chainspec/Cargo.toml index 682289145aab..f473acc4b20a 100644 --- a/crates/chainspec/Cargo.toml +++ b/crates/chainspec/Cargo.toml @@ -26,6 +26,7 @@ alloy-trie.workspace = true # misc once_cell.workspace = true +serde = { workspace = true, optional = true } serde_json.workspace = true derive_more.workspace = true @@ -42,7 +43,8 @@ rand.workspace = true [features] default = ["std"] optimism = [ - "reth-ethereum-forks/optimism" + "reth-ethereum-forks/optimism", + "serde" ] std = [] arbitrary = [ diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index bf16a88f0913..4d53352dc498 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -298,6 +298,12 @@ pub enum BaseFeeParamsKind { Variable(ForkBaseFeeParams), } +impl Default for BaseFeeParamsKind { + fn default() -> Self { + BaseFeeParams::ethereum().into() + } +} + impl From for BaseFeeParamsKind { fn from(params: BaseFeeParams) -> Self { Self::Constant(params) @@ -1160,98 +1166,78 @@ impl DepositContract { } } +/// Genesis info for Optimism. #[cfg(feature = "optimism")] +#[derive(Default, Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] struct OptimismGenesisInfo { bedrock_block: Option, regolith_time: Option, canyon_time: Option, ecotone_time: Option, fjord_time: Option, + #[serde(skip)] base_fee_params: BaseFeeParamsKind, } +#[cfg(feature = "optimism")] +#[derive(Debug, Eq, PartialEq, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct OptimismBaseFeeInfo { + eip1559_elasticity: Option, + eip1559_denominator: Option, + eip1559_denominator_canyon: Option, +} + #[cfg(feature = "optimism")] impl OptimismGenesisInfo { fn extract_from(genesis: &Genesis) -> Self { - let optimism_config = - genesis.config.extra_fields.get("optimism").and_then(|value| value.as_object()); + let mut optimism_genesis_info: Self = + genesis.config.extra_fields.deserialize_as().unwrap_or_default(); - let eip1559_elasticity = optimism_config - .and_then(|config| config.get("eip1559Elasticity")) - .and_then(|value| value.as_u64()); - - let eip1559_denominator = optimism_config - .and_then(|config| config.get("eip1559Denominator")) - .and_then(|value| value.as_u64()); - - let eip1559_denominator_canyon = optimism_config - .and_then(|config| config.get("eip1559DenominatorCanyon")) - .and_then(|value| value.as_u64()); - - let base_fee_params = if let (Some(elasticity), Some(denominator)) = - (eip1559_elasticity, eip1559_denominator) + if let Some(Ok(optimism_base_fee_info)) = + genesis.config.extra_fields.get_deserialized::("optimism") { - if let Some(canyon_denominator) = eip1559_denominator_canyon { - BaseFeeParamsKind::Variable( - vec![ - ( - Hardfork::London, - BaseFeeParams::new(denominator as u128, elasticity as u128), - ), - ( - Hardfork::Canyon, - BaseFeeParams::new(canyon_denominator as u128, elasticity as u128), - ), - ] - .into(), - ) - } else { - BaseFeeParams::new(denominator as u128, elasticity as u128).into() - } - } else { - BaseFeeParams::ethereum().into() - }; + if let (Some(elasticity), Some(denominator)) = ( + optimism_base_fee_info.eip1559_elasticity, + optimism_base_fee_info.eip1559_denominator, + ) { + let base_fee_params = if let Some(canyon_denominator) = + optimism_base_fee_info.eip1559_denominator_canyon + { + BaseFeeParamsKind::Variable( + vec![ + ( + Hardfork::London, + BaseFeeParams::new(denominator as u128, elasticity as u128), + ), + ( + Hardfork::Canyon, + BaseFeeParams::new(canyon_denominator as u128, elasticity as u128), + ), + ] + .into(), + ) + } else { + BaseFeeParams::new(denominator as u128, elasticity as u128).into() + }; - Self { - bedrock_block: genesis - .config - .extra_fields - .get("bedrockBlock") - .and_then(|value| value.as_u64()), - regolith_time: genesis - .config - .extra_fields - .get("regolithTime") - .and_then(|value| value.as_u64()), - canyon_time: genesis - .config - .extra_fields - .get("canyonTime") - .and_then(|value| value.as_u64()), - ecotone_time: genesis - .config - .extra_fields - .get("ecotoneTime") - .and_then(|value| value.as_u64()), - fjord_time: genesis - .config - .extra_fields - .get("fjordTime") - .and_then(|value| value.as_u64()), - base_fee_params, + optimism_genesis_info.base_fee_params = base_fee_params; + } } + + optimism_genesis_info } } #[cfg(test)] mod tests { + use super::*; use alloy_chains::Chain; use alloy_genesis::{ChainConfig, GenesisAccount}; + use alloy_primitives::{b256, hex}; use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head}; use reth_trie_common::TrieAccount; - - use super::*; - use alloy_primitives::{b256, hex}; use std::{collections::HashMap, str::FromStr}; fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) { @@ -2967,4 +2953,76 @@ Post-merge hard forks (timestamp based): assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Ecotone, 40)); assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Fjord, 50)); } + + #[cfg(feature = "optimism")] + #[test] + fn parse_genesis_optimism_with_variable_base_fee_params() { + let geth_genesis = r#" + { + "config": { + "chainId": 8453, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "mergeNetsplitBlock": 0, + "bedrockBlock": 0, + "regolithTime": 15, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "optimism": { + "eip1559Elasticity": 6, + "eip1559Denominator": 50 + } + } + } + "#; + let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + let chainspec = ChainSpec::from(genesis.clone()); + + let actual_chain_id = genesis.config.chain_id; + assert_eq!(actual_chain_id, 8453); + + assert_eq!(chainspec.hardforks.get(&Hardfork::Istanbul), Some(&ForkCondition::Block(0))); + + let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock"); + assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(0)).as_ref()); + let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime"); + assert_eq!(actual_canyon_timestamp, None); + + assert!(genesis.config.terminal_total_difficulty_passed); + + let optimism_object = genesis.config.extra_fields.get("optimism").unwrap(); + let optimism_base_fee_info = + serde_json::from_value::(optimism_object.clone()).unwrap(); + + assert_eq!( + optimism_base_fee_info, + OptimismBaseFeeInfo { + eip1559_elasticity: Some(6), + eip1559_denominator: Some(50), + eip1559_denominator_canyon: None, + } + ); + assert_eq!( + chainspec.base_fee_params, + BaseFeeParamsKind::Constant(BaseFeeParams { + max_change_denominator: 50, + elasticity_multiplier: 6, + }) + ); + + assert!(chainspec.is_fork_active_at_block(Hardfork::Bedrock, 0)); + + assert!(chainspec.is_fork_active_at_timestamp(Hardfork::Regolith, 20)); + } } From 4984bc6cc9ff89136f639b6198ad3ba553f3155d Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 25 Jun 2024 10:58:55 +0100 Subject: [PATCH 200/405] fix(ci): use correct profile for iai benches (#9081) --- .github/workflows/bench.yml | 4 ++-- .github/workflows/lint.yml | 2 ++ .github/workflows/unit.yml | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 9291f7a6cf20..7d1524e60375 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -51,10 +51,10 @@ jobs: - name: Generate test vectors run: cargo run --bin reth -- test-vectors tables - name: Save baseline - run: cargo bench -p reth-db --bench iai --features test-utils -- --save-baseline=$BASELINE + run: cargo bench -p reth-db --bench iai --profile profiling --features test-utils -- --save-baseline=$BASELINE - name: Checkout PR uses: actions/checkout@v4 with: clean: false - name: Compare PR benchmarks - run: cargo bench -p reth-db --bench iai --features test-utils -- --baseline=$BASELINE + run: cargo bench -p reth-db --bench iai --profile profiling --features test-utils -- --baseline=$BASELINE diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c758f6945a05..0e73aafefc60 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -42,6 +42,8 @@ jobs: with: cache-on-failure: true - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked env: RUSTFLAGS: -D warnings diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index a5d42a85d208..46b25b370b39 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -85,6 +85,8 @@ jobs: with: cache-on-failure: true - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Run doctests run: cargo test --doc --workspace --features "${{ matrix.network }}" From bbc4f308f5eb8ef55538db4317b95907caabda54 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 25 Jun 2024 12:27:49 +0200 Subject: [PATCH 201/405] chore(hive): update failed tests comments (#9080) --- .github/workflows/hive.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index e34470e4dedd..3340393d0873 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -68,9 +68,9 @@ jobs: matrix: # TODO: enable etherem/sync once resolved: # https://github.com/paradigmxyz/reth/issues/8579 - # TODO: enable ethereum/rpc once resolved: + # ethereum/rpc to be deprecated: # https://github.com/ethereum/hive/pull/1117 - # sim: [ethereum/rpc, smoke/genesis, smoke/network, ethereum/sync] + # sim: [smoke/genesis, smoke/network, ethereum/sync] sim: [smoke/genesis, smoke/network] include: - sim: devp2p @@ -107,11 +107,12 @@ jobs: - sim: ethereum/engine limit: engine-transition # TODO: enable engine-api once resolved: - # https://github.com/paradigmxyz/reth/issues/6217 # https://github.com/paradigmxyz/reth/issues/8305 + # https://github.com/paradigmxyz/reth/issues/6217 # - sim: ethereum/engine # limit: engine-api # TODO: enable cancun once resolved: + # https://github.com/paradigmxyz/reth/issues/8305 # https://github.com/paradigmxyz/reth/issues/6217 # https://github.com/paradigmxyz/reth/issues/8306 # https://github.com/paradigmxyz/reth/issues/7144 From ec5795f7ee1e725278527e03f676ea7876ca02ae Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Tue, 25 Jun 2024 16:42:55 +0530 Subject: [PATCH 202/405] Using associated trait bound for db error (#8951) --- crates/ethereum/evm/src/execute.rs | 40 ++++++++++++++++++------------ crates/evm/src/either.rs | 16 +++++++----- crates/evm/src/execute.rs | 17 +++++++------ crates/evm/src/noop.rs | 10 +++++--- crates/evm/src/test_utils.rs | 10 ++++---- crates/optimism/evm/src/execute.rs | 31 +++++++++++++++-------- crates/revm/src/state_change.rs | 8 +++--- 7 files changed, 78 insertions(+), 54 deletions(-) diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 149fbc9557b9..e4c89dd778dd 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -4,6 +4,8 @@ use crate::{ dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, EthEvmConfig, }; +#[cfg(not(feature = "std"))] +use alloc::{sync::Arc, vec, vec::Vec}; use reth_chainspec::{ChainSpec, MAINNET}; use reth_ethereum_consensus::validate_block_post_execution; use reth_evm::{ @@ -29,15 +31,11 @@ use reth_revm::{ }; use revm_primitives::{ db::{Database, DatabaseCommit}, - BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState, + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ResultAndState, }; -#[cfg(not(feature = "std"))] -use alloc::{sync::Arc, vec, vec::Vec}; - #[cfg(feature = "std")] -use std::sync::Arc; - +use std::{fmt::Display, sync::Arc}; /// Provides executors to execute regular ethereum blocks #[derive(Debug, Clone)] pub struct EthExecutorProvider { @@ -70,7 +68,7 @@ where { fn eth_executor(&self, db: DB) -> EthBlockExecutor where - DB: Database, + DB: Database>, { EthBlockExecutor::new( self.chain_spec.clone(), @@ -84,20 +82,22 @@ impl BlockExecutorProvider for EthExecutorProvider where EvmConfig: ConfigureEvm, { - type Executor> = EthBlockExecutor; + type Executor + Display>> = + EthBlockExecutor; - type BatchExecutor> = EthBatchExecutor; + type BatchExecutor + Display>> = + EthBatchExecutor; fn executor(&self, db: DB) -> Self::Executor where - DB: Database, + DB: Database + Display>, { self.eth_executor(db) } fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor where - DB: Database, + DB: Database + Display>, { let executor = self.eth_executor(db); EthBatchExecutor { @@ -145,7 +145,8 @@ where mut evm: Evm<'_, Ext, &mut State>, ) -> Result where - DB: Database, + DB: Database, + DB::Error: Into + std::fmt::Display, { // apply pre execution changes apply_beacon_root_contract_call( @@ -182,10 +183,17 @@ where // Execute transaction. let ResultAndState { result, state } = evm.transact().map_err(move |err| { + let new_err = match err { + EVMError::Transaction(e) => EVMError::Transaction(e), + EVMError::Header(e) => EVMError::Header(e), + EVMError::Database(e) => EVMError::Database(e.into()), + EVMError::Custom(e) => EVMError::Custom(e), + EVMError::Precompile(e) => EVMError::Precompile(e), + }; // Ensure hash is calculated for error log, if not already done BlockValidationError::EVM { hash: transaction.recalculate_hash(), - error: err.into(), + error: Box::new(new_err), } })?; evm.db_mut().commit(state); @@ -260,7 +268,7 @@ impl EthBlockExecutor { impl EthBlockExecutor where EvmConfig: ConfigureEvm, - DB: Database, + DB: Database + Display>, { /// Configures a new evm configuration and block environment for the given block. /// @@ -358,7 +366,7 @@ where impl Executor for EthBlockExecutor where EvmConfig: ConfigureEvm, - DB: Database, + DB: Database + std::fmt::Display>, { type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; type Output = BlockExecutionOutput; @@ -408,7 +416,7 @@ impl EthBatchExecutor { impl BatchExecutor for EthBatchExecutor where EvmConfig: ConfigureEvm, - DB: Database, + DB: Database + Display>, { type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; type Output = ExecutionOutcome; diff --git a/crates/evm/src/either.rs b/crates/evm/src/either.rs index 2c8edfd29265..2f55f1668923 100644 --- a/crates/evm/src/either.rs +++ b/crates/evm/src/either.rs @@ -1,5 +1,7 @@ //! Helper type that represents one of two possible executor types +use std::fmt::Display; + use crate::execute::{ BatchExecutor, BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, Executor, }; @@ -18,13 +20,15 @@ where A: BlockExecutorProvider, B: BlockExecutorProvider, { - type Executor> = Either, B::Executor>; - type BatchExecutor> = + type Executor + Display>> = + Either, B::Executor>; + + type BatchExecutor + Display>> = Either, B::BatchExecutor>; fn executor(&self, db: DB) -> Self::Executor where - DB: Database, + DB: Database + Display>, { match self { Self::Left(a) => Either::Left(a.executor(db)), @@ -34,7 +38,7 @@ where fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor where - DB: Database, + DB: Database + Display>, { match self { Self::Left(a) => Either::Left(a.batch_executor(db, prune_modes)), @@ -57,7 +61,7 @@ where Output = BlockExecutionOutput, Error = BlockExecutionError, >, - DB: Database, + DB: Database + Display>, { type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; type Output = BlockExecutionOutput; @@ -85,7 +89,7 @@ where Output = ExecutionOutcome, Error = BlockExecutionError, >, - DB: Database, + DB: Database + Display>, { type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; type Output = ExecutionOutcome; diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index ea8b7abb0779..6d076fd45303 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -5,6 +5,7 @@ use reth_primitives::{BlockNumber, BlockWithSenders, Receipt, Request, U256}; use reth_prune_types::PruneModes; use revm::db::BundleState; use revm_primitives::db::Database; +use std::fmt::Display; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -142,7 +143,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { /// /// It is not expected to validate the state trie root, this must be done by the caller using /// the returned state. - type Executor>: for<'a> Executor< + type Executor + Display>>: for<'a> Executor< DB, Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, Output = BlockExecutionOutput, @@ -150,7 +151,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { >; /// An executor that can execute a batch of blocks given a database. - type BatchExecutor>: for<'a> BatchExecutor< + type BatchExecutor + Display>>: for<'a> BatchExecutor< DB, Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, Output = ExecutionOutcome, @@ -162,7 +163,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { /// This is used to execute a single block and get the changed state. fn executor(&self, db: DB) -> Self::Executor where - DB: Database; + DB: Database + Display>; /// Creates a new batch executor with the given database and pruning modes. /// @@ -173,7 +174,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { /// execution. fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor where - DB: Database; + DB: Database + Display>; } #[cfg(test)] @@ -187,19 +188,19 @@ mod tests { struct TestExecutorProvider; impl BlockExecutorProvider for TestExecutorProvider { - type Executor> = TestExecutor; - type BatchExecutor> = TestExecutor; + type Executor + Display>> = TestExecutor; + type BatchExecutor + Display>> = TestExecutor; fn executor(&self, _db: DB) -> Self::Executor where - DB: Database, + DB: Database + Display>, { TestExecutor(PhantomData) } fn batch_executor(&self, _db: DB, _prune_modes: PruneModes) -> Self::BatchExecutor where - DB: Database, + DB: Database + Display>, { TestExecutor(PhantomData) } diff --git a/crates/evm/src/noop.rs b/crates/evm/src/noop.rs index fdee35239369..d393f66d566d 100644 --- a/crates/evm/src/noop.rs +++ b/crates/evm/src/noop.rs @@ -1,5 +1,7 @@ //! A no operation block executor implementation. +use std::fmt::Display; + use reth_execution_errors::BlockExecutionError; use reth_execution_types::ExecutionOutcome; use reth_primitives::{BlockNumber, BlockWithSenders, Receipt}; @@ -19,20 +21,20 @@ const UNAVAILABLE_FOR_NOOP: &str = "execution unavailable for noop"; pub struct NoopBlockExecutorProvider; impl BlockExecutorProvider for NoopBlockExecutorProvider { - type Executor> = Self; + type Executor + Display>> = Self; - type BatchExecutor> = Self; + type BatchExecutor + Display>> = Self; fn executor(&self, _: DB) -> Self::Executor where - DB: Database, + DB: Database + Display>, { Self } fn batch_executor(&self, _: DB, _: PruneModes) -> Self::BatchExecutor where - DB: Database, + DB: Database + Display>, { Self } diff --git a/crates/evm/src/test_utils.rs b/crates/evm/src/test_utils.rs index c9627933a7fc..a4d098f0b3aa 100644 --- a/crates/evm/src/test_utils.rs +++ b/crates/evm/src/test_utils.rs @@ -10,7 +10,7 @@ use reth_primitives::{BlockNumber, BlockWithSenders, Receipt}; use reth_prune_types::PruneModes; use reth_storage_errors::provider::ProviderError; use revm_primitives::db::Database; -use std::sync::Arc; +use std::{fmt::Display, sync::Arc}; /// A [`BlockExecutorProvider`] that returns mocked execution results. #[derive(Clone, Debug, Default)] @@ -26,20 +26,20 @@ impl MockExecutorProvider { } impl BlockExecutorProvider for MockExecutorProvider { - type Executor> = Self; + type Executor + Display>> = Self; - type BatchExecutor> = Self; + type BatchExecutor + Display>> = Self; fn executor(&self, _: DB) -> Self::Executor where - DB: Database, + DB: Database + Display>, { self.clone() } fn batch_executor(&self, _: DB, _: PruneModes) -> Self::BatchExecutor where - DB: Database, + DB: Database + Display>, { self.clone() } diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index e6130295f455..12edb225fe43 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -23,7 +23,7 @@ use reth_revm::{ }; use revm_primitives::{ db::{Database, DatabaseCommit}, - BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState, + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ResultAndState, }; use std::sync::Arc; use tracing::trace; @@ -55,7 +55,7 @@ where { fn op_executor(&self, db: DB) -> OpBlockExecutor where - DB: Database, + DB: Database + std::fmt::Display>, { OpBlockExecutor::new( self.chain_spec.clone(), @@ -69,19 +69,21 @@ impl BlockExecutorProvider for OpExecutorProvider where EvmConfig: ConfigureEvm, { - type Executor> = OpBlockExecutor; + type Executor + std::fmt::Display>> = + OpBlockExecutor; - type BatchExecutor> = OpBatchExecutor; + type BatchExecutor + std::fmt::Display>> = + OpBatchExecutor; fn executor(&self, db: DB) -> Self::Executor where - DB: Database, + DB: Database + std::fmt::Display>, { self.op_executor(db) } fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor where - DB: Database, + DB: Database + std::fmt::Display>, { let executor = self.op_executor(db); OpBatchExecutor { @@ -118,7 +120,7 @@ where mut evm: Evm<'_, Ext, &mut State>, ) -> Result<(Vec, u64), BlockExecutionError> where - DB: Database, + DB: Database + std::fmt::Display>, { // apply pre execution changes apply_beacon_root_contract_call( @@ -179,10 +181,17 @@ where // Execute transaction. let ResultAndState { result, state } = evm.transact().map_err(move |err| { + let new_err = match err { + EVMError::Transaction(e) => EVMError::Transaction(e), + EVMError::Header(e) => EVMError::Header(e), + EVMError::Database(e) => EVMError::Database(e.into()), + EVMError::Custom(e) => EVMError::Custom(e), + EVMError::Precompile(e) => EVMError::Precompile(e), + }; // Ensure hash is calculated for error log, if not already done BlockValidationError::EVM { hash: transaction.recalculate_hash(), - error: err.into(), + error: Box::new(new_err), } })?; @@ -255,7 +264,7 @@ impl OpBlockExecutor { impl OpBlockExecutor where EvmConfig: ConfigureEvm, - DB: Database, + DB: Database + std::fmt::Display>, { /// Configures a new evm configuration and block environment for the given block. /// @@ -337,7 +346,7 @@ where impl Executor for OpBlockExecutor where EvmConfig: ConfigureEvm, - DB: Database, + DB: Database + std::fmt::Display>, { type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; type Output = BlockExecutionOutput; @@ -394,7 +403,7 @@ impl OpBatchExecutor { impl BatchExecutor for OpBatchExecutor where EvmConfig: ConfigureEvm, - DB: Database, + DB: Database + std::fmt::Display>, { type Input<'a> = BlockExecutionInput<'a, BlockWithSenders>; type Output = ExecutionOutcome; diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index b9fba6b2578e..4505d2ee9761 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -86,7 +86,7 @@ pub fn post_block_balance_increments( /// /// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935 #[inline] -pub fn apply_blockhashes_update + DatabaseCommit>( +pub fn apply_blockhashes_update> + DatabaseCommit>( db: &mut DB, chain_spec: &ChainSpec, block_timestamp: u64, @@ -108,7 +108,7 @@ where // nonce of 1, so it does not get deleted. let mut account: Account = db .basic(HISTORY_STORAGE_ADDRESS) - .map_err(BlockValidationError::BlockHashAccountLoadingFailed)? + .map_err(|err| BlockValidationError::BlockHashAccountLoadingFailed(err.into()))? .unwrap_or_else(|| AccountInfo { nonce: 1, code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())), @@ -132,7 +132,7 @@ where /// /// This calculates the correct storage slot in the `BLOCKHASH` history storage address, fetches the /// blockhash and creates a [`EvmStorageSlot`] with appropriate previous and new values. -fn eip2935_block_hash_slot>( +fn eip2935_block_hash_slot>>( db: &mut DB, block_number: u64, block_hash: B256, @@ -140,7 +140,7 @@ fn eip2935_block_hash_slot>( let slot = U256::from(block_number % BLOCKHASH_SERVE_WINDOW as u64); let current_hash = db .storage(HISTORY_STORAGE_ADDRESS, slot) - .map_err(BlockValidationError::BlockHashAccountLoadingFailed)?; + .map_err(|err| BlockValidationError::BlockHashAccountLoadingFailed(err.into()))?; Ok((slot, EvmStorageSlot::new_changed(current_hash, block_hash.into()))) } From 23d8e4e6f6871037c844558b970be2027f792b9d Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Tue, 25 Jun 2024 14:30:31 +0300 Subject: [PATCH 203/405] readme: rm wip note --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index bfb1c6c65f1b..3b845c951b35 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ | [Developer Docs](./docs) | [Crate Docs](https://reth.rs/docs) -_The project is still work in progress, see the [disclaimer below](#status)._ - [gh-ci]: https://github.com/paradigmxyz/reth/actions/workflows/unit.yml [gh-deny]: https://github.com/paradigmxyz/reth/actions/workflows/deny.yml [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fparadigm%5Freth From 6699c6a3d715b099ecc27e69c55ac7ab115ee6cc Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 25 Jun 2024 12:21:01 +0100 Subject: [PATCH 204/405] feat(examples): remove Remote ExEx (#9085) --- .github/workflows/integration.yml | 2 +- .github/workflows/lint.yml | 5 - .github/workflows/unit.yml | 5 +- Cargo.lock | 301 +------- examples/README.md | 1 - examples/exex/remote/Cargo.toml | 42 -- examples/exex/remote/bin/consumer.rs | 32 - examples/exex/remote/bin/exex.rs | 77 --- examples/exex/remote/build.rs | 4 - examples/exex/remote/proto/exex.proto | 279 -------- examples/exex/remote/src/codec.rs | 961 -------------------------- examples/exex/remote/src/lib.rs | 4 - 12 files changed, 19 insertions(+), 1694 deletions(-) delete mode 100644 examples/exex/remote/Cargo.toml delete mode 100644 examples/exex/remote/bin/consumer.rs delete mode 100644 examples/exex/remote/bin/exex.rs delete mode 100644 examples/exex/remote/build.rs delete mode 100644 examples/exex/remote/proto/exex.proto delete mode 100644 examples/exex/remote/src/codec.rs delete mode 100644 examples/exex/remote/src/lib.rs diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index a36cb1f194ef..b4b494a90256 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -41,7 +41,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude example-exex-remote --exclude ef-tests \ + --workspace --exclude ef-tests \ -E "kind(test)" - if: matrix.network == 'optimism' name: Run tests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0e73aafefc60..c07cee38830b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -41,9 +41,6 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked env: RUSTFLAGS: -D warnings @@ -73,7 +70,6 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - uses: arduino/setup-protoc@v3 - run: cargo hack check msrv: @@ -109,7 +105,6 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - uses: arduino/setup-protoc@v3 - run: cargo docs --document-private-items env: # Keep in sync with ./book.yml:jobs.build diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 46b25b370b39..a6663aea8843 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -39,7 +39,7 @@ jobs: run: | cargo nextest run \ --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude example-exex-remote --exclude ef-tests \ + --workspace --exclude ef-tests \ --partition hash:${{ matrix.partition }}/2 \ -E "!kind(test)" @@ -84,9 +84,6 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Run doctests run: cargo test --doc --workspace --features "${{ matrix.network }}" diff --git a/Cargo.lock b/Cargo.lock index 9661d075a516..89f2eecd0666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1064,51 +1064,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "axum" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "backon" version = "0.4.4" @@ -2921,25 +2876,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "example-exex-remote" -version = "0.0.0" -dependencies = [ - "bincode", - "eyre", - "prost", - "reth", - "reth-exex", - "reth-exex-test-utils", - "reth-node-api", - "reth-node-ethereum", - "reth-tracing", - "tokio", - "tokio-stream", - "tonic", - "tonic-build", -] - [[package]] name = "example-exex-rollup" version = "0.0.0" @@ -3193,12 +3129,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "flate2" version = "1.0.30" @@ -3487,25 +3417,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.2.6", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.5" @@ -3706,17 +3617,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -3736,7 +3636,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "pin-project-lite", ] @@ -3800,30 +3700,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hyper" -version = "0.14.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.3.1" @@ -3833,9 +3709,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2", "http 1.1.0", - "http-body 1.0.0", + "http-body", "httparse", "httpdate", "itoa", @@ -3853,7 +3729,7 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper", "hyper-util", "log", "rustls", @@ -3865,18 +3741,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.29", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - [[package]] name = "hyper-util" version = "0.1.5" @@ -3887,8 +3751,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body", + "hyper", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -4423,7 +4287,7 @@ dependencies = [ "futures-timer", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", "jsonrpsee-types", "parking_lot 0.12.3", @@ -4447,8 +4311,8 @@ checksum = "fb25cab482c8512c4f3323a5c90b95a3b8f7c90681a87bf7a68b942d52f08933" dependencies = [ "async-trait", "base64 0.22.1", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body", + "hyper", "hyper-rustls", "hyper-util", "jsonrpsee-core", @@ -4486,9 +4350,9 @@ dependencies = [ "anyhow", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -4915,12 +4779,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "memchr" version = "2.7.4" @@ -5160,12 +5018,6 @@ dependencies = [ "unsigned-varint 0.7.2", ] -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - [[package]] name = "multistream-select" version = "0.13.0" @@ -5574,16 +5426,6 @@ dependencies = [ "ucd-trie", ] -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap 2.2.6", -] - [[package]] name = "ph" version = "0.8.3" @@ -5970,59 +5812,6 @@ dependencies = [ "syn 2.0.67", ] -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" -dependencies = [ - "bytes", - "heck 0.5.0", - "itertools 0.12.1", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn 2.0.67", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.67", -] - -[[package]] -name = "prost-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" -dependencies = [ - "prost", -] - [[package]] name = "quanta" version = "0.12.3" @@ -6363,9 +6152,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-rustls", "hyper-util", "ipnet", @@ -6383,7 +6172,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper", "tokio", "tokio-rustls", "tokio-util", @@ -8098,8 +7887,8 @@ dependencies = [ "dyn-clone", "futures", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body", + "hyper", "jsonrpsee", "jsonwebtoken", "metrics", @@ -9697,12 +9486,6 @@ dependencies = [ "syn 2.0.67", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.1" @@ -10011,16 +9794,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.3.0" @@ -10131,46 +9904,6 @@ dependencies = [ "winnow 0.6.13", ] -[[package]] -name = "tonic" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.21.7", - "bytes", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-build" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "quote", - "syn 2.0.67", -] - [[package]] name = "tower" version = "0.4.13" @@ -10205,7 +9938,7 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body", "http-body-util", "http-range-header", "httpdate", diff --git a/examples/README.md b/examples/README.md index ac9afb8fe583..423d9224be67 100644 --- a/examples/README.md +++ b/examples/README.md @@ -29,7 +29,6 @@ to make a PR! | [In Memory State](./exex/in-memory-state) | Illustrates an ExEx that tracks the plain state in memory | | [Minimal](./exex/minimal) | Illustrates how to build a simple ExEx | | [OP Bridge](./exex/op-bridge) | Illustrates an ExEx that decodes Optimism deposit and withdrawal receipts from L1 | -| [Remote](./exex/remote) | Illustrates an ExEx that emits notifications using a gRPC server, and a consumer that receives them | | [Rollup](./exex/rollup) | Illustrates a rollup ExEx that derives the state from L1 | ## RPC diff --git a/examples/exex/remote/Cargo.toml b/examples/exex/remote/Cargo.toml deleted file mode 100644 index 634b9a7fef7e..000000000000 --- a/examples/exex/remote/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "example-exex-remote" -version = "0.0.0" -publish = false -edition.workspace = true -license.workspace = true - -[dependencies] -reth.workspace = true -reth-exex = { workspace = true, features = ["serde"] } -reth-node-api.workspace = true -reth-node-ethereum.workspace = true -reth-tracing.workspace = true - -eyre.workspace = true - -tonic = "0.11" -prost = "0.12" -tokio = { version = "1.0", features = ["full"] } -tokio-stream = "0.1" - -bincode = "1.3" - -[build-dependencies] -tonic-build = "0.11" - -[dev-dependencies] -reth-exex-test-utils.workspace = true - -tokio.workspace = true - -[features] -default = [] -optimism = ["reth/optimism"] - -[[bin]] -name = "exex" -path = "bin/exex.rs" - -[[bin]] -name = "consumer" -path = "bin/consumer.rs" diff --git a/examples/exex/remote/bin/consumer.rs b/examples/exex/remote/bin/consumer.rs deleted file mode 100644 index 71d4ebbe6ed6..000000000000 --- a/examples/exex/remote/bin/consumer.rs +++ /dev/null @@ -1,32 +0,0 @@ -use example_exex_remote::proto::{remote_ex_ex_client::RemoteExExClient, SubscribeRequest}; -use reth_exex::ExExNotification; -use reth_tracing::{tracing::info, RethTracer, Tracer}; - -#[tokio::main] -async fn main() -> eyre::Result<()> { - let _ = RethTracer::new().init()?; - - let mut client = RemoteExExClient::connect("http://[::1]:10000") - .await? - .max_encoding_message_size(usize::MAX) - .max_decoding_message_size(usize::MAX); - - let mut stream = client.subscribe(SubscribeRequest {}).await?.into_inner(); - while let Some(notification) = stream.message().await? { - let notification = ExExNotification::try_from(¬ification)?; - - match notification { - ExExNotification::ChainCommitted { new } => { - info!(committed_chain = ?new.range(), "Received commit"); - } - ExExNotification::ChainReorged { old, new } => { - info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg"); - } - ExExNotification::ChainReverted { old } => { - info!(reverted_chain = ?old.range(), "Received revert"); - } - }; - } - - Ok(()) -} diff --git a/examples/exex/remote/bin/exex.rs b/examples/exex/remote/bin/exex.rs deleted file mode 100644 index ed1e5ec1e8c4..000000000000 --- a/examples/exex/remote/bin/exex.rs +++ /dev/null @@ -1,77 +0,0 @@ -use example_exex_remote::proto::{ - remote_ex_ex_server::{RemoteExEx, RemoteExExServer}, - ExExNotification as ProtoExExNotification, SubscribeRequest as ProtoSubscribeRequest, -}; -use reth_exex::{ExExContext, ExExEvent, ExExNotification}; -use reth_node_api::FullNodeComponents; -use reth_node_ethereum::EthereumNode; -use tokio::sync::{broadcast, mpsc}; -use tokio_stream::wrappers::ReceiverStream; -use tonic::{transport::Server, Request, Response, Status}; - -#[derive(Debug)] -struct ExExService { - notifications: broadcast::Sender, -} - -#[tonic::async_trait] -impl RemoteExEx for ExExService { - type SubscribeStream = ReceiverStream>; - - async fn subscribe( - &self, - _request: Request, - ) -> Result, Status> { - let (tx, rx) = mpsc::channel(1); - - let mut notifications = self.notifications.subscribe(); - tokio::spawn(async move { - while let Ok(notification) = notifications.recv().await { - tx.send(Ok((¬ification).try_into().expect("failed to encode"))) - .await - .expect("failed to send notification to client"); - } - }); - - Ok(Response::new(ReceiverStream::new(rx))) - } -} - -async fn exex( - mut ctx: ExExContext, - notifications: broadcast::Sender, -) -> eyre::Result<()> { - while let Some(notification) = ctx.notifications.recv().await { - if let Some(committed_chain) = notification.committed_chain() { - ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; - } - - let _ = notifications.send(notification); - } - - Ok(()) -} - -fn main() -> eyre::Result<()> { - reth::cli::Cli::parse_args().run(|builder, _| async move { - let notifications = broadcast::channel(1).0; - - let server = Server::builder() - .add_service(RemoteExExServer::new(ExExService { - notifications: notifications.clone(), - })) - .serve("[::1]:10000".parse().unwrap()); - - let handle = builder - .node(EthereumNode::default()) - .install_exex("Remote", |ctx| async move { Ok(exex(ctx, notifications)) }) - .launch() - .await?; - - handle.node.task_executor.spawn_critical("gRPC server", async move { - server.await.expect("gRPC server crashed") - }); - - handle.wait_for_node_exit().await - }) -} diff --git a/examples/exex/remote/build.rs b/examples/exex/remote/build.rs deleted file mode 100644 index 9e70bbe9b31f..000000000000 --- a/examples/exex/remote/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - tonic_build::compile_protos("proto/exex.proto") - .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); -} diff --git a/examples/exex/remote/proto/exex.proto b/examples/exex/remote/proto/exex.proto deleted file mode 100644 index 17620b6802ab..000000000000 --- a/examples/exex/remote/proto/exex.proto +++ /dev/null @@ -1,279 +0,0 @@ -syntax = "proto3"; - -package exex; - -import "google/protobuf/empty.proto"; - -service RemoteExEx { - rpc Subscribe(SubscribeRequest) returns (stream ExExNotification) {} -} - -message SubscribeRequest {} - -message ExExNotification { - oneof notification { - ChainCommitted chain_committed = 1; - ChainReorged chain_reorged = 2; - ChainReverted chain_reverted = 3; - } -} - -message ChainCommitted { - Chain new = 1; -} - -message ChainReorged { - Chain old = 1; - Chain new = 2; -} - -message ChainReverted { - Chain old = 1; -} - -message Chain { - repeated Block blocks = 1; - ExecutionOutcome execution_outcome = 2; -} - -message Block { - SealedHeader header = 1; - repeated Transaction body = 2; - repeated Header ommers = 3; - repeated bytes senders = 4; - // TODO: add withdrawals and requests -} - -message SealedHeader { - bytes hash = 1; - Header header = 2; -} - -message Header { - bytes parent_hash = 1; - bytes ommers_hash = 2; - bytes beneficiary = 3; - bytes state_root = 4; - bytes transactions_root = 5; - bytes receipts_root = 6; - optional bytes withdrawals_root = 7; - bytes logs_bloom = 8; - bytes difficulty = 9; - uint64 number = 10; - uint64 gas_limit = 11; - uint64 gas_used = 12; - uint64 timestamp = 13; - bytes mix_hash = 14; - uint64 nonce = 15; - optional uint64 base_fee_per_gas = 16; - optional uint64 blob_gas_used = 17; - optional uint64 excess_blob_gas = 18; - optional bytes parent_beacon_block_root = 19; - // TODO: add requests_root - bytes extra_data = 20; -} - -message Transaction { - bytes hash = 1; - Signature signature = 2; - oneof transaction { - TransactionLegacy legacy = 3; - TransactionEip2930 eip2930 = 4; - TransactionEip1559 eip1559 = 5; - TransactionEip4844 eip4844 = 6; - } -} - -message Signature { - bytes r = 1; - bytes s = 2; - bool odd_y_parity = 3; -} - -message TransactionLegacy { - optional uint64 chain_id = 1; - uint64 nonce = 2; - bytes gas_price = 3; - uint64 gas_limit = 4; - TxKind to = 5; - bytes value = 6; - bytes input = 7; -} - -message TransactionEip2930 { - uint64 chain_id = 1; - uint64 nonce = 2; - bytes gas_price = 3; - uint64 gas_limit = 4; - TxKind to = 5; - bytes value = 6; - repeated AccessListItem access_list = 7; - bytes input = 8; -} - -message TransactionEip1559 { - uint64 chain_id = 1; - uint64 nonce = 2; - uint64 gas_limit = 3; - bytes max_fee_per_gas = 4; - bytes max_priority_fee_per_gas = 5; - TxKind to = 6; - bytes value = 7; - repeated AccessListItem access_list = 8; - bytes input = 9; -} - -message TransactionEip4844 { - uint64 chain_id = 1; - uint64 nonce = 2; - uint64 gas_limit = 3; - bytes max_fee_per_gas = 4; - bytes max_priority_fee_per_gas = 5; - bytes to = 6; - bytes value = 7; - repeated AccessListItem access_list = 8; - repeated bytes blob_versioned_hashes = 9; - bytes max_fee_per_blob_gas = 10; - bytes input = 11; -} - -message TxKind { - oneof kind { - google.protobuf.Empty create = 1; - bytes call = 2; - } -} - -message AccessListItem { - bytes address = 1; - repeated bytes storage_keys = 2; -} - -message ExecutionOutcome { - BundleState bundle = 1; - repeated BlockReceipts receipts = 2; - uint64 first_block = 3; - // TODO: add requests -} - -message BundleState { - repeated BundleAccount state = 1; - repeated ContractBytecode contracts = 2; - repeated BlockReverts reverts = 3; - uint64 state_size = 4; - uint64 reverts_size = 5; -} - -message BundleAccount { - bytes address = 1; - AccountInfo info = 2; - AccountInfo original_info = 3; - repeated StorageSlot storage = 4; - AccountStatus status = 5; -} - -message AccountInfo { - bytes balance = 1; - uint64 nonce = 2; - bytes code_hash = 3; - Bytecode code = 4; -} - -message StorageSlot { - bytes key = 1; - bytes previous_or_original_value = 2; - bytes present_value = 3; -} - -enum AccountStatus { - LOADED_NOT_EXISTING = 0; - LOADED = 1; - LOADED_EMPTY_EIP161 = 2; - IN_MEMORY_CHANGE = 3; - CHANGED = 4; - DESTROYED = 5; - DESTROYED_CHANGED = 6; - DESTROYED_AGAIN = 7; -} - -message ContractBytecode { - bytes hash = 1; - Bytecode bytecode = 2; -} - -message Bytecode { - oneof bytecode { - bytes legacy_raw = 1; - LegacyAnalyzedBytecode legacy_analyzed = 2; - // TODO: add EOF - } -} - -message LegacyAnalyzedBytecode { - bytes bytecode = 1; - uint64 original_len = 2; - repeated uint32 jump_table = 3; -} - -message BlockReverts { - repeated Revert reverts = 1; -} - -message Revert { - bytes address = 1; - AccountInfoRevert account = 2; - repeated RevertToSlot storage = 3; - AccountStatus previous_status = 4; - bool wipe_storage = 5; -} - -message AccountInfoRevert { - oneof revert { - google.protobuf.Empty do_nothing = 1; - google.protobuf.Empty delete_it = 2; - AccountInfo revert_to = 3; - } -} - -message RevertToSlot { - bytes key = 1; - oneof revert { - bytes some = 2; - google.protobuf.Empty destroyed = 3; - } -} - -message BlockReceipts { - repeated Receipt receipts = 1; -} - -message Receipt { - oneof receipt { - google.protobuf.Empty empty = 1; - NonEmptyReceipt non_empty = 2; - } -} - -message NonEmptyReceipt { - TxType tx_type = 1; - bool success = 2; - uint64 cumulative_gas_used = 3; - repeated Log logs = 4; -} - -enum TxType { - LEGACY = 0; - EIP2930 = 1; - EIP1559 = 2; - EIP4844 = 3; -} - -message Log { - bytes address = 1; - LogData data = 2; -} - -message LogData { - repeated bytes topics = 1; - bytes data = 2; -} diff --git a/examples/exex/remote/src/codec.rs b/examples/exex/remote/src/codec.rs deleted file mode 100644 index d36b60b92a5c..000000000000 --- a/examples/exex/remote/src/codec.rs +++ /dev/null @@ -1,961 +0,0 @@ -use std::sync::Arc; - -use eyre::OptionExt; -use reth::primitives::{Address, BlockHash, Bloom, TxHash, B256, U256}; - -use crate::proto; - -impl TryFrom<&reth_exex::ExExNotification> for proto::ExExNotification { - type Error = eyre::Error; - - fn try_from(notification: &reth_exex::ExExNotification) -> Result { - let notification = match notification { - reth_exex::ExExNotification::ChainCommitted { new } => { - proto::ex_ex_notification::Notification::ChainCommitted(proto::ChainCommitted { - new: Some(new.as_ref().try_into()?), - }) - } - reth_exex::ExExNotification::ChainReorged { old, new } => { - proto::ex_ex_notification::Notification::ChainReorged(proto::ChainReorged { - old: Some(old.as_ref().try_into()?), - new: Some(new.as_ref().try_into()?), - }) - } - reth_exex::ExExNotification::ChainReverted { old } => { - proto::ex_ex_notification::Notification::ChainReverted(proto::ChainReverted { - old: Some(old.as_ref().try_into()?), - }) - } - }; - - Ok(proto::ExExNotification { notification: Some(notification) }) - } -} - -impl TryFrom<&reth::providers::Chain> for proto::Chain { - type Error = eyre::Error; - - fn try_from(chain: &reth::providers::Chain) -> Result { - let bundle_state = chain.execution_outcome().state(); - Ok(proto::Chain { - blocks: chain - .blocks_iter() - .map(|block| { - Ok(proto::Block { - header: Some(proto::SealedHeader { - hash: block.header.hash().to_vec(), - header: Some(block.header.header().into()), - }), - body: block - .transactions() - .map(TryInto::try_into) - .collect::>()?, - ommers: block.ommers.iter().map(Into::into).collect(), - senders: block.senders.iter().map(|sender| sender.to_vec()).collect(), - }) - }) - .collect::>()?, - execution_outcome: Some(proto::ExecutionOutcome { - bundle: Some(proto::BundleState { - state: bundle_state - .state - .iter() - .map(|(address, account)| (*address, account).try_into()) - .collect::>()?, - contracts: bundle_state - .contracts - .iter() - .map(|(hash, bytecode)| { - Ok(proto::ContractBytecode { - hash: hash.to_vec(), - bytecode: Some(bytecode.try_into()?), - }) - }) - .collect::>()?, - reverts: bundle_state - .reverts - .iter() - .map(|block_reverts| { - Ok(proto::BlockReverts { - reverts: block_reverts - .iter() - .map(|(address, revert)| (*address, revert).try_into()) - .collect::>()?, - }) - }) - .collect::>()?, - state_size: bundle_state.state_size as u64, - reverts_size: bundle_state.reverts_size as u64, - }), - receipts: chain - .execution_outcome() - .receipts() - .iter() - .map(|block_receipts| { - Ok(proto::BlockReceipts { - receipts: block_receipts - .iter() - .map(TryInto::try_into) - .collect::>()?, - }) - }) - .collect::>()?, - first_block: chain.execution_outcome().first_block, - }), - }) - } -} - -impl From<&reth::primitives::Header> for proto::Header { - fn from(header: &reth::primitives::Header) -> Self { - proto::Header { - parent_hash: header.parent_hash.to_vec(), - ommers_hash: header.ommers_hash.to_vec(), - beneficiary: header.beneficiary.to_vec(), - state_root: header.state_root.to_vec(), - transactions_root: header.transactions_root.to_vec(), - receipts_root: header.receipts_root.to_vec(), - withdrawals_root: header.withdrawals_root.map(|root| root.to_vec()), - logs_bloom: header.logs_bloom.to_vec(), - difficulty: header.difficulty.to_le_bytes_vec(), - number: header.number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - mix_hash: header.mix_hash.to_vec(), - nonce: header.nonce, - base_fee_per_gas: header.base_fee_per_gas, - blob_gas_used: header.blob_gas_used, - excess_blob_gas: header.excess_blob_gas, - parent_beacon_block_root: header.parent_beacon_block_root.map(|root| root.to_vec()), - extra_data: header.extra_data.to_vec(), - } - } -} - -impl TryFrom<&reth::primitives::TransactionSigned> for proto::Transaction { - type Error = eyre::Error; - - fn try_from(transaction: &reth::primitives::TransactionSigned) -> Result { - let hash = transaction.hash().to_vec(); - let signature = proto::Signature { - r: transaction.signature.r.to_le_bytes_vec(), - s: transaction.signature.s.to_le_bytes_vec(), - odd_y_parity: transaction.signature.odd_y_parity, - }; - let transaction = match &transaction.transaction { - reth::primitives::Transaction::Legacy(reth::primitives::TxLegacy { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - input, - }) => proto::transaction::Transaction::Legacy(proto::TransactionLegacy { - chain_id: *chain_id, - nonce: *nonce, - gas_price: gas_price.to_le_bytes().to_vec(), - gas_limit: *gas_limit, - to: Some(to.into()), - value: value.to_le_bytes_vec(), - input: input.to_vec(), - }), - reth::primitives::Transaction::Eip2930(reth::primitives::TxEip2930 { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - access_list, - input, - }) => proto::transaction::Transaction::Eip2930(proto::TransactionEip2930 { - chain_id: *chain_id, - nonce: *nonce, - gas_price: gas_price.to_le_bytes().to_vec(), - gas_limit: *gas_limit, - to: Some(to.into()), - value: value.to_le_bytes_vec(), - access_list: access_list.iter().map(Into::into).collect(), - input: input.to_vec(), - }), - reth::primitives::Transaction::Eip1559(reth::primitives::TxEip1559 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - to, - value, - access_list, - input, - }) => proto::transaction::Transaction::Eip1559(proto::TransactionEip1559 { - chain_id: *chain_id, - nonce: *nonce, - gas_limit: *gas_limit, - max_fee_per_gas: max_fee_per_gas.to_le_bytes().to_vec(), - max_priority_fee_per_gas: max_priority_fee_per_gas.to_le_bytes().to_vec(), - to: Some(to.into()), - value: value.to_le_bytes_vec(), - access_list: access_list.iter().map(Into::into).collect(), - input: input.to_vec(), - }), - reth::primitives::Transaction::Eip4844(reth::primitives::TxEip4844 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - placeholder: _, - to, - value, - access_list, - blob_versioned_hashes, - max_fee_per_blob_gas, - input, - }) => proto::transaction::Transaction::Eip4844(proto::TransactionEip4844 { - chain_id: *chain_id, - nonce: *nonce, - gas_limit: *gas_limit, - max_fee_per_gas: max_fee_per_gas.to_le_bytes().to_vec(), - max_priority_fee_per_gas: max_priority_fee_per_gas.to_le_bytes().to_vec(), - to: to.to_vec(), - value: value.to_le_bytes_vec(), - access_list: access_list.iter().map(Into::into).collect(), - blob_versioned_hashes: blob_versioned_hashes - .iter() - .map(|hash| hash.to_vec()) - .collect(), - max_fee_per_blob_gas: max_fee_per_blob_gas.to_le_bytes().to_vec(), - input: input.to_vec(), - }), - #[cfg(feature = "optimism")] - reth::primitives::Transaction::Deposit(_) => { - eyre::bail!("deposit transaction not supported") - } - }; - - Ok(proto::Transaction { hash, signature: Some(signature), transaction: Some(transaction) }) - } -} - -impl From<&reth::primitives::TxKind> for proto::TxKind { - fn from(kind: &reth::primitives::TxKind) -> Self { - proto::TxKind { - kind: match kind { - reth::primitives::TxKind::Create => Some(proto::tx_kind::Kind::Create(())), - reth::primitives::TxKind::Call(address) => { - Some(proto::tx_kind::Kind::Call(address.to_vec())) - } - }, - } - } -} - -impl From<&reth::primitives::AccessListItem> for proto::AccessListItem { - fn from(item: &reth::primitives::AccessListItem) -> Self { - proto::AccessListItem { - address: item.address.to_vec(), - storage_keys: item.storage_keys.iter().map(|key| key.to_vec()).collect(), - } - } -} - -impl TryFrom<(Address, &reth::revm::db::BundleAccount)> for proto::BundleAccount { - type Error = eyre::Error; - - fn try_from( - (address, account): (Address, &reth::revm::db::BundleAccount), - ) -> Result { - Ok(proto::BundleAccount { - address: address.to_vec(), - info: account.info.as_ref().map(TryInto::try_into).transpose()?, - original_info: account.original_info.as_ref().map(TryInto::try_into).transpose()?, - storage: account - .storage - .iter() - .map(|(key, slot)| proto::StorageSlot { - key: key.to_le_bytes_vec(), - previous_or_original_value: slot.previous_or_original_value.to_le_bytes_vec(), - present_value: slot.present_value.to_le_bytes_vec(), - }) - .collect(), - status: proto::AccountStatus::from(account.status) as i32, - }) - } -} - -impl TryFrom<&reth::revm::primitives::AccountInfo> for proto::AccountInfo { - type Error = eyre::Error; - - fn try_from(account_info: &reth::revm::primitives::AccountInfo) -> Result { - Ok(proto::AccountInfo { - balance: account_info.balance.to_le_bytes_vec(), - nonce: account_info.nonce, - code_hash: account_info.code_hash.to_vec(), - code: account_info.code.as_ref().map(TryInto::try_into).transpose()?, - }) - } -} - -impl TryFrom<&reth::revm::primitives::Bytecode> for proto::Bytecode { - type Error = eyre::Error; - - fn try_from(bytecode: &reth::revm::primitives::Bytecode) -> Result { - let bytecode = match bytecode { - reth::revm::primitives::Bytecode::LegacyRaw(code) => { - proto::bytecode::Bytecode::LegacyRaw(code.to_vec()) - } - reth::revm::primitives::Bytecode::LegacyAnalyzed(legacy_analyzed) => { - proto::bytecode::Bytecode::LegacyAnalyzed(proto::LegacyAnalyzedBytecode { - bytecode: legacy_analyzed.bytecode().to_vec(), - original_len: legacy_analyzed.original_len() as u64, - jump_table: legacy_analyzed - .jump_table() - .0 - .iter() - .by_vals() - .map(|x| x.into()) - .collect(), - }) - } - reth::revm::primitives::Bytecode::Eof(_) => { - eyre::bail!("EOF bytecode not supported"); - } - }; - Ok(proto::Bytecode { bytecode: Some(bytecode) }) - } -} - -impl From for proto::AccountStatus { - fn from(status: reth::revm::db::AccountStatus) -> Self { - match status { - reth::revm::db::AccountStatus::LoadedNotExisting => { - proto::AccountStatus::LoadedNotExisting - } - reth::revm::db::AccountStatus::Loaded => proto::AccountStatus::Loaded, - reth::revm::db::AccountStatus::LoadedEmptyEIP161 => { - proto::AccountStatus::LoadedEmptyEip161 - } - reth::revm::db::AccountStatus::InMemoryChange => proto::AccountStatus::InMemoryChange, - reth::revm::db::AccountStatus::Changed => proto::AccountStatus::Changed, - reth::revm::db::AccountStatus::Destroyed => proto::AccountStatus::Destroyed, - reth::revm::db::AccountStatus::DestroyedChanged => { - proto::AccountStatus::DestroyedChanged - } - reth::revm::db::AccountStatus::DestroyedAgain => proto::AccountStatus::DestroyedAgain, - } - } -} - -impl TryFrom<(Address, &reth::revm::db::states::reverts::AccountRevert)> for proto::Revert { - type Error = eyre::Error; - - fn try_from( - (address, revert): (Address, &reth::revm::db::states::reverts::AccountRevert), - ) -> Result { - Ok(proto::Revert { - address: address.to_vec(), - account: Some(proto::AccountInfoRevert { - revert: Some(match &revert.account { - reth::revm::db::states::reverts::AccountInfoRevert::DoNothing => { - proto::account_info_revert::Revert::DoNothing(()) - } - reth::revm::db::states::reverts::AccountInfoRevert::DeleteIt => { - proto::account_info_revert::Revert::DeleteIt(()) - } - reth::revm::db::states::reverts::AccountInfoRevert::RevertTo(account_info) => { - proto::account_info_revert::Revert::RevertTo(account_info.try_into()?) - } - }), - }), - storage: revert - .storage - .iter() - .map(|(key, slot)| { - Ok(proto::RevertToSlot { - key: key.to_le_bytes_vec(), - revert: Some(match slot { - reth::revm::db::RevertToSlot::Some(value) => { - proto::revert_to_slot::Revert::Some(value.to_le_bytes_vec()) - } - reth::revm::db::RevertToSlot::Destroyed => { - proto::revert_to_slot::Revert::Destroyed(()) - } - }), - }) - }) - .collect::>()?, - previous_status: proto::AccountStatus::from(revert.previous_status) as i32, - wipe_storage: revert.wipe_storage, - }) - } -} - -impl TryFrom<&Option> for proto::Receipt { - type Error = eyre::Error; - - fn try_from(receipt: &Option) -> Result { - Ok(proto::Receipt { - receipt: Some( - receipt - .as_ref() - .map_or(eyre::Ok(proto::receipt::Receipt::Empty(())), |receipt| { - Ok(proto::receipt::Receipt::NonEmpty(receipt.try_into()?)) - })?, - ), - }) - } -} - -impl TryFrom<&reth::primitives::Receipt> for proto::NonEmptyReceipt { - type Error = eyre::Error; - - fn try_from(receipt: &reth::primitives::Receipt) -> Result { - Ok(proto::NonEmptyReceipt { - tx_type: match receipt.tx_type { - reth::primitives::TxType::Legacy => proto::TxType::Legacy, - reth::primitives::TxType::Eip2930 => proto::TxType::Eip2930, - reth::primitives::TxType::Eip1559 => proto::TxType::Eip1559, - reth::primitives::TxType::Eip4844 => proto::TxType::Eip4844, - #[cfg(feature = "optimism")] - reth::primitives::TxType::Deposit => { - eyre::bail!("deposit transaction not supported") - } - } as i32, - success: receipt.success, - cumulative_gas_used: receipt.cumulative_gas_used, - logs: receipt - .logs - .iter() - .map(|log| proto::Log { - address: log.address.to_vec(), - data: Some(proto::LogData { - topics: log.data.topics().iter().map(|topic| topic.to_vec()).collect(), - data: log.data.data.to_vec(), - }), - }) - .collect(), - }) - } -} - -impl TryFrom<&proto::ExExNotification> for reth_exex::ExExNotification { - type Error = eyre::Error; - - fn try_from(notification: &proto::ExExNotification) -> Result { - Ok(match notification.notification.as_ref().ok_or_eyre("no notification")? { - proto::ex_ex_notification::Notification::ChainCommitted(proto::ChainCommitted { - new, - }) => reth_exex::ExExNotification::ChainCommitted { - new: Arc::new(new.as_ref().ok_or_eyre("no new chain")?.try_into()?), - }, - proto::ex_ex_notification::Notification::ChainReorged(proto::ChainReorged { - old, - new, - }) => reth_exex::ExExNotification::ChainReorged { - old: Arc::new(old.as_ref().ok_or_eyre("no old chain")?.try_into()?), - new: Arc::new(new.as_ref().ok_or_eyre("no new chain")?.try_into()?), - }, - proto::ex_ex_notification::Notification::ChainReverted(proto::ChainReverted { - old, - }) => reth_exex::ExExNotification::ChainReverted { - old: Arc::new(old.as_ref().ok_or_eyre("no old chain")?.try_into()?), - }, - }) - } -} - -impl TryFrom<&proto::Chain> for reth::providers::Chain { - type Error = eyre::Error; - - fn try_from(chain: &proto::Chain) -> Result { - let execution_outcome = - chain.execution_outcome.as_ref().ok_or_eyre("no execution outcome")?; - let bundle = execution_outcome.bundle.as_ref().ok_or_eyre("no bundle")?; - Ok(reth::providers::Chain::new( - chain.blocks.iter().map(TryInto::try_into).collect::>>()?, - reth::providers::ExecutionOutcome { - bundle: reth::revm::db::BundleState { - state: bundle - .state - .iter() - .map(TryInto::try_into) - .collect::>()?, - contracts: bundle - .contracts - .iter() - .map(|contract| { - Ok(( - B256::try_from(contract.hash.as_slice())?, - contract.bytecode.as_ref().ok_or_eyre("no bytecode")?.try_into()?, - )) - }) - .collect::>()?, - reverts: reth::revm::db::states::reverts::Reverts::new( - bundle - .reverts - .iter() - .map(|block_reverts| { - block_reverts - .reverts - .iter() - .map(TryInto::try_into) - .collect::>() - }) - .collect::>()?, - ), - state_size: bundle.state_size as usize, - reverts_size: bundle.reverts_size as usize, - }, - receipts: reth::primitives::Receipts::from_iter( - execution_outcome - .receipts - .iter() - .map(|block_receipts| { - block_receipts - .receipts - .iter() - .map(TryInto::try_into) - .collect::>() - }) - .collect::>>()?, - ), - first_block: execution_outcome.first_block, - requests: Default::default(), - }, - None, - )) - } -} - -impl TryFrom<&proto::Block> for reth::primitives::SealedBlockWithSenders { - type Error = eyre::Error; - - fn try_from(block: &proto::Block) -> Result { - let sealed_header = block.header.as_ref().ok_or_eyre("no sealed header")?; - let header = sealed_header.header.as_ref().ok_or_eyre("no header")?.try_into()?; - let sealed_header = reth::primitives::SealedHeader::new( - header, - BlockHash::try_from(sealed_header.hash.as_slice())?, - ); - - let transactions = block.body.iter().map(TryInto::try_into).collect::>()?; - let ommers = block.ommers.iter().map(TryInto::try_into).collect::>()?; - let senders = block - .senders - .iter() - .map(|sender| Address::try_from(sender.as_slice())) - .collect::>()?; - - reth::primitives::SealedBlockWithSenders::new( - reth::primitives::SealedBlock::new( - sealed_header, - reth::primitives::BlockBody { - transactions, - ommers, - withdrawals: Default::default(), - requests: Default::default(), - }, - ), - senders, - ) - .ok_or_eyre("senders do not match transactions") - } -} - -impl TryFrom<&proto::Header> for reth::primitives::Header { - type Error = eyre::Error; - - fn try_from(header: &proto::Header) -> Result { - Ok(reth::primitives::Header { - parent_hash: B256::try_from(header.parent_hash.as_slice())?, - ommers_hash: B256::try_from(header.ommers_hash.as_slice())?, - beneficiary: Address::try_from(header.beneficiary.as_slice())?, - state_root: B256::try_from(header.state_root.as_slice())?, - transactions_root: B256::try_from(header.transactions_root.as_slice())?, - receipts_root: B256::try_from(header.receipts_root.as_slice())?, - withdrawals_root: header - .withdrawals_root - .as_ref() - .map(|root| B256::try_from(root.as_slice())) - .transpose()?, - logs_bloom: Bloom::try_from(header.logs_bloom.as_slice())?, - difficulty: U256::try_from_le_slice(&header.difficulty) - .ok_or_eyre("failed to parse difficulty")?, - number: header.number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - mix_hash: B256::try_from(header.mix_hash.as_slice())?, - nonce: header.nonce, - base_fee_per_gas: header.base_fee_per_gas, - blob_gas_used: header.blob_gas_used, - excess_blob_gas: header.excess_blob_gas, - parent_beacon_block_root: header - .parent_beacon_block_root - .as_ref() - .map(|root| B256::try_from(root.as_slice())) - .transpose()?, - requests_root: None, - extra_data: header.extra_data.as_slice().to_vec().into(), - }) - } -} - -impl TryFrom<&proto::Transaction> for reth::primitives::TransactionSigned { - type Error = eyre::Error; - - fn try_from(transaction: &proto::Transaction) -> Result { - let hash = TxHash::try_from(transaction.hash.as_slice())?; - let signature = transaction.signature.as_ref().ok_or_eyre("no signature")?; - let signature = reth::primitives::Signature { - r: U256::try_from_le_slice(signature.r.as_slice()).ok_or_eyre("failed to parse r")?, - s: U256::try_from_le_slice(signature.s.as_slice()).ok_or_eyre("failed to parse s")?, - odd_y_parity: signature.odd_y_parity, - }; - let transaction = match transaction.transaction.as_ref().ok_or_eyre("no transaction")? { - proto::transaction::Transaction::Legacy(proto::TransactionLegacy { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - input, - }) => reth::primitives::Transaction::Legacy(reth::primitives::TxLegacy { - chain_id: *chain_id, - nonce: *nonce, - gas_price: u128::from_le_bytes(gas_price.as_slice().try_into()?), - gas_limit: *gas_limit, - to: to.as_ref().ok_or_eyre("no to")?.try_into()?, - value: U256::try_from_le_slice(value.as_slice()) - .ok_or_eyre("failed to parse value")?, - input: input.to_vec().into(), - }), - proto::transaction::Transaction::Eip2930(proto::TransactionEip2930 { - chain_id, - nonce, - gas_price, - gas_limit, - to, - value, - access_list, - input, - }) => reth::primitives::Transaction::Eip2930(reth::primitives::TxEip2930 { - chain_id: *chain_id, - nonce: *nonce, - gas_price: u128::from_le_bytes(gas_price.as_slice().try_into()?), - gas_limit: *gas_limit, - to: to.as_ref().ok_or_eyre("no to")?.try_into()?, - value: U256::try_from_le_slice(value.as_slice()) - .ok_or_eyre("failed to parse value")?, - access_list: access_list - .iter() - .map(TryInto::try_into) - .collect::>>()? - .into(), - input: input.to_vec().into(), - }), - proto::transaction::Transaction::Eip1559(proto::TransactionEip1559 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - to, - value, - access_list, - input, - }) => reth::primitives::Transaction::Eip1559(reth::primitives::TxEip1559 { - chain_id: *chain_id, - nonce: *nonce, - gas_limit: *gas_limit, - max_fee_per_gas: u128::from_le_bytes(max_fee_per_gas.as_slice().try_into()?), - max_priority_fee_per_gas: u128::from_le_bytes( - max_priority_fee_per_gas.as_slice().try_into()?, - ), - to: to.as_ref().ok_or_eyre("no to")?.try_into()?, - value: U256::try_from_le_slice(value.as_slice()) - .ok_or_eyre("failed to parse value")?, - access_list: access_list - .iter() - .map(TryInto::try_into) - .collect::>>()? - .into(), - input: input.to_vec().into(), - }), - proto::transaction::Transaction::Eip4844(proto::TransactionEip4844 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas, - max_priority_fee_per_gas, - to, - value, - access_list, - blob_versioned_hashes, - max_fee_per_blob_gas, - input, - }) => reth::primitives::Transaction::Eip4844(reth::primitives::TxEip4844 { - chain_id: *chain_id, - nonce: *nonce, - gas_limit: *gas_limit, - max_fee_per_gas: u128::from_le_bytes(max_fee_per_gas.as_slice().try_into()?), - max_priority_fee_per_gas: u128::from_le_bytes( - max_priority_fee_per_gas.as_slice().try_into()?, - ), - placeholder: None, - to: Address::try_from(to.as_slice())?, - value: U256::try_from_le_slice(value.as_slice()) - .ok_or_eyre("failed to parse value")?, - access_list: access_list - .iter() - .map(TryInto::try_into) - .collect::>>()? - .into(), - blob_versioned_hashes: blob_versioned_hashes - .iter() - .map(|hash| B256::try_from(hash.as_slice())) - .collect::>()?, - max_fee_per_blob_gas: u128::from_le_bytes( - max_fee_per_blob_gas.as_slice().try_into()?, - ), - input: input.to_vec().into(), - }), - }; - - Ok(reth::primitives::TransactionSigned { hash, signature, transaction }) - } -} - -impl TryFrom<&proto::TxKind> for reth::primitives::TxKind { - type Error = eyre::Error; - - fn try_from(tx_kind: &proto::TxKind) -> Result { - Ok(match tx_kind.kind.as_ref().ok_or_eyre("no kind")? { - proto::tx_kind::Kind::Create(()) => reth::primitives::TxKind::Create, - proto::tx_kind::Kind::Call(address) => { - reth::primitives::TxKind::Call(Address::try_from(address.as_slice())?) - } - }) - } -} - -impl TryFrom<&proto::AccessListItem> for reth::primitives::AccessListItem { - type Error = eyre::Error; - - fn try_from(item: &proto::AccessListItem) -> Result { - Ok(reth::primitives::AccessListItem { - address: Address::try_from(item.address.as_slice())?, - storage_keys: item - .storage_keys - .iter() - .map(|key| B256::try_from(key.as_slice())) - .collect::>()?, - }) - } -} - -impl TryFrom<&proto::AccountInfo> for reth::revm::primitives::AccountInfo { - type Error = eyre::Error; - - fn try_from(account_info: &proto::AccountInfo) -> Result { - Ok(reth::revm::primitives::AccountInfo { - balance: U256::try_from_le_slice(account_info.balance.as_slice()) - .ok_or_eyre("failed to parse balance")?, - nonce: account_info.nonce, - code_hash: B256::try_from(account_info.code_hash.as_slice())?, - code: account_info.code.as_ref().map(TryInto::try_into).transpose()?, - }) - } -} - -impl TryFrom<&proto::Bytecode> for reth::revm::primitives::Bytecode { - type Error = eyre::Error; - - fn try_from(bytecode: &proto::Bytecode) -> Result { - Ok(match bytecode.bytecode.as_ref().ok_or_eyre("no bytecode")? { - proto::bytecode::Bytecode::LegacyRaw(code) => { - reth::revm::primitives::Bytecode::LegacyRaw(code.clone().into()) - } - proto::bytecode::Bytecode::LegacyAnalyzed(legacy_analyzed) => { - reth::revm::primitives::Bytecode::LegacyAnalyzed( - reth::revm::primitives::LegacyAnalyzedBytecode::new( - legacy_analyzed.bytecode.clone().into(), - legacy_analyzed.original_len as usize, - reth::revm::primitives::JumpTable::from_slice( - legacy_analyzed - .jump_table - .iter() - .map(|dest| *dest as u8) - .collect::>() - .as_slice(), - ), - ), - ) - } - }) - } -} - -impl From for reth::revm::db::AccountStatus { - fn from(status: proto::AccountStatus) -> Self { - match status { - proto::AccountStatus::LoadedNotExisting => { - reth::revm::db::AccountStatus::LoadedNotExisting - } - proto::AccountStatus::Loaded => reth::revm::db::AccountStatus::Loaded, - proto::AccountStatus::LoadedEmptyEip161 => { - reth::revm::db::AccountStatus::LoadedEmptyEIP161 - } - proto::AccountStatus::InMemoryChange => reth::revm::db::AccountStatus::InMemoryChange, - proto::AccountStatus::Changed => reth::revm::db::AccountStatus::Changed, - proto::AccountStatus::Destroyed => reth::revm::db::AccountStatus::Destroyed, - proto::AccountStatus::DestroyedChanged => { - reth::revm::db::AccountStatus::DestroyedChanged - } - proto::AccountStatus::DestroyedAgain => reth::revm::db::AccountStatus::DestroyedAgain, - } - } -} - -impl TryFrom<&proto::BundleAccount> for (Address, reth::revm::db::BundleAccount) { - type Error = eyre::Error; - - fn try_from(account: &proto::BundleAccount) -> Result { - Ok(( - Address::try_from(account.address.as_slice())?, - reth::revm::db::BundleAccount { - info: account.info.as_ref().map(TryInto::try_into).transpose()?, - original_info: account.original_info.as_ref().map(TryInto::try_into).transpose()?, - storage: account - .storage - .iter() - .map(|slot| { - Ok(( - U256::try_from_le_slice(slot.key.as_slice()) - .ok_or_eyre("failed to parse key")?, - reth::revm::db::states::StorageSlot { - previous_or_original_value: U256::try_from_le_slice( - slot.previous_or_original_value.as_slice(), - ) - .ok_or_eyre("failed to parse previous or original value")?, - present_value: U256::try_from_le_slice( - slot.present_value.as_slice(), - ) - .ok_or_eyre("failed to parse present value")?, - }, - )) - }) - .collect::>()?, - status: proto::AccountStatus::try_from(account.status)?.into(), - }, - )) - } -} - -impl TryFrom<&proto::Revert> for (Address, reth::revm::db::states::reverts::AccountRevert) { - type Error = eyre::Error; - - fn try_from(revert: &proto::Revert) -> Result { - Ok(( - Address::try_from(revert.address.as_slice())?, - reth::revm::db::states::reverts::AccountRevert { - account: match revert - .account - .as_ref() - .ok_or_eyre("no revert account")? - .revert - .as_ref() - .ok_or_eyre("no revert account revert")? - { - proto::account_info_revert::Revert::DoNothing(()) => { - reth::revm::db::states::reverts::AccountInfoRevert::DoNothing - } - proto::account_info_revert::Revert::DeleteIt(()) => { - reth::revm::db::states::reverts::AccountInfoRevert::DeleteIt - } - proto::account_info_revert::Revert::RevertTo(account_info) => { - reth::revm::db::states::reverts::AccountInfoRevert::RevertTo( - account_info.try_into()?, - ) - } - }, - storage: revert - .storage - .iter() - .map(|slot| { - Ok(( - U256::try_from_le_slice(slot.key.as_slice()) - .ok_or_eyre("failed to parse slot key")?, - match slot.revert.as_ref().ok_or_eyre("no slot revert")? { - proto::revert_to_slot::Revert::Some(value) => { - reth::revm::db::states::reverts::RevertToSlot::Some( - U256::try_from_le_slice(value.as_slice()) - .ok_or_eyre("failed to parse slot revert")?, - ) - } - proto::revert_to_slot::Revert::Destroyed(()) => { - reth::revm::db::states::reverts::RevertToSlot::Destroyed - } - }, - )) - }) - .collect::>()?, - previous_status: proto::AccountStatus::try_from(revert.previous_status)?.into(), - wipe_storage: revert.wipe_storage, - }, - )) - } -} - -impl TryFrom<&proto::Receipt> for Option { - type Error = eyre::Error; - - fn try_from(receipt: &proto::Receipt) -> Result { - Ok(match receipt.receipt.as_ref().ok_or_eyre("no receipt")? { - proto::receipt::Receipt::Empty(()) => None, - proto::receipt::Receipt::NonEmpty(receipt) => Some(receipt.try_into()?), - }) - } -} - -impl TryFrom<&proto::NonEmptyReceipt> for reth::primitives::Receipt { - type Error = eyre::Error; - - fn try_from(receipt: &proto::NonEmptyReceipt) -> Result { - Ok(reth::primitives::Receipt { - tx_type: match proto::TxType::try_from(receipt.tx_type)? { - proto::TxType::Legacy => reth::primitives::TxType::Legacy, - proto::TxType::Eip2930 => reth::primitives::TxType::Eip2930, - proto::TxType::Eip1559 => reth::primitives::TxType::Eip1559, - proto::TxType::Eip4844 => reth::primitives::TxType::Eip4844, - }, - success: receipt.success, - cumulative_gas_used: receipt.cumulative_gas_used, - logs: receipt - .logs - .iter() - .map(|log| { - let data = log.data.as_ref().ok_or_eyre("no log data")?; - Ok(reth::primitives::Log { - address: Address::try_from(log.address.as_slice())?, - data: reth::primitives::LogData::new_unchecked( - data.topics - .iter() - .map(|topic| Ok(B256::try_from(topic.as_slice())?)) - .collect::>()?, - data.data.clone().into(), - ), - }) - }) - .collect::>()?, - #[cfg(feature = "optimism")] - deposit_nonce: None, - #[cfg(feature = "optimism")] - deposit_receipt_version: None, - }) - } -} diff --git a/examples/exex/remote/src/lib.rs b/examples/exex/remote/src/lib.rs deleted file mode 100644 index 9b8aa5781a8f..000000000000 --- a/examples/exex/remote/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod codec; -pub mod proto { - tonic::include_proto!("exex"); -} From 46fdc3883352127b60e91079322bb055df28f4ce Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 25 Jun 2024 13:49:27 +0200 Subject: [PATCH 205/405] feat: initial cli abstraction (#9082) --- Cargo.lock | 4 ++++ Cargo.toml | 2 ++ crates/cli/cli/Cargo.toml | 10 ++++++++++ crates/cli/cli/src/lib.rs | 25 +++++++++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 crates/cli/cli/Cargo.toml create mode 100644 crates/cli/cli/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 89f2eecd0666..a92397fea46e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6494,6 +6494,10 @@ dependencies = [ "serde_json", ] +[[package]] +name = "reth-cli" +version = "1.0.0" + [[package]] name = "reth-cli-commands" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 62770b9deb4c..24f3af5adf28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/blockchain-tree/", "crates/blockchain-tree-api/", "crates/chainspec/", + "crates/cli/cli/", "crates/cli/commands/", "crates/cli/runner/", "crates/config/", @@ -258,6 +259,7 @@ reth-beacon-consensus = { path = "crates/consensus/beacon" } reth-blockchain-tree = { path = "crates/blockchain-tree" } reth-blockchain-tree-api = { path = "crates/blockchain-tree-api" } reth-chainspec = { path = "crates/chainspec" } +reth-cli = { path = "crates/cli/cli" } reth-cli-commands = { path = "crates/cli/commands" } reth-cli-runner = { path = "crates/cli/runner" } reth-codecs = { path = "crates/storage/codecs" } diff --git a/crates/cli/cli/Cargo.toml b/crates/cli/cli/Cargo.toml new file mode 100644 index 000000000000..3487782c9e20 --- /dev/null +++ b/crates/cli/cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "reth-cli" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] diff --git a/crates/cli/cli/src/lib.rs b/crates/cli/cli/src/lib.rs new file mode 100644 index 000000000000..513380fdd052 --- /dev/null +++ b/crates/cli/cli/src/lib.rs @@ -0,0 +1,25 @@ +//! Cli abstraction for reth based nodes. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use std::borrow::Cow; + +/// Reth based node cli. +/// +/// This trait is supposed to be implemented by the main struct of the CLI. +/// +/// It provides commonly used functionality for running commands and information about the CL, such +/// as the name and version. +pub trait RethCli: Sized { + /// The name of the implementation, eg. `reth`, `op-reth`, etc. + fn name(&self) -> Cow<'static, str>; + + /// The version of the node, such as `reth/v1.0.0` + fn version(&self) -> Cow<'static, str>; +} From 6dffb92c0b7792125cd3818e9406716d708cc972 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 25 Jun 2024 04:57:26 -0700 Subject: [PATCH 206/405] perf(trie): hold direct reference to post state storage in the cursor (#9077) --- .../trie/trie/src/hashed_cursor/post_state.rs | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/crates/trie/trie/src/hashed_cursor/post_state.rs b/crates/trie/trie/src/hashed_cursor/post_state.rs index 41b051b2a7a7..4eb98169760e 100644 --- a/crates/trie/trie/src/hashed_cursor/post_state.rs +++ b/crates/trie/trie/src/hashed_cursor/post_state.rs @@ -1,5 +1,5 @@ use super::{HashedCursor, HashedCursorFactory, HashedStorageCursor}; -use crate::state::HashedPostStateSorted; +use crate::{state::HashedPostStateSorted, HashedStorageSorted}; use reth_primitives::{Account, B256, U256}; /// The hashed cursor factory for the post state. @@ -30,18 +30,18 @@ impl<'a, CF: HashedCursorFactory> HashedCursorFactory for HashedPostStateCursorF hashed_address: B256, ) -> Result { let cursor = self.cursor_factory.hashed_storage_cursor(hashed_address)?; - Ok(HashedPostStateStorageCursor::new(cursor, self.post_state, hashed_address)) + Ok(HashedPostStateStorageCursor::new(cursor, self.post_state.storages.get(&hashed_address))) } } /// The cursor to iterate over post state hashed accounts and corresponding database entries. /// It will always give precedence to the data from the hashed post state. #[derive(Debug, Clone)] -pub struct HashedPostStateAccountCursor<'b, C> { +pub struct HashedPostStateAccountCursor<'a, C> { /// The database cursor. cursor: C, /// The reference to the in-memory [`HashedPostStateSorted`]. - post_state: &'b HashedPostStateSorted, + post_state: &'a HashedPostStateSorted, /// The post state account index where the cursor is currently at. post_state_account_index: usize, /// The last hashed account that was returned by the cursor. @@ -49,9 +49,9 @@ pub struct HashedPostStateAccountCursor<'b, C> { last_account: Option, } -impl<'b, C> HashedPostStateAccountCursor<'b, C> { +impl<'a, C> HashedPostStateAccountCursor<'a, C> { /// Create new instance of [`HashedPostStateAccountCursor`]. - pub const fn new(cursor: C, post_state: &'b HashedPostStateSorted) -> Self { + pub const fn new(cursor: C, post_state: &'a HashedPostStateSorted) -> Self { Self { cursor, post_state, last_account: None, post_state_account_index: 0 } } @@ -88,7 +88,7 @@ impl<'b, C> HashedPostStateAccountCursor<'b, C> { } } -impl<'b, C> HashedCursor for HashedPostStateAccountCursor<'b, C> +impl<'a, C> HashedCursor for HashedPostStateAccountCursor<'a, C> where C: HashedCursor, { @@ -179,13 +179,11 @@ where /// The cursor to iterate over post state hashed storages and corresponding database entries. /// It will always give precedence to the data from the post state. #[derive(Debug, Clone)] -pub struct HashedPostStateStorageCursor<'b, C> { +pub struct HashedPostStateStorageCursor<'a, C> { /// The database cursor. cursor: C, - /// The reference to the post state. - post_state: &'b HashedPostStateSorted, - /// The current hashed account key. - hashed_address: B256, + /// The reference to post state storage. + post_state_storage: Option<&'a HashedStorageSorted>, /// The post state index where the cursor is currently at. post_state_storage_index: usize, /// The last slot that has been returned by the cursor. @@ -193,20 +191,21 @@ pub struct HashedPostStateStorageCursor<'b, C> { last_slot: Option, } -impl<'b, C> HashedPostStateStorageCursor<'b, C> { +impl<'a, C> HashedPostStateStorageCursor<'a, C> { /// Create new instance of [`HashedPostStateStorageCursor`] for the given hashed address. - pub const fn new( - cursor: C, - post_state: &'b HashedPostStateSorted, - hashed_address: B256, - ) -> Self { - Self { cursor, post_state, hashed_address, last_slot: None, post_state_storage_index: 0 } + pub const fn new(cursor: C, post_state: Option<&'a HashedStorageSorted>) -> Self { + Self { + cursor, + post_state_storage: post_state, + last_slot: None, + post_state_storage_index: 0, + } } /// Returns `true` if the storage for the given /// The database is not checked since it already has no wiped storage entries. - fn is_db_storage_wiped(&self) -> bool { - match self.post_state.storages.get(&self.hashed_address) { + const fn is_db_storage_wiped(&self) -> bool { + match self.post_state_storage { Some(storage) => storage.wiped, None => false, } @@ -215,9 +214,7 @@ impl<'b, C> HashedPostStateStorageCursor<'b, C> { /// Check if the slot was zeroed out in the post state. /// The database is not checked since it already has no zero-valued slots. fn is_slot_zero_valued(&self, slot: &B256) -> bool { - self.post_state - .storages - .get(&self.hashed_address) + self.post_state_storage .map(|storage| storage.zero_valued_slots.contains(slot)) .unwrap_or_default() } @@ -246,7 +243,7 @@ impl<'b, C> HashedPostStateStorageCursor<'b, C> { } } -impl<'b, C> HashedCursor for HashedPostStateStorageCursor<'b, C> +impl<'a, C> HashedCursor for HashedPostStateStorageCursor<'a, C> where C: HashedStorageCursor, { @@ -259,7 +256,7 @@ where ) -> Result, reth_db::DatabaseError> { // Attempt to find the account's storage in post state. let mut post_state_entry = None; - if let Some(storage) = self.post_state.storages.get(&self.hashed_address) { + if let Some(storage) = self.post_state_storage { post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index); while post_state_entry.map(|(slot, _)| slot < &subkey).unwrap_or_default() { @@ -332,7 +329,7 @@ where // Attempt to find the account's storage in post state. let mut post_state_entry = None; - if let Some(storage) = self.post_state.storages.get(&self.hashed_address) { + if let Some(storage) = self.post_state_storage { post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index); while post_state_entry.map(|(slot, _)| slot <= last_slot).unwrap_or_default() { self.post_state_storage_index += 1; @@ -347,7 +344,7 @@ where } } -impl<'b, C> HashedStorageCursor for HashedPostStateStorageCursor<'b, C> +impl<'a, C> HashedStorageCursor for HashedPostStateStorageCursor<'a, C> where C: HashedStorageCursor, { @@ -356,7 +353,7 @@ where /// This function should be called before attempting to call [`HashedCursor::seek`] or /// [`HashedCursor::next`]. fn is_storage_empty(&mut self) -> Result { - let is_empty = match self.post_state.storages.get(&self.hashed_address) { + let is_empty = match self.post_state_storage { Some(storage) => { // If the storage has been wiped at any point storage.wiped && From e2015143b35d9d6c2cf180bec752673767063f0f Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 25 Jun 2024 04:57:28 -0700 Subject: [PATCH 207/405] chore(trie): add helpers to return trie keys as variants (#9075) --- crates/trie/trie/src/updates.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index afad628cd74e..53830fd8a3c2 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -23,6 +23,35 @@ pub enum TrieKey { StorageTrie(B256), } +impl TrieKey { + /// Returns reference to account node key if the key is for [`Self::AccountNode`]. + pub const fn as_account_node_key(&self) -> Option<&StoredNibbles> { + if let Self::AccountNode(nibbles) = &self { + Some(nibbles) + } else { + None + } + } + + /// Returns reference to storage node key if the key is for [`Self::StorageNode`]. + pub const fn as_storage_node_key(&self) -> Option<(&B256, &StoredNibblesSubKey)> { + if let Self::StorageNode(key, subkey) = &self { + Some((key, subkey)) + } else { + None + } + } + + /// Returns reference to storage trie key if the key is for [`Self::StorageTrie`]. + pub const fn as_storage_trie_key(&self) -> Option<&B256> { + if let Self::StorageTrie(key) = &self { + Some(key) + } else { + None + } + } +} + /// The operation to perform on the trie. #[derive(PartialEq, Eq, Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] From c3cae954313524ede09854af02132266601d56b1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 25 Jun 2024 14:30:47 +0200 Subject: [PATCH 208/405] test: include unexpected event in panic (#9087) --- crates/e2e-test-utils/src/network.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/e2e-test-utils/src/network.rs b/crates/e2e-test-utils/src/network.rs index b575d7001dda..2bb69044fc42 100644 --- a/crates/e2e-test-utils/src/network.rs +++ b/crates/e2e-test-utils/src/network.rs @@ -23,7 +23,7 @@ impl NetworkTestContext { match self.network_events.next().await { Some(NetworkEvent::PeerAdded(_)) => (), - _ => panic!("Expected a peer added event"), + ev => panic!("Expected a peer added event, got: {ev:?}"), } } @@ -38,7 +38,7 @@ impl NetworkTestContext { Some(NetworkEvent::SessionEstablished { remote_addr, .. }) => { info!(?remote_addr, "Session established") } - _ => panic!("Expected session established event"), + ev => panic!("Expected session established event, got: {ev:?}"), } } } From 6e146e1140e874b88c304fdc3c5ac0e046e66263 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 25 Jun 2024 06:23:58 -0700 Subject: [PATCH 209/405] chore(trie): hold direct reference to hashed accounts in cursor (#9078) --- .../trie/trie/src/hashed_cursor/post_state.rs | 24 ++++++++++--------- crates/trie/trie/src/state.rs | 20 +++++++++++----- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/crates/trie/trie/src/hashed_cursor/post_state.rs b/crates/trie/trie/src/hashed_cursor/post_state.rs index 4eb98169760e..b609048fafa9 100644 --- a/crates/trie/trie/src/hashed_cursor/post_state.rs +++ b/crates/trie/trie/src/hashed_cursor/post_state.rs @@ -1,5 +1,5 @@ use super::{HashedCursor, HashedCursorFactory, HashedStorageCursor}; -use crate::{state::HashedPostStateSorted, HashedStorageSorted}; +use crate::{HashedAccountsSorted, HashedPostStateSorted, HashedStorageSorted}; use reth_primitives::{Account, B256, U256}; /// The hashed cursor factory for the post state. @@ -22,7 +22,7 @@ impl<'a, CF: HashedCursorFactory> HashedCursorFactory for HashedPostStateCursorF fn hashed_account_cursor(&self) -> Result { let cursor = self.cursor_factory.hashed_account_cursor()?; - Ok(HashedPostStateAccountCursor::new(cursor, self.post_state)) + Ok(HashedPostStateAccountCursor::new(cursor, &self.post_state.accounts)) } fn hashed_storage_cursor( @@ -40,8 +40,8 @@ impl<'a, CF: HashedCursorFactory> HashedCursorFactory for HashedPostStateCursorF pub struct HashedPostStateAccountCursor<'a, C> { /// The database cursor. cursor: C, - /// The reference to the in-memory [`HashedPostStateSorted`]. - post_state: &'a HashedPostStateSorted, + /// The reference to the in-memory [`HashedAccountsSorted`]. + post_state_accounts: &'a HashedAccountsSorted, /// The post state account index where the cursor is currently at. post_state_account_index: usize, /// The last hashed account that was returned by the cursor. @@ -51,8 +51,8 @@ pub struct HashedPostStateAccountCursor<'a, C> { impl<'a, C> HashedPostStateAccountCursor<'a, C> { /// Create new instance of [`HashedPostStateAccountCursor`]. - pub const fn new(cursor: C, post_state: &'a HashedPostStateSorted) -> Self { - Self { cursor, post_state, last_account: None, post_state_account_index: 0 } + pub const fn new(cursor: C, post_state_accounts: &'a HashedAccountsSorted) -> Self { + Self { cursor, post_state_accounts, last_account: None, post_state_account_index: 0 } } /// Returns `true` if the account has been destroyed. @@ -61,7 +61,7 @@ impl<'a, C> HashedPostStateAccountCursor<'a, C> { /// This function only checks the post state, not the database, because the latter does not /// store destroyed accounts. fn is_account_cleared(&self, account: &B256) -> bool { - self.post_state.destroyed_accounts.contains(account) + self.post_state_accounts.destroyed_accounts.contains(account) } /// Return the account with the lowest hashed account key. @@ -107,10 +107,11 @@ where // Take the next account from the post state with the key greater than or equal to the // sought key. - let mut post_state_entry = self.post_state.accounts.get(self.post_state_account_index); + let mut post_state_entry = + self.post_state_accounts.accounts.get(self.post_state_account_index); while post_state_entry.map(|(k, _)| k < &key).unwrap_or_default() { self.post_state_account_index += 1; - post_state_entry = self.post_state.accounts.get(self.post_state_account_index); + post_state_entry = self.post_state_accounts.accounts.get(self.post_state_account_index); } // It's an exact match, return the account from post state without looking up in the @@ -163,10 +164,11 @@ where } // Take the next account from the post state with the key greater than the last sought key. - let mut post_state_entry = self.post_state.accounts.get(self.post_state_account_index); + let mut post_state_entry = + self.post_state_accounts.accounts.get(self.post_state_account_index); while post_state_entry.map(|(k, _)| k <= last_account).unwrap_or_default() { self.post_state_account_index += 1; - post_state_entry = self.post_state.accounts.get(self.post_state_account_index); + post_state_entry = self.post_state_accounts.accounts.get(self.post_state_account_index); } // Compare two entries and return the lowest. diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index 821dcc971b62..de7ecc236dbe 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -149,16 +149,17 @@ impl HashedPostState { /// Converts hashed post state into [`HashedPostStateSorted`]. pub fn into_sorted(self) -> HashedPostStateSorted { - let mut accounts = Vec::new(); + let mut updated_accounts = Vec::new(); let mut destroyed_accounts = HashSet::default(); for (hashed_address, info) in self.accounts { if let Some(info) = info { - accounts.push((hashed_address, info)); + updated_accounts.push((hashed_address, info)); } else { destroyed_accounts.insert(hashed_address); } } - accounts.sort_unstable_by_key(|(address, _)| *address); + updated_accounts.sort_unstable_by_key(|(address, _)| *address); + let accounts = HashedAccountsSorted { accounts: updated_accounts, destroyed_accounts }; let storages = self .storages @@ -166,7 +167,7 @@ impl HashedPostState { .map(|(hashed_address, storage)| (hashed_address, storage.into_sorted())) .collect(); - HashedPostStateSorted { accounts, destroyed_accounts, storages } + HashedPostStateSorted { accounts, storages } } /// Construct [`TriePrefixSets`] from hashed post state. @@ -309,12 +310,19 @@ impl HashedStorage { /// Sorted hashed post state optimized for iterating during state trie calculation. #[derive(PartialEq, Eq, Clone, Debug)] pub struct HashedPostStateSorted { + /// Updated state of accounts. + pub(crate) accounts: HashedAccountsSorted, + /// Map of hashed addresses to hashed storage. + pub(crate) storages: HashMap, +} + +/// Sorted account state optimized for iterating during state trie calculation. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct HashedAccountsSorted { /// Sorted collection of hashed addresses and their account info. pub(crate) accounts: Vec<(B256, Account)>, /// Set of destroyed account keys. pub(crate) destroyed_accounts: HashSet, - /// Map of hashed addresses to hashed storage. - pub(crate) storages: HashMap, } /// Sorted hashed storage optimized for iterating during state trie calculation. From a3a472a784bca2f0e769b28f9c4f4b19961ac51c Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Tue, 25 Jun 2024 17:22:35 +0200 Subject: [PATCH 210/405] fix: do not drop sub protocol messages during EthStream Handshake (#9086) --- crates/net/network/src/session/mod.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index 6ce4e97aa519..e8a80bcff193 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -1006,10 +1006,7 @@ async fn authenticate_stream( (eth_stream.into(), their_status) } else { // Multiplex the stream with the extra protocols - let (mut multiplex_stream, their_status) = RlpxProtocolMultiplexer::new(p2p_stream) - .into_eth_satellite_stream(status, fork_filter) - .await - .unwrap(); + let mut multiplex_stream = RlpxProtocolMultiplexer::new(p2p_stream); // install additional handlers for handler in extra_handlers.into_iter() { @@ -1022,6 +1019,19 @@ async fn authenticate_stream( .ok(); } + let (multiplex_stream, their_status) = + match multiplex_stream.into_eth_satellite_stream(status, fork_filter).await { + Ok((multiplex_stream, their_status)) => (multiplex_stream, their_status), + Err(err) => { + return PendingSessionEvent::Disconnected { + remote_addr, + session_id, + direction, + error: Some(PendingSessionHandshakeError::Eth(err)), + } + } + }; + (multiplex_stream.into(), their_status) }; From e1af75dad4630cbf64a1571bb26c7c8818f2ed70 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Tue, 25 Jun 2024 17:36:24 +0200 Subject: [PATCH 211/405] ExEx Discv5 (#8873) Co-authored-by: Matthias Seitz --- Cargo.lock | 26 +++++ examples/README.md | 1 + examples/exex/discv5/Cargo.toml | 33 ++++++ examples/exex/discv5/src/exex/mod.rs | 70 +++++++++++ examples/exex/discv5/src/main.rs | 29 +++++ examples/exex/discv5/src/network/cli_ext.rs | 15 +++ examples/exex/discv5/src/network/mod.rs | 123 ++++++++++++++++++++ 7 files changed, 297 insertions(+) create mode 100644 examples/exex/discv5/Cargo.toml create mode 100644 examples/exex/discv5/src/exex/mod.rs create mode 100644 examples/exex/discv5/src/main.rs create mode 100644 examples/exex/discv5/src/network/cli_ext.rs create mode 100644 examples/exex/discv5/src/network/mod.rs diff --git a/Cargo.lock b/Cargo.lock index a92397fea46e..88b7b807c034 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2822,6 +2822,32 @@ dependencies = [ "reth-rpc-types", ] +[[package]] +name = "example-exex-discv5" +version = "0.0.0" +dependencies = [ + "clap", + "discv5", + "enr", + "eyre", + "futures", + "futures-util", + "reth", + "reth-chainspec", + "reth-discv5", + "reth-exex", + "reth-exex-test-utils", + "reth-network-peers", + "reth-node-api", + "reth-node-ethereum", + "reth-testing-utils", + "reth-tracing", + "serde_json", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "example-exex-in-memory-state" version = "0.0.0" diff --git a/examples/README.md b/examples/README.md index 423d9224be67..b24b7387f32d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -30,6 +30,7 @@ to make a PR! | [Minimal](./exex/minimal) | Illustrates how to build a simple ExEx | | [OP Bridge](./exex/op-bridge) | Illustrates an ExEx that decodes Optimism deposit and withdrawal receipts from L1 | | [Rollup](./exex/rollup) | Illustrates a rollup ExEx that derives the state from L1 | +| [Discv5 as ExEx](./exex/discv5) | Illustrates an ExEx that runs discv5 discovery stack | ## RPC diff --git a/examples/exex/discv5/Cargo.toml b/examples/exex/discv5/Cargo.toml new file mode 100644 index 000000000000..b1777cfa1516 --- /dev/null +++ b/examples/exex/discv5/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "example-exex-discv5" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +discv5.workspace = true +enr.workspace = true + +reth-discv5.workspace = true +reth.workspace = true +reth-exex.workspace = true +reth-node-api.workspace = true +reth-node-ethereum.workspace = true +reth-network-peers.workspace = true +reth-tracing.workspace = true +futures.workspace = true + +clap.workspace = true +reth-chainspec.workspace = true +serde_json.workspace = true +tokio.workspace = true +tokio-stream.workspace = true +futures-util.workspace = true + +tracing.workspace = true +eyre.workspace = true + +[dev-dependencies] +reth-exex-test-utils.workspace = true +reth-testing-utils.workspace = true diff --git a/examples/exex/discv5/src/exex/mod.rs b/examples/exex/discv5/src/exex/mod.rs new file mode 100644 index 000000000000..4631f392979c --- /dev/null +++ b/examples/exex/discv5/src/exex/mod.rs @@ -0,0 +1,70 @@ +use eyre::Result; +use futures::{Future, FutureExt}; +use reth_exex::{ExExContext, ExExEvent, ExExNotification}; +use reth_node_api::FullNodeComponents; +use reth_tracing::tracing::info; +use std::{ + pin::Pin, + task::{ready, Context, Poll}, +}; +use tracing::error; + +use crate::network::DiscV5ExEx; + +/// The ExEx struct, representing the initialization and execution of the ExEx. +pub struct ExEx { + exex: ExExContext, + disc_v5: DiscV5ExEx, +} + +impl ExEx { + pub fn new(exex: ExExContext, disc_v5: DiscV5ExEx) -> Self { + Self { exex, disc_v5 } + } +} + +impl Future for ExEx { + type Output = Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Poll the Discv5 future until its drained + loop { + match self.disc_v5.poll_unpin(cx) { + Poll::Ready(Ok(())) => { + info!("Discv5 task completed successfully"); + } + Poll::Ready(Err(e)) => { + error!(?e, "Discv5 task encountered an error"); + return Poll::Ready(Err(e)); + } + Poll::Pending => { + // Exit match and continue to poll notifications + break; + } + } + } + + // Continuously poll the ExExContext notifications + loop { + if let Some(notification) = ready!(self.exex.notifications.poll_recv(cx)) { + match ¬ification { + ExExNotification::ChainCommitted { new } => { + info!(committed_chain = ?new.range(), "Received commit"); + } + ExExNotification::ChainReorged { old, new } => { + info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg"); + } + ExExNotification::ChainReverted { old } => { + info!(reverted_chain = ?old.range(), "Received revert"); + } + } + + if let Some(committed_chain) = notification.committed_chain() { + self.exex + .events + .send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + } + } + } +} diff --git a/examples/exex/discv5/src/main.rs b/examples/exex/discv5/src/main.rs new file mode 100644 index 000000000000..2374326050b7 --- /dev/null +++ b/examples/exex/discv5/src/main.rs @@ -0,0 +1,29 @@ +use clap::Parser; + +use exex::ExEx; +use network::{cli_ext::Discv5ArgsExt, DiscV5ExEx}; +use reth_node_ethereum::EthereumNode; + +mod exex; +mod network; + +fn main() -> eyre::Result<()> { + reth::cli::Cli::::parse().run(|builder, args| async move { + let tcp_port = args.tcp_port; + let udp_port = args.udp_port; + + let handle = builder + .node(EthereumNode::default()) + .install_exex("exex-discv5", move |ctx| async move { + // start Discv5 task + let disc_v5 = DiscV5ExEx::new(tcp_port, udp_port).await?; + + // start exex task with discv5 + Ok(ExEx::new(ctx, disc_v5)) + }) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) +} diff --git a/examples/exex/discv5/src/network/cli_ext.rs b/examples/exex/discv5/src/network/cli_ext.rs new file mode 100644 index 000000000000..1eb864de3611 --- /dev/null +++ b/examples/exex/discv5/src/network/cli_ext.rs @@ -0,0 +1,15 @@ +use clap::Args; + +pub const DEFAULT_DISCOVERY_PORT: u16 = 30304; +pub const DEFAULT_RLPX_PORT: u16 = 30303; + +#[derive(Debug, Clone, Args)] +pub(crate) struct Discv5ArgsExt { + /// TCP port used by RLPx + #[clap(long = "exex-discv5.tcp-port", default_value_t = DEFAULT_RLPX_PORT)] + pub tcp_port: u16, + + /// UDP port used for discovery + #[clap(long = "exex-discv5.udp-port", default_value_t = DEFAULT_DISCOVERY_PORT)] + pub udp_port: u16, +} diff --git a/examples/exex/discv5/src/network/mod.rs b/examples/exex/discv5/src/network/mod.rs new file mode 100644 index 000000000000..41f57bffc478 --- /dev/null +++ b/examples/exex/discv5/src/network/mod.rs @@ -0,0 +1,123 @@ +#![allow(dead_code)] + +use discv5::{enr::secp256k1::rand, Enr, Event, ListenConfig}; +use reth::network::config::SecretKey; +use reth_chainspec::net::NodeRecord; +use reth_discv5::{enr::EnrCombinedKeyWrapper, Config, Discv5}; +use reth_tracing::tracing::info; +use std::{ + future::Future, + net::SocketAddr, + pin::Pin, + task::{ready, Context, Poll}, +}; +use tokio::sync::mpsc; + +pub(crate) mod cli_ext; + +/// Helper struct to manage a discovery node using discv5. +pub(crate) struct DiscV5ExEx { + /// The inner discv5 instance. + inner: Discv5, + /// The node record of the discv5 instance. + node_record: NodeRecord, + /// The events stream of the discv5 instance. + events: mpsc::Receiver, +} + +impl DiscV5ExEx { + /// Starts a new discv5 node. + pub async fn new(udp_port: u16, tcp_port: u16) -> eyre::Result { + let secret_key = SecretKey::new(&mut rand::thread_rng()); + + let discv5_addr: SocketAddr = format!("127.0.0.1:{udp_port}").parse()?; + let rlpx_addr: SocketAddr = format!("127.0.0.1:{tcp_port}").parse()?; + + let discv5_listen_config = ListenConfig::from(discv5_addr); + let discv5_config = Config::builder(rlpx_addr) + .discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build()) + .build(); + + let (discv5, events, node_record) = Discv5::start(&secret_key, discv5_config).await?; + Ok(Self { inner: discv5, events, node_record }) + } + + /// Adds a node to the table if its not already present. + pub fn add_node(&mut self, enr: Enr) -> eyre::Result<()> { + let reth_enr: enr::Enr = EnrCombinedKeyWrapper(enr.clone()).into(); + self.inner.add_node(reth_enr)?; + Ok(()) + } + + /// Returns the local ENR of the discv5 node. + pub fn local_enr(&self) -> Enr { + self.inner.with_discv5(|discv5| discv5.local_enr()) + } +} + +impl Future for DiscV5ExEx { + type Output = eyre::Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.as_mut(); + loop { + match ready!(this.events.poll_recv(cx)) { + Some(evt) => { + if let Event::SessionEstablished(enr, socket_addr) = evt { + info!(?enr, ?socket_addr, "Session established with a new peer."); + } + } + None => return Poll::Ready(Ok(())), + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::network::DiscV5ExEx; + use tracing::info; + + #[tokio::test] + async fn can_establish_discv5_session_with_peer() { + reth_tracing::init_test_tracing(); + let mut node_1 = DiscV5ExEx::new(30301, 30303).await.unwrap(); + let node_1_enr = node_1.local_enr(); + + let mut node_2 = DiscV5ExEx::new(30302, 30303).await.unwrap(); + + let node_2_enr = node_2.local_enr(); + + info!(?node_1_enr, ?node_2_enr, "Started discovery nodes."); + + // add node_2 to node_1 table + node_1.add_node(node_2_enr.clone()).unwrap(); + + // verify node_2 is in node_1 table + assert!(node_1 + .inner + .with_discv5(|discv5| discv5.table_entries_id().contains(&node_2_enr.node_id()))); + + // send ping from node_1 to node_2 + node_1.inner.with_discv5(|discv5| discv5.send_ping(node_2_enr.clone())).await.unwrap(); + + // verify they both established a session + let event_2_v5 = node_2.events.recv().await.unwrap(); + let event_1_v5 = node_1.events.recv().await.unwrap(); + assert!(matches!( + event_1_v5, + discv5::Event::SessionEstablished(node, socket) if node == node_2_enr && socket == node_2_enr.udp4_socket().unwrap().into() + )); + assert!(matches!( + event_2_v5, + discv5::Event::SessionEstablished(node, socket) if node == node_1_enr && socket == node_1_enr.udp4_socket().unwrap().into() + )); + + // verify node_1 is in + let event_2_v5 = node_2.events.recv().await.unwrap(); + assert!(matches!( + event_2_v5, + discv5::Event::NodeInserted { node_id, replaced } if node_id == node_1_enr.node_id() && replaced.is_none() + )); + } +} From 270df3eeb807e148d317d0037b2bf1499530a1d1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 25 Jun 2024 18:32:34 +0200 Subject: [PATCH 212/405] chore: add optimism cli crate (#9096) --- Cargo.lock | 4 ++++ Cargo.toml | 2 ++ crates/optimism/cli/Cargo.toml | 10 ++++++++++ crates/optimism/cli/src/lib.rs | 9 +++++++++ 4 files changed, 25 insertions(+) create mode 100644 crates/optimism/cli/Cargo.toml create mode 100644 crates/optimism/cli/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 88b7b807c034..400aba307a9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7636,6 +7636,10 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-optimism-cli" +version = "1.0.0" + [[package]] name = "reth-optimism-consensus" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 24f3af5adf28..a7e1ab3d92ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ members = [ "crates/node/api/", "crates/node/builder/", "crates/node/events/", + "crates/optimism/cli", "crates/optimism/consensus", "crates/optimism/evm/", "crates/optimism/node/", @@ -313,6 +314,7 @@ reth-node-core = { path = "crates/node-core" } reth-node-ethereum = { path = "crates/ethereum/node" } reth-node-events = { path = "crates/node/events" } reth-node-optimism = { path = "crates/optimism/node" } +reth-optimism-cli = { path = "crates/optimism/cli" } reth-optimism-consensus = { path = "crates/optimism/consensus" } reth-optimism-payload-builder = { path = "crates/optimism/payload" } reth-optimism-primitives = { path = "crates/optimism/primitives" } diff --git a/crates/optimism/cli/Cargo.toml b/crates/optimism/cli/Cargo.toml new file mode 100644 index 000000000000..05201b658f76 --- /dev/null +++ b/crates/optimism/cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "reth-optimism-cli" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] diff --git a/crates/optimism/cli/src/lib.rs b/crates/optimism/cli/src/lib.rs new file mode 100644 index 000000000000..a5133bea139c --- /dev/null +++ b/crates/optimism/cli/src/lib.rs @@ -0,0 +1,9 @@ +//! OP-Reth CLI implementation. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] From aa952309e879fd93f3e94ceb4dbd3b9ce9ec54bd Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 25 Jun 2024 14:50:30 -0400 Subject: [PATCH 213/405] feat: reth stage unwind --offline (#9097) Co-authored-by: Georgios Konstantopoulos Co-authored-by: Oliver --- bin/reth/src/commands/stage/unwind.rs | 58 +++++++++++++++++++-------- book/cli/reth/stage/unwind.md | 3 ++ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/bin/reth/src/commands/stage/unwind.rs b/bin/reth/src/commands/stage/unwind.rs index 157f33bff7cb..5e6b62537572 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/bin/reth/src/commands/stage/unwind.rs @@ -15,7 +15,7 @@ use reth_provider::{ }; use reth_prune_types::PruneModes; use reth_stages::{ - sets::DefaultStages, + sets::{DefaultStages, OfflineStages}, stages::{ExecutionStage, ExecutionStageThresholds}, Pipeline, StageSet, }; @@ -40,6 +40,11 @@ pub struct Command { #[command(subcommand)] command: Subcommands, + + /// If this is enabled, then all stages except headers, bodies, and sender recovery will be + /// unwound. + #[arg(long)] + offline: bool, } impl Command { @@ -52,16 +57,30 @@ impl Command { eyre::bail!("Cannot unwind genesis block") } - // Only execute a pipeline unwind if the start of the range overlaps the existing static - // files. If that's the case, then copy all available data from MDBX to static files, and - // only then, proceed with the unwind. - if let Some(highest_static_block) = provider_factory + let highest_static_file_block = provider_factory .static_file_provider() .get_highest_static_files() .max() - .filter(|highest_static_file_block| highest_static_file_block >= range.start()) - { - info!(target: "reth::cli", ?range, ?highest_static_block, "Executing a pipeline unwind."); + .filter(|highest_static_file_block| highest_static_file_block >= range.start()); + + // Execute a pipeline unwind if the start of the range overlaps the existing static + // files. If that's the case, then copy all available data from MDBX to static files, and + // only then, proceed with the unwind. + // + // We also execute a pipeline unwind if `offline` is specified, because we need to only + // unwind the data associated with offline stages. + if highest_static_file_block.is_some() || self.offline { + if self.offline { + info!(target: "reth::cli", "Performing an unwind for offline-only data!"); + } + + if let Some(highest_static_file_block) = highest_static_file_block { + info!(target: "reth::cli", ?range, ?highest_static_file_block, "Executing a pipeline unwind."); + } else { + info!(target: "reth::cli", ?range, "Executing a pipeline unwind."); + } + + // This will build an offline-only pipeline if the `offline` flag is enabled let mut pipeline = self.build_pipeline(config, provider_factory.clone()).await?; // Move all applicable data from database to static files. @@ -87,7 +106,7 @@ impl Command { provider.commit()?; } - println!("Unwound {} blocks", range.count()); + info!(target: "reth::cli", range=?range.clone(), count=range.count(), "Unwound blocks"); Ok(()) } @@ -105,9 +124,14 @@ impl Command { let (tip_tx, tip_rx) = watch::channel(B256::ZERO); let executor = block_executor!(provider_factory.chain_spec()); - let pipeline = Pipeline::builder() - .with_tip_sender(tip_tx) - .add_stages( + let builder = if self.offline { + Pipeline::builder().add_stages( + OfflineStages::new(executor, config.stages, PruneModes::default()) + .builder() + .disable(reth_stages::StageId::SenderRecovery), + ) + } else { + Pipeline::builder().with_tip_sender(tip_tx).add_stages( DefaultStages::new( provider_factory.clone(), tip_rx, @@ -131,10 +155,12 @@ impl Command { ExExManagerHandle::empty(), )), ) - .build( - provider_factory.clone(), - StaticFileProducer::new(provider_factory, PruneModes::default()), - ); + }; + + let pipeline = builder.build( + provider_factory.clone(), + StaticFileProducer::new(provider_factory, PruneModes::default()), + ); Ok(pipeline) } } diff --git a/book/cli/reth/stage/unwind.md b/book/cli/reth/stage/unwind.md index a1a538f3b1dc..3af76e1d567e 100644 --- a/book/cli/reth/stage/unwind.md +++ b/book/cli/reth/stage/unwind.md @@ -204,6 +204,9 @@ Networking: [default: 131072] + --offline + If this is enabled, then all stages except headers, bodies, and sender recovery will be unwound + Logging: --log.stdout.format The format to use for logs written to stdout From 83ef1f7641d5ff696441875882e8b7d0d4a32bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Tue, 25 Jun 2024 23:30:12 +0200 Subject: [PATCH 214/405] Add a metric for blob transactions nonce gaps (#9106) --- crates/transaction-pool/src/metrics.rs | 2 ++ crates/transaction-pool/src/pool/txpool.rs | 2 ++ etc/grafana/dashboards/reth-mempool.json | 17 +++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/crates/transaction-pool/src/metrics.rs b/crates/transaction-pool/src/metrics.rs index 90d46854d43c..d61816831294 100644 --- a/crates/transaction-pool/src/metrics.rs +++ b/crates/transaction-pool/src/metrics.rs @@ -104,4 +104,6 @@ pub struct AllTransactionsMetrics { pub(crate) all_transactions_by_id: Gauge, /// Number of all transactions by all senders in the pool pub(crate) all_transactions_by_all_senders: Gauge, + /// Number of blob transactions nonce gaps. + pub(crate) blob_transactions_nonce_gaps: Counter, } diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 48048412eba0..3e22bb9ca99b 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -1335,11 +1335,13 @@ impl AllTransactions { if let Some(ancestor) = ancestor { let Some(ancestor_tx) = self.txs.get(&ancestor) else { // ancestor tx is missing, so we can't insert the new blob + self.metrics.blob_transactions_nonce_gaps.increment(1); return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) }) }; if ancestor_tx.state.has_nonce_gap() { // the ancestor transaction already has a nonce gap, so we can't insert the new // blob + self.metrics.blob_transactions_nonce_gaps.increment(1); return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) }) } diff --git a/etc/grafana/dashboards/reth-mempool.json b/etc/grafana/dashboards/reth-mempool.json index 092faaccb878..90fe5ba8d2d3 100644 --- a/etc/grafana/dashboards/reth-mempool.json +++ b/etc/grafana/dashboards/reth-mempool.json @@ -1719,6 +1719,23 @@ "range": true, "refId": "C", "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "reth_transaction_pool_blob_transactions_nonce_gaps{instance=~\"$instance\"}", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Blob transactions nonce gaps", + "range": true, + "refId": "D", + "useBackend": false } ], "title": "All Transactions metrics", From b36bd58dbe1f2639ec5bb6bd5bcbc7b5a3cfb6fe Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 25 Jun 2024 23:30:58 +0100 Subject: [PATCH 215/405] feat(cli): fail on invalid config (#9107) --- bin/reth/src/commands/common.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/reth/src/commands/common.rs b/bin/reth/src/commands/common.rs index 31c0329a5540..41758a9f09da 100644 --- a/bin/reth/src/commands/common.rs +++ b/bin/reth/src/commands/common.rs @@ -65,7 +65,11 @@ impl EnvironmentArgs { } let config_path = self.config.clone().unwrap_or_else(|| data_dir.config()); - let mut config: Config = confy::load_path(config_path).unwrap_or_default(); + let mut config: Config = confy::load_path(config_path) + .inspect_err( + |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"), + ) + .unwrap_or_default(); // Make sure ETL doesn't default to /tmp/, but to whatever datadir is set to if config.stages.etl.dir.is_none() { From ce5a191af6ec9de3734d80d5ca106b087f35efcf Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 26 Jun 2024 11:52:56 +0200 Subject: [PATCH 216/405] chore: import from static files crate directly (#9111) --- bin/reth/src/commands/db/clear.rs | 2 +- bin/reth/src/commands/db/get.rs | 3 ++- bin/reth/src/commands/db/stats.rs | 2 +- bin/reth/src/commands/import_receipts_op.rs | 3 ++- bin/reth/src/commands/stage/drop.rs | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bin/reth/src/commands/db/clear.rs b/bin/reth/src/commands/db/clear.rs index 76c1b97e38ad..b9edf458d3f4 100644 --- a/bin/reth/src/commands/db/clear.rs +++ b/bin/reth/src/commands/db/clear.rs @@ -5,8 +5,8 @@ use reth_db_api::{ table::Table, transaction::{DbTx, DbTxMut}, }; -use reth_primitives::{static_file::find_fixed_range, StaticFileSegment}; use reth_provider::{ProviderFactory, StaticFileProviderFactory}; +use reth_static_file_types::{find_fixed_range, StaticFileSegment}; /// The arguments for the `reth db clear` command #[derive(Parser, Debug)] diff --git a/bin/reth/src/commands/db/get.rs b/bin/reth/src/commands/db/get.rs index 699a31471802..a4987e4b4281 100644 --- a/bin/reth/src/commands/db/get.rs +++ b/bin/reth/src/commands/db/get.rs @@ -8,8 +8,9 @@ use reth_db_api::{ database::Database, table::{Decompress, DupSort, Table}, }; -use reth_primitives::{BlockHash, Header, StaticFileSegment}; +use reth_primitives::{BlockHash, Header}; use reth_provider::StaticFileProviderFactory; +use reth_static_file_types::StaticFileSegment; use tracing::error; /// The arguments for the `reth db get` command diff --git a/bin/reth/src/commands/db/stats.rs b/bin/reth/src/commands/db/stats.rs index 517b9c9e591f..9e1eebd95220 100644 --- a/bin/reth/src/commands/db/stats.rs +++ b/bin/reth/src/commands/db/stats.rs @@ -8,8 +8,8 @@ use reth_db::{mdbx, static_file::iter_static_files, DatabaseEnv, TableViewer, Ta use reth_db_api::database::Database; use reth_fs_util as fs; use reth_node_core::dirs::{ChainPath, DataDirPath}; -use reth_primitives::static_file::{find_fixed_range, SegmentRangeInclusive}; use reth_provider::providers::StaticFileProvider; +use reth_static_file_types::{find_fixed_range, SegmentRangeInclusive}; use std::{sync::Arc, time::Duration}; #[derive(Parser, Debug)] diff --git a/bin/reth/src/commands/import_receipts_op.rs b/bin/reth/src/commands/import_receipts_op.rs index d77332f86be2..7623c626cb02 100644 --- a/bin/reth/src/commands/import_receipts_op.rs +++ b/bin/reth/src/commands/import_receipts_op.rs @@ -13,12 +13,13 @@ use reth_downloaders::{ use reth_execution_types::ExecutionOutcome; use reth_node_core::version::SHORT_VERSION; use reth_optimism_primitives::bedrock_import::is_dup_tx; -use reth_primitives::{Receipts, StaticFileSegment}; +use reth_primitives::Receipts; use reth_provider::{ OriginalValuesKnown, ProviderFactory, StageCheckpointReader, StateWriter, StaticFileProviderFactory, StaticFileWriter, StatsReader, }; use reth_stages::StageId; +use reth_static_file_types::StaticFileSegment; use std::path::{Path, PathBuf}; use tracing::{debug, error, info, trace}; diff --git a/bin/reth/src/commands/stage/drop.rs b/bin/reth/src/commands/stage/drop.rs index 8297eafef81a..320c88682a8d 100644 --- a/bin/reth/src/commands/stage/drop.rs +++ b/bin/reth/src/commands/stage/drop.rs @@ -10,9 +10,9 @@ use itertools::Itertools; use reth_db::{static_file::iter_static_files, tables, DatabaseEnv}; use reth_db_api::transaction::DbTxMut; use reth_db_common::init::{insert_genesis_header, insert_genesis_history, insert_genesis_state}; -use reth_primitives::{static_file::find_fixed_range, StaticFileSegment}; use reth_provider::{providers::StaticFileWriter, StaticFileProviderFactory}; use reth_stages::StageId; +use reth_static_file_types::{find_fixed_range, StaticFileSegment}; /// `reth drop-stage` command #[derive(Debug, Parser)] From 832f7a5170974246abd7558eda9a90cb158bf822 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 26 Jun 2024 12:07:49 +0200 Subject: [PATCH 217/405] test: fix flaky connect (#9113) --- crates/e2e-test-utils/src/network.rs | 21 ++++++++++++++------- crates/e2e-test-utils/src/node.rs | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/e2e-test-utils/src/network.rs b/crates/e2e-test-utils/src/network.rs index 2bb69044fc42..8c1a60ba7bd9 100644 --- a/crates/e2e-test-utils/src/network.rs +++ b/crates/e2e-test-utils/src/network.rs @@ -1,5 +1,8 @@ use futures_util::StreamExt; -use reth::network::{NetworkEvent, NetworkEvents, NetworkHandle, PeersInfo}; +use reth::{ + network::{NetworkEvent, NetworkEvents, NetworkHandle, PeersInfo}, + rpc::types::PeerId, +}; use reth_chainspec::net::NodeRecord; use reth_tokio_util::EventStream; use reth_tracing::tracing::info; @@ -32,13 +35,17 @@ impl NetworkTestContext { self.network.local_node_record() } - /// Expects a session to be established - pub async fn expect_session(&mut self) { - match self.network_events.next().await { - Some(NetworkEvent::SessionEstablished { remote_addr, .. }) => { - info!(?remote_addr, "Session established") + /// Awaits the next event for an established session. + pub async fn next_session_established(&mut self) -> Option { + while let Some(ev) = self.network_events.next().await { + match ev { + NetworkEvent::SessionEstablished { peer_id, .. } => { + info!("Session established with peer: {:?}", peer_id); + return Some(peer_id) + } + _ => continue, } - ev => panic!("Expected session established event, got: {ev:?}"), } + None } } diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index 524a5d5562be..684e0f401c1b 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -52,11 +52,11 @@ where }) } + /// Establish a connection to the node pub async fn connect(&mut self, node: &mut NodeTestContext) { self.network.add_peer(node.network.record()).await; - node.network.add_peer(self.network.record()).await; - node.network.expect_session().await; - self.network.expect_session().await; + node.network.next_session_established().await; + self.network.next_session_established().await; } /// Advances the chain `length` blocks. From 2a2eb0e0eda6dc148be249d13fb40659a09bd45c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 26 Jun 2024 12:04:14 +0200 Subject: [PATCH 218/405] chore: rm beta checks (#9116) --- .github/workflows/docker.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 20ae6644b909..2af324a39eb7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,14 +39,10 @@ jobs: docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 docker buildx create --use --name cross-builder - name: Build and push reth image, tag as "latest" - if: ${{ contains(github.event.ref, 'beta') }} run: make PROFILE=maxperf docker-build-push-latest - name: Build and push reth image - if: ${{ ! contains(github.event.ref, 'beta') }} run: make PROFILE=maxperf docker-build-push - name: Build and push op-reth image, tag as "latest" - if: ${{ contains(github.event.ref, 'beta') }} run: make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push-latest - name: Build and push op-reth image - if: ${{ ! contains(github.event.ref, 'beta') }} run: make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push From 221035d73f522b7a7555ad498eadc0c8ac5d9fc0 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 26 Jun 2024 12:07:09 +0200 Subject: [PATCH 219/405] chore(deps): bump ratatui 0.27 (#9110) --- Cargo.lock | 7 ++++--- bin/reth/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 400aba307a9f..76615d5719ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6031,19 +6031,20 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.26.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" dependencies = [ "bitflags 2.5.0", "cassowary", "compact_str", "crossterm", - "itertools 0.12.1", + "itertools 0.13.0", "lru", "paste", "stability", "strum", + "strum_macros", "unicode-segmentation", "unicode-truncate", "unicode-width", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 1440922ee225..dd7b6ee034a0 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -91,9 +91,9 @@ proptest-arbitrary-interop.workspace = true rand.workspace = true # tui -comfy-table = "7.0" +comfy-table = "7.1" crossterm = "0.27.0" -ratatui = { version = "0.26", default-features = false, features = [ +ratatui = { version = "0.27", default-features = false, features = [ "crossterm", ] } human_bytes = "0.4.1" From 1d1fb797e237cd856334991e9260e043225b94d5 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 26 Jun 2024 13:31:30 +0100 Subject: [PATCH 220/405] feat(ci): update GPG key in release workflow (#9121) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c71b8c530990..5735ae6ef528 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -175,7 +175,7 @@ jobs: [See pre-built binaries documentation.](https://paradigmxyz.github.io/reth/installation/binaries.html) - The binaries are signed with the PGP key: `A3AE 097C 8909 3A12 4049 DF1F 5391 A3C4 1005 30B4` + The binaries are signed with the PGP key: `50FB 7CC5 5B2E 8AFA 59FE 03B7 AA5E D56A 7FBF 253E` | System | Architecture | Binary | PGP Signature | |:---:|:---:|:---:|:---| From bdabe66426412ce0d7950c0ddafa31a46521c3df Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 26 Jun 2024 16:05:18 +0200 Subject: [PATCH 221/405] refactor: move `DbTool` type to `db-common` (#9119) --- Cargo.lock | 3 +- Cargo.toml | 1 + bin/reth/Cargo.toml | 1 - bin/reth/src/commands/db/checksum.rs | 6 +- bin/reth/src/commands/db/diff.rs | 2 +- bin/reth/src/commands/db/get.rs | 2 +- bin/reth/src/commands/db/list.rs | 2 +- bin/reth/src/commands/db/mod.rs | 6 +- bin/reth/src/commands/db/stats.rs | 3 +- bin/reth/src/commands/stage/drop.rs | 6 +- bin/reth/src/commands/stage/dump/execution.rs | 3 +- .../commands/stage/dump/hashing_account.rs | 2 +- .../commands/stage/dump/hashing_storage.rs | 2 +- bin/reth/src/commands/stage/dump/merkle.rs | 3 +- bin/reth/src/commands/stage/dump/mod.rs | 5 +- bin/reth/src/utils.rs | 188 ----------------- crates/storage/db-common/Cargo.toml | 2 + crates/storage/db-common/src/db_tool/mod.rs | 189 ++++++++++++++++++ crates/storage/db-common/src/lib.rs | 3 + 19 files changed, 218 insertions(+), 211 deletions(-) create mode 100644 crates/storage/db-common/src/db_tool/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 76615d5719ce..92cbc1734016 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6233,7 +6233,6 @@ dependencies = [ "arbitrary", "assert_matches", "backon", - "boyer-moore-magiclen", "clap", "comfy-table", "confy", @@ -6700,6 +6699,7 @@ name = "reth-db-common" version = "1.0.0" dependencies = [ "alloy-genesis", + "boyer-moore-magiclen", "eyre", "reth-chainspec", "reth-codecs", @@ -6707,6 +6707,7 @@ dependencies = [ "reth-db", "reth-db-api", "reth-etl", + "reth-fs-util", "reth-primitives", "reth-primitives-traits", "reth-provider", diff --git a/Cargo.toml b/Cargo.toml index a7e1ab3d92ae..e0b2cf90b447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -439,6 +439,7 @@ sha2 = { version = "0.10", default-features = false } paste = "1.0" url = "2.3" backon = "0.4" +boyer-moore-magiclen = "0.2.16" # metrics metrics = "0.23.0" diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index dd7b6ee034a0..8c8e8a0e71a2 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -116,7 +116,6 @@ backon.workspace = true similar-asserts.workspace = true itertools.workspace = true rayon.workspace = true -boyer-moore-magiclen = "0.2.16" ahash = "0.8" # p2p diff --git a/bin/reth/src/commands/db/checksum.rs b/bin/reth/src/commands/db/checksum.rs index 6aa6b69e6d3b..9af9a2321637 100644 --- a/bin/reth/src/commands/db/checksum.rs +++ b/bin/reth/src/commands/db/checksum.rs @@ -1,11 +1,9 @@ -use crate::{ - commands::db::get::{maybe_json_value_parser, table_key}, - utils::DbTool, -}; +use crate::commands::db::get::{maybe_json_value_parser, table_key}; use ahash::RandomState; use clap::Parser; use reth_db::{DatabaseEnv, RawKey, RawTable, RawValue, TableViewer, Tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, table::Table, transaction::DbTx}; +use reth_db_common::DbTool; use std::{ hash::{BuildHasher, Hasher}, sync::Arc, diff --git a/bin/reth/src/commands/db/diff.rs b/bin/reth/src/commands/db/diff.rs index 246b107fa4a1..cd9e24c1d761 100644 --- a/bin/reth/src/commands/db/diff.rs +++ b/bin/reth/src/commands/db/diff.rs @@ -1,11 +1,11 @@ use crate::{ args::DatabaseArgs, dirs::{DataDirPath, PlatformPath}, - utils::DbTool, }; use clap::Parser; use reth_db::{open_db_read_only, tables_to_generic, DatabaseEnv, Tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, table::Table, transaction::DbTx}; +use reth_db_common::DbTool; use std::{ collections::HashMap, fmt::Debug, diff --git a/bin/reth/src/commands/db/get.rs b/bin/reth/src/commands/db/get.rs index a4987e4b4281..cd721a1db4b1 100644 --- a/bin/reth/src/commands/db/get.rs +++ b/bin/reth/src/commands/db/get.rs @@ -1,4 +1,3 @@ -use crate::utils::DbTool; use clap::Parser; use reth_db::{ static_file::{ColumnSelectorOne, ColumnSelectorTwo, HeaderMask, ReceiptMask, TransactionMask}, @@ -8,6 +7,7 @@ use reth_db_api::{ database::Database, table::{Decompress, DupSort, Table}, }; +use reth_db_common::DbTool; use reth_primitives::{BlockHash, Header}; use reth_provider::StaticFileProviderFactory; use reth_static_file_types::StaticFileSegment; diff --git a/bin/reth/src/commands/db/list.rs b/bin/reth/src/commands/db/list.rs index dd1a1846acbd..ed337bdcf81e 100644 --- a/bin/reth/src/commands/db/list.rs +++ b/bin/reth/src/commands/db/list.rs @@ -1,9 +1,9 @@ use super::tui::DbListTUI; -use crate::utils::{DbTool, ListFilter}; use clap::Parser; use eyre::WrapErr; use reth_db::{DatabaseEnv, RawValue, TableViewer, Tables}; use reth_db_api::{database::Database, table::Table}; +use reth_db_common::{DbTool, ListFilter}; use reth_primitives::hex; use std::{cell::RefCell, sync::Arc}; use tracing::error; diff --git a/bin/reth/src/commands/db/mod.rs b/bin/reth/src/commands/db/mod.rs index fcafcc41ac09..736d825c31b2 100644 --- a/bin/reth/src/commands/db/mod.rs +++ b/bin/reth/src/commands/db/mod.rs @@ -1,11 +1,9 @@ //! Database debugging tool -use crate::{ - commands::common::{AccessRights, Environment, EnvironmentArgs}, - utils::DbTool, -}; +use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; use clap::{Parser, Subcommand}; use reth_db::version::{get_db_version, DatabaseVersionError, DB_VERSION}; +use reth_db_common::DbTool; use std::io::{self, Write}; mod checksum; diff --git a/bin/reth/src/commands/db/stats.rs b/bin/reth/src/commands/db/stats.rs index 9e1eebd95220..b1a979e54918 100644 --- a/bin/reth/src/commands/db/stats.rs +++ b/bin/reth/src/commands/db/stats.rs @@ -1,4 +1,4 @@ -use crate::{commands::db::checksum::ChecksumViewer, utils::DbTool}; +use crate::commands::db::checksum::ChecksumViewer; use clap::Parser; use comfy_table::{Cell, Row, Table as ComfyTable}; use eyre::WrapErr; @@ -6,6 +6,7 @@ use human_bytes::human_bytes; use itertools::Itertools; use reth_db::{mdbx, static_file::iter_static_files, DatabaseEnv, TableViewer, Tables}; use reth_db_api::database::Database; +use reth_db_common::DbTool; use reth_fs_util as fs; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_provider::providers::StaticFileProvider; diff --git a/bin/reth/src/commands/stage/drop.rs b/bin/reth/src/commands/stage/drop.rs index 320c88682a8d..ec32af330e97 100644 --- a/bin/reth/src/commands/stage/drop.rs +++ b/bin/reth/src/commands/stage/drop.rs @@ -3,13 +3,15 @@ use crate::{ args::StageEnum, commands::common::{AccessRights, Environment, EnvironmentArgs}, - utils::DbTool, }; use clap::Parser; use itertools::Itertools; use reth_db::{static_file::iter_static_files, tables, DatabaseEnv}; use reth_db_api::transaction::DbTxMut; -use reth_db_common::init::{insert_genesis_header, insert_genesis_history, insert_genesis_state}; +use reth_db_common::{ + init::{insert_genesis_header, insert_genesis_history, insert_genesis_state}, + DbTool, +}; use reth_provider::{providers::StaticFileWriter, StaticFileProviderFactory}; use reth_stages::StageId; use reth_static_file_types::{find_fixed_range, StaticFileSegment}; diff --git a/bin/reth/src/commands/stage/dump/execution.rs b/bin/reth/src/commands/stage/dump/execution.rs index b6d6721dcf8d..67b6d5a659c4 100644 --- a/bin/reth/src/commands/stage/dump/execution.rs +++ b/bin/reth/src/commands/stage/dump/execution.rs @@ -1,9 +1,10 @@ use super::setup; -use crate::{macros::block_executor, utils::DbTool}; +use crate::macros::block_executor; use reth_db::{tables, DatabaseEnv}; use reth_db_api::{ cursor::DbCursorRO, database::Database, table::TableImporter, transaction::DbTx, }; +use reth_db_common::DbTool; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_provider::{providers::StaticFileProvider, ChainSpecProvider, ProviderFactory}; use reth_stages::{stages::ExecutionStage, Stage, StageCheckpoint, UnwindInput}; diff --git a/bin/reth/src/commands/stage/dump/hashing_account.rs b/bin/reth/src/commands/stage/dump/hashing_account.rs index 2e50a8ad6059..899b521fdc57 100644 --- a/bin/reth/src/commands/stage/dump/hashing_account.rs +++ b/bin/reth/src/commands/stage/dump/hashing_account.rs @@ -1,8 +1,8 @@ use super::setup; -use crate::utils::DbTool; use eyre::Result; use reth_db::{tables, DatabaseEnv}; use reth_db_api::{database::Database, table::TableImporter}; +use reth_db_common::DbTool; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_primitives::BlockNumber; use reth_provider::{providers::StaticFileProvider, ProviderFactory}; diff --git a/bin/reth/src/commands/stage/dump/hashing_storage.rs b/bin/reth/src/commands/stage/dump/hashing_storage.rs index 1dfd722f5099..f05ac390dc8e 100644 --- a/bin/reth/src/commands/stage/dump/hashing_storage.rs +++ b/bin/reth/src/commands/stage/dump/hashing_storage.rs @@ -1,8 +1,8 @@ use super::setup; -use crate::utils::DbTool; use eyre::Result; use reth_db::{tables, DatabaseEnv}; use reth_db_api::{database::Database, table::TableImporter}; +use reth_db_common::DbTool; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_provider::{providers::StaticFileProvider, ProviderFactory}; use reth_stages::{stages::StorageHashingStage, Stage, StageCheckpoint, UnwindInput}; diff --git a/bin/reth/src/commands/stage/dump/merkle.rs b/bin/reth/src/commands/stage/dump/merkle.rs index fa345bb474a4..a81d5f524539 100644 --- a/bin/reth/src/commands/stage/dump/merkle.rs +++ b/bin/reth/src/commands/stage/dump/merkle.rs @@ -1,9 +1,10 @@ use super::setup; -use crate::{macros::block_executor, utils::DbTool}; +use crate::macros::block_executor; use eyre::Result; use reth_config::config::EtlConfig; use reth_db::{tables, DatabaseEnv}; use reth_db_api::{database::Database, table::TableImporter}; +use reth_db_common::DbTool; use reth_exex::ExExManagerHandle; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_primitives::BlockNumber; diff --git a/bin/reth/src/commands/stage/dump/mod.rs b/bin/reth/src/commands/stage/dump/mod.rs index 287708b00d68..f1fbdbbdecbd 100644 --- a/bin/reth/src/commands/stage/dump/mod.rs +++ b/bin/reth/src/commands/stage/dump/mod.rs @@ -1,18 +1,17 @@ //! Database debugging tool use crate::{ + args::DatadirArgs, commands::common::{AccessRights, Environment, EnvironmentArgs}, dirs::DataDirPath, - utils::DbTool, }; - -use crate::args::DatadirArgs; use clap::Parser; use reth_db::{init_db, mdbx::DatabaseArguments, tables, DatabaseEnv}; use reth_db_api::{ cursor::DbCursorRO, database::Database, models::ClientVersion, table::TableImporter, transaction::DbTx, }; +use reth_db_common::DbTool; use reth_node_core::dirs::PlatformPath; use std::path::PathBuf; use tracing::info; diff --git a/bin/reth/src/utils.rs b/bin/reth/src/utils.rs index 1dd4f6893c1f..cf8985b290d9 100644 --- a/bin/reth/src/utils.rs +++ b/bin/reth/src/utils.rs @@ -1,21 +1,5 @@ //! Common CLI utility functions. -use boyer_moore_magiclen::BMByte; -use eyre::Result; -use reth_chainspec::ChainSpec; -use reth_db::{RawTable, TableRawRow}; -use reth_db_api::{ - cursor::{DbCursorRO, DbDupCursorRO}, - database::Database, - table::{Decode, Decompress, DupSort, Table, TableRow}, - transaction::{DbTx, DbTxMut}, - DatabaseError, -}; -use reth_fs_util as fs; -use reth_provider::{ChainSpecProvider, ProviderFactory}; -use std::{path::Path, rc::Rc, sync::Arc}; -use tracing::info; - /// Exposing `open_db_read_only` function pub mod db { pub use reth_db::open_db_read_only; @@ -24,175 +8,3 @@ pub mod db { /// Re-exported from `reth_node_core`, also to prevent a breaking change. See the comment on /// the `reth_node_core::args` re-export for more details. pub use reth_node_core::utils::*; - -/// Wrapper over DB that implements many useful DB queries. -#[derive(Debug)] -pub struct DbTool { - /// The provider factory that the db tool will use. - pub provider_factory: ProviderFactory, -} - -impl DbTool { - /// Takes a DB where the tables have already been created. - pub fn new(provider_factory: ProviderFactory) -> eyre::Result { - // Disable timeout because we are entering a TUI which might read for a long time. We - // disable on the [`DbTool`] level since it's only used in the CLI. - provider_factory.provider()?.disable_long_read_transaction_safety(); - Ok(Self { provider_factory }) - } - - /// Get an [`Arc`] to the [`ChainSpec`]. - pub fn chain(&self) -> Arc { - self.provider_factory.chain_spec() - } - - /// Grabs the contents of the table within a certain index range and places the - /// entries into a [`HashMap`][std::collections::HashMap]. - /// - /// [`ListFilter`] can be used to further - /// filter down the desired results. (eg. List only rows which include `0xd3adbeef`) - pub fn list(&self, filter: &ListFilter) -> Result<(Vec>, usize)> { - let bmb = Rc::new(BMByte::from(&filter.search)); - if bmb.is_none() && filter.has_search() { - eyre::bail!("Invalid search.") - } - - let mut hits = 0; - - let data = self.provider_factory.db_ref().view(|tx| { - let mut cursor = - tx.cursor_read::>().expect("Was not able to obtain a cursor."); - - let map_filter = |row: Result, _>| { - if let Ok((k, v)) = row { - let (key, value) = (k.into_key(), v.into_value()); - - if key.len() + value.len() < filter.min_row_size { - return None - } - if key.len() < filter.min_key_size { - return None - } - if value.len() < filter.min_value_size { - return None - } - - let result = || { - if filter.only_count { - return None - } - Some(( - ::Key::decode(&key).unwrap(), - ::Value::decompress(&value).unwrap(), - )) - }; - - match &*bmb { - Some(searcher) => { - if searcher.find_first_in(&value).is_some() || - searcher.find_first_in(&key).is_some() - { - hits += 1; - return result() - } - } - None => { - hits += 1; - return result() - } - } - } - None - }; - - if filter.reverse { - Ok(cursor - .walk_back(None)? - .skip(filter.skip) - .filter_map(map_filter) - .take(filter.len) - .collect::>()) - } else { - Ok(cursor - .walk(None)? - .skip(filter.skip) - .filter_map(map_filter) - .take(filter.len) - .collect::>()) - } - })?; - - Ok((data.map_err(|e: DatabaseError| eyre::eyre!(e))?, hits)) - } - - /// Grabs the content of the table for the given key - pub fn get(&self, key: T::Key) -> Result> { - self.provider_factory.db_ref().view(|tx| tx.get::(key))?.map_err(|e| eyre::eyre!(e)) - } - - /// Grabs the content of the `DupSort` table for the given key and subkey - pub fn get_dup(&self, key: T::Key, subkey: T::SubKey) -> Result> { - self.provider_factory - .db_ref() - .view(|tx| tx.cursor_dup_read::()?.seek_by_key_subkey(key, subkey))? - .map_err(|e| eyre::eyre!(e)) - } - - /// Drops the database and the static files at the given path. - pub fn drop( - &self, - db_path: impl AsRef, - static_files_path: impl AsRef, - ) -> Result<()> { - let db_path = db_path.as_ref(); - info!(target: "reth::cli", "Dropping database at {:?}", db_path); - fs::remove_dir_all(db_path)?; - - let static_files_path = static_files_path.as_ref(); - info!(target: "reth::cli", "Dropping static files at {:?}", static_files_path); - fs::remove_dir_all(static_files_path)?; - fs::create_dir_all(static_files_path)?; - - Ok(()) - } - - /// Drops the provided table from the database. - pub fn drop_table(&self) -> Result<()> { - self.provider_factory.db_ref().update(|tx| tx.clear::())??; - Ok(()) - } -} - -/// Filters the results coming from the database. -#[derive(Debug)] -pub struct ListFilter { - /// Skip first N entries. - pub skip: usize, - /// Take N entries. - pub len: usize, - /// Sequence of bytes that will be searched on values and keys from the database. - pub search: Vec, - /// Minimum row size. - pub min_row_size: usize, - /// Minimum key size. - pub min_key_size: usize, - /// Minimum value size. - pub min_value_size: usize, - /// Reverse order of entries. - pub reverse: bool, - /// Only counts the number of filtered entries without decoding and returning them. - pub only_count: bool, -} - -impl ListFilter { - /// If `search` has a list of bytes, then filter for rows that have this sequence. - pub fn has_search(&self) -> bool { - !self.search.is_empty() - } - - /// Updates the page with new `skip` and `len` values. - pub fn update_page(&mut self, skip: usize, len: usize) { - self.skip = skip; - self.len = len; - } -} diff --git a/crates/storage/db-common/Cargo.toml b/crates/storage/db-common/Cargo.toml index c2760f672793..d80236defd32 100644 --- a/crates/storage/db-common/Cargo.toml +++ b/crates/storage/db-common/Cargo.toml @@ -19,6 +19,7 @@ reth-trie.workspace = true reth-etl.workspace = true reth-codecs.workspace = true reth-stages-types.workspace = true +reth-fs-util.workspace = true # eth alloy-genesis.workspace = true @@ -26,6 +27,7 @@ alloy-genesis.workspace = true # misc eyre.workspace = true thiserror.workspace = true +boyer-moore-magiclen.workspace = true # io serde.workspace = true diff --git a/crates/storage/db-common/src/db_tool/mod.rs b/crates/storage/db-common/src/db_tool/mod.rs new file mode 100644 index 000000000000..3884089b4370 --- /dev/null +++ b/crates/storage/db-common/src/db_tool/mod.rs @@ -0,0 +1,189 @@ +//! Common db operations + +use boyer_moore_magiclen::BMByte; +use eyre::Result; +use reth_chainspec::ChainSpec; +use reth_db::{RawTable, TableRawRow}; +use reth_db_api::{ + cursor::{DbCursorRO, DbDupCursorRO}, + database::Database, + table::{Decode, Decompress, DupSort, Table, TableRow}, + transaction::{DbTx, DbTxMut}, + DatabaseError, +}; +use reth_fs_util as fs; +use reth_provider::{ChainSpecProvider, ProviderFactory}; +use std::{path::Path, rc::Rc, sync::Arc}; +use tracing::info; + +/// Wrapper over DB that implements many useful DB queries. +#[derive(Debug)] +pub struct DbTool { + /// The provider factory that the db tool will use. + pub provider_factory: ProviderFactory, +} + +impl DbTool { + /// Takes a DB where the tables have already been created. + pub fn new(provider_factory: ProviderFactory) -> eyre::Result { + // Disable timeout because we are entering a TUI which might read for a long time. We + // disable on the [`DbTool`] level since it's only used in the CLI. + provider_factory.provider()?.disable_long_read_transaction_safety(); + Ok(Self { provider_factory }) + } + + /// Get an [`Arc`] to the [`ChainSpec`]. + pub fn chain(&self) -> Arc { + self.provider_factory.chain_spec() + } + + /// Grabs the contents of the table within a certain index range and places the + /// entries into a [`HashMap`][std::collections::HashMap]. + /// + /// [`ListFilter`] can be used to further + /// filter down the desired results. (eg. List only rows which include `0xd3adbeef`) + pub fn list(&self, filter: &ListFilter) -> Result<(Vec>, usize)> { + let bmb = Rc::new(BMByte::from(&filter.search)); + if bmb.is_none() && filter.has_search() { + eyre::bail!("Invalid search.") + } + + let mut hits = 0; + + let data = self.provider_factory.db_ref().view(|tx| { + let mut cursor = + tx.cursor_read::>().expect("Was not able to obtain a cursor."); + + let map_filter = |row: Result, _>| { + if let Ok((k, v)) = row { + let (key, value) = (k.into_key(), v.into_value()); + + if key.len() + value.len() < filter.min_row_size { + return None + } + if key.len() < filter.min_key_size { + return None + } + if value.len() < filter.min_value_size { + return None + } + + let result = || { + if filter.only_count { + return None + } + Some(( + ::Key::decode(&key).unwrap(), + ::Value::decompress(&value).unwrap(), + )) + }; + + match &*bmb { + Some(searcher) => { + if searcher.find_first_in(&value).is_some() || + searcher.find_first_in(&key).is_some() + { + hits += 1; + return result() + } + } + None => { + hits += 1; + return result() + } + } + } + None + }; + + if filter.reverse { + Ok(cursor + .walk_back(None)? + .skip(filter.skip) + .filter_map(map_filter) + .take(filter.len) + .collect::>()) + } else { + Ok(cursor + .walk(None)? + .skip(filter.skip) + .filter_map(map_filter) + .take(filter.len) + .collect::>()) + } + })?; + + Ok((data.map_err(|e: DatabaseError| eyre::eyre!(e))?, hits)) + } + + /// Grabs the content of the table for the given key + pub fn get(&self, key: T::Key) -> Result> { + self.provider_factory.db_ref().view(|tx| tx.get::(key))?.map_err(|e| eyre::eyre!(e)) + } + + /// Grabs the content of the `DupSort` table for the given key and subkey + pub fn get_dup(&self, key: T::Key, subkey: T::SubKey) -> Result> { + self.provider_factory + .db_ref() + .view(|tx| tx.cursor_dup_read::()?.seek_by_key_subkey(key, subkey))? + .map_err(|e| eyre::eyre!(e)) + } + + /// Drops the database and the static files at the given path. + pub fn drop( + &self, + db_path: impl AsRef, + static_files_path: impl AsRef, + ) -> Result<()> { + let db_path = db_path.as_ref(); + info!(target: "reth::cli", "Dropping database at {:?}", db_path); + fs::remove_dir_all(db_path)?; + + let static_files_path = static_files_path.as_ref(); + info!(target: "reth::cli", "Dropping static files at {:?}", static_files_path); + fs::remove_dir_all(static_files_path)?; + fs::create_dir_all(static_files_path)?; + + Ok(()) + } + + /// Drops the provided table from the database. + pub fn drop_table(&self) -> Result<()> { + self.provider_factory.db_ref().update(|tx| tx.clear::())??; + Ok(()) + } +} + +/// Filters the results coming from the database. +#[derive(Debug)] +pub struct ListFilter { + /// Skip first N entries. + pub skip: usize, + /// Take N entries. + pub len: usize, + /// Sequence of bytes that will be searched on values and keys from the database. + pub search: Vec, + /// Minimum row size. + pub min_row_size: usize, + /// Minimum key size. + pub min_key_size: usize, + /// Minimum value size. + pub min_value_size: usize, + /// Reverse order of entries. + pub reverse: bool, + /// Only counts the number of filtered entries without decoding and returning them. + pub only_count: bool, +} + +impl ListFilter { + /// If `search` has a list of bytes, then filter for rows that have this sequence. + pub fn has_search(&self) -> bool { + !self.search.is_empty() + } + + /// Updates the page with new `skip` and `len` values. + pub fn update_page(&mut self, skip: usize, len: usize) { + self.skip = skip; + self.len = len; + } +} diff --git a/crates/storage/db-common/src/lib.rs b/crates/storage/db-common/src/lib.rs index abcbc62762a4..173e53143408 100644 --- a/crates/storage/db-common/src/lib.rs +++ b/crates/storage/db-common/src/lib.rs @@ -9,3 +9,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod init; + +mod db_tool; +pub use db_tool::*; From 8775a93d335f2b8ed600bc71b4428188edc02bf0 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 26 Jun 2024 15:54:06 +0100 Subject: [PATCH 222/405] feat(cli): `reth prune` (#9055) --- Cargo.lock | 3 +- bin/reth/Cargo.toml | 2 +- bin/reth/src/cli/mod.rs | 6 +- bin/reth/src/commands/debug_cmd/execution.rs | 2 +- bin/reth/src/commands/debug_cmd/merkle.rs | 2 +- .../src/commands/debug_cmd/replay_engine.rs | 2 +- bin/reth/src/commands/import.rs | 2 +- bin/reth/src/commands/import_op.rs | 2 +- bin/reth/src/commands/mod.rs | 3 +- bin/reth/src/commands/prune.rs | 43 ++++++ bin/reth/src/commands/stage/dump/merkle.rs | 2 +- bin/reth/src/commands/stage/unwind.rs | 2 +- book/SUMMARY.md | 1 + book/cli/SUMMARY.md | 1 + book/cli/reth.md | 1 + book/cli/reth/prune.md | 146 ++++++++++++++++++ crates/stages/api/src/pipeline/mod.rs | 22 +-- crates/static-file/static-file/Cargo.toml | 1 + .../static-file/src/static_file_producer.rs | 30 +++- crates/static-file/types/src/lib.rs | 7 +- 20 files changed, 245 insertions(+), 35 deletions(-) create mode 100644 bin/reth/src/commands/prune.rs create mode 100644 book/cli/reth/prune.md diff --git a/Cargo.lock b/Cargo.lock index 92cbc1734016..b6f3715e2c39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6288,7 +6288,7 @@ dependencies = [ "reth-payload-validator", "reth-primitives", "reth-provider", - "reth-prune-types", + "reth-prune", "reth-revm", "reth-rpc", "reth-rpc-api", @@ -8239,6 +8239,7 @@ dependencies = [ "reth-provider", "reth-prune-types", "reth-stages", + "reth-stages-types", "reth-static-file-types", "reth-storage-errors", "reth-testing-utils", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 8c8e8a0e71a2..69deb9c7bc87 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -66,7 +66,7 @@ reth-node-builder.workspace = true reth-node-events.workspace = true reth-consensus.workspace = true reth-optimism-primitives.workspace = true -reth-prune-types.workspace = true +reth-prune.workspace = true # crypto alloy-rlp.workspace = true diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index ff5c4add541c..4dd567630756 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -8,7 +8,7 @@ use crate::{ commands::{ config_cmd, db, debug_cmd, dump_genesis, import, init_cmd, init_state, node::{self, NoArgs}, - p2p, recover, stage, test_vectors, + p2p, prune, recover, stage, test_vectors, }, version::{LONG_VERSION, SHORT_VERSION}, }; @@ -164,6 +164,7 @@ impl Cli { Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), Commands::Debug(command) => runner.run_command_until_exit(|ctx| command.execute(ctx)), Commands::Recover(command) => runner.run_command_until_exit(|ctx| command.execute(ctx)), + Commands::Prune(command) => runner.run_until_ctrl_c(command.execute()), } } @@ -223,6 +224,9 @@ pub enum Commands { /// Scripts for node recovery #[command(name = "recover")] Recover(recover::Command), + /// Prune according to the configuration without any limits + #[command(name = "prune")] + Prune(prune::PruneCommand), } #[cfg(test)] diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index c1fd4cfa5fa2..9e39c90b39fc 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -26,7 +26,7 @@ use reth_primitives::{BlockHashOrNumber, BlockNumber, B256}; use reth_provider::{ BlockExecutionWriter, ChainSpecProvider, ProviderFactory, StageCheckpointReader, }; -use reth_prune_types::PruneModes; +use reth_prune::PruneModes; use reth_stages::{ sets::DefaultStages, stages::{ExecutionStage, ExecutionStageThresholds}, diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 46e76d1da090..5244cbad316f 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -23,7 +23,7 @@ use reth_provider::{ BlockNumReader, BlockWriter, ChainSpecProvider, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, ProviderFactory, StateWriter, }; -use reth_prune_types::PruneModes; +use reth_prune::PruneModes; use reth_revm::database::StateProviderDatabase; use reth_stages::{ stages::{AccountHashingStage, MerkleStage, StorageHashingStage}, diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 26c8a3558e73..7b8e7167eeeb 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -22,7 +22,7 @@ use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::{ providers::BlockchainProvider, CanonStateSubscriptions, ChainSpecProvider, ProviderFactory, }; -use reth_prune_types::PruneModes; +use reth_prune::PruneModes; use reth_stages::Pipeline; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; diff --git a/bin/reth/src/commands/import.rs b/bin/reth/src/commands/import.rs index 25d1864a2434..71357e083aaf 100644 --- a/bin/reth/src/commands/import.rs +++ b/bin/reth/src/commands/import.rs @@ -27,7 +27,7 @@ use reth_provider::{ BlockNumReader, ChainSpecProvider, HeaderProvider, ProviderError, ProviderFactory, StageCheckpointReader, }; -use reth_prune_types::PruneModes; +use reth_prune::PruneModes; use reth_stages::{prelude::*, Pipeline, StageId, StageSet}; use reth_static_file::StaticFileProducer; use std::{path::PathBuf, sync::Arc}; diff --git a/bin/reth/src/commands/import_op.rs b/bin/reth/src/commands/import_op.rs index 646cd4f97232..f4b8716fe210 100644 --- a/bin/reth/src/commands/import_op.rs +++ b/bin/reth/src/commands/import_op.rs @@ -17,7 +17,7 @@ use reth_downloaders::file_client::{ }; use reth_optimism_primitives::bedrock_import::is_dup_tx; use reth_provider::StageCheckpointReader; -use reth_prune_types::PruneModes; +use reth_prune::PruneModes; use reth_stages::StageId; use reth_static_file::StaticFileProducer; use std::{path::PathBuf, sync::Arc}; diff --git a/bin/reth/src/commands/mod.rs b/bin/reth/src/commands/mod.rs index cd5a7e7ba6a3..0763ecc2203e 100644 --- a/bin/reth/src/commands/mod.rs +++ b/bin/reth/src/commands/mod.rs @@ -7,12 +7,11 @@ pub mod dump_genesis; pub mod import; pub mod import_op; pub mod import_receipts_op; - pub mod init_cmd; pub mod init_state; - pub mod node; pub mod p2p; +pub mod prune; pub mod recover; pub mod stage; pub mod test_vectors; diff --git a/bin/reth/src/commands/prune.rs b/bin/reth/src/commands/prune.rs new file mode 100644 index 000000000000..f3b0fcaab966 --- /dev/null +++ b/bin/reth/src/commands/prune.rs @@ -0,0 +1,43 @@ +//! Command that runs pruning without any limits. + +use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; +use clap::Parser; +use reth_prune::PrunerBuilder; +use reth_static_file::StaticFileProducer; +use tracing::info; + +/// Prunes according to the configuration without any limits +#[derive(Debug, Parser)] +pub struct PruneCommand { + #[command(flatten)] + env: EnvironmentArgs, +} + +impl PruneCommand { + /// Execute the `prune` command + pub async fn execute(self) -> eyre::Result<()> { + let Environment { config, provider_factory, .. } = self.env.init(AccessRights::RW)?; + let prune_config = config.prune.unwrap_or_default(); + + // Copy data from database to static files + info!(target: "reth::cli", "Copying data from database to static files..."); + let static_file_producer = + StaticFileProducer::new(provider_factory.clone(), prune_config.segments.clone()); + let lowest_static_file_height = static_file_producer.lock().copy_to_static_files()?.min(); + info!(target: "reth::cli", ?lowest_static_file_height, "Copied data from database to static files"); + + // Delete data which has been copied to static files. + if let Some(prune_tip) = lowest_static_file_height { + info!(target: "reth::cli", ?prune_tip, ?prune_config, "Pruning data from database..."); + // Run the pruner according to the configuration, and don't enforce any limits on it + let mut pruner = PrunerBuilder::new(prune_config) + .prune_delete_limit(usize::MAX) + .build(provider_factory); + + pruner.run(prune_tip)?; + info!(target: "reth::cli", "Pruned data from database"); + } + + Ok(()) + } +} diff --git a/bin/reth/src/commands/stage/dump/merkle.rs b/bin/reth/src/commands/stage/dump/merkle.rs index a81d5f524539..85fd0bfcab57 100644 --- a/bin/reth/src/commands/stage/dump/merkle.rs +++ b/bin/reth/src/commands/stage/dump/merkle.rs @@ -9,7 +9,7 @@ use reth_exex::ExExManagerHandle; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_primitives::BlockNumber; use reth_provider::{providers::StaticFileProvider, ProviderFactory}; -use reth_prune_types::PruneModes; +use reth_prune::PruneModes; use reth_stages::{ stages::{ AccountHashingStage, ExecutionStage, ExecutionStageThresholds, MerkleStage, diff --git a/bin/reth/src/commands/stage/unwind.rs b/bin/reth/src/commands/stage/unwind.rs index 5e6b62537572..57088eaf2e2d 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/bin/reth/src/commands/stage/unwind.rs @@ -13,7 +13,7 @@ use reth_provider::{ BlockExecutionWriter, BlockNumReader, ChainSpecProvider, FinalizedBlockReader, FinalizedBlockWriter, ProviderFactory, StaticFileProviderFactory, }; -use reth_prune_types::PruneModes; +use reth_prune::PruneModes; use reth_stages::{ sets::{DefaultStages, OfflineStages}, stages::{ExecutionStage, ExecutionStageThresholds}, diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 499b6dd97f9a..ad1a54633674 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -72,6 +72,7 @@ - [`reth debug replay-engine`](./cli/reth/debug/replay-engine.md) - [`reth recover`](./cli/reth/recover.md) - [`reth recover storage-tries`](./cli/reth/recover/storage-tries.md) + - [`reth prune`](./cli/reth/prune.md) - [Developers](./developers/developers.md) - [Execution Extensions](./developers/exex/exex.md) - [How do ExExes work?](./developers/exex/how-it-works.md) diff --git a/book/cli/SUMMARY.md b/book/cli/SUMMARY.md index 089de1b65a67..9f9d0fdb1dc3 100644 --- a/book/cli/SUMMARY.md +++ b/book/cli/SUMMARY.md @@ -43,4 +43,5 @@ - [`reth debug replay-engine`](./reth/debug/replay-engine.md) - [`reth recover`](./reth/recover.md) - [`reth recover storage-tries`](./reth/recover/storage-tries.md) + - [`reth prune`](./reth/prune.md) diff --git a/book/cli/reth.md b/book/cli/reth.md index a4ba8f3d3d9c..b8ac550816d0 100644 --- a/book/cli/reth.md +++ b/book/cli/reth.md @@ -19,6 +19,7 @@ Commands: config Write config to stdout debug Various debug routines recover Scripts for node recovery + prune Prune according to the configuration without any limits help Print this message or the help of the given subcommand(s) Options: diff --git a/book/cli/reth/prune.md b/book/cli/reth/prune.md new file mode 100644 index 000000000000..77ea724abd88 --- /dev/null +++ b/book/cli/reth/prune.md @@ -0,0 +1,146 @@ +# reth prune + +Prune according to the configuration without any limits + +```bash +$ reth prune --help +Usage: reth prune [OPTIONS] + +Options: + --instance + Add a new instance of a node. + + Configures the ports of the node to avoid conflicts with the defaults. This is useful for running multiple nodes on the same machine. + + Max number of instances is 200. It is chosen in a way so that it's not possible to have port numbers that conflict with each other. + + Changes to the following port numbers: - `DISCOVERY_PORT`: default + `instance` - 1 - `AUTH_PORT`: default + `instance` * 100 - 100 - `HTTP_RPC_PORT`: default - `instance` + 1 - `WS_RPC_PORT`: default + `instance` * 2 - 2 + + [default: 1] + + -h, --help + Print help (see a summary with '-h') + +Datadir: + --datadir + The path to the data dir for all reth files and subdirectories. + + Defaults to the OS-specific data directory: + + - Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` + - Windows: `{FOLDERID_RoamingAppData}/reth/` + - macOS: `$HOME/Library/Application Support/reth/` + + [default: default] + + --datadir.static_files + The absolute path to store static files in. + + --config + The path to the configuration file to use + + --chain + The chain this node is running. + Possible values are either a built-in chain or the path to a chain specification file. + + Built-in chains: + mainnet, sepolia, goerli, holesky, dev + + [default: mainnet] + +Database: + --db.log-level + Database logging level. Levels higher than "notice" require a debug build + + Possible values: + - fatal: Enables logging for critical conditions, i.e. assertion failures + - error: Enables logging for error conditions + - warn: Enables logging for warning conditions + - notice: Enables logging for normal but significant condition + - verbose: Enables logging for verbose informational + - debug: Enables logging for debug-level messages + - trace: Enables logging for trace debug-level messages + - extra: Enables logging for extra debug-level messages + + --db.exclusive + Open environment in exclusive/monopolistic mode. Makes it possible to open a database on an NFS volume + + [possible values: true, false] + +Logging: + --log.stdout.format + The format to use for logs written to stdout + + [default: terminal] + + Possible values: + - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging + - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications + - terminal: Represents terminal-friendly formatting for logs + + --log.stdout.filter + The filter to use for logs written to stdout + + [default: ] + + --log.file.format + The format to use for logs written to the log file + + [default: terminal] + + Possible values: + - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging + - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications + - terminal: Represents terminal-friendly formatting for logs + + --log.file.filter + The filter to use for logs written to the log file + + [default: debug] + + --log.file.directory + The path to put log files in + + [default: /logs] + + --log.file.max-size + The maximum size (in MB) of one log file + + [default: 200] + + --log.file.max-files + The maximum amount of log files that will be stored. If set to 0, background file logging is disabled + + [default: 5] + + --log.journald + Write logs to journald + + --log.journald.filter + The filter to use for logs written to journald + + [default: error] + + --color + Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting + + [default: always] + + Possible values: + - always: Colors on + - auto: Colors on + - never: Colors off + +Display: + -v, --verbosity... + Set the minimum log level. + + -v Errors + -vv Warnings + -vvv Info + -vvvv Debug + -vvvvv Traces (warning: very verbose!) + + -q, --quiet + Silence all log output +``` \ No newline at end of file diff --git a/crates/stages/api/src/pipeline/mod.rs b/crates/stages/api/src/pipeline/mod.rs index 67ef53855b15..1be468702e43 100644 --- a/crates/stages/api/src/pipeline/mod.rs +++ b/crates/stages/api/src/pipeline/mod.rs @@ -13,7 +13,6 @@ use reth_provider::{ }; use reth_prune::PrunerBuilder; use reth_static_file::StaticFileProducer; -use reth_static_file_types::HighestStaticFiles; use reth_tokio_util::{EventSender, EventStream}; use std::pin::Pin; use tokio::sync::watch; @@ -248,26 +247,9 @@ where /// CAUTION: This method locks the static file producer Mutex, hence can block the thread if the /// lock is occupied. pub fn move_to_static_files(&self) -> RethResult<()> { - let static_file_producer = self.static_file_producer.lock(); - // Copies data from database to static files - let lowest_static_file_height = { - let provider = self.provider_factory.provider()?; - let stages_checkpoints = [StageId::Headers, StageId::Execution, StageId::Bodies] - .into_iter() - .map(|stage| { - provider.get_stage_checkpoint(stage).map(|c| c.map(|c| c.block_number)) - }) - .collect::, _>>()?; - - let targets = static_file_producer.get_static_file_targets(HighestStaticFiles { - headers: stages_checkpoints[0], - receipts: stages_checkpoints[1], - transactions: stages_checkpoints[2], - })?; - static_file_producer.run(targets)?; - stages_checkpoints.into_iter().min().expect("exists") - }; + let lowest_static_file_height = + self.static_file_producer.lock().copy_to_static_files()?.min(); // Deletes data which has been copied to static files. if let Some(prune_tip) = lowest_static_file_height { diff --git a/crates/static-file/static-file/Cargo.toml b/crates/static-file/static-file/Cargo.toml index 29a601f050d0..1a1921d58c5c 100644 --- a/crates/static-file/static-file/Cargo.toml +++ b/crates/static-file/static-file/Cargo.toml @@ -21,6 +21,7 @@ reth-nippy-jar.workspace = true reth-tokio-util.workspace = true reth-prune-types.workspace = true reth-static-file-types.workspace = true +reth-stages-types.workspace = true alloy-primitives.workspace = true diff --git a/crates/static-file/static-file/src/static_file_producer.rs b/crates/static-file/static-file/src/static_file_producer.rs index 44ea3a5c84b4..eb9422804495 100644 --- a/crates/static-file/static-file/src/static_file_producer.rs +++ b/crates/static-file/static-file/src/static_file_producer.rs @@ -5,8 +5,12 @@ use alloy_primitives::BlockNumber; use parking_lot::Mutex; use rayon::prelude::*; use reth_db_api::database::Database; -use reth_provider::{providers::StaticFileWriter, ProviderFactory, StaticFileProviderFactory}; +use reth_provider::{ + providers::StaticFileWriter, ProviderFactory, StageCheckpointReader as _, + StaticFileProviderFactory, +}; use reth_prune_types::PruneModes; +use reth_stages_types::StageId; use reth_static_file_types::HighestStaticFiles; use reth_storage_errors::provider::ProviderResult; use reth_tokio_util::{EventSender, EventStream}; @@ -56,7 +60,7 @@ pub struct StaticFileProducerInner { event_sender: EventSender, } -/// Static File targets, per data part, measured in [`BlockNumber`]. +/// Static File targets, per data segment, measured in [`BlockNumber`]. #[derive(Debug, Clone, Eq, PartialEq)] pub struct StaticFileTargets { headers: Option>, @@ -167,6 +171,28 @@ impl StaticFileProducerInner { Ok(targets) } + /// Copies data from database to static files according to + /// [stage checkpoints](reth_stages_types::StageCheckpoint). + /// + /// Returns highest block numbers for all static file segments. + pub fn copy_to_static_files(&self) -> ProviderResult { + let provider = self.provider_factory.provider()?; + let stages_checkpoints = [StageId::Headers, StageId::Execution, StageId::Bodies] + .into_iter() + .map(|stage| provider.get_stage_checkpoint(stage).map(|c| c.map(|c| c.block_number))) + .collect::, _>>()?; + + let highest_static_files = HighestStaticFiles { + headers: stages_checkpoints[0], + receipts: stages_checkpoints[1], + transactions: stages_checkpoints[2], + }; + let targets = self.get_static_file_targets(highest_static_files)?; + self.run(targets)?; + + Ok(highest_static_files) + } + /// Returns a static file targets at the provided finalized block numbers per segment. /// The target is determined by the check against highest `static_files` using /// [`reth_provider::providers::StaticFileProvider::get_highest_static_files`]. diff --git a/crates/static-file/types/src/lib.rs b/crates/static-file/types/src/lib.rs index f78d61f6961b..556ec8f90676 100644 --- a/crates/static-file/types/src/lib.rs +++ b/crates/static-file/types/src/lib.rs @@ -20,7 +20,7 @@ pub use segment::{SegmentConfig, SegmentHeader, SegmentRangeInclusive, StaticFil /// Default static file block count. pub const BLOCKS_PER_STATIC_FILE: u64 = 500_000; -/// Highest static file block numbers, per data part. +/// Highest static file block numbers, per data segment. #[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] pub struct HighestStaticFiles { /// Highest static file block of headers, inclusive. @@ -53,6 +53,11 @@ impl HighestStaticFiles { } } + /// Returns the minimum block of all segments. + pub fn min(&self) -> Option { + [self.headers, self.transactions, self.receipts].iter().filter_map(|&option| option).min() + } + /// Returns the maximum block of all segments. pub fn max(&self) -> Option { [self.headers, self.transactions, self.receipts].iter().filter_map(|&option| option).max() From 1fde1dca1e2af4dc2cc5af5a7d3a3d92e0cb1c4c Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:34:35 +0200 Subject: [PATCH 223/405] refactor: move node-core/engine to standalone crate (#9120) Co-authored-by: Matthias Seitz --- Cargo.lock | 25 +++++++++-- Cargo.toml | 2 + bin/reth/Cargo.toml | 2 + .../src/commands/debug_cmd/replay_engine.rs | 2 +- crates/engine/util/Cargo.toml | 42 +++++++++++++++++++ .../util/src}/engine_store.rs | 0 .../engine/mod.rs => engine/util/src/lib.rs} | 0 .../engine => engine/util/src}/skip_fcu.rs | 0 .../util/src}/skip_new_payload.rs | 0 crates/node-core/Cargo.toml | 4 -- crates/node-core/src/lib.rs | 1 - crates/node/builder/Cargo.toml | 1 + crates/node/builder/src/launch/mod.rs | 3 +- 13 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 crates/engine/util/Cargo.toml rename crates/{node-core/src/engine => engine/util/src}/engine_store.rs (100%) rename crates/{node-core/src/engine/mod.rs => engine/util/src/lib.rs} (100%) rename crates/{node-core/src/engine => engine/util/src}/skip_fcu.rs (100%) rename crates/{node-core/src/engine => engine/util/src}/skip_new_payload.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b6f3715e2c39..addac7812a4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6265,6 +6265,7 @@ dependencies = [ "reth-discv4", "reth-discv5", "reth-downloaders", + "reth-engine-util", "reth-errors", "reth-ethereum-payload-builder", "reth-evm", @@ -6289,6 +6290,7 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-prune", + "reth-prune-types", "reth-revm", "reth-rpc", "reth-rpc-api", @@ -6904,6 +6906,24 @@ dependencies = [ "serde", ] +[[package]] +name = "reth-engine-util" +version = "1.0.0" +dependencies = [ + "eyre", + "futures", + "pin-project", + "reth-beacon-consensus", + "reth-engine-primitives", + "reth-fs-util", + "reth-rpc", + "reth-rpc-types", + "serde", + "serde_json", + "tokio-util", + "tracing", +] + [[package]] name = "reth-errors" version = "1.0.0" @@ -7447,6 +7467,7 @@ dependencies = [ "reth-db-api", "reth-db-common", "reth-downloaders", + "reth-engine-util", "reth-evm", "reth-exex", "reth-network", @@ -7494,7 +7515,6 @@ dependencies = [ "metrics-process", "metrics-util", "once_cell", - "pin-project", "procfs", "proptest", "rand 0.8.5", @@ -7506,7 +7526,6 @@ dependencies = [ "reth-db-api", "reth-discv4", "reth-discv5", - "reth-engine-primitives", "reth-fs-util", "reth-metrics", "reth-net-nat", @@ -7527,13 +7546,11 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "secp256k1", - "serde", "serde_json", "shellexpand", "thiserror", "tikv-jemalloc-ctl", "tokio", - "tokio-util", "tower", "tracing", "vergen", diff --git a/Cargo.toml b/Cargo.toml index e0b2cf90b447..3f90af12f287 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "crates/ethereum-forks/", "crates/e2e-test-utils/", "crates/engine-primitives/", + "crates/engine/util/", "crates/errors/", "crates/ethereum-forks/", "crates/ethereum/consensus/", @@ -279,6 +280,7 @@ reth-downloaders = { path = "crates/net/downloaders" } reth-e2e-test-utils = { path = "crates/e2e-test-utils" } reth-ecies = { path = "crates/net/ecies" } reth-engine-primitives = { path = "crates/engine-primitives" } +reth-engine-util = { path = "crates/engine/util" } reth-errors = { path = "crates/errors" } reth-eth-wire = { path = "crates/net/eth-wire" } reth-eth-wire-types = { path = "crates/net/eth-wire-types" } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 69deb9c7bc87..88506f5637f3 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -66,6 +66,8 @@ reth-node-builder.workspace = true reth-node-events.workspace = true reth-consensus.workspace = true reth-optimism-primitives.workspace = true +reth-prune-types.workspace = true +reth-engine-util.workspace = true reth-prune.workspace = true # crypto diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 7b8e7167eeeb..171b828bbc2b 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -14,10 +14,10 @@ use reth_cli_runner::CliContext; use reth_config::Config; use reth_consensus::Consensus; use reth_db::DatabaseEnv; +use reth_engine_util::engine_store::{EngineMessageStore, StoredEngineApiMessage}; use reth_fs_util as fs; use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; -use reth_node_core::engine::engine_store::{EngineMessageStore, StoredEngineApiMessage}; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::{ providers::BlockchainProvider, CanonStateSubscriptions, ChainSpecProvider, ProviderFactory, diff --git a/crates/engine/util/Cargo.toml b/crates/engine/util/Cargo.toml new file mode 100644 index 000000000000..26d504a745af --- /dev/null +++ b/crates/engine/util/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "reth-engine-util" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-fs-util.workspace = true +reth-rpc.workspace = true +reth-rpc-types.workspace = true +reth-engine-primitives.workspace = true +reth-beacon-consensus.workspace = true + +# async +tokio-util.workspace = true +pin-project.workspace = true + +# misc +eyre.workspace = true + +# io +serde.workspace = true +serde_json.workspace = true + +# tracing +tracing.workspace = true + +# async +futures.workspace = true + +[features] +optimism = [ + "reth-rpc/optimism", + "reth-beacon-consensus/optimism", +] diff --git a/crates/node-core/src/engine/engine_store.rs b/crates/engine/util/src/engine_store.rs similarity index 100% rename from crates/node-core/src/engine/engine_store.rs rename to crates/engine/util/src/engine_store.rs diff --git a/crates/node-core/src/engine/mod.rs b/crates/engine/util/src/lib.rs similarity index 100% rename from crates/node-core/src/engine/mod.rs rename to crates/engine/util/src/lib.rs diff --git a/crates/node-core/src/engine/skip_fcu.rs b/crates/engine/util/src/skip_fcu.rs similarity index 100% rename from crates/node-core/src/engine/skip_fcu.rs rename to crates/engine/util/src/skip_fcu.rs diff --git a/crates/node-core/src/engine/skip_new_payload.rs b/crates/engine/util/src/skip_new_payload.rs similarity index 100% rename from crates/node-core/src/engine/skip_new_payload.rs rename to crates/engine/util/src/skip_new_payload.rs diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index d9cae558b29a..82e5c4dfc0fd 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -33,7 +33,6 @@ reth-discv4.workspace = true reth-discv5.workspace = true reth-net-nat.workspace = true reth-network-peers.workspace = true -reth-engine-primitives.workspace = true reth-tasks.workspace = true reth-consensus-common.workspace = true reth-beacon-consensus.workspace = true @@ -46,8 +45,6 @@ alloy-rpc-types-engine.workspace = true # async tokio.workspace = true -tokio-util.workspace = true -pin-project.workspace = true # metrics reth-metrics.workspace = true @@ -69,7 +66,6 @@ once_cell.workspace = true # io dirs-next = "2.0.0" shellexpand = "3.0.0" -serde.workspace = true serde_json.workspace = true # http/rpc diff --git a/crates/node-core/src/lib.rs b/crates/node-core/src/lib.rs index a8761110aeae..5894da8ee9bb 100644 --- a/crates/node-core/src/lib.rs +++ b/crates/node-core/src/lib.rs @@ -11,7 +11,6 @@ pub mod args; pub mod cli; pub mod dirs; -pub mod engine; pub mod exit; pub mod metrics; pub mod node_config; diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index 87746ff13571..fc13a8621c06 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -45,6 +45,7 @@ reth-node-events.workspace = true reth-consensus.workspace = true reth-consensus-debug-client.workspace = true reth-rpc-types.workspace = true +reth-engine-util.workspace = true ## async futures.workspace = true diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 9795cead4155..532e87fecba5 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -13,17 +13,16 @@ use reth_beacon_consensus::{ BeaconConsensusEngine, }; use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider}; +use reth_engine_util::EngineMessageStreamExt; use reth_exex::ExExManagerHandle; use reth_network::NetworkEvents; use reth_node_api::FullNodeTypes; use reth_node_core::{ dirs::{ChainPath, DataDirPath}, - engine::EngineMessageStreamExt, exit::NodeExitFuture, version::{CARGO_PKG_VERSION, CLIENT_CODE, NAME_CLIENT, VERGEN_GIT_SHA}, }; use reth_node_events::{cl::ConsensusLayerHealthEvents, node}; - use reth_primitives::format_ether; use reth_provider::providers::BlockchainProvider; use reth_rpc_engine_api::EngineApi; From 063c08f561e90070414ab5a454e1cdd529de4b02 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Wed, 26 Jun 2024 17:54:26 +0200 Subject: [PATCH 224/405] Subprotocol example (#8991) Co-authored-by: owanikin Co-authored-by: Matthias Seitz --- Cargo.lock | 20 ++++ Cargo.toml | 3 + examples/custom-rlpx-subprotocol/Cargo.toml | 23 ++++ examples/custom-rlpx-subprotocol/src/main.rs | 104 ++++++++++++++++ .../src/subprotocol/connection/handler.rs | 53 ++++++++ .../src/subprotocol/connection/mod.rs | 76 ++++++++++++ .../src/subprotocol/mod.rs | 2 + .../src/subprotocol/protocol/event.rs | 15 +++ .../src/subprotocol/protocol/handler.rs | 34 ++++++ .../src/subprotocol/protocol/mod.rs | 3 + .../src/subprotocol/protocol/proto.rs | 113 ++++++++++++++++++ 11 files changed, 446 insertions(+) create mode 100644 examples/custom-rlpx-subprotocol/Cargo.toml create mode 100644 examples/custom-rlpx-subprotocol/src/main.rs create mode 100644 examples/custom-rlpx-subprotocol/src/subprotocol/connection/handler.rs create mode 100644 examples/custom-rlpx-subprotocol/src/subprotocol/connection/mod.rs create mode 100644 examples/custom-rlpx-subprotocol/src/subprotocol/mod.rs create mode 100644 examples/custom-rlpx-subprotocol/src/subprotocol/protocol/event.rs create mode 100644 examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs create mode 100644 examples/custom-rlpx-subprotocol/src/subprotocol/protocol/mod.rs create mode 100644 examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs diff --git a/Cargo.lock b/Cargo.lock index addac7812a4c..a111aa4a9bfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2810,6 +2810,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "example-custom-rlpx-subprotocol" +version = "0.0.0" +dependencies = [ + "eyre", + "futures", + "rand 0.8.5", + "reth", + "reth-eth-wire", + "reth-network", + "reth-network-api", + "reth-node-ethereum", + "reth-primitives", + "reth-provider", + "reth-rpc-types", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "example-db-access" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 3f90af12f287..961d8f6967dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,9 @@ members = [ "examples/polygon-p2p/", "examples/rpc-db/", "examples/txpool-tracing/", + "examples/custom-rlpx-subprotocol", + "examples/exex/minimal/", + "examples/exex/op-bridge/", "testing/ef-tests/", "testing/testing-utils", ] diff --git a/examples/custom-rlpx-subprotocol/Cargo.toml b/examples/custom-rlpx-subprotocol/Cargo.toml new file mode 100644 index 000000000000..ae3a7c088c04 --- /dev/null +++ b/examples/custom-rlpx-subprotocol/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "example-custom-rlpx-subprotocol" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + + +[dependencies] +tokio = { workspace = true, features = ["full"] } +futures.workspace = true +reth-eth-wire.workspace = true +reth-network.workspace = true +reth-network-api.workspace = true +reth-node-ethereum.workspace = true +reth-provider.workspace = true +reth-primitives.workspace = true +reth-rpc-types.workspace = true +reth.workspace = true +tokio-stream.workspace = true +eyre.workspace = true +rand.workspace = true +tracing.workspace = true diff --git a/examples/custom-rlpx-subprotocol/src/main.rs b/examples/custom-rlpx-subprotocol/src/main.rs new file mode 100644 index 000000000000..3a198c38d285 --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/main.rs @@ -0,0 +1,104 @@ +//! Example for how to customize the network layer by adding a custom rlpx subprotocol. +//! +//! Run with +//! +//! ```not_rust +//! cargo run -p example-custom-rlpx-subprotocol -- node +//! ``` +//! +//! This launch a regular reth node with a custom rlpx subprotocol. +use reth::builder::NodeHandle; +use reth_network::{ + config::SecretKey, protocol::IntoRlpxSubProtocol, NetworkConfig, NetworkManager, + NetworkProtocols, +}; +use reth_network_api::NetworkInfo; +use reth_node_ethereum::EthereumNode; +use reth_provider::test_utils::NoopProvider; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use subprotocol::{ + connection::CustomCommand, + protocol::{ + event::ProtocolEvent, + handler::{CustomRlpxProtoHandler, ProtocolState}, + }, +}; +use tokio::sync::{mpsc, oneshot}; +use tracing::info; + +mod subprotocol; + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _args| async move { + // launch the node + let NodeHandle { node, node_exit_future } = + builder.node(EthereumNode::default()).launch().await?; + let peer_id = node.network.peer_id(); + let peer_addr = node.network.local_addr(); + + // add the custom network subprotocol to the launched node + let (tx, mut from_peer0) = mpsc::unbounded_channel(); + let custom_rlpx_handler = CustomRlpxProtoHandler { state: ProtocolState { events: tx } }; + node.network.add_rlpx_sub_protocol(custom_rlpx_handler.into_rlpx_sub_protocol()); + + // creates a separate network instance and adds the custom network subprotocol + let secret_key = SecretKey::new(&mut rand::thread_rng()); + let (tx, mut from_peer1) = mpsc::unbounded_channel(); + let custom_rlpx_handler_2 = CustomRlpxProtoHandler { state: ProtocolState { events: tx } }; + let net_cfg = NetworkConfig::builder(secret_key) + .listener_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))) + .disable_discovery() + .add_rlpx_sub_protocol(custom_rlpx_handler_2.into_rlpx_sub_protocol()) + .build(NoopProvider::default()); + + // spawn the second network instance + let subnetwork = NetworkManager::new(net_cfg).await?; + let subnetwork_peer_id = *subnetwork.peer_id(); + let subnetwork_peer_addr = subnetwork.local_addr(); + let subnetwork_handle = subnetwork.peers_handle(); + node.task_executor.spawn(subnetwork); + + // connect the launched node to the subnetwork + node.network.peers_handle().add_peer(subnetwork_peer_id, subnetwork_peer_addr); + + // connect the subnetwork to the launched node + subnetwork_handle.add_peer(*peer_id, peer_addr); + + // establish connection between peer0 and peer1 + let peer0_to_peer1 = from_peer0.recv().await.expect("peer0 connecting to peer1"); + let peer0_conn = match peer0_to_peer1 { + ProtocolEvent::Established { direction: _, peer_id, to_connection } => { + assert_eq!(peer_id, subnetwork_peer_id); + to_connection + } + }; + + // establish connection between peer1 and peer0 + let peer1_to_peer0 = from_peer1.recv().await.expect("peer1 connecting to peer0"); + let peer1_conn = match peer1_to_peer0 { + ProtocolEvent::Established { direction: _, peer_id: peer1_id, to_connection } => { + assert_eq!(peer1_id, *peer_id); + to_connection + } + }; + info!(target:"rlpx-subprotocol", "Connection established!"); + + // send a ping message from peer0 to peer1 + let (tx, rx) = oneshot::channel(); + peer0_conn.send(CustomCommand::Message { msg: "hello!".to_string(), response: tx })?; + let response = rx.await?; + assert_eq!(response, "hello!"); + info!(target:"rlpx-subprotocol", ?response, "New message received"); + + // send a ping message from peer1 to peer0 + let (tx, rx) = oneshot::channel(); + peer1_conn.send(CustomCommand::Message { msg: "world!".to_string(), response: tx })?; + let response = rx.await?; + assert_eq!(response, "world!"); + info!(target:"rlpx-subprotocol", ?response, "New message received"); + + info!(target:"rlpx-subprotocol", "Peers connected via custom rlpx subprotocol!"); + + node_exit_future.await + }) +} diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/connection/handler.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/connection/handler.rs new file mode 100644 index 000000000000..dae2d5c8679e --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/connection/handler.rs @@ -0,0 +1,53 @@ +use super::CustomRlpxConnection; +use crate::subprotocol::protocol::{ + event::ProtocolEvent, handler::ProtocolState, proto::CustomRlpxProtoMessage, +}; +use reth_eth_wire::{ + capability::SharedCapabilities, multiplex::ProtocolConnection, protocol::Protocol, +}; +use reth_network::protocol::{ConnectionHandler, OnNotSupported}; +use reth_network_api::Direction; +use reth_rpc_types::PeerId; +use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; + +/// The connection handler for the custom RLPx protocol. +pub(crate) struct CustomRlpxConnectionHandler { + pub(crate) state: ProtocolState, +} + +impl ConnectionHandler for CustomRlpxConnectionHandler { + type Connection = CustomRlpxConnection; + + fn protocol(&self) -> Protocol { + CustomRlpxProtoMessage::protocol() + } + + fn on_unsupported_by_peer( + self, + _supported: &SharedCapabilities, + _direction: Direction, + _peer_id: PeerId, + ) -> OnNotSupported { + OnNotSupported::KeepAlive + } + + fn into_connection( + self, + direction: Direction, + peer_id: PeerId, + conn: ProtocolConnection, + ) -> Self::Connection { + let (tx, rx) = mpsc::unbounded_channel(); + self.state + .events + .send(ProtocolEvent::Established { direction, peer_id, to_connection: tx }) + .ok(); + CustomRlpxConnection { + conn, + initial_ping: direction.is_outgoing().then(CustomRlpxProtoMessage::ping), + commands: UnboundedReceiverStream::new(rx), + pending_pong: None, + } + } +} diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/connection/mod.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/connection/mod.rs new file mode 100644 index 000000000000..a6d835b70c26 --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/connection/mod.rs @@ -0,0 +1,76 @@ +use super::protocol::proto::{CustomRlpxProtoMessage, CustomRlpxProtoMessageKind}; +use futures::{Stream, StreamExt}; +use reth_eth_wire::multiplex::ProtocolConnection; +use reth_primitives::BytesMut; +use std::{ + pin::Pin, + task::{ready, Context, Poll}, +}; +use tokio::sync::oneshot; +use tokio_stream::wrappers::UnboundedReceiverStream; + +pub(crate) mod handler; + +/// We define some custom commands that the subprotocol supports. +pub(crate) enum CustomCommand { + /// Sends a message to the peer + Message { + msg: String, + /// The response will be sent to this channel. + response: oneshot::Sender, + }, +} + +/// The connection handler for the custom RLPx protocol. +pub(crate) struct CustomRlpxConnection { + conn: ProtocolConnection, + initial_ping: Option, + commands: UnboundedReceiverStream, + pending_pong: Option>, +} + +impl Stream for CustomRlpxConnection { + type Item = BytesMut; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + if let Some(initial_ping) = this.initial_ping.take() { + return Poll::Ready(Some(initial_ping.encoded())) + } + + loop { + if let Poll::Ready(Some(cmd)) = this.commands.poll_next_unpin(cx) { + return match cmd { + CustomCommand::Message { msg, response } => { + this.pending_pong = Some(response); + Poll::Ready(Some(CustomRlpxProtoMessage::ping_message(msg).encoded())) + } + } + } + + let Some(msg) = ready!(this.conn.poll_next_unpin(cx)) else { return Poll::Ready(None) }; + + let Some(msg) = CustomRlpxProtoMessage::decode_message(&mut &msg[..]) else { + return Poll::Ready(None) + }; + + match msg.message { + CustomRlpxProtoMessageKind::Ping => { + return Poll::Ready(Some(CustomRlpxProtoMessage::pong().encoded())) + } + CustomRlpxProtoMessageKind::Pong => {} + CustomRlpxProtoMessageKind::PingMessage(msg) => { + return Poll::Ready(Some(CustomRlpxProtoMessage::pong_message(msg).encoded())) + } + CustomRlpxProtoMessageKind::PongMessage(msg) => { + if let Some(sender) = this.pending_pong.take() { + sender.send(msg).ok(); + } + continue + } + } + + return Poll::Pending + } + } +} diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/mod.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/mod.rs new file mode 100644 index 000000000000..53ec0dc1d4e7 --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod connection; +pub(crate) mod protocol; diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/event.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/event.rs new file mode 100644 index 000000000000..ea9e588e592b --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/event.rs @@ -0,0 +1,15 @@ +use crate::subprotocol::connection::CustomCommand; +use reth_network::Direction; +use reth_network_api::PeerId; +use tokio::sync::mpsc; + +/// The events that can be emitted by our custom protocol. +#[derive(Debug)] +pub(crate) enum ProtocolEvent { + Established { + #[allow(dead_code)] + direction: Direction, + peer_id: PeerId, + to_connection: mpsc::UnboundedSender, + }, +} diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs new file mode 100644 index 000000000000..d5a35398dae1 --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs @@ -0,0 +1,34 @@ +use super::event::ProtocolEvent; +use crate::subprotocol::connection::handler::CustomRlpxConnectionHandler; +use reth_network::protocol::ProtocolHandler; +use reth_network_api::PeerId; +use std::net::SocketAddr; +use tokio::sync::mpsc; + +/// Protocol state is an helper struct to store the protocol events. +#[derive(Clone, Debug)] +pub(crate) struct ProtocolState { + pub(crate) events: mpsc::UnboundedSender, +} + +/// The protocol handler takes care of incoming and outgoing connections. +#[derive(Debug)] +pub(crate) struct CustomRlpxProtoHandler { + pub state: ProtocolState, +} + +impl ProtocolHandler for CustomRlpxProtoHandler { + type ConnectionHandler = CustomRlpxConnectionHandler; + + fn on_incoming(&self, _socket_addr: SocketAddr) -> Option { + Some(CustomRlpxConnectionHandler { state: self.state.clone() }) + } + + fn on_outgoing( + &self, + _socket_addr: SocketAddr, + _peer_id: PeerId, + ) -> Option { + Some(CustomRlpxConnectionHandler { state: self.state.clone() }) + } +} diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/mod.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/mod.rs new file mode 100644 index 000000000000..8aba9a4e3506 --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod event; +pub(crate) mod handler; +pub(crate) mod proto; diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs new file mode 100644 index 000000000000..8b179a447d9f --- /dev/null +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs @@ -0,0 +1,113 @@ +//! Simple RLPx Ping Pong protocol that also support sending messages, +//! following [RLPx specs](https://github.com/ethereum/devp2p/blob/master/rlpx.md) + +use reth_eth_wire::{protocol::Protocol, Capability}; +use reth_primitives::{Buf, BufMut, BytesMut}; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum CustomRlpxProtoMessageId { + Ping = 0x00, + Pong = 0x01, + PingMessage = 0x02, + PongMessage = 0x03, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum CustomRlpxProtoMessageKind { + Ping, + Pong, + PingMessage(String), + PongMessage(String), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct CustomRlpxProtoMessage { + pub message_type: CustomRlpxProtoMessageId, + pub message: CustomRlpxProtoMessageKind, +} + +impl CustomRlpxProtoMessage { + /// Returns the capability for the `custom_rlpx` protocol. + pub fn capability() -> Capability { + Capability::new_static("custom_rlpx", 1) + } + + /// Returns the protocol for the `custom_rlpx` protocol. + pub fn protocol() -> Protocol { + Protocol::new(Self::capability(), 4) + } + + /// Creates a ping message + pub fn ping_message(msg: impl Into) -> Self { + Self { + message_type: CustomRlpxProtoMessageId::PingMessage, + message: CustomRlpxProtoMessageKind::PingMessage(msg.into()), + } + } + /// Creates a ping message + pub fn pong_message(msg: impl Into) -> Self { + Self { + message_type: CustomRlpxProtoMessageId::PongMessage, + message: CustomRlpxProtoMessageKind::PongMessage(msg.into()), + } + } + + /// Creates a ping message + pub fn ping() -> Self { + Self { + message_type: CustomRlpxProtoMessageId::Ping, + message: CustomRlpxProtoMessageKind::Ping, + } + } + + /// Creates a pong message + pub fn pong() -> Self { + Self { + message_type: CustomRlpxProtoMessageId::Pong, + message: CustomRlpxProtoMessageKind::Pong, + } + } + + /// Creates a new `CustomRlpxProtoMessage` with the given message ID and payload. + pub fn encoded(&self) -> BytesMut { + let mut buf = BytesMut::new(); + buf.put_u8(self.message_type as u8); + match &self.message { + CustomRlpxProtoMessageKind::Ping | CustomRlpxProtoMessageKind::Pong => {} + CustomRlpxProtoMessageKind::PingMessage(msg) | + CustomRlpxProtoMessageKind::PongMessage(msg) => { + buf.put(msg.as_bytes()); + } + } + buf + } + + /// Decodes a `CustomRlpxProtoMessage` from the given message buffer. + pub fn decode_message(buf: &mut &[u8]) -> Option { + if buf.is_empty() { + return None; + } + let id = buf[0]; + buf.advance(1); + let message_type = match id { + 0x00 => CustomRlpxProtoMessageId::Ping, + 0x01 => CustomRlpxProtoMessageId::Pong, + 0x02 => CustomRlpxProtoMessageId::PingMessage, + 0x03 => CustomRlpxProtoMessageId::PongMessage, + _ => return None, + }; + let message = match message_type { + CustomRlpxProtoMessageId::Ping => CustomRlpxProtoMessageKind::Ping, + CustomRlpxProtoMessageId::Pong => CustomRlpxProtoMessageKind::Pong, + CustomRlpxProtoMessageId::PingMessage => CustomRlpxProtoMessageKind::PingMessage( + String::from_utf8_lossy(&buf[..]).into_owned(), + ), + CustomRlpxProtoMessageId::PongMessage => CustomRlpxProtoMessageKind::PongMessage( + String::from_utf8_lossy(&buf[..]).into_owned(), + ), + }; + + Some(Self { message_type, message }) + } +} From 4308e1b22b8b3470066dd3144ef9a64657be7980 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:48:14 -0400 Subject: [PATCH 225/405] feat: add temporary docker tag action (#9126) --- .github/workflows/docker-tag.yml | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/docker-tag.yml diff --git a/.github/workflows/docker-tag.yml b/.github/workflows/docker-tag.yml new file mode 100644 index 000000000000..b4b941c0e708 --- /dev/null +++ b/.github/workflows/docker-tag.yml @@ -0,0 +1,51 @@ +# Tags the given docker image as latest + +name: docker-tag + +on: + workflow_dispatch: + inputs: + dryRun: + description: 'Whether or not to perform a dry run' + required: true + default: 'true' + type: boolean + tag: + description: 'The tag to push as latest' + required: true + type: string + +env: + REPO_NAME: ${{ github.repository_owner }}/reth + IMAGE_NAME: ${{ github.repository_owner }}/reth + OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth + CARGO_TERM_COLOR: always + DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth + OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth + DOCKER_USERNAME: ${{ github.actor }} + +jobs: + build: + name: build and push + runs-on: ubuntu-20.04 + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Log in to Docker + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin + - name: Tag reth image as "latest" + run: | + docker pull ghcr.io/paradigmxyz/reth:${{ inputs.tag }} + docker tag ghcr.io/paradigmxyz/reth:${{ inputs.tag }} ghcr.io/paradigmxyz/reth:latest + - name: Tag op-reth image as "latest" + run: | + docker pull ghcr.io/paradigmxyz/op-reth:${{ inputs.tag }} + docker tag ghcr.io/paradigmxyz/op-reth:${{ inputs.tag }} ghcr.io/paradigmxyz/op-reth:latest + - name: Push all tags + if: ${{ !inputs.dryRun }} + run: | + docker image push --all-tags ghcr.io/paradigmxyz/op-reth + docker image push --all-tags ghcr.io/paradigmxyz/reth From cbdd56af6fd52e35fc9f2d45266193f33c53e145 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:31:05 -0400 Subject: [PATCH 226/405] fix: remove temp docker tag action (#9128) --- .github/workflows/docker-tag.yml | 51 -------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/docker-tag.yml diff --git a/.github/workflows/docker-tag.yml b/.github/workflows/docker-tag.yml deleted file mode 100644 index b4b941c0e708..000000000000 --- a/.github/workflows/docker-tag.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Tags the given docker image as latest - -name: docker-tag - -on: - workflow_dispatch: - inputs: - dryRun: - description: 'Whether or not to perform a dry run' - required: true - default: 'true' - type: boolean - tag: - description: 'The tag to push as latest' - required: true - type: string - -env: - REPO_NAME: ${{ github.repository_owner }}/reth - IMAGE_NAME: ${{ github.repository_owner }}/reth - OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth - CARGO_TERM_COLOR: always - DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth - OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth - DOCKER_USERNAME: ${{ github.actor }} - -jobs: - build: - name: build and push - runs-on: ubuntu-20.04 - permissions: - packages: write - contents: read - steps: - - uses: actions/checkout@v4 - - name: Log in to Docker - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin - - name: Tag reth image as "latest" - run: | - docker pull ghcr.io/paradigmxyz/reth:${{ inputs.tag }} - docker tag ghcr.io/paradigmxyz/reth:${{ inputs.tag }} ghcr.io/paradigmxyz/reth:latest - - name: Tag op-reth image as "latest" - run: | - docker pull ghcr.io/paradigmxyz/op-reth:${{ inputs.tag }} - docker tag ghcr.io/paradigmxyz/op-reth:${{ inputs.tag }} ghcr.io/paradigmxyz/op-reth:latest - - name: Push all tags - if: ${{ !inputs.dryRun }} - run: | - docker image push --all-tags ghcr.io/paradigmxyz/op-reth - docker image push --all-tags ghcr.io/paradigmxyz/reth From 818375438a2fec44c00d6c944e6d21b9f36a2ef0 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:12:04 -0400 Subject: [PATCH 227/405] feat: add base fee metrics (#9129) --- crates/transaction-pool/src/metrics.rs | 4 ++++ crates/transaction-pool/src/pool/txpool.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/crates/transaction-pool/src/metrics.rs b/crates/transaction-pool/src/metrics.rs index d61816831294..c75e3403cbd5 100644 --- a/crates/transaction-pool/src/metrics.rs +++ b/crates/transaction-pool/src/metrics.rs @@ -106,4 +106,8 @@ pub struct AllTransactionsMetrics { pub(crate) all_transactions_by_all_senders: Gauge, /// Number of blob transactions nonce gaps. pub(crate) blob_transactions_nonce_gaps: Counter, + /// The current blob base fee + pub(crate) blob_base_fee: Gauge, + /// The current base fee + pub(crate) base_fee: Gauge, } diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 3e22bb9ca99b..a51cdc44ee4c 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -982,9 +982,13 @@ impl AllTransactions { } = block_info; self.last_seen_block_number = last_seen_block_number; self.last_seen_block_hash = last_seen_block_hash; + self.pending_fees.base_fee = pending_basefee; + self.metrics.base_fee.set(pending_basefee as f64); + if let Some(pending_blob_fee) = pending_blob_fee { self.pending_fees.blob_fee = pending_blob_fee; + self.metrics.blob_base_fee.set(pending_blob_fee as f64); } } From 9542f3bcf081f1fd171071cfdd66c545306f5ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:39:49 +0200 Subject: [PATCH 228/405] feat: add parser functionality to `RethCli` (#9127) --- Cargo.lock | 3 +++ crates/cli/cli/Cargo.toml | 5 +++++ crates/cli/cli/src/lib.rs | 22 +++++++++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a111aa4a9bfa..f7a490cf7644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6545,6 +6545,9 @@ dependencies = [ [[package]] name = "reth-cli" version = "1.0.0" +dependencies = [ + "clap", +] [[package]] name = "reth-cli-commands" diff --git a/crates/cli/cli/Cargo.toml b/crates/cli/cli/Cargo.toml index 3487782c9e20..effd1ed29962 100644 --- a/crates/cli/cli/Cargo.toml +++ b/crates/cli/cli/Cargo.toml @@ -8,3 +8,8 @@ homepage.workspace = true repository.workspace = true [lints] + + +[dependencies] +# misc +clap.workspace = true diff --git a/crates/cli/cli/src/lib.rs b/crates/cli/cli/src/lib.rs index 513380fdd052..5e273d88717f 100644 --- a/crates/cli/cli/src/lib.rs +++ b/crates/cli/cli/src/lib.rs @@ -8,7 +8,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use std::borrow::Cow; +use std::{borrow::Cow, ffi::OsString}; + +use clap::{Error, Parser}; /// Reth based node cli. /// @@ -22,4 +24,22 @@ pub trait RethCli: Sized { /// The version of the node, such as `reth/v1.0.0` fn version(&self) -> Cow<'static, str>; + + /// Parse args from iterator from [`std::env::args_os()`]. + fn parse_args() -> Result + where + Self: Parser + Sized, + { + ::try_parse_from(std::env::args_os()) + } + + /// Parse args from the given iterator. + fn try_parse_from(itr: I) -> Result + where + Self: Parser + Sized, + I: IntoIterator, + T: Into + Clone, + { + ::try_parse_from(itr) + } } From 18eef6a9919cc5ecda053e8c1f45acd9e2fbb8c4 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 27 Jun 2024 13:33:13 +0400 Subject: [PATCH 229/405] refactor: extract configuration types to `reth-network-types` (#9136) --- Cargo.lock | 16 +- Cargo.toml | 2 + crates/config/Cargo.toml | 2 +- crates/config/src/config.rs | 2 +- crates/net/network-types/Cargo.toml | 30 ++ crates/net/network-types/src/backoff.rs | 27 ++ crates/net/network-types/src/lib.rs | 24 ++ crates/net/network-types/src/peers/config.rs | 292 ++++++++++++++ crates/net/network-types/src/peers/mod.rs | 5 + .../src/peers/reputation.rs | 14 +- .../src/session/config.rs | 118 +----- crates/net/network-types/src/session/mod.rs | 4 + crates/net/network/Cargo.toml | 6 +- crates/net/network/src/config.rs | 3 +- crates/net/network/src/error.rs | 29 +- crates/net/network/src/lib.rs | 4 +- .../src/{peers/manager.rs => peers.rs} | 359 ++---------------- crates/net/network/src/peers/mod.rs | 20 - crates/net/network/src/session/active.rs | 8 +- crates/net/network/src/session/counter.rs | 106 ++++++ crates/net/network/src/session/mod.rs | 11 +- .../net/network/src/transactions/constants.rs | 6 +- 22 files changed, 584 insertions(+), 504 deletions(-) create mode 100644 crates/net/network-types/Cargo.toml create mode 100644 crates/net/network-types/src/backoff.rs create mode 100644 crates/net/network-types/src/lib.rs create mode 100644 crates/net/network-types/src/peers/config.rs create mode 100644 crates/net/network-types/src/peers/mod.rs rename crates/net/{network => network-types}/src/peers/reputation.rs (92%) rename crates/net/{network => network-types}/src/session/config.rs (65%) create mode 100644 crates/net/network-types/src/session/mod.rs rename crates/net/network/src/{peers/manager.rs => peers.rs} (88%) delete mode 100644 crates/net/network/src/peers/mod.rs create mode 100644 crates/net/network/src/session/counter.rs diff --git a/Cargo.lock b/Cargo.lock index f7a490cf7644..8aded4fc5e47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6599,7 +6599,7 @@ version = "1.0.0" dependencies = [ "confy", "humantime-serde", - "reth-network", + "reth-network-types", "reth-prune-types", "serde", "tempfile", @@ -7362,6 +7362,7 @@ dependencies = [ "reth-network-api", "reth-network-p2p", "reth-network-peers", + "reth-network-types", "reth-primitives", "reth-provider", "reth-tasks", @@ -7431,6 +7432,19 @@ dependencies = [ "url", ] +[[package]] +name = "reth-network-types" +version = "1.0.0" +dependencies = [ + "humantime-serde", + "reth-net-banlist", + "reth-network-api", + "reth-network-peers", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "reth-nippy-jar" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 961d8f6967dd..3b52472ad6c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ members = [ "crates/net/eth-wire/", "crates/net/nat/", "crates/net/network-api/", + "crates/net/network-types/", "crates/net/network/", "crates/net/p2p/", "crates/net/peers/", @@ -310,6 +311,7 @@ reth-net-banlist = { path = "crates/net/banlist" } reth-net-nat = { path = "crates/net/nat" } reth-network = { path = "crates/net/network" } reth-network-api = { path = "crates/net/network-api" } +reth-network-types = { path = "crates/net/network-types" } reth-network-peers = { path = "crates/net/peers", default-features = false } reth-network-p2p = { path = "crates/net/p2p" } reth-nippy-jar = { path = "crates/storage/nippy-jar" } diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 527f5b1538ed..1468fccd32d0 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] # reth -reth-network.workspace = true +reth-network-types = { workspace = true, features = ["serde"] } reth-prune-types.workspace = true # serde diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index f458ef41646f..5d79549d4e47 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -1,6 +1,6 @@ //! Configuration files. -use reth_network::{PeersConfig, SessionsConfig}; +use reth_network_types::{PeersConfig, SessionsConfig}; use reth_prune_types::PruneModes; use serde::{Deserialize, Deserializer, Serialize}; use std::{ diff --git a/crates/net/network-types/Cargo.toml b/crates/net/network-types/Cargo.toml new file mode 100644 index 000000000000..66c1f4d84a38 --- /dev/null +++ b/crates/net/network-types/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "reth-network-types" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Commonly used network types" + +[lints] +workspace = true + +[dependencies] +# reth +reth-network-api.workspace = true +reth-network-peers.workspace = true +reth-net-banlist.workspace = true + +# io +serde = { workspace = true, optional = true } +humantime-serde = { workspace = true, optional = true } +serde_json = { workspace = true } + +# misc +tracing.workspace = true + +[features] +serde = ["dep:serde", "dep:humantime-serde"] +test-utils = [] diff --git a/crates/net/network-types/src/backoff.rs b/crates/net/network-types/src/backoff.rs new file mode 100644 index 000000000000..8ee9f68a4e31 --- /dev/null +++ b/crates/net/network-types/src/backoff.rs @@ -0,0 +1,27 @@ +/// Describes the type of backoff should be applied. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BackoffKind { + /// Use the lowest configured backoff duration. + /// + /// This applies to connection problems where there is a chance that they will be resolved + /// after the short duration. + Low, + /// Use a slightly higher duration to put a peer in timeout + /// + /// This applies to more severe connection problems where there is a lower chance that they + /// will be resolved. + Medium, + /// Use the max configured backoff duration. + /// + /// This is intended for spammers, or bad peers in general. + High, +} + +// === impl BackoffKind === + +impl BackoffKind { + /// Returns true if the backoff is considered severe. + pub const fn is_severe(&self) -> bool { + matches!(self, Self::Medium | Self::High) + } +} diff --git a/crates/net/network-types/src/lib.rs b/crates/net/network-types/src/lib.rs new file mode 100644 index 000000000000..5b075d609bc1 --- /dev/null +++ b/crates/net/network-types/src/lib.rs @@ -0,0 +1,24 @@ +//! Commonly used networking types. +//! +//! ## Feature Flags +//! +//! - `serde` (default): Enable serde support + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +/// Types related to peering. +pub mod peers; +pub use peers::{ConnectionsConfig, PeersConfig, ReputationChangeWeights}; + +pub mod session; +pub use session::{SessionLimits, SessionsConfig}; + +/// [`BackoffKind`] definition. +mod backoff; +pub use backoff::BackoffKind; diff --git a/crates/net/network-types/src/peers/config.rs b/crates/net/network-types/src/peers/config.rs new file mode 100644 index 000000000000..5143c4c6f2bf --- /dev/null +++ b/crates/net/network-types/src/peers/config.rs @@ -0,0 +1,292 @@ +//! Configuration for peering. + +use crate::{BackoffKind, ReputationChangeWeights}; +use reth_net_banlist::BanList; +use reth_network_peers::NodeRecord; +use std::{ + collections::HashSet, + io::{self, ErrorKind}, + path::Path, + time::Duration, +}; +use tracing::info; + +/// Maximum number of available slots for outbound sessions. +pub const DEFAULT_MAX_COUNT_PEERS_OUTBOUND: u32 = 100; + +/// Maximum number of available slots for inbound sessions. +pub const DEFAULT_MAX_COUNT_PEERS_INBOUND: u32 = 30; + +/// Maximum number of available slots for concurrent outgoing dials. +/// +/// This restricts how many outbound dials can be performed concurrently. +pub const DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS: usize = 15; + +/// The durations to use when a backoff should be applied to a peer. +/// +/// See also [`BackoffKind`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PeerBackoffDurations { + /// Applies to connection problems where there is a chance that they will be resolved after the + /// short duration. + #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] + pub low: Duration, + /// Applies to more severe connection problems where there is a lower chance that they will be + /// resolved. + #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] + pub medium: Duration, + /// Intended for spammers, or bad peers in general. + #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] + pub high: Duration, + /// Maximum total backoff duration. + #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] + pub max: Duration, +} + +impl PeerBackoffDurations { + /// Returns the corresponding [`Duration`] + pub const fn backoff(&self, kind: BackoffKind) -> Duration { + match kind { + BackoffKind::Low => self.low, + BackoffKind::Medium => self.medium, + BackoffKind::High => self.high, + } + } + + /// Returns the timestamp until which we should backoff. + /// + /// The Backoff duration is capped by the configured maximum backoff duration. + pub fn backoff_until(&self, kind: BackoffKind, backoff_counter: u8) -> std::time::Instant { + let backoff_time = self.backoff(kind); + let backoff_time = backoff_time + backoff_time * backoff_counter as u32; + let now = std::time::Instant::now(); + now + backoff_time.min(self.max) + } + + /// Returns durations for testing. + #[cfg(any(test, feature = "test-utils"))] + pub const fn test() -> Self { + Self { + low: Duration::from_millis(200), + medium: Duration::from_millis(200), + high: Duration::from_millis(200), + max: Duration::from_millis(200), + } + } +} + +impl Default for PeerBackoffDurations { + fn default() -> Self { + Self { + low: Duration::from_secs(30), + // 3min + medium: Duration::from_secs(60 * 3), + // 15min + high: Duration::from_secs(60 * 15), + // 1h + max: Duration::from_secs(60 * 60), + } + } +} + +/// Tracks stats about connected nodes +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))] +pub struct ConnectionsConfig { + /// Maximum allowed outbound connections. + pub max_outbound: usize, + /// Maximum allowed inbound connections. + pub max_inbound: usize, + /// Maximum allowed concurrent outbound dials. + #[cfg_attr(feature = "serde", serde(default))] + pub max_concurrent_outbound_dials: usize, +} + +impl Default for ConnectionsConfig { + fn default() -> Self { + Self { + max_outbound: DEFAULT_MAX_COUNT_PEERS_OUTBOUND as usize, + max_inbound: DEFAULT_MAX_COUNT_PEERS_INBOUND as usize, + max_concurrent_outbound_dials: DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS, + } + } +} + +/// Config type for initiating a `PeersManager` instance. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct PeersConfig { + /// How often to recheck free slots for outbound connections. + #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] + pub refill_slots_interval: Duration, + /// Trusted nodes to connect to or accept from + pub trusted_nodes: HashSet, + /// Connect to or accept from trusted nodes only? + #[cfg_attr(feature = "serde", serde(alias = "connect_trusted_nodes_only"))] + pub trusted_nodes_only: bool, + /// Maximum number of backoff attempts before we give up on a peer and dropping. + /// + /// The max time spent of a peer before it's removed from the set is determined by the + /// configured backoff duration and the max backoff count. + /// + /// With a backoff counter of 5 and a backoff duration of 1h, the minimum time spent of the + /// peer in the table is the sum of all backoffs (1h + 2h + 3h + 4h + 5h = 15h). + /// + /// Note: this does not apply to trusted peers. + pub max_backoff_count: u8, + /// Basic nodes to connect to. + #[cfg_attr(feature = "serde", serde(skip))] + pub basic_nodes: HashSet, + /// How long to ban bad peers. + #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] + pub ban_duration: Duration, + /// Restrictions on `PeerIds` and Ips. + #[cfg_attr(feature = "serde", serde(skip))] + pub ban_list: BanList, + /// Restrictions on connections. + pub connection_info: ConnectionsConfig, + /// How to weigh reputation changes. + pub reputation_weights: ReputationChangeWeights, + /// How long to backoff peers that we are failed to connect to for non-fatal reasons. + /// + /// The backoff duration increases with number of backoff attempts. + pub backoff_durations: PeerBackoffDurations, +} + +impl Default for PeersConfig { + fn default() -> Self { + Self { + refill_slots_interval: Duration::from_millis(5_000), + connection_info: Default::default(), + reputation_weights: Default::default(), + ban_list: Default::default(), + // Ban peers for 12h + ban_duration: Duration::from_secs(60 * 60 * 12), + backoff_durations: Default::default(), + trusted_nodes: Default::default(), + trusted_nodes_only: false, + basic_nodes: Default::default(), + max_backoff_count: 5, + } + } +} + +impl PeersConfig { + /// A set of `peer_ids` and ip addr that we want to never connect to + pub fn with_ban_list(mut self, ban_list: BanList) -> Self { + self.ban_list = ban_list; + self + } + + /// Configure how long to ban bad peers + pub const fn with_ban_duration(mut self, ban_duration: Duration) -> Self { + self.ban_duration = ban_duration; + self + } + + /// Maximum allowed outbound connections. + pub const fn with_max_outbound(mut self, max_outbound: usize) -> Self { + self.connection_info.max_outbound = max_outbound; + self + } + + /// Maximum allowed inbound connections with optional update. + pub const fn with_max_inbound_opt(mut self, max_inbound: Option) -> Self { + if let Some(max_inbound) = max_inbound { + self.connection_info.max_inbound = max_inbound; + } + self + } + + /// Maximum allowed outbound connections with optional update. + pub const fn with_max_outbound_opt(mut self, max_outbound: Option) -> Self { + if let Some(max_outbound) = max_outbound { + self.connection_info.max_outbound = max_outbound; + } + self + } + + /// Maximum allowed inbound connections. + pub const fn with_max_inbound(mut self, max_inbound: usize) -> Self { + self.connection_info.max_inbound = max_inbound; + self + } + + /// Maximum allowed concurrent outbound dials. + pub const fn with_max_concurrent_dials(mut self, max_concurrent_outbound_dials: usize) -> Self { + self.connection_info.max_concurrent_outbound_dials = max_concurrent_outbound_dials; + self + } + + /// Nodes to always connect to. + pub fn with_trusted_nodes(mut self, nodes: HashSet) -> Self { + self.trusted_nodes = nodes; + self + } + + /// Connect only to trusted nodes. + pub const fn with_trusted_nodes_only(mut self, trusted_only: bool) -> Self { + self.trusted_nodes_only = trusted_only; + self + } + + /// Nodes available at launch. + pub fn with_basic_nodes(mut self, nodes: HashSet) -> Self { + self.basic_nodes = nodes; + self + } + + /// Configures the max allowed backoff count. + pub const fn with_max_backoff_count(mut self, max_backoff_count: u8) -> Self { + self.max_backoff_count = max_backoff_count; + self + } + + /// Configures how to weigh reputation changes. + pub const fn with_reputation_weights( + mut self, + reputation_weights: ReputationChangeWeights, + ) -> Self { + self.reputation_weights = reputation_weights; + self + } + + /// Configures how long to backoff peers that are we failed to connect to for non-fatal reasons + pub const fn with_backoff_durations(mut self, backoff_durations: PeerBackoffDurations) -> Self { + self.backoff_durations = backoff_durations; + self + } + + /// Returns the maximum number of peers, inbound and outbound. + pub const fn max_peers(&self) -> usize { + self.connection_info.max_outbound + self.connection_info.max_inbound + } + + /// Read from file nodes available at launch. Ignored if None. + pub fn with_basic_nodes_from_file( + self, + optional_file: Option>, + ) -> Result { + let Some(file_path) = optional_file else { return Ok(self) }; + let reader = match std::fs::File::open(file_path.as_ref()) { + Ok(file) => io::BufReader::new(file), + Err(e) if e.kind() == ErrorKind::NotFound => return Ok(self), + Err(e) => Err(e)?, + }; + info!(target: "net::peers", file = %file_path.as_ref().display(), "Loading saved peers"); + let nodes: HashSet = serde_json::from_reader(reader)?; + Ok(self.with_basic_nodes(nodes)) + } + + /// Returns settings for testing + #[cfg(any(test, feature = "test-utils"))] + pub fn test() -> Self { + Self { + refill_slots_interval: Duration::from_millis(100), + backoff_durations: PeerBackoffDurations::test(), + ..Default::default() + } + } +} diff --git a/crates/net/network-types/src/peers/mod.rs b/crates/net/network-types/src/peers/mod.rs new file mode 100644 index 000000000000..4b195750b516 --- /dev/null +++ b/crates/net/network-types/src/peers/mod.rs @@ -0,0 +1,5 @@ +pub mod reputation; +pub use reputation::ReputationChangeWeights; + +pub mod config; +pub use config::{ConnectionsConfig, PeersConfig}; diff --git a/crates/net/network/src/peers/reputation.rs b/crates/net/network-types/src/peers/reputation.rs similarity index 92% rename from crates/net/network/src/peers/reputation.rs rename to crates/net/network-types/src/peers/reputation.rs index 9d3ec256bea0..13fac4c1ebc2 100644 --- a/crates/net/network/src/peers/reputation.rs +++ b/crates/net/network-types/src/peers/reputation.rs @@ -3,13 +3,13 @@ use reth_network_api::{Reputation, ReputationChangeKind}; /// The default reputation of a peer -pub(crate) const DEFAULT_REPUTATION: Reputation = 0; +pub const DEFAULT_REPUTATION: Reputation = 0; /// The minimal unit we're measuring reputation const REPUTATION_UNIT: i32 = -1024; /// The reputation value below which new connection from/to peers are rejected. -pub(crate) const BANNED_REPUTATION: i32 = 50 * REPUTATION_UNIT; +pub const BANNED_REPUTATION: i32 = 50 * REPUTATION_UNIT; /// The reputation change to apply to a peer that dropped the connection. const REMOTE_DISCONNECT_REPUTATION_CHANGE: i32 = 4 * REPUTATION_UNIT; @@ -42,11 +42,11 @@ const BAD_ANNOUNCEMENT_REPUTATION_CHANGE: i32 = REPUTATION_UNIT; /// This gives a trusted peer more leeway when interacting with the node, which is useful for in /// custom setups. By not setting this to `0` we still allow trusted peer penalization but less than /// untrusted peers. -pub(crate) const MAX_TRUSTED_PEER_REPUTATION_CHANGE: Reputation = 2 * REPUTATION_UNIT; +pub const MAX_TRUSTED_PEER_REPUTATION_CHANGE: Reputation = 2 * REPUTATION_UNIT; /// Returns `true` if the given reputation is below the [`BANNED_REPUTATION`] threshold #[inline] -pub(crate) const fn is_banned_reputation(reputation: i32) -> bool { +pub const fn is_banned_reputation(reputation: i32) -> bool { reputation < BANNED_REPUTATION } @@ -80,7 +80,7 @@ pub struct ReputationChangeWeights { impl ReputationChangeWeights { /// Returns the quantifiable [`ReputationChange`] for the given [`ReputationChangeKind`] using /// the configured weights - pub(crate) fn change(&self, kind: ReputationChangeKind) -> ReputationChange { + pub fn change(&self, kind: ReputationChangeKind) -> ReputationChange { match kind { ReputationChangeKind::BadMessage => self.bad_message.into(), ReputationChangeKind::BadBlock => self.bad_block.into(), @@ -115,14 +115,14 @@ impl Default for ReputationChangeWeights { /// Represents a change in a peer's reputation. #[derive(Debug, Copy, Clone, Default)] -pub(crate) struct ReputationChange(Reputation); +pub struct ReputationChange(Reputation); // === impl ReputationChange === impl ReputationChange { /// Helper type for easier conversion #[inline] - pub(crate) const fn as_i32(self) -> Reputation { + pub const fn as_i32(self) -> Reputation { self.0 } } diff --git a/crates/net/network/src/session/config.rs b/crates/net/network-types/src/session/config.rs similarity index 65% rename from crates/net/network/src/session/config.rs rename to crates/net/network-types/src/session/config.rs index 6c7fc282d0a5..941448effd6b 100644 --- a/crates/net/network/src/session/config.rs +++ b/crates/net/network-types/src/session/config.rs @@ -1,9 +1,6 @@ -//! Configuration types for [`SessionManager`](crate::session::SessionManager). +//! Configuration types for peer sessions manager. -use crate::{ - peers::{DEFAULT_MAX_COUNT_PEERS_INBOUND, DEFAULT_MAX_COUNT_PEERS_OUTBOUND}, - session::{Direction, ExceedsSessionLimit}, -}; +use crate::peers::config::{DEFAULT_MAX_COUNT_PEERS_INBOUND, DEFAULT_MAX_COUNT_PEERS_OUTBOUND}; use std::time::Duration; /// Default request timeout for a single request. @@ -29,7 +26,7 @@ const DEFAULT_MAX_PEERS: usize = /// With maxed out peers, this will allow for 3 messages per session (average) const DEFAULT_SESSION_EVENT_BUFFER_SIZE: usize = DEFAULT_MAX_PEERS * 2; -/// Configuration options when creating a [`SessionManager`](crate::session::SessionManager). +/// Configuration options for peer session management. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(default))] @@ -111,10 +108,14 @@ impl SessionsConfig { #[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SessionLimits { - max_pending_inbound: Option, - max_pending_outbound: Option, - max_established_inbound: Option, - max_established_outbound: Option, + /// Maximum allowed inbound connections. + pub max_pending_inbound: Option, + /// Maximum allowed outbound connections. + pub max_pending_outbound: Option, + /// Maximum allowed established inbound connections. + pub max_established_inbound: Option, + /// Maximum allowed established outbound connections. + pub max_established_outbound: Option, } impl SessionLimits { @@ -143,107 +144,10 @@ impl SessionLimits { } } -/// Keeps track of all sessions. -#[derive(Debug, Clone)] -pub struct SessionCounter { - /// Limits to enforce. - limits: SessionLimits, - /// Number of pending incoming sessions. - pending_inbound: u32, - /// Number of pending outgoing sessions. - pending_outbound: u32, - /// Number of active inbound sessions. - active_inbound: u32, - /// Number of active outbound sessions. - active_outbound: u32, -} - -// === impl SessionCounter === - -impl SessionCounter { - pub(crate) const fn new(limits: SessionLimits) -> Self { - Self { - limits, - pending_inbound: 0, - pending_outbound: 0, - active_inbound: 0, - active_outbound: 0, - } - } - - pub(crate) fn inc_pending_inbound(&mut self) { - self.pending_inbound += 1; - } - - pub(crate) fn inc_pending_outbound(&mut self) { - self.pending_outbound += 1; - } - - pub(crate) fn dec_pending(&mut self, direction: &Direction) { - match direction { - Direction::Outgoing(_) => { - self.pending_outbound -= 1; - } - Direction::Incoming => { - self.pending_inbound -= 1; - } - } - } - - pub(crate) fn inc_active(&mut self, direction: &Direction) { - match direction { - Direction::Outgoing(_) => { - self.active_outbound += 1; - } - Direction::Incoming => { - self.active_inbound += 1; - } - } - } - - pub(crate) fn dec_active(&mut self, direction: &Direction) { - match direction { - Direction::Outgoing(_) => { - self.active_outbound -= 1; - } - Direction::Incoming => { - self.active_inbound -= 1; - } - } - } - - pub(crate) const fn ensure_pending_outbound(&self) -> Result<(), ExceedsSessionLimit> { - Self::ensure(self.pending_outbound, self.limits.max_pending_outbound) - } - - pub(crate) const fn ensure_pending_inbound(&self) -> Result<(), ExceedsSessionLimit> { - Self::ensure(self.pending_inbound, self.limits.max_pending_inbound) - } - - const fn ensure(current: u32, limit: Option) -> Result<(), ExceedsSessionLimit> { - if let Some(limit) = limit { - if current >= limit { - return Err(ExceedsSessionLimit(limit)) - } - } - Ok(()) - } -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn test_limits() { - let mut limits = SessionCounter::new(SessionLimits::default().with_max_pending_inbound(2)); - assert!(limits.ensure_pending_outbound().is_ok()); - limits.inc_pending_inbound(); - assert!(limits.ensure_pending_inbound().is_ok()); - limits.inc_pending_inbound(); - assert!(limits.ensure_pending_inbound().is_err()); - } - #[test] fn scale_session_event_buffer() { let config = SessionsConfig::default().with_upscaled_event_buffer(10); diff --git a/crates/net/network-types/src/session/mod.rs b/crates/net/network-types/src/session/mod.rs new file mode 100644 index 000000000000..a5b613189c02 --- /dev/null +++ b/crates/net/network-types/src/session/mod.rs @@ -0,0 +1,4 @@ +//! Peer sessions configuration. + +pub mod config; +pub use config::{SessionLimits, SessionsConfig}; diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 1dde28cfe2cf..d61caa7bec96 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -29,6 +29,7 @@ reth-provider.workspace = true reth-tokio-util.workspace = true reth-consensus.workspace = true reth-network-peers.workspace = true +reth-network-types.workspace = true # ethereum enr = { workspace = true, features = ["serde", "rust-secp256k1"] } @@ -75,6 +76,7 @@ reth-primitives = { workspace = true, features = ["test-utils"] } # integration tests reth-network = { workspace = true, features = ["test-utils"] } reth-network-p2p = { workspace = true, features = ["test-utils"] } +reth-network-types = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } reth-tracing.workspace = true @@ -95,8 +97,8 @@ criterion = { workspace = true, features = ["async_tokio", "html_reports"] } [features] default = ["serde"] geth-tests = [] -serde = ["dep:serde", "dep:humantime-serde", "secp256k1/serde", "enr/serde", "dep:serde_json"] -test-utils = ["reth-provider/test-utils", "dep:tempfile", "reth-transaction-pool/test-utils"] +serde = ["dep:serde", "dep:humantime-serde", "secp256k1/serde", "enr/serde", "dep:serde_json", "reth-network-types/serde"] +test-utils = ["reth-provider/test-utils", "dep:tempfile", "reth-transaction-pool/test-utils", "reth-network-types/test-utils"] [[bench]] name = "bench" diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 151421b4a5de..c42d204f5e29 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -3,8 +3,6 @@ use crate::{ error::NetworkError, import::{BlockImport, ProofOfStakeBlockImport}, - peers::PeersConfig, - session::SessionsConfig, transactions::TransactionsManagerConfig, NetworkHandle, NetworkManager, }; @@ -17,6 +15,7 @@ use reth_discv5::NetworkStackId; use reth_dns_discovery::DnsDiscoveryConfig; use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status}; use reth_network_peers::{pk2id, PeerId}; +use reth_network_types::{PeersConfig, SessionsConfig}; use reth_primitives::{ForkFilter, Head}; use reth_provider::{BlockReader, HeaderProvider}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; diff --git a/crates/net/network/src/error.rs b/crates/net/network/src/error.rs index 9019a79f2371..d5e0f453721d 100644 --- a/crates/net/network/src/error.rs +++ b/crates/net/network/src/error.rs @@ -6,6 +6,7 @@ use reth_eth_wire::{ errors::{EthHandshakeError, EthStreamError, P2PHandshakeError, P2PStreamError}, DisconnectReason, }; +use reth_network_types::BackoffKind; use std::{fmt, io, io::ErrorKind, net::SocketAddr}; /// Service kind. @@ -104,34 +105,6 @@ pub(crate) trait SessionError: fmt::Debug + fmt::Display { fn should_backoff(&self) -> Option; } -/// Describes the type of backoff should be applied. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BackoffKind { - /// Use the lowest configured backoff duration. - /// - /// This applies to connection problems where there is a chance that they will be resolved - /// after the short duration. - Low, - /// Use a slightly higher duration to put a peer in timeout - /// - /// This applies to more severe connection problems where there is a lower chance that they - /// will be resolved. - Medium, - /// Use the max configured backoff duration. - /// - /// This is intended for spammers, or bad peers in general. - High, -} - -// === impl BackoffKind === - -impl BackoffKind { - /// Returns true if the backoff is considered severe. - pub(crate) const fn is_severe(&self) -> bool { - matches!(self, Self::Medium | Self::High) - } -} - impl SessionError for EthStreamError { fn merits_discovery_ban(&self) -> bool { match self { diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index b8f3f8be1697..f03889f98414 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -143,12 +143,12 @@ pub use fetch::FetchClient; pub use manager::{NetworkEvent, NetworkManager}; pub use message::PeerRequest; pub use network::{NetworkEvents, NetworkHandle, NetworkProtocols}; -pub use peers::PeersConfig; pub use session::{ ActiveSessionHandle, ActiveSessionMessage, Direction, PeerInfo, PendingSessionEvent, PendingSessionHandle, PendingSessionHandshakeError, SessionCommand, SessionEvent, SessionId, - SessionLimits, SessionManager, SessionsConfig, + SessionManager, }; pub use transactions::{FilterAnnouncement, MessageFilter, ValidateTx68}; pub use reth_eth_wire::{DisconnectReason, HelloMessageWithProtocols}; +pub use reth_network_types::{PeersConfig, SessionsConfig}; diff --git a/crates/net/network/src/peers/manager.rs b/crates/net/network/src/peers.rs similarity index 88% rename from crates/net/network/src/peers/manager.rs rename to crates/net/network/src/peers.rs index bde0bd066f07..c7e6a05a57dc 100644 --- a/crates/net/network/src/peers/manager.rs +++ b/crates/net/network/src/peers.rs @@ -1,12 +1,7 @@ +//! Peer related implementations + use crate::{ - error::{BackoffKind, SessionError}, - peers::{ - reputation::{ - is_banned_reputation, DEFAULT_REPUTATION, MAX_TRUSTED_PEER_REPUTATION_CHANGE, - }, - ReputationChangeWeights, DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS, - DEFAULT_MAX_COUNT_PEERS_INBOUND, DEFAULT_MAX_COUNT_PEERS_OUTBOUND, - }, + error::SessionError, session::{Direction, PendingSessionHandshakeError}, swarm::NetworkConnectionState, }; @@ -15,13 +10,21 @@ use reth_eth_wire::{errors::EthStreamError, DisconnectReason}; use reth_net_banlist::BanList; use reth_network_api::{PeerKind, ReputationChangeKind}; use reth_network_peers::{NodeRecord, PeerId}; +use reth_network_types::{ + peers::{ + config::PeerBackoffDurations, + reputation::{ + is_banned_reputation, DEFAULT_REPUTATION, MAX_TRUSTED_PEER_REPUTATION_CHANGE, + }, + }, + ConnectionsConfig, PeersConfig, ReputationChangeWeights, +}; use reth_primitives::ForkId; use std::{ collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, fmt::Display, - io::{self, ErrorKind}, + io::{self}, net::{IpAddr, SocketAddr}, - path::Path, task::{Context, Poll}, time::Duration, }; @@ -31,7 +34,7 @@ use tokio::{ time::{Instant, Interval}, }; use tokio_stream::wrappers::UnboundedReceiverStream; -use tracing::{info, trace}; +use tracing::trace; /// A communication channel to the [`PeersManager`] to apply manual changes to the peer set. #[derive(Clone, Debug)] @@ -170,7 +173,7 @@ impl PeersManager { reputation_weights, refill_slots_interval: tokio::time::interval(refill_slots_interval), release_interval: tokio::time::interval_at(now + unban_interval, unban_interval), - connection_info, + connection_info: ConnectionInfo::new(connection_info), ban_list, backed_off_peers: Default::default(), ban_duration, @@ -238,9 +241,7 @@ impl PeersManager { return Err(InboundConnectionError::IpBanned) } - if (!self.connection_info.has_in_capacity() || self.connection_info.max_inbound == 0) && - self.trusted_peer_ids.is_empty() - { + if !self.connection_info.has_in_capacity() && self.trusted_peer_ids.is_empty() { // if we don't have any inbound slots and no trusted peers, we don't accept any new // connections return Err(InboundConnectionError::ExceedsCapacity) @@ -918,42 +919,37 @@ impl Default for PeersManager { } /// Tracks stats about connected nodes -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct ConnectionInfo { /// Counter for currently occupied slots for active outbound connections. - #[cfg_attr(feature = "serde", serde(skip))] num_outbound: usize, /// Counter for pending outbound connections. - #[cfg_attr(feature = "serde", serde(skip))] num_pending_out: usize, /// Counter for currently occupied slots for active inbound connections. - #[cfg_attr(feature = "serde", serde(skip))] num_inbound: usize, /// Counter for pending inbound connections. - #[cfg_attr(feature = "serde", serde(skip))] num_pending_in: usize, - /// Maximum allowed outbound connections. - max_outbound: usize, - /// Maximum allowed inbound connections. - max_inbound: usize, - /// Maximum allowed concurrent outbound dials. - #[cfg_attr(feature = "serde", serde(default))] - max_concurrent_outbound_dials: usize, + /// Restrictions on number of connections. + config: ConnectionsConfig, } // === impl ConnectionInfo === impl ConnectionInfo { + /// Returns a new [`ConnectionInfo`] with the given config. + const fn new(config: ConnectionsConfig) -> Self { + Self { config, num_outbound: 0, num_pending_out: 0, num_inbound: 0, num_pending_in: 0 } + } + /// Returns `true` if there's still capacity for a new outgoing connection. const fn has_out_capacity(&self) -> bool { - self.num_pending_out < self.max_concurrent_outbound_dials && - self.num_outbound < self.max_outbound + self.num_pending_out < self.config.max_concurrent_outbound_dials && + self.num_outbound < self.config.max_outbound } /// Returns `true` if there's still capacity for a new incoming connection. const fn has_in_capacity(&self) -> bool { - self.num_inbound < self.max_inbound + self.num_inbound < self.config.max_inbound } fn decr_state(&mut self, state: PeerConnectionState) { @@ -998,20 +994,6 @@ impl ConnectionInfo { } } -impl Default for ConnectionInfo { - fn default() -> Self { - Self { - num_outbound: 0, - num_inbound: 0, - max_outbound: DEFAULT_MAX_COUNT_PEERS_OUTBOUND as usize, - max_inbound: DEFAULT_MAX_COUNT_PEERS_INBOUND as usize, - max_concurrent_outbound_dials: DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS, - num_pending_out: 0, - num_pending_in: 0, - } - } -} - /// Tracks info about a single peer. #[derive(Debug, Clone)] pub struct Peer { @@ -1029,7 +1011,8 @@ pub struct Peer { kind: PeerKind, /// Whether the peer is currently backed off. backed_off: bool, - /// Counts number of times the peer was backed off due to a severe [`BackoffKind`]. + /// Counts number of times the peer was backed off due to a severe + /// [`reth_network_types::BackoffKind`]. severe_backoff_counter: u8, } @@ -1263,265 +1246,6 @@ pub enum PeerAction { PeerRemoved(PeerId), } -/// Config type for initiating a [`PeersManager`] instance. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(default))] -pub struct PeersConfig { - /// How often to recheck free slots for outbound connections. - #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] - pub refill_slots_interval: Duration, - /// Trusted nodes to connect to or accept from - pub trusted_nodes: HashSet, - /// Connect to or accept from trusted nodes only? - #[cfg_attr(feature = "serde", serde(alias = "connect_trusted_nodes_only"))] - pub trusted_nodes_only: bool, - /// Maximum number of backoff attempts before we give up on a peer and dropping. - /// - /// The max time spent of a peer before it's removed from the set is determined by the - /// configured backoff duration and the max backoff count. - /// - /// With a backoff counter of 5 and a backoff duration of 1h, the minimum time spent of the - /// peer in the table is the sum of all backoffs (1h + 2h + 3h + 4h + 5h = 15h). - /// - /// Note: this does not apply to trusted peers. - pub max_backoff_count: u8, - /// Basic nodes to connect to. - #[cfg_attr(feature = "serde", serde(skip))] - pub basic_nodes: HashSet, - /// How long to ban bad peers. - #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] - pub ban_duration: Duration, - /// Restrictions on `PeerIds` and Ips. - #[cfg_attr(feature = "serde", serde(skip))] - pub ban_list: BanList, - /// Restrictions on connections. - pub connection_info: ConnectionInfo, - /// How to weigh reputation changes. - pub reputation_weights: ReputationChangeWeights, - /// How long to backoff peers that are we failed to connect to for non-fatal reasons, such as - /// [`DisconnectReason::TooManyPeers`]. - /// - /// The backoff duration increases with number of backoff attempts. - pub backoff_durations: PeerBackoffDurations, -} - -impl Default for PeersConfig { - fn default() -> Self { - Self { - refill_slots_interval: Duration::from_millis(5_000), - connection_info: Default::default(), - reputation_weights: Default::default(), - ban_list: Default::default(), - // Ban peers for 12h - ban_duration: Duration::from_secs(60 * 60 * 12), - backoff_durations: Default::default(), - trusted_nodes: Default::default(), - trusted_nodes_only: false, - basic_nodes: Default::default(), - max_backoff_count: 5, - } - } -} - -impl PeersConfig { - /// A set of `peer_ids` and ip addr that we want to never connect to - pub fn with_ban_list(mut self, ban_list: BanList) -> Self { - self.ban_list = ban_list; - self - } - - /// Configure how long to ban bad peers - pub const fn with_ban_duration(mut self, ban_duration: Duration) -> Self { - self.ban_duration = ban_duration; - self - } - - /// Maximum occupied slots for outbound connections. - pub const fn with_max_pending_outbound(mut self, num_outbound: usize) -> Self { - self.connection_info.num_outbound = num_outbound; - self - } - - /// Maximum occupied slots for inbound connections. - pub const fn with_max_pending_inbound(mut self, num_inbound: usize) -> Self { - self.connection_info.num_inbound = num_inbound; - self - } - - /// Maximum allowed outbound connections. - pub const fn with_max_outbound(mut self, max_outbound: usize) -> Self { - self.connection_info.max_outbound = max_outbound; - self - } - - /// Maximum allowed inbound connections with optional update. - pub const fn with_max_inbound_opt(mut self, max_inbound: Option) -> Self { - if let Some(max_inbound) = max_inbound { - self.connection_info.max_inbound = max_inbound; - } - self - } - - /// Maximum allowed outbound connections with optional update. - pub const fn with_max_outbound_opt(mut self, max_outbound: Option) -> Self { - if let Some(max_outbound) = max_outbound { - self.connection_info.max_outbound = max_outbound; - } - self - } - - /// Maximum allowed inbound connections. - pub const fn with_max_inbound(mut self, max_inbound: usize) -> Self { - self.connection_info.max_inbound = max_inbound; - self - } - - /// Maximum allowed concurrent outbound dials. - pub const fn with_max_concurrent_dials(mut self, max_concurrent_outbound_dials: usize) -> Self { - self.connection_info.max_concurrent_outbound_dials = max_concurrent_outbound_dials; - self - } - - /// Nodes to always connect to. - pub fn with_trusted_nodes(mut self, nodes: HashSet) -> Self { - self.trusted_nodes = nodes; - self - } - - /// Connect only to trusted nodes. - pub const fn with_trusted_nodes_only(mut self, trusted_only: bool) -> Self { - self.trusted_nodes_only = trusted_only; - self - } - - /// Nodes available at launch. - pub fn with_basic_nodes(mut self, nodes: HashSet) -> Self { - self.basic_nodes = nodes; - self - } - - /// Configures the max allowed backoff count. - pub const fn with_max_backoff_count(mut self, max_backoff_count: u8) -> Self { - self.max_backoff_count = max_backoff_count; - self - } - - /// Configures how to weigh reputation changes. - pub const fn with_reputation_weights( - mut self, - reputation_weights: ReputationChangeWeights, - ) -> Self { - self.reputation_weights = reputation_weights; - self - } - - /// Configures how long to backoff peers that are we failed to connect to for non-fatal reasons - pub const fn with_backoff_durations(mut self, backoff_durations: PeerBackoffDurations) -> Self { - self.backoff_durations = backoff_durations; - self - } - - /// Returns the maximum number of peers, inbound and outbound. - pub const fn max_peers(&self) -> usize { - self.connection_info.max_outbound + self.connection_info.max_inbound - } - - /// Read from file nodes available at launch. Ignored if None. - pub fn with_basic_nodes_from_file( - self, - optional_file: Option>, - ) -> Result { - let Some(file_path) = optional_file else { return Ok(self) }; - let reader = match std::fs::File::open(file_path.as_ref()) { - Ok(file) => io::BufReader::new(file), - Err(e) if e.kind() == ErrorKind::NotFound => return Ok(self), - Err(e) => Err(e)?, - }; - info!(target: "net::peers", file = %file_path.as_ref().display(), "Loading saved peers"); - let nodes: HashSet = serde_json::from_reader(reader)?; - Ok(self.with_basic_nodes(nodes)) - } - - /// Returns settings for testing - #[cfg(test)] - fn test() -> Self { - Self { - refill_slots_interval: Duration::from_millis(100), - backoff_durations: PeerBackoffDurations::test(), - ..Default::default() - } - } -} - -/// The durations to use when a backoff should be applied to a peer. -/// -/// See also [`BackoffKind`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PeerBackoffDurations { - /// Applies to connection problems where there is a chance that they will be resolved after the - /// short duration. - #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] - pub low: Duration, - /// Applies to more severe connection problems where there is a lower chance that they will be - /// resolved. - #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] - pub medium: Duration, - /// Intended for spammers, or bad peers in general. - #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] - pub high: Duration, - /// Maximum total backoff duration. - #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))] - pub max: Duration, -} - -impl PeerBackoffDurations { - /// Returns the corresponding [`Duration`] - pub const fn backoff(&self, kind: BackoffKind) -> Duration { - match kind { - BackoffKind::Low => self.low, - BackoffKind::Medium => self.medium, - BackoffKind::High => self.high, - } - } - - /// Returns the timestamp until which we should backoff. - /// - /// The Backoff duration is capped by the configured maximum backoff duration. - pub fn backoff_until(&self, kind: BackoffKind, backoff_counter: u8) -> std::time::Instant { - let backoff_time = self.backoff(kind); - let backoff_time = backoff_time + backoff_time * backoff_counter as u32; - let now = std::time::Instant::now(); - now + backoff_time.min(self.max) - } - - /// Returns durations for testing. - #[cfg(test)] - const fn test() -> Self { - Self { - low: Duration::from_millis(200), - medium: Duration::from_millis(200), - high: Duration::from_millis(200), - max: Duration::from_millis(200), - } - } -} - -impl Default for PeerBackoffDurations { - fn default() -> Self { - Self { - low: Duration::from_secs(30), - // 3min - medium: Duration::from_secs(60 * 3), - // 15min - high: Duration::from_secs(60 * 15), - // 1h - max: Duration::from_secs(60 * 60), - } - } -} - /// Error thrown when a incoming connection is rejected right away #[derive(Debug, Error, PartialEq, Eq)] pub enum InboundConnectionError { @@ -1541,11 +1265,9 @@ impl Display for InboundConnectionError { mod tests { use super::PeersManager; use crate::{ - error::BackoffKind, peers::{ - manager::{ConnectionInfo, PeerBackoffDurations, PeerConnectionState}, - reputation::DEFAULT_REPUTATION, - InboundConnectionError, PeerAction, + ConnectionInfo, InboundConnectionError, PeerAction, PeerBackoffDurations, + PeerConnectionState, }, session::PendingSessionHandshakeError, PeersConfig, @@ -1558,6 +1280,7 @@ mod tests { use reth_net_banlist::BanList; use reth_network_api::{Direction, ReputationChangeKind}; use reth_network_peers::PeerId; + use reth_network_types::{peers::reputation::DEFAULT_REPUTATION, BackoffKind}; use reth_primitives::B512; use std::{ collections::HashSet, @@ -2106,7 +1829,7 @@ mod tests { peers.add_trusted_peer_id(trusted); // saturate the inbound slots - for i in 0..peers.connection_info.max_inbound { + for i in 0..peers.connection_info.config.max_inbound { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, i as u8)), 8008); assert!(peers.on_incoming_pending_session(socket_addr.ip()).is_ok()); let peer_id = PeerId::random(); @@ -2404,7 +2127,7 @@ mod tests { match a { Ok(_) => panic!(), Err(err) => match err { - super::InboundConnectionError::IpBanned {} => { + InboundConnectionError::IpBanned {} => { assert_eq!(peer_manager.connection_info.num_pending_in, 0) } _ => unreachable!(), @@ -2769,7 +2492,7 @@ mod tests { let mut peer_manager = PeersManager::new(config); let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)); let socket_addr = SocketAddr::new(ip, 8008); - for _ in 0..peer_manager.connection_info.max_concurrent_outbound_dials * 2 { + for _ in 0..peer_manager.connection_info.config.max_concurrent_outbound_dials * 2 { peer_manager.add_peer(PeerId::random(), socket_addr, None); } @@ -2779,7 +2502,7 @@ mod tests { .iter() .filter(|ev| matches!(ev, PeerAction::Connect { .. })) .count(); - assert_eq!(dials, peer_manager.connection_info.max_concurrent_outbound_dials); + assert_eq!(dials, peer_manager.connection_info.config.max_concurrent_outbound_dials); } #[tokio::test] @@ -2790,18 +2513,18 @@ mod tests { let socket_addr = SocketAddr::new(ip, 8008); // add more peers than allowed - for _ in 0..peer_manager.connection_info.max_concurrent_outbound_dials * 2 { + for _ in 0..peer_manager.connection_info.config.max_concurrent_outbound_dials * 2 { peer_manager.add_peer(PeerId::random(), socket_addr, None); } - for _ in 0..peer_manager.connection_info.max_concurrent_outbound_dials * 2 { + for _ in 0..peer_manager.connection_info.config.max_concurrent_outbound_dials * 2 { match event!(peer_manager) { PeerAction::PeerAdded(_) => {} _ => unreachable!(), } } - for _ in 0..peer_manager.connection_info.max_concurrent_outbound_dials { + for _ in 0..peer_manager.connection_info.config.max_concurrent_outbound_dials { match event!(peer_manager) { PeerAction::Connect { .. } => {} _ => unreachable!(), @@ -2813,7 +2536,7 @@ mod tests { // all dialed connections should be in 'PendingOut' state let dials = peer_manager.connection_info.num_pending_out; - assert_eq!(dials, peer_manager.connection_info.max_concurrent_outbound_dials); + assert_eq!(dials, peer_manager.connection_info.config.max_concurrent_outbound_dials); let num_pendingout_states = peer_manager .peers @@ -2823,7 +2546,7 @@ mod tests { .collect::>(); assert_eq!( num_pendingout_states.len(), - peer_manager.connection_info.max_concurrent_outbound_dials + peer_manager.connection_info.config.max_concurrent_outbound_dials ); // establish dialed connections diff --git a/crates/net/network/src/peers/mod.rs b/crates/net/network/src/peers/mod.rs deleted file mode 100644 index fafb2d7622e7..000000000000 --- a/crates/net/network/src/peers/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Peer related implementations - -mod manager; -mod reputation; - -pub(crate) use manager::InboundConnectionError; -pub use manager::{ConnectionInfo, Peer, PeerAction, PeersConfig, PeersHandle, PeersManager}; -pub use reputation::ReputationChangeWeights; -pub use reth_network_api::PeerKind; - -/// Maximum number of available slots for outbound sessions. -pub const DEFAULT_MAX_COUNT_PEERS_OUTBOUND: u32 = 100; - -/// Maximum number of available slots for inbound sessions. -pub const DEFAULT_MAX_COUNT_PEERS_INBOUND: u32 = 30; - -/// Maximum number of available slots for concurrent outgoing dials. -/// -/// This restricts how many outbound dials can be performed concurrently. -pub const DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS: usize = 15; diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 7551721ea231..97ccaf2c6a78 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -3,7 +3,6 @@ use crate::{ message::{NewBlockMessage, PeerMessage, PeerRequest, PeerResponse, PeerResponseResult}, session::{ - config::INITIAL_REQUEST_TIMEOUT, conn::EthRlpxConnection, handle::{ActiveSessionMessage, SessionCommand}, SessionId, @@ -20,6 +19,7 @@ use reth_eth_wire::{ use reth_metrics::common::mpsc::MeteredPollSender; use reth_network_p2p::error::RequestError; use reth_network_peers::PeerId; +use reth_network_types::session::config::INITIAL_REQUEST_TIMEOUT; use rustc_hash::FxHashMap; use std::{ collections::VecDeque, @@ -759,10 +759,7 @@ fn calculate_new_timeout(current_timeout: Duration, estimated_rtt: Duration) -> #[cfg(test)] mod tests { use super::*; - use crate::session::{ - config::PROTOCOL_BREACH_REQUEST_TIMEOUT, handle::PendingSessionEvent, - start_pending_incoming_session, - }; + use crate::session::{handle::PendingSessionEvent, start_pending_incoming_session}; use reth_chainspec::MAINNET; use reth_ecies::stream::ECIESStream; use reth_eth_wire::{ @@ -770,6 +767,7 @@ mod tests { UnauthedEthStream, UnauthedP2PStream, }; use reth_network_peers::pk2id; + use reth_network_types::session::config::PROTOCOL_BREACH_REQUEST_TIMEOUT; use reth_primitives::{ForkFilter, Hardfork}; use secp256k1::{SecretKey, SECP256K1}; use tokio::{ diff --git a/crates/net/network/src/session/counter.rs b/crates/net/network/src/session/counter.rs new file mode 100644 index 000000000000..0d8f764f206d --- /dev/null +++ b/crates/net/network/src/session/counter.rs @@ -0,0 +1,106 @@ +use reth_network_api::Direction; +use reth_network_types::SessionLimits; + +use super::ExceedsSessionLimit; + +/// Keeps track of all sessions. +#[derive(Debug, Clone)] +pub struct SessionCounter { + /// Limits to enforce. + limits: SessionLimits, + /// Number of pending incoming sessions. + pending_inbound: u32, + /// Number of pending outgoing sessions. + pending_outbound: u32, + /// Number of active inbound sessions. + active_inbound: u32, + /// Number of active outbound sessions. + active_outbound: u32, +} + +// === impl SessionCounter === + +impl SessionCounter { + pub(crate) const fn new(limits: SessionLimits) -> Self { + Self { + limits, + pending_inbound: 0, + pending_outbound: 0, + active_inbound: 0, + active_outbound: 0, + } + } + + pub(crate) fn inc_pending_inbound(&mut self) { + self.pending_inbound += 1; + } + + pub(crate) fn inc_pending_outbound(&mut self) { + self.pending_outbound += 1; + } + + pub(crate) fn dec_pending(&mut self, direction: &Direction) { + match direction { + Direction::Outgoing(_) => { + self.pending_outbound -= 1; + } + Direction::Incoming => { + self.pending_inbound -= 1; + } + } + } + + pub(crate) fn inc_active(&mut self, direction: &Direction) { + match direction { + Direction::Outgoing(_) => { + self.active_outbound += 1; + } + Direction::Incoming => { + self.active_inbound += 1; + } + } + } + + pub(crate) fn dec_active(&mut self, direction: &Direction) { + match direction { + Direction::Outgoing(_) => { + self.active_outbound -= 1; + } + Direction::Incoming => { + self.active_inbound -= 1; + } + } + } + + pub(crate) const fn ensure_pending_outbound(&self) -> Result<(), ExceedsSessionLimit> { + Self::ensure(self.pending_outbound, self.limits.max_pending_outbound) + } + + pub(crate) const fn ensure_pending_inbound(&self) -> Result<(), ExceedsSessionLimit> { + Self::ensure(self.pending_inbound, self.limits.max_pending_inbound) + } + + const fn ensure(current: u32, limit: Option) -> Result<(), ExceedsSessionLimit> { + if let Some(limit) = limit { + if current >= limit { + return Err(ExceedsSessionLimit(limit)) + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_limits() { + let mut limits = SessionCounter::new(SessionLimits::default().with_max_pending_inbound(2)); + assert!(limits.ensure_pending_outbound().is_ok()); + limits.inc_pending_inbound(); + assert!(limits.ensure_pending_inbound().is_ok()); + limits.inc_pending_inbound(); + assert!(limits.ensure_pending_inbound().is_err()); + } +} diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index e8a80bcff193..715ed59cf635 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -1,10 +1,7 @@ //! Support for handling peer sessions. -use crate::{ - message::PeerMessage, - metrics::SessionManagerMetrics, - session::{active::ActiveSession, config::SessionCounter}, -}; +use crate::{message::PeerMessage, metrics::SessionManagerMetrics, session::active::ActiveSession}; +use counter::SessionCounter; use futures::{future::Either, io, FutureExt, StreamExt}; use reth_ecies::{stream::ECIESStream, ECIESError}; use reth_eth_wire::{ @@ -15,6 +12,7 @@ use reth_eth_wire::{ }; use reth_metrics::common::mpsc::MeteredPollSender; use reth_network_peers::PeerId; +use reth_network_types::SessionsConfig; use reth_primitives::{ForkFilter, ForkId, ForkTransition, Head}; use reth_tasks::TaskSpawner; use rustc_hash::FxHashMap; @@ -37,12 +35,11 @@ use tokio_util::sync::PollSender; use tracing::{debug, instrument, trace}; mod active; -mod config; mod conn; +mod counter; mod handle; pub use crate::message::PeerRequestSender; use crate::protocol::{IntoRlpxSubProtocol, RlpxSubProtocolHandlers, RlpxSubProtocols}; -pub use config::{SessionLimits, SessionsConfig}; pub use handle::{ ActiveSessionHandle, ActiveSessionMessage, PendingSessionEvent, PendingSessionHandle, SessionCommand, diff --git a/crates/net/network/src/transactions/constants.rs b/crates/net/network/src/transactions/constants.rs index 59ec103cdace..48fb8857cc35 100644 --- a/crates/net/network/src/transactions/constants.rs +++ b/crates/net/network/src/transactions/constants.rs @@ -57,9 +57,9 @@ pub mod tx_manager { /// Constants used by [`TransactionFetcher`](super::TransactionFetcher). pub mod tx_fetcher { - use crate::{ - peers::{DEFAULT_MAX_COUNT_PEERS_INBOUND, DEFAULT_MAX_COUNT_PEERS_OUTBOUND}, - transactions::fetcher::TransactionFetcherInfo, + use crate::transactions::fetcher::TransactionFetcherInfo; + use reth_network_types::peers::config::{ + DEFAULT_MAX_COUNT_PEERS_INBOUND, DEFAULT_MAX_COUNT_PEERS_OUTBOUND, }; use super::{ From 7a82f4eaec23e3664ae53b90f6b506e6536d2598 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 27 Jun 2024 02:43:29 -0700 Subject: [PATCH 230/405] feat(trie): forward-only in-memory cursor (#9079) --- crates/trie/trie/src/forward_cursor.rs | 51 +++ .../trie/trie/src/hashed_cursor/post_state.rs | 381 ++++++++---------- crates/trie/trie/src/lib.rs | 3 + 3 files changed, 223 insertions(+), 212 deletions(-) create mode 100644 crates/trie/trie/src/forward_cursor.rs diff --git a/crates/trie/trie/src/forward_cursor.rs b/crates/trie/trie/src/forward_cursor.rs new file mode 100644 index 000000000000..1f14a462b1ef --- /dev/null +++ b/crates/trie/trie/src/forward_cursor.rs @@ -0,0 +1,51 @@ +/// The implementation of forward-only in memory cursor over the entries. +/// The cursor operates under the assumption that the supplied collection is pre-sorted. +#[derive(Debug)] +pub struct ForwardInMemoryCursor<'a, K, V> { + /// The reference to the pre-sorted collection of entries. + entries: &'a Vec<(K, V)>, + /// The index where cursor is currently positioned. + index: usize, +} + +impl<'a, K, V> ForwardInMemoryCursor<'a, K, V> { + /// Create new forward cursor positioned at the beginning of the collection. + /// The cursor expects all of the entries have been sorted in advance. + pub const fn new(entries: &'a Vec<(K, V)>) -> Self { + Self { entries, index: 0 } + } + + /// Returns `true` if the cursor is empty, regardless of its position. + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } +} + +impl<'a, K, V> ForwardInMemoryCursor<'a, K, V> +where + K: PartialOrd + Copy, + V: Copy, +{ + /// Advances the cursor forward while `comparator` returns `true` or until the collection is + /// exhausted. Returns the first entry for which `comparator` returns `false` or `None`. + fn advance_while_false(&mut self, comparator: impl Fn(&K) -> bool) -> Option<(K, V)> { + let mut entry = self.entries.get(self.index); + while entry.map_or(false, |entry| comparator(&entry.0)) { + self.index += 1; + entry = self.entries.get(self.index); + } + entry.copied() + } + + /// Returns the first entry from the current cursor position that's greater or equal to the + /// provided key. This method advances the cursor forward. + pub fn seek(&mut self, key: &K) -> Option<(K, V)> { + self.advance_while_false(|k| k < key) + } + + /// Returns the first entry from the current cursor position that's greater than the provided + /// key. This method advances the cursor forward. + pub fn first_after(&mut self, key: &K) -> Option<(K, V)> { + self.advance_while_false(|k| k <= key) + } +} diff --git a/crates/trie/trie/src/hashed_cursor/post_state.rs b/crates/trie/trie/src/hashed_cursor/post_state.rs index b609048fafa9..ac262f3d44fc 100644 --- a/crates/trie/trie/src/hashed_cursor/post_state.rs +++ b/crates/trie/trie/src/hashed_cursor/post_state.rs @@ -1,6 +1,11 @@ use super::{HashedCursor, HashedCursorFactory, HashedStorageCursor}; -use crate::{HashedAccountsSorted, HashedPostStateSorted, HashedStorageSorted}; +use crate::{ + forward_cursor::ForwardInMemoryCursor, HashedAccountsSorted, HashedPostStateSorted, + HashedStorageSorted, +}; +use reth_db::DatabaseError; use reth_primitives::{Account, B256, U256}; +use std::collections::HashSet; /// The hashed cursor factory for the post state. #[derive(Debug, Clone)] @@ -20,7 +25,7 @@ impl<'a, CF: HashedCursorFactory> HashedCursorFactory for HashedPostStateCursorF type AccountCursor = HashedPostStateAccountCursor<'a, CF::AccountCursor>; type StorageCursor = HashedPostStateStorageCursor<'a, CF::StorageCursor>; - fn hashed_account_cursor(&self) -> Result { + fn hashed_account_cursor(&self) -> Result { let cursor = self.cursor_factory.hashed_account_cursor()?; Ok(HashedPostStateAccountCursor::new(cursor, &self.post_state.accounts)) } @@ -28,7 +33,7 @@ impl<'a, CF: HashedCursorFactory> HashedCursorFactory for HashedPostStateCursorF fn hashed_storage_cursor( &self, hashed_address: B256, - ) -> Result { + ) -> Result { let cursor = self.cursor_factory.hashed_storage_cursor(hashed_address)?; Ok(HashedPostStateStorageCursor::new(cursor, self.post_state.storages.get(&hashed_address))) } @@ -36,23 +41,28 @@ impl<'a, CF: HashedCursorFactory> HashedCursorFactory for HashedPostStateCursorF /// The cursor to iterate over post state hashed accounts and corresponding database entries. /// It will always give precedence to the data from the hashed post state. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct HashedPostStateAccountCursor<'a, C> { /// The database cursor. cursor: C, - /// The reference to the in-memory [`HashedAccountsSorted`]. - post_state_accounts: &'a HashedAccountsSorted, - /// The post state account index where the cursor is currently at. - post_state_account_index: usize, + /// Forward-only in-memory cursor over accounts. + post_state_cursor: ForwardInMemoryCursor<'a, B256, Account>, + /// Reference to the collection of account keys that were destroyed. + destroyed_accounts: &'a HashSet, /// The last hashed account that was returned by the cursor. /// De facto, this is a current cursor position. last_account: Option, } -impl<'a, C> HashedPostStateAccountCursor<'a, C> { +impl<'a, C> HashedPostStateAccountCursor<'a, C> +where + C: HashedCursor, +{ /// Create new instance of [`HashedPostStateAccountCursor`]. pub const fn new(cursor: C, post_state_accounts: &'a HashedAccountsSorted) -> Self { - Self { cursor, post_state_accounts, last_account: None, post_state_account_index: 0 } + let post_state_cursor = ForwardInMemoryCursor::new(&post_state_accounts.accounts); + let destroyed_accounts = &post_state_accounts.destroyed_accounts; + Self { cursor, post_state_cursor, destroyed_accounts, last_account: None } } /// Returns `true` if the account has been destroyed. @@ -61,29 +71,62 @@ impl<'a, C> HashedPostStateAccountCursor<'a, C> { /// This function only checks the post state, not the database, because the latter does not /// store destroyed accounts. fn is_account_cleared(&self, account: &B256) -> bool { - self.post_state_accounts.destroyed_accounts.contains(account) + self.destroyed_accounts.contains(account) + } + + fn seek_inner(&mut self, key: B256) -> Result, DatabaseError> { + // Take the next account from the post state with the key greater than or equal to the + // sought key. + let post_state_entry = self.post_state_cursor.seek(&key); + + // It's an exact match, return the account from post state without looking up in the + // database. + if post_state_entry.map_or(false, |entry| entry.0 == key) { + return Ok(post_state_entry) + } + + // It's not an exact match, reposition to the first greater or equal account that wasn't + // cleared. + let mut db_entry = self.cursor.seek(key)?; + while db_entry.as_ref().map_or(false, |(address, _)| self.is_account_cleared(address)) { + db_entry = self.cursor.next()?; + } + + // Compare two entries and return the lowest. + Ok(Self::compare_entries(post_state_entry, db_entry)) + } + + fn next_inner(&mut self, last_account: B256) -> Result, DatabaseError> { + // Take the next account from the post state with the key greater than the last sought key. + let post_state_entry = self.post_state_cursor.first_after(&last_account); + + // If post state was given precedence or account was cleared, move the cursor forward. + let mut db_entry = self.cursor.seek(last_account)?; + while db_entry.as_ref().map_or(false, |(address, _)| { + address <= &last_account || self.is_account_cleared(address) + }) { + db_entry = self.cursor.next()?; + } + + // Compare two entries and return the lowest. + Ok(Self::compare_entries(post_state_entry, db_entry)) } /// Return the account with the lowest hashed account key. /// /// Given the next post state and database entries, return the smallest of the two. /// If the account keys are the same, the post state entry is given precedence. - fn next_account( - post_state_item: Option<&(B256, Account)>, + fn compare_entries( + post_state_item: Option<(B256, Account)>, db_item: Option<(B256, Account)>, ) -> Option<(B256, Account)> { - match (post_state_item, db_item) { + if let Some((post_state_entry, db_entry)) = post_state_item.zip(db_item) { // If both are not empty, return the smallest of the two // Post state is given precedence if keys are equal - (Some((post_state_address, post_state_account)), Some((db_address, db_account))) => { - if post_state_address <= &db_address { - Some((*post_state_address, *post_state_account)) - } else { - Some((db_address, db_account)) - } - } + Some(if post_state_entry.0 <= db_entry.0 { post_state_entry } else { db_entry }) + } else { // Return either non-empty entry - _ => post_state_item.copied().or(db_item), + db_item.or(post_state_item) } } } @@ -102,42 +145,11 @@ where /// /// The returned account key is memoized and the cursor remains positioned at that key until /// [`HashedCursor::seek`] or [`HashedCursor::next`] are called. - fn seek(&mut self, key: B256) -> Result, reth_db::DatabaseError> { - self.last_account = None; - - // Take the next account from the post state with the key greater than or equal to the - // sought key. - let mut post_state_entry = - self.post_state_accounts.accounts.get(self.post_state_account_index); - while post_state_entry.map(|(k, _)| k < &key).unwrap_or_default() { - self.post_state_account_index += 1; - post_state_entry = self.post_state_accounts.accounts.get(self.post_state_account_index); - } - - // It's an exact match, return the account from post state without looking up in the - // database. - if let Some((address, account)) = post_state_entry { - if address == &key { - self.last_account = Some(*address); - return Ok(Some((*address, *account))) - } - } - - // It's not an exact match, reposition to the first greater or equal account that wasn't - // cleared. - let mut db_entry = self.cursor.seek(key)?; - while db_entry - .as_ref() - .map(|(address, _)| self.is_account_cleared(address)) - .unwrap_or_default() - { - db_entry = self.cursor.next()?; - } - - // Compare two entries and return the lowest. - let result = Self::next_account(post_state_entry, db_entry); - self.last_account = result.as_ref().map(|(address, _)| *address); - Ok(result) + fn seek(&mut self, key: B256) -> Result, DatabaseError> { + // Find the closes account. + let entry = self.seek_inner(key)?; + self.last_account = entry.as_ref().map(|entry| entry.0); + Ok(entry) } /// Retrieve the next entry from the cursor. @@ -147,100 +159,118 @@ where /// /// NOTE: This function will not return any entry unless [`HashedCursor::seek`] has been /// called. - fn next(&mut self) -> Result, reth_db::DatabaseError> { - let last_account = match self.last_account.as_ref() { - Some(account) => account, - None => return Ok(None), // no previous entry was found + fn next(&mut self) -> Result, DatabaseError> { + let next = match self.last_account { + Some(account) => { + let entry = self.next_inner(account)?; + self.last_account = entry.as_ref().map(|entry| entry.0); + entry + } + // no previous entry was found + None => None, }; - - // If post state was given precedence, move the cursor forward. - let mut db_entry = self.cursor.seek(*last_account)?; - while db_entry - .as_ref() - .map(|(address, _)| address <= last_account || self.is_account_cleared(address)) - .unwrap_or_default() - { - db_entry = self.cursor.next()?; - } - - // Take the next account from the post state with the key greater than the last sought key. - let mut post_state_entry = - self.post_state_accounts.accounts.get(self.post_state_account_index); - while post_state_entry.map(|(k, _)| k <= last_account).unwrap_or_default() { - self.post_state_account_index += 1; - post_state_entry = self.post_state_accounts.accounts.get(self.post_state_account_index); - } - - // Compare two entries and return the lowest. - let result = Self::next_account(post_state_entry, db_entry); - self.last_account = result.as_ref().map(|(address, _)| *address); - Ok(result) + Ok(next) } } /// The cursor to iterate over post state hashed storages and corresponding database entries. /// It will always give precedence to the data from the post state. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct HashedPostStateStorageCursor<'a, C> { /// The database cursor. cursor: C, - /// The reference to post state storage. - post_state_storage: Option<&'a HashedStorageSorted>, - /// The post state index where the cursor is currently at. - post_state_storage_index: usize, + /// Forward-only in-memory cursor over non zero-valued account storage slots. + post_state_cursor: Option>, + /// Reference to the collection of storage slot keys that were cleared. + cleared_slots: Option<&'a HashSet>, + /// Flag indicating whether database storage was wiped. + storage_wiped: bool, /// The last slot that has been returned by the cursor. /// De facto, this is the cursor's position for the given account key. last_slot: Option, } -impl<'a, C> HashedPostStateStorageCursor<'a, C> { +impl<'a, C> HashedPostStateStorageCursor<'a, C> +where + C: HashedStorageCursor, +{ /// Create new instance of [`HashedPostStateStorageCursor`] for the given hashed address. - pub const fn new(cursor: C, post_state: Option<&'a HashedStorageSorted>) -> Self { - Self { - cursor, - post_state_storage: post_state, - last_slot: None, - post_state_storage_index: 0, - } - } - - /// Returns `true` if the storage for the given - /// The database is not checked since it already has no wiped storage entries. - const fn is_db_storage_wiped(&self) -> bool { - match self.post_state_storage { - Some(storage) => storage.wiped, - None => false, - } + pub fn new(cursor: C, post_state_storage: Option<&'a HashedStorageSorted>) -> Self { + let post_state_cursor = + post_state_storage.map(|s| ForwardInMemoryCursor::new(&s.non_zero_valued_slots)); + let cleared_slots = post_state_storage.map(|s| &s.zero_valued_slots); + let storage_wiped = post_state_storage.map_or(false, |s| s.wiped); + Self { cursor, post_state_cursor, cleared_slots, storage_wiped, last_slot: None } } /// Check if the slot was zeroed out in the post state. /// The database is not checked since it already has no zero-valued slots. fn is_slot_zero_valued(&self, slot: &B256) -> bool { - self.post_state_storage - .map(|storage| storage.zero_valued_slots.contains(slot)) - .unwrap_or_default() + self.cleared_slots.map_or(false, |s| s.contains(slot)) + } + + /// Find the storage entry in post state or database that's greater or equal to provided subkey. + fn seek_inner(&mut self, subkey: B256) -> Result, DatabaseError> { + // Attempt to find the account's storage in post state. + let post_state_entry = self.post_state_cursor.as_mut().and_then(|c| c.seek(&subkey)); + + // If database storage was wiped or it's an exact match, + // return the storage slot from post state without looking up in the database. + if self.storage_wiped || post_state_entry.map_or(false, |entry| entry.0 == subkey) { + return Ok(post_state_entry) + } + + // It's not an exact match and storage was not wiped, + // reposition to the first greater or equal account. + let mut db_entry = self.cursor.seek(subkey)?; + while db_entry.as_ref().map_or(false, |entry| self.is_slot_zero_valued(&entry.0)) { + db_entry = self.cursor.next()?; + } + + // Compare two entries and return the lowest. + Ok(Self::compare_entries(post_state_entry, db_entry)) + } + + /// Find the storage entry that is right after current cursor position. + fn next_inner(&mut self, last_slot: B256) -> Result, DatabaseError> { + // Attempt to find the account's storage in post state. + let post_state_entry = + self.post_state_cursor.as_mut().and_then(|c| c.first_after(&last_slot)); + + // Return post state entry immediately if database was wiped. + if self.storage_wiped { + return Ok(post_state_entry) + } + + // If post state was given precedence, move the cursor forward. + // If the entry was already returned or is zero-valued, move to the next. + let mut db_entry = self.cursor.seek(last_slot)?; + while db_entry + .as_ref() + .map_or(false, |entry| entry.0 == last_slot || self.is_slot_zero_valued(&entry.0)) + { + db_entry = self.cursor.next()?; + } + + // Compare two entries and return the lowest. + Ok(Self::compare_entries(post_state_entry, db_entry)) } /// Return the storage entry with the lowest hashed storage key (hashed slot). /// /// Given the next post state and database entries, return the smallest of the two. /// If the storage keys are the same, the post state entry is given precedence. - fn next_slot( - post_state_item: Option<&(B256, U256)>, + fn compare_entries( + post_state_item: Option<(B256, U256)>, db_item: Option<(B256, U256)>, ) -> Option<(B256, U256)> { - match (post_state_item, db_item) { + if let Some((post_state_entry, db_entry)) = post_state_item.zip(db_item) { // If both are not empty, return the smallest of the two // Post state is given precedence if keys are equal - (Some((post_state_slot, post_state_value)), Some((db_slot, db_value))) => { - if post_state_slot <= &db_slot { - Some((*post_state_slot, *post_state_value)) - } else { - Some((db_slot, db_value)) - } - } + Some(if post_state_entry.0 <= db_entry.0 { post_state_entry } else { db_entry }) + } else { // Return either non-empty entry - _ => db_item.or_else(|| post_state_item.copied()), + db_item.or(post_state_item) } } } @@ -252,97 +282,24 @@ where type Value = U256; /// Seek the next account storage entry for a given hashed key pair. - fn seek( - &mut self, - subkey: B256, - ) -> Result, reth_db::DatabaseError> { - // Attempt to find the account's storage in post state. - let mut post_state_entry = None; - if let Some(storage) = self.post_state_storage { - post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index); - - while post_state_entry.map(|(slot, _)| slot < &subkey).unwrap_or_default() { - self.post_state_storage_index += 1; - post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index); - } - } - - // It's an exact match, return the storage slot from post state without looking up in - // the database. - if let Some((slot, value)) = post_state_entry { - if slot == &subkey { - self.last_slot = Some(*slot); - return Ok(Some((*slot, *value))) - } - } - - // It's not an exact match, reposition to the first greater or equal account. - let db_entry = if self.is_db_storage_wiped() { - None - } else { - let mut db_entry = self.cursor.seek(subkey)?; - - while db_entry - .as_ref() - .map(|entry| self.is_slot_zero_valued(&entry.0)) - .unwrap_or_default() - { - db_entry = self.cursor.next()?; - } - - db_entry - }; - - // Compare two entries and return the lowest. - let result = Self::next_slot(post_state_entry, db_entry); - self.last_slot = result.as_ref().map(|entry| entry.0); - Ok(result) + fn seek(&mut self, subkey: B256) -> Result, DatabaseError> { + let entry = self.seek_inner(subkey)?; + self.last_slot = entry.as_ref().map(|entry| entry.0); + Ok(entry) } /// Return the next account storage entry for the current account key. - /// - /// # Panics - /// - /// If the account key is not set. [`HashedCursor::seek`] must be called first in order to - /// position the cursor. - fn next(&mut self) -> Result, reth_db::DatabaseError> { - let last_slot = match self.last_slot.as_ref() { - Some(slot) => slot, - None => return Ok(None), // no previous entry was found - }; - - let db_entry = if self.is_db_storage_wiped() { - None - } else { - // If post state was given precedence, move the cursor forward. - let mut db_entry = self.cursor.seek(*last_slot)?; - - // If the entry was already returned or is zero-values, move to the next. - while db_entry - .as_ref() - .map(|entry| &entry.0 == last_slot || self.is_slot_zero_valued(&entry.0)) - .unwrap_or_default() - { - db_entry = self.cursor.next()?; + fn next(&mut self) -> Result, DatabaseError> { + let next = match self.last_slot { + Some(last_slot) => { + let entry = self.next_inner(last_slot)?; + self.last_slot = entry.as_ref().map(|entry| entry.0); + entry } - - db_entry + // no previous entry was found + None => None, }; - - // Attempt to find the account's storage in post state. - let mut post_state_entry = None; - if let Some(storage) = self.post_state_storage { - post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index); - while post_state_entry.map(|(slot, _)| slot <= last_slot).unwrap_or_default() { - self.post_state_storage_index += 1; - post_state_entry = storage.non_zero_valued_slots.get(self.post_state_storage_index); - } - } - - // Compare two entries and return the lowest. - let result = Self::next_slot(post_state_entry, db_entry); - self.last_slot = result.as_ref().map(|entry| entry.0); - Ok(result) + Ok(next) } } @@ -354,13 +311,13 @@ where /// /// This function should be called before attempting to call [`HashedCursor::seek`] or /// [`HashedCursor::next`]. - fn is_storage_empty(&mut self) -> Result { - let is_empty = match self.post_state_storage { - Some(storage) => { + fn is_storage_empty(&mut self) -> Result { + let is_empty = match &self.post_state_cursor { + Some(cursor) => { // If the storage has been wiped at any point - storage.wiped && + self.storage_wiped && // and the current storage does not contain any non-zero values - storage.non_zero_valued_slots.is_empty() + cursor.is_empty() } None => self.cursor.is_storage_empty()?, }; diff --git a/crates/trie/trie/src/lib.rs b/crates/trie/trie/src/lib.rs index eea65a7b34d8..07af0775705a 100644 --- a/crates/trie/trie/src/lib.rs +++ b/crates/trie/trie/src/lib.rs @@ -17,6 +17,9 @@ /// The container indicates when the trie has been modified. pub mod prefix_set; +/// The implementation of forward-only in-memory cursor. +pub mod forward_cursor; + /// The cursor implementations for navigating account and storage tries. pub mod trie_cursor; From 16fc18bf5bcbae7a5b9c1cbe6e57c2c23200d538 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 27 Jun 2024 06:04:20 -0400 Subject: [PATCH 231/405] chore: remove empty ban_list.rs file (#9133) --- crates/net/banlist/src/ban_list.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 crates/net/banlist/src/ban_list.rs diff --git a/crates/net/banlist/src/ban_list.rs b/crates/net/banlist/src/ban_list.rs deleted file mode 100644 index e69de29bb2d1..000000000000 From d3091cbb58bfff07eca44af61f3ba8ec01e0323c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 27 Jun 2024 12:04:36 +0200 Subject: [PATCH 232/405] chore: rm utils.rs from cli crate (#9132) --- bin/reth/src/lib.rs | 10 +++++++++- bin/reth/src/utils.rs | 10 ---------- examples/rpc-db/src/main.rs | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 bin/reth/src/utils.rs diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 7c024438ae27..35cd1357a12c 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -32,7 +32,15 @@ pub mod cli; pub mod commands; mod macros; -pub mod utils; + +/// Re-exported utils. +pub mod utils { + pub use reth_db::open_db_read_only; + + /// Re-exported from `reth_node_core`, also to prevent a breaking change. See the comment + /// on the `reth_node_core::args` re-export for more details. + pub use reth_node_core::utils::*; +} /// Re-exported payload related types pub mod payload { diff --git a/bin/reth/src/utils.rs b/bin/reth/src/utils.rs deleted file mode 100644 index cf8985b290d9..000000000000 --- a/bin/reth/src/utils.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Common CLI utility functions. - -/// Exposing `open_db_read_only` function -pub mod db { - pub use reth_db::open_db_read_only; -} - -/// Re-exported from `reth_node_core`, also to prevent a breaking change. See the comment on -/// the `reth_node_core::args` re-export for more details. -pub use reth_node_core::utils::*; diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index 96863d4f009f..90447790561f 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -17,7 +17,7 @@ use reth::{ providers::{BlockchainProvider, StaticFileProvider}, ProviderFactory, }, - utils::db::open_db_read_only, + utils::open_db_read_only, }; use reth_chainspec::ChainSpecBuilder; use reth_db::mdbx::DatabaseArguments; From 793aa85269c68e656ef8204311d03c329953a139 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 27 Jun 2024 12:17:32 +0200 Subject: [PATCH 233/405] chore: move engine-primitives to engine folder (#9130) --- Cargo.toml | 4 ++-- crates/{engine-primitives => engine/primitives}/Cargo.toml | 0 crates/{engine-primitives => engine/primitives}/src/error.rs | 0 crates/{engine-primitives => engine/primitives}/src/lib.rs | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename crates/{engine-primitives => engine/primitives}/Cargo.toml (100%) rename crates/{engine-primitives => engine/primitives}/src/error.rs (100%) rename crates/{engine-primitives => engine/primitives}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 3b52472ad6c5..8f101dc709a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ "crates/consensus/debug-client/", "crates/ethereum-forks/", "crates/e2e-test-utils/", - "crates/engine-primitives/", + "crates/engine/primitives/", "crates/engine/util/", "crates/errors/", "crates/ethereum-forks/", @@ -283,7 +283,7 @@ reth-dns-discovery = { path = "crates/net/dns" } reth-downloaders = { path = "crates/net/downloaders" } reth-e2e-test-utils = { path = "crates/e2e-test-utils" } reth-ecies = { path = "crates/net/ecies" } -reth-engine-primitives = { path = "crates/engine-primitives" } +reth-engine-primitives = { path = "crates/engine/primitives" } reth-engine-util = { path = "crates/engine/util" } reth-errors = { path = "crates/errors" } reth-eth-wire = { path = "crates/net/eth-wire" } diff --git a/crates/engine-primitives/Cargo.toml b/crates/engine/primitives/Cargo.toml similarity index 100% rename from crates/engine-primitives/Cargo.toml rename to crates/engine/primitives/Cargo.toml diff --git a/crates/engine-primitives/src/error.rs b/crates/engine/primitives/src/error.rs similarity index 100% rename from crates/engine-primitives/src/error.rs rename to crates/engine/primitives/src/error.rs diff --git a/crates/engine-primitives/src/lib.rs b/crates/engine/primitives/src/lib.rs similarity index 100% rename from crates/engine-primitives/src/lib.rs rename to crates/engine/primitives/src/lib.rs From e09ed75a9bd38edee3a50c5f9740ba4534d6247f Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 27 Jun 2024 21:03:40 +1000 Subject: [PATCH 234/405] fix: use 8 byte SHA in getClientVersionV1 (#9137) --- Cargo.lock | 44 +++++++++++++++++-- Cargo.toml | 1 + crates/node-core/Cargo.toml | 2 +- crates/node-core/build.rs | 5 ++- .../node-core/src/metrics/version_metrics.rs | 4 +- crates/node-core/src/version.rs | 29 +++++++----- deny.toml | 1 + 7 files changed, 66 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8aded4fc5e47..e8a8039233bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1865,10 +1865,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "const-str" -version = "0.5.7" +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] [[package]] name = "convert_case" @@ -4500,6 +4515,21 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "konst" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "lazy_static" version = "1.5.0" @@ -7539,7 +7569,7 @@ dependencies = [ "alloy-genesis", "alloy-rpc-types-engine", "clap", - "const-str", + "const_format", "derive_more", "dirs-next", "eyre", @@ -10358,6 +10388,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "universal-hash" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 8f101dc709a2..7cc284de43f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -414,6 +414,7 @@ aquamarine = "0.5" bytes = "1.5" bitflags = "2.4" clap = "4" +const_format = { version = "0.2.32", features = ["rust_1_64"] } dashmap = "5.5" derive_more = "0.99.17" fdlimit = "0.3.0" diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index 82e5c4dfc0fd..2390cbfb1a8e 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -58,7 +58,7 @@ eyre.workspace = true clap = { workspace = true, features = ["derive"] } humantime.workspace = true thiserror.workspace = true -const-str = "0.5.6" +const_format.workspace = true rand.workspace = true derive_more.workspace = true once_cell.workspace = true diff --git a/crates/node-core/build.rs b/crates/node-core/build.rs index 043505cdfb67..1a78793a4746 100644 --- a/crates/node-core/build.rs +++ b/crates/node-core/build.rs @@ -8,19 +8,20 @@ fn main() -> Result<(), Box> { EmitBuilder::builder() .git_describe(false, true, None) .git_dirty(true) - .git_sha(true) + .git_sha(false) .build_timestamp() .cargo_features() .cargo_target_triple() .emit_and_set()?; let sha = env::var("VERGEN_GIT_SHA")?; + let sha_short = &sha[0..7]; let is_dirty = env::var("VERGEN_GIT_DIRTY")? == "true"; // > git describe --always --tags // if not on a tag: v0.2.0-beta.3-82-g1939939b // if on a tag: v0.2.0-beta.3 - let not_on_tag = env::var("VERGEN_GIT_DESCRIBE")?.ends_with(&format!("-g{sha}")); + let not_on_tag = env::var("VERGEN_GIT_DESCRIBE")?.ends_with(&format!("-g{sha_short}")); let is_dev = is_dirty || not_on_tag; println!("cargo:rustc-env=RETH_VERSION_SUFFIX={}", if is_dev { "-dev" } else { "" }); Ok(()) diff --git a/crates/node-core/src/metrics/version_metrics.rs b/crates/node-core/src/metrics/version_metrics.rs index f0b11c3b7e8b..253e759dbb33 100644 --- a/crates/node-core/src/metrics/version_metrics.rs +++ b/crates/node-core/src/metrics/version_metrics.rs @@ -1,13 +1,13 @@ //! This exposes reth's version information over prometheus. -use crate::version::build_profile_name; +use crate::version::{build_profile_name, VERGEN_GIT_SHA}; use metrics::gauge; const LABELS: [(&str, &str); 6] = [ ("version", env!("CARGO_PKG_VERSION")), ("build_timestamp", env!("VERGEN_BUILD_TIMESTAMP")), ("cargo_features", env!("VERGEN_CARGO_FEATURES")), - ("git_sha", env!("VERGEN_GIT_SHA")), + ("git_sha", VERGEN_GIT_SHA), ("target_triple", env!("VERGEN_CARGO_TARGET_TRIPLE")), ("build_profile", build_profile_name()), ]; diff --git a/crates/node-core/src/version.rs b/crates/node-core/src/version.rs index db8bf09d1fe5..5151b861d586 100644 --- a/crates/node-core/src/version.rs +++ b/crates/node-core/src/version.rs @@ -11,8 +11,11 @@ pub const NAME_CLIENT: &str = "Reth"; /// The latest version from Cargo.toml. pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// The short SHA of the latest commit. -pub const VERGEN_GIT_SHA: &str = env!("VERGEN_GIT_SHA"); +/// The full SHA of the latest commit. +pub const VERGEN_GIT_SHA_LONG: &str = env!("VERGEN_GIT_SHA"); + +/// The 8 character short SHA of the latest commit. +pub const VERGEN_GIT_SHA: &str = const_format::str_index!(VERGEN_GIT_SHA_LONG, ..8); /// The build timestamp. pub const VERGEN_BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP"); @@ -27,11 +30,11 @@ pub const VERGEN_BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP"); /// ```text /// 0.1.0 (defa64b2) /// ``` -pub const SHORT_VERSION: &str = concat!( +pub const SHORT_VERSION: &str = const_format::concatcp!( env!("CARGO_PKG_VERSION"), env!("RETH_VERSION_SUFFIX"), " (", - env!("VERGEN_GIT_SHA"), + VERGEN_GIT_SHA, ")" ); @@ -52,13 +55,13 @@ pub const SHORT_VERSION: &str = concat!( /// Build Features: jemalloc /// Build Profile: maxperf /// ``` -pub const LONG_VERSION: &str = const_str::concat!( +pub const LONG_VERSION: &str = const_format::concatcp!( "Version: ", env!("CARGO_PKG_VERSION"), env!("RETH_VERSION_SUFFIX"), "\n", "Commit SHA: ", - env!("VERGEN_GIT_SHA"), + VERGEN_GIT_SHA_LONG, "\n", "Build Timestamp: ", env!("VERGEN_BUILD_TIMESTAMP"), @@ -81,11 +84,11 @@ pub const LONG_VERSION: &str = const_str::concat!( /// reth/v{major}.{minor}.{patch}-{sha1}/{target} /// ``` /// e.g.: `reth/v0.1.0-alpha.1-428a6dc2f/aarch64-apple-darwin` -pub(crate) const P2P_CLIENT_VERSION: &str = concat!( +pub(crate) const P2P_CLIENT_VERSION: &str = const_format::concatcp!( "reth/v", env!("CARGO_PKG_VERSION"), "-", - env!("VERGEN_GIT_SHA"), + VERGEN_GIT_SHA, "/", env!("VERGEN_CARGO_TARGET_TRIPLE") ); @@ -118,9 +121,13 @@ pub(crate) const fn build_profile_name() -> &'static str { // We split on the path separator of the *host* machine, which may be different from // `std::path::MAIN_SEPARATOR_STR`. const OUT_DIR: &str = env!("OUT_DIR"); - const SEP: char = if const_str::contains!(OUT_DIR, "/") { '/' } else { '\\' }; - let parts = const_str::split!(OUT_DIR, SEP); - parts[parts.len() - 4] + let unix_parts = const_format::str_split!(OUT_DIR, '/'); + if unix_parts.len() >= 4 { + unix_parts[unix_parts.len() - 4] + } else { + let win_parts = const_format::str_split!(OUT_DIR, '\\'); + win_parts[win_parts.len() - 4] + } } #[cfg(test)] diff --git a/deny.toml b/deny.toml index 4dff1b9ce250..bfe4169798b0 100644 --- a/deny.toml +++ b/deny.toml @@ -43,6 +43,7 @@ allow = [ "Unicode-DFS-2016", "Unlicense", "Unicode-3.0", + "Zlib", # https://github.com/briansmith/ring/issues/902 "LicenseRef-ring", # https://github.com/rustls/webpki/blob/main/LICENSE ISC Style From acc07537bceab33dd0aa217c03933043715573a0 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 27 Jun 2024 13:22:15 +0200 Subject: [PATCH 235/405] fix(docs): Fix links node builder docs (#9140) --- crates/node/builder/src/builder/mod.rs | 27 +++++++++++---------- examples/custom-node-components/src/main.rs | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index f23fc873e885..85fab2c27f00 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -66,7 +66,7 @@ pub type RethFullAdapter = FullNodeTypesAdapter = FullNodeTypesAdapter = FullNodeTypesAdapter = FullNodeTypesAdapter PoolBuilder for CustomPoolBuilder From 933a1dea399d92e9aff07fd98162ba03d7749a09 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 27 Jun 2024 13:37:52 +0200 Subject: [PATCH 236/405] chore(rpc): `reth-eth-api` crate (#8887) Co-authored-by: Matthias Seitz --- Cargo.lock | 72 +- Cargo.toml | 2 + crates/e2e-test-utils/src/rpc.rs | 2 +- crates/ethereum/evm/src/execute.rs | 2 +- crates/ethereum/evm/src/lib.rs | 42 +- crates/ethereum/node/tests/e2e/dev.rs | 2 +- crates/evm/src/lib.rs | 38 +- crates/evm/src/provider.rs | 12 +- crates/node-core/Cargo.toml | 4 +- crates/node-core/src/args/gas_price_oracle.rs | 2 +- crates/node-core/src/args/rpc_server.rs | 2 +- crates/node-core/src/lib.rs | 4 +- crates/node/builder/src/rpc.rs | 5 +- crates/optimism/evm/src/execute.rs | 2 +- crates/optimism/evm/src/lib.rs | 2 +- crates/optimism/node/src/rpc.rs | 2 +- crates/rpc/rpc-api/Cargo.toml | 9 +- crates/rpc/rpc-api/src/debug.rs | 2 +- crates/rpc/rpc-api/src/lib.rs | 18 +- crates/rpc/rpc-builder/src/auth.rs | 2 +- crates/rpc/rpc-builder/src/config.rs | 2 +- crates/rpc/rpc-builder/src/eth.rs | 20 +- crates/rpc/rpc-builder/src/lib.rs | 19 +- crates/rpc/rpc-eth-api/Cargo.toml | 78 + .../src => rpc-eth-api/src/api}/bundle.rs | 2 +- .../src/api/filter.rs} | 3 + .../src/eth.rs => rpc-eth-api/src/api/mod.rs} | 7 + .../src/api/pubsub.rs} | 2 + .../src/api/servers}/bundle.rs | 29 +- .../src/api/servers}/filter.rs | 132 +- .../src/api/servers/helpers/block.rs | 35 + .../src/api/servers/helpers/call.rs | 29 + .../src/api/servers/helpers/fees.rs | 39 + .../src/api/servers/helpers/mod.rs | 19 + .../src/api/servers/helpers/optimism.rs | 231 ++ .../src/api/servers/helpers/pending_block.rs | 41 + .../src/api/servers/helpers/receipt.rs | 13 + .../src/api/servers/helpers}/signer.rs | 49 +- .../src/api/servers/helpers/spec.rs | 64 + .../src/api/servers/helpers/state.rs | 104 + .../src/api/servers/helpers/trace.rs | 19 + .../src/api/servers/helpers/traits/block.rs | 244 +++ .../servers/helpers/traits/blocking_task.rs | 55 + .../src/api/servers/helpers/traits/call.rs | 775 +++++++ .../src/api/servers/helpers/traits/fee.rs | 346 +++ .../src/api/servers/helpers/traits/mod.rs | 45 + .../servers/helpers/traits}/pending_block.rs | 399 ++-- .../src/api/servers/helpers/traits/receipt.rs | 37 + .../src/api/servers/helpers/traits/signer.rs | 40 + .../src/api/servers/helpers/traits/spec.rs | 31 + .../src/api/servers/helpers/traits/state.rs | 247 +++ .../src/api/servers/helpers/traits/trace.rs | 412 ++++ .../api/servers/helpers/traits/transaction.rs | 651 ++++++ .../src/api/servers/helpers/transaction.rs | 126 ++ crates/rpc/rpc-eth-api/src/api/servers/mod.rs | 323 +++ .../src/api/servers}/pubsub.rs | 18 +- .../src/api/servers}/server.rs | 158 +- .../eth => rpc-eth-api/src}/cache/config.rs | 4 +- crates/rpc/rpc-eth-api/src/cache/db.rs | 169 ++ .../eth => rpc-eth-api/src}/cache/metrics.rs | 2 + .../src/eth => rpc-eth-api/src}/cache/mod.rs | 19 +- .../src}/cache/multi_consumer.rs | 7 +- .../{rpc/src/eth => rpc-eth-api/src}/error.rs | 0 .../api => rpc-eth-api/src}/fee_history.rs | 14 +- .../src/eth => rpc-eth-api/src}/gas_oracle.rs | 47 +- .../eth => rpc-eth-api/src}/id_provider.rs | 7 +- crates/rpc/rpc-eth-api/src/lib.rs | 62 + .../src/eth => rpc-eth-api/src}/logs_utils.rs | 12 +- crates/rpc/rpc-eth-api/src/pending_block.rs | 162 ++ crates/rpc/rpc-eth-api/src/receipt.rs | 126 ++ crates/rpc/{rpc => rpc-eth-api}/src/result.rs | 15 +- .../src/eth => rpc-eth-api/src}/revm_utils.rs | 63 +- crates/rpc/rpc-eth-api/src/transaction.rs | 96 + .../{rpc/src/eth => rpc-eth-api/src}/utils.rs | 7 +- crates/rpc/rpc-testing-util/Cargo.toml | 1 + crates/rpc/rpc-testing-util/tests/it/trace.rs | 2 +- crates/rpc/rpc/Cargo.toml | 30 +- crates/rpc/rpc/src/admin.rs | 6 +- crates/rpc/rpc/src/debug.rs | 63 +- crates/rpc/rpc/src/eth/api/block.rs | 217 -- crates/rpc/rpc/src/eth/api/call.rs | 530 ----- crates/rpc/rpc/src/eth/api/fees.rs | 228 -- crates/rpc/rpc/src/eth/api/mod.rs | 503 ----- crates/rpc/rpc/src/eth/api/optimism.rs | 31 - crates/rpc/rpc/src/eth/api/sign.rs | 41 - crates/rpc/rpc/src/eth/api/state.rs | 178 -- crates/rpc/rpc/src/eth/api/transactions.rs | 1861 ----------------- crates/rpc/rpc/src/eth/mod.rs | 28 - crates/rpc/rpc/src/eth/optimism.rs | 32 - crates/rpc/rpc/src/eth/traits.rs | 13 - crates/rpc/rpc/src/lib.rs | 17 +- crates/rpc/rpc/src/net.rs | 2 +- crates/rpc/rpc/src/otterscan.rs | 10 +- crates/rpc/rpc/src/reth.rs | 5 +- crates/rpc/rpc/src/trace.rs | 29 +- crates/rpc/rpc/src/web3.rs | 3 +- crates/storage/db-api/Cargo.toml | 2 +- crates/storage/db-api/src/models/mod.rs | 125 +- crates/storage/nippy-jar/src/error.rs | 2 +- crates/storage/provider/src/traits/spec.rs | 1 + crates/storage/storage-api/src/receipts.rs | 1 + crates/tasks/Cargo.toml | 1 + crates/tasks/src/lib.rs | 2 + crates/tasks/src/pool.rs | 3 +- crates/transaction-pool/src/traits.rs | 4 +- examples/custom-dev-node/src/main.rs | 2 +- examples/custom-evm/src/main.rs | 24 +- examples/custom-inspector/src/main.rs | 2 +- examples/stateful-precompile/src/main.rs | 4 +- 109 files changed, 5427 insertions(+), 4458 deletions(-) create mode 100644 crates/rpc/rpc-eth-api/Cargo.toml rename crates/rpc/{rpc-api/src => rpc-eth-api/src/api}/bundle.rs (98%) rename crates/rpc/{rpc-api/src/eth_filter.rs => rpc-eth-api/src/api/filter.rs} (97%) rename crates/rpc/{rpc-api/src/eth.rs => rpc-eth-api/src/api/mod.rs} (99%) rename crates/rpc/{rpc-api/src/eth_pubsub.rs => rpc-eth-api/src/api/pubsub.rs} (92%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers}/bundle.rs (94%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers}/filter.rs (94%) create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers/helpers}/signer.rs (85%) create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs rename crates/rpc/{rpc/src/eth/api => rpc-eth-api/src/api/servers/helpers/traits}/pending_block.rs (53%) create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/mod.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers}/pubsub.rs (98%) rename crates/rpc/{rpc/src/eth/api => rpc-eth-api/src/api/servers}/server.rs (85%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/config.rs (91%) create mode 100644 crates/rpc/rpc-eth-api/src/cache/db.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/metrics.rs (93%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/mod.rs (99%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/multi_consumer.rs (94%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/error.rs (100%) rename crates/rpc/{rpc/src/eth/api => rpc-eth-api/src}/fee_history.rs (99%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/gas_oracle.rs (89%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/id_provider.rs (93%) create mode 100644 crates/rpc/rpc-eth-api/src/lib.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/logs_utils.rs (96%) create mode 100644 crates/rpc/rpc-eth-api/src/pending_block.rs create mode 100644 crates/rpc/rpc-eth-api/src/receipt.rs rename crates/rpc/{rpc => rpc-eth-api}/src/result.rs (95%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/revm_utils.rs (90%) create mode 100644 crates/rpc/rpc-eth-api/src/transaction.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/utils.rs (79%) delete mode 100644 crates/rpc/rpc/src/eth/api/block.rs delete mode 100644 crates/rpc/rpc/src/eth/api/call.rs delete mode 100644 crates/rpc/rpc/src/eth/api/fees.rs delete mode 100644 crates/rpc/rpc/src/eth/api/mod.rs delete mode 100644 crates/rpc/rpc/src/eth/api/optimism.rs delete mode 100644 crates/rpc/rpc/src/eth/api/sign.rs delete mode 100644 crates/rpc/rpc/src/eth/api/state.rs delete mode 100644 crates/rpc/rpc/src/eth/api/transactions.rs delete mode 100644 crates/rpc/rpc/src/eth/mod.rs delete mode 100644 crates/rpc/rpc/src/eth/optimism.rs delete mode 100644 crates/rpc/rpc/src/eth/traits.rs diff --git a/Cargo.lock b/Cargo.lock index e8a8039233bb..4c2408d2ac58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7602,8 +7602,8 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-prune-types", - "reth-rpc", "reth-rpc-api", + "reth-rpc-eth-api", "reth-rpc-server-types", "reth-rpc-types", "reth-rpc-types-compat", @@ -7996,33 +7996,22 @@ dependencies = [ name = "reth-rpc" version = "1.0.0" dependencies = [ - "alloy-dyn-abi", "alloy-genesis", "alloy-primitives", "alloy-rlp", - "alloy-sol-types", "assert_matches", "async-trait", - "derive_more", - "dyn-clone", "futures", "http 1.1.0", "http-body", "hyper", "jsonrpsee", "jsonwebtoken", - "metrics", - "parking_lot 0.12.3", "pin-project", - "rand 0.8.5", "reth-chainspec", "reth-consensus-common", "reth-errors", - "reth-evm", "reth-evm-ethereum", - "reth-evm-optimism", - "reth-execution-types", - "reth-metrics", "reth-network-api", "reth-network-peers", "reth-primitives", @@ -8030,7 +8019,7 @@ dependencies = [ "reth-revm", "reth-rpc-api", "reth-rpc-engine-api", - "reth-rpc-server-types", + "reth-rpc-eth-api", "reth-rpc-types", "reth-rpc-types-compat", "reth-tasks", @@ -8039,14 +8028,8 @@ dependencies = [ "revm", "revm-inspectors", "revm-primitives", - "schnellru", - "secp256k1", - "serde", - "serde_json", "tempfile", - "thiserror", "tokio", - "tokio-stream", "tower", "tracing", "tracing-futures", @@ -8056,11 +8039,11 @@ dependencies = [ name = "reth-rpc-api" version = "1.0.0" dependencies = [ - "alloy-dyn-abi", "jsonrpsee", "reth-engine-primitives", "reth-network-peers", "reth-primitives", + "reth-rpc-eth-api", "reth-rpc-types", "serde", "serde_json", @@ -8074,6 +8057,7 @@ dependencies = [ "jsonrpsee", "reth-primitives", "reth-rpc-api", + "reth-rpc-eth-api", "reth-rpc-types", "serde_json", "similar-asserts", @@ -8155,6 +8139,53 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-rpc-eth-api" +version = "1.0.0" +dependencies = [ + "alloy-dyn-abi", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more", + "dyn-clone", + "futures", + "jsonrpsee", + "jsonrpsee-types", + "metrics", + "parking_lot 0.12.3", + "rand 0.8.5", + "reth-chainspec", + "reth-errors", + "reth-evm", + "reth-evm-ethereum", + "reth-evm-optimism", + "reth-execution-types", + "reth-metrics", + "reth-network-api", + "reth-primitives", + "reth-provider", + "reth-revm", + "reth-rpc-server-types", + "reth-rpc-types", + "reth-rpc-types-compat", + "reth-tasks", + "reth-testing-utils", + "reth-transaction-pool", + "reth-trie", + "revm", + "revm-inspectors", + "revm-primitives", + "schnellru", + "secp256k1", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "reth-rpc-layer" version = "1.0.0" @@ -8372,6 +8403,7 @@ dependencies = [ name = "reth-tasks" version = "1.0.0" dependencies = [ + "auto_impl", "dyn-clone", "futures-util", "metrics", diff --git a/Cargo.toml b/Cargo.toml index 7cc284de43f8..441b69b2aa8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ members = [ "crates/rpc/rpc-api/", "crates/rpc/rpc-builder/", "crates/rpc/rpc-engine-api/", + "crates/rpc/rpc-eth-api/", "crates/rpc/rpc-layer", "crates/rpc/rpc-testing-util/", "crates/rpc/rpc-types-compat/", @@ -339,6 +340,7 @@ reth-rpc-api = { path = "crates/rpc/rpc-api" } reth-rpc-api-testing-util = { path = "crates/rpc/rpc-testing-util" } reth-rpc-builder = { path = "crates/rpc/rpc-builder" } reth-rpc-engine-api = { path = "crates/rpc/rpc-engine-api" } +reth-rpc-eth-api = { path = "crates/rpc/rpc-eth-api" } reth-rpc-layer = { path = "crates/rpc/rpc-layer" } reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-types = { path = "crates/rpc/rpc-types" } diff --git a/crates/e2e-test-utils/src/rpc.rs b/crates/e2e-test-utils/src/rpc.rs index 09f161a91dc7..0c55e9c23c0d 100644 --- a/crates/e2e-test-utils/src/rpc.rs +++ b/crates/e2e-test-utils/src/rpc.rs @@ -2,7 +2,7 @@ use alloy_consensus::TxEnvelope; use alloy_network::eip2718::Decodable2718; use reth::{api::FullNodeComponents, builder::rpc::RpcRegistry, rpc::api::DebugApiServer}; use reth_primitives::{Bytes, B256}; -use reth_rpc::eth::{error::EthResult, EthTransactions}; +use reth_rpc::eth::{servers::EthTransactions, EthResult}; pub struct RpcTestContext { pub inner: RpcRegistry, diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index e4c89dd778dd..d4cc8be77d20 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -179,7 +179,7 @@ where .into()) } - EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender); + self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender); // Execute transaction. let ResultAndState { result, state } = evm.transact().map_err(move |err| { diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 4134849ea8f9..73d8a8111b81 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -12,13 +12,7 @@ #[cfg(not(feature = "std"))] extern crate alloc; -use reth_chainspec::ChainSpec; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; -use reth_primitives::{ - revm::{config::revm_spec, env::fill_tx_env}, - revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, - Address, Head, Header, TransactionSigned, U256, -}; use reth_revm::{Database, EvmBuilder}; pub mod execute; @@ -34,34 +28,7 @@ pub mod eip6110; #[non_exhaustive] pub struct EthEvmConfig; -impl ConfigureEvmEnv for EthEvmConfig { - fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { - fill_tx_env(tx_env, transaction, sender) - } - - fn fill_cfg_env( - cfg_env: &mut CfgEnvWithHandlerCfg, - chain_spec: &ChainSpec, - header: &Header, - total_difficulty: U256, - ) { - let spec_id = revm_spec( - chain_spec, - Head { - number: header.number, - timestamp: header.timestamp, - difficulty: header.difficulty, - total_difficulty, - hash: Default::default(), - }, - ); - - cfg_env.chain_id = chain_spec.chain().id(); - cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; - - cfg_env.handler_cfg.spec_id = spec_id; - } -} +impl ConfigureEvmEnv for EthEvmConfig {} impl ConfigureEvm for EthEvmConfig { type DefaultExternalContext<'a> = (); @@ -77,7 +44,12 @@ impl ConfigureEvm for EthEvmConfig { #[cfg(test)] mod tests { use super::*; - use reth_primitives::revm_primitives::{BlockEnv, CfgEnv, SpecId}; + use reth_chainspec::ChainSpec; + use reth_primitives::{ + revm_primitives::{BlockEnv, CfgEnv, SpecId}, + Header, U256, + }; + use revm_primitives::CfgEnvWithHandlerCfg; #[test] #[ignore] diff --git a/crates/ethereum/node/tests/e2e/dev.rs b/crates/ethereum/node/tests/e2e/dev.rs index 990c6f0bf2b0..4a95231d4fb4 100644 --- a/crates/ethereum/node/tests/e2e/dev.rs +++ b/crates/ethereum/node/tests/e2e/dev.rs @@ -2,7 +2,7 @@ use crate::utils::EthNode; use alloy_genesis::Genesis; use alloy_primitives::{b256, hex}; use futures::StreamExt; -use reth::rpc::eth::EthTransactions; +use reth::rpc::eth::servers::EthTransactions; use reth_chainspec::ChainSpec; use reth_e2e_test_utils::setup; use reth_provider::CanonStateSubscriptions; diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index a3e643e88b4c..dd16aa0d046d 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -13,9 +13,17 @@ extern crate alloc; use reth_chainspec::ChainSpec; -use reth_primitives::{revm::env::fill_block_env, Address, Header, TransactionSigned, U256}; +use reth_primitives::{ + revm::{ + config::revm_spec, + env::{fill_block_env, fill_tx_env}, + }, + Address, Head, Header, TransactionSigned, U256, +}; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; -use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; +use revm_primitives::{ + AnalysisKind, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv, +}; pub mod either; pub mod execute; @@ -27,6 +35,7 @@ pub mod provider; pub mod test_utils; /// Trait for configuring the EVM for executing full blocks. +#[auto_impl::auto_impl(&, Arc)] pub trait ConfigureEvm: ConfigureEvmEnv { /// Associated type for the default external context that should be configured for the EVM. type DefaultExternalContext<'a>; @@ -98,9 +107,14 @@ pub trait ConfigureEvm: ConfigureEvmEnv { /// This represents the set of methods used to configure the EVM's environment before block /// execution. +/// +/// Default trait method implementation is done w.r.t. L1. +#[auto_impl::auto_impl(&, Arc)] pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { /// Fill transaction environment from a [`TransactionSigned`] and the given sender address. - fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address); + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + fill_tx_env(tx_env, transaction, sender) + } /// Fill [`CfgEnvWithHandlerCfg`] fields according to the chain spec and given header fn fill_cfg_env( @@ -108,7 +122,23 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { chain_spec: &ChainSpec, header: &Header, total_difficulty: U256, - ); + ) { + let spec_id = revm_spec( + chain_spec, + Head { + number: header.number, + timestamp: header.timestamp, + difficulty: header.difficulty, + total_difficulty, + hash: Default::default(), + }, + ); + + cfg_env.chain_id = chain_spec.chain().id(); + cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; + + cfg_env.handler_cfg.spec_id = spec_id; + } /// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and /// [`fill_block_env`]. diff --git a/crates/evm/src/provider.rs b/crates/evm/src/provider.rs index abf04be8938c..b976351c66b0 100644 --- a/crates/evm/src/provider.rs +++ b/crates/evm/src/provider.rs @@ -6,13 +6,13 @@ use reth_storage_errors::provider::ProviderResult; use revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}; /// A provider type that knows chain specific information required to configure a -/// [CfgEnvWithHandlerCfg]. +/// [`CfgEnvWithHandlerCfg`]. /// /// This type is mainly used to provide required data to configure the EVM environment that is /// usually stored on disk. #[auto_impl::auto_impl(&, Arc)] pub trait EvmEnvProvider: Send + Sync { - /// Fills the [CfgEnvWithHandlerCfg] and [BlockEnv] fields with values specific to the given + /// Fills the [`CfgEnvWithHandlerCfg`] and [BlockEnv] fields with values specific to the given /// [BlockHashOrNumber]. fn fill_env_at( &self, @@ -24,7 +24,7 @@ pub trait EvmEnvProvider: Send + Sync { where EvmConfig: ConfigureEvmEnv; - /// Fills the default [CfgEnvWithHandlerCfg] and [BlockEnv] fields with values specific to the + /// Fills the default [`CfgEnvWithHandlerCfg`] and [BlockEnv] fields with values specific to the /// given [Header]. fn env_with_header( &self, @@ -40,7 +40,7 @@ pub trait EvmEnvProvider: Send + Sync { Ok((cfg, block_env)) } - /// Fills the [CfgEnvWithHandlerCfg] and [BlockEnv] fields with values specific to the given + /// Fills the [`CfgEnvWithHandlerCfg`] and [BlockEnv] fields with values specific to the given /// [Header]. fn fill_env_with_header( &self, @@ -66,7 +66,7 @@ pub trait EvmEnvProvider: Send + Sync { header: &Header, ) -> ProviderResult<()>; - /// Fills the [CfgEnvWithHandlerCfg] fields with values specific to the given + /// Fills the [`CfgEnvWithHandlerCfg`] fields with values specific to the given /// [BlockHashOrNumber]. fn fill_cfg_env_at( &self, @@ -77,7 +77,7 @@ pub trait EvmEnvProvider: Send + Sync { where EvmConfig: ConfigureEvmEnv; - /// Fills the [CfgEnvWithHandlerCfg] fields with values specific to the given [Header]. + /// Fills the [`CfgEnvWithHandlerCfg`] fields with values specific to the given [Header]. fn fill_cfg_env_with_header( &self, cfg: &mut CfgEnvWithHandlerCfg, diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index 2390cbfb1a8e..259cfcdb5b49 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -21,11 +21,11 @@ reth-storage-errors.workspace = true reth-provider.workspace = true reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true -reth-rpc.workspace = true reth-rpc-server-types.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true reth-rpc-api = { workspace = true, features = ["client"] } +reth-rpc-eth-api = { workspace = true, features = ["client"] } reth-transaction-pool.workspace = true reth-tracing.workspace = true reth-config.workspace = true @@ -99,10 +99,10 @@ proptest.workspace = true [features] optimism = [ "reth-primitives/optimism", - "reth-rpc/optimism", "reth-provider/optimism", "reth-rpc-types-compat/optimism", "reth-beacon-consensus/optimism", + "reth-rpc-eth-api/optimism", ] jemalloc = ["dep:tikv-jemalloc-ctl"] diff --git a/crates/node-core/src/args/gas_price_oracle.rs b/crates/node-core/src/args/gas_price_oracle.rs index 5148fdca34b2..c719e9c8a5fb 100644 --- a/crates/node-core/src/args/gas_price_oracle.rs +++ b/crates/node-core/src/args/gas_price_oracle.rs @@ -1,6 +1,6 @@ use crate::primitives::U256; use clap::Args; -use reth_rpc::eth::gas_oracle::GasPriceOracleConfig; +use reth_rpc_eth_api::GasPriceOracleConfig; use reth_rpc_server_types::constants::gas_oracle::{ DEFAULT_GAS_PRICE_BLOCKS, DEFAULT_GAS_PRICE_PERCENTILE, DEFAULT_IGNORE_GAS_PRICE, DEFAULT_MAX_GAS_PRICE, diff --git a/crates/node-core/src/args/rpc_server.rs b/crates/node-core/src/args/rpc_server.rs index 7ab2dd268fa1..62c675d50777 100644 --- a/crates/node-core/src/args/rpc_server.rs +++ b/crates/node-core/src/args/rpc_server.rs @@ -10,7 +10,7 @@ use clap::{ Arg, Args, Command, }; use rand::Rng; -use reth_rpc::eth::RPC_DEFAULT_GAS_CAP; +use reth_rpc_eth_api::RPC_DEFAULT_GAS_CAP; use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection}; use std::{ diff --git a/crates/node-core/src/lib.rs b/crates/node-core/src/lib.rs index 5894da8ee9bb..8fbc0b2f9d8b 100644 --- a/crates/node-core/src/lib.rs +++ b/crates/node-core/src/lib.rs @@ -38,12 +38,12 @@ pub mod rpc { } /// Re-exported from `reth_rpc::eth`. pub mod eth { - pub use reth_rpc::eth::*; + pub use reth_rpc_eth_api::*; } /// Re-exported from `reth_rpc::rpc`. pub mod result { - pub use reth_rpc::result::*; + pub use reth_rpc_eth_api::result::*; } /// Re-exported from `reth_rpc::eth`. diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 9a6ada8f916d..9a7a86b91b5a 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -189,8 +189,9 @@ impl Clone for RpcRegistry { /// [`AuthRpcModule`]. /// /// This can be used to access installed modules, or create commonly used handlers like -/// [`reth_rpc::EthApi`], and ultimately merge additional rpc handler into the configured transport -/// modules [`TransportRpcModules`] as well as configured authenticated methods [`AuthRpcModule`]. +/// [`reth_rpc::eth::EthApi`], and ultimately merge additional rpc handler into the configured +/// transport modules [`TransportRpcModules`] as well as configured authenticated methods +/// [`AuthRpcModule`]. #[allow(missing_debug_implementations)] pub struct RpcContext<'a, Node: FullNodeComponents> { /// The node components. diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 12edb225fe43..3580c9108dd4 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -177,7 +177,7 @@ where .transpose() .map_err(|_| OptimismBlockExecutionError::AccountLoadFailed(*sender))?; - EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender); + self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender); // Execute transaction. let ResultAndState { result, state } = evm.transact().map_err(move |err| { diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index d13168bfe0c6..68aabf452ca5 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -32,7 +32,7 @@ pub use error::OptimismBlockExecutionError; pub struct OptimismEvmConfig; impl ConfigureEvmEnv for OptimismEvmConfig { - fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { let mut buf = Vec::with_capacity(transaction.length_without_header()); transaction.encode_enveloped(&mut buf); fill_op_tx_env(tx_env, transaction, sender, buf.into()); diff --git a/crates/optimism/node/src/rpc.rs b/crates/optimism/node/src/rpc.rs index 5ae1ba7b2538..01cb08c268d6 100644 --- a/crates/optimism/node/src/rpc.rs +++ b/crates/optimism/node/src/rpc.rs @@ -4,7 +4,7 @@ use jsonrpsee::types::ErrorObject; use reqwest::Client; use reth_rpc::eth::{ error::{EthApiError, EthResult}, - traits::RawTransactionForwarder, + servers::RawTransactionForwarder, }; use reth_rpc_types::ToRpcError; use std::sync::{atomic::AtomicUsize, Arc}; diff --git a/crates/rpc/rpc-api/Cargo.toml b/crates/rpc/rpc-api/Cargo.toml index 5374c46e4898..59ae5d4cfcdc 100644 --- a/crates/rpc/rpc-api/Cargo.toml +++ b/crates/rpc/rpc-api/Cargo.toml @@ -15,11 +15,11 @@ workspace = true # reth reth-primitives.workspace = true reth-rpc-types.workspace = true +reth-rpc-eth-api.workspace = true reth-engine-primitives.workspace = true reth-network-peers.workspace = true # misc -alloy-dyn-abi = { workspace = true, features = ["eip712"] } jsonrpsee = { workspace = true, features = ["server", "macros"] } serde = { workspace = true, features = ["derive"] } @@ -27,4 +27,9 @@ serde = { workspace = true, features = ["derive"] } serde_json.workspace = true [features] -client = ["jsonrpsee/client", "jsonrpsee/async-client"] +client = [ + "jsonrpsee/client", + "jsonrpsee/async-client", + "reth-rpc-eth-api/client" +] +optimism = ["reth-rpc-eth-api/optimism"] \ No newline at end of file diff --git a/crates/rpc/rpc-api/src/debug.rs b/crates/rpc/rpc-api/src/debug.rs index ccee09cc2b44..580245b1014c 100644 --- a/crates/rpc/rpc-api/src/debug.rs +++ b/crates/rpc/rpc-api/src/debug.rs @@ -26,7 +26,7 @@ pub trait DebugApi { #[method(name = "getRawTransaction")] async fn raw_transaction(&self, hash: B256) -> RpcResult>; - /// Returns an array of EIP-2718 binary-encoded transactions for the given [BlockId]. + /// Returns an array of EIP-2718 binary-encoded transactions for the given [`BlockId`]. #[method(name = "getRawTransactions")] async fn raw_transactions(&self, block_id: BlockId) -> RpcResult>; diff --git a/crates/rpc/rpc-api/src/lib.rs b/crates/rpc/rpc-api/src/lib.rs index 82af34a86d73..661b7780e18c 100644 --- a/crates/rpc/rpc-api/src/lib.rs +++ b/crates/rpc/rpc-api/src/lib.rs @@ -16,12 +16,8 @@ mod admin; mod anvil; -mod bundle; mod debug; mod engine; -mod eth; -mod eth_filter; -mod eth_pubsub; mod ganache; mod hardhat; mod mev; @@ -42,12 +38,8 @@ pub use servers::*; pub mod servers { pub use crate::{ admin::AdminApiServer, - bundle::{EthBundleApiServer, EthCallBundleApiServer}, debug::DebugApiServer, engine::{EngineApiServer, EngineEthApiServer}, - eth::EthApiServer, - eth_filter::EthFilterApiServer, - eth_pubsub::EthPubSubApiServer, mev::MevApiServer, net::NetApiServer, otterscan::OtterscanServer, @@ -58,6 +50,10 @@ pub mod servers { validation::BlockSubmissionValidationApiServer, web3::Web3ApiServer, }; + pub use reth_rpc_eth_api::{ + EthApiServer, EthBundleApiServer, EthCallBundleApiServer, EthFilterApiServer, + EthPubSubApiServer, + }; } /// re-export of all client traits @@ -70,11 +66,8 @@ pub mod clients { pub use crate::{ admin::AdminApiClient, anvil::AnvilApiClient, - bundle::{EthBundleApiClient, EthCallBundleApiClient}, debug::DebugApiClient, engine::{EngineApiClient, EngineEthApiClient}, - eth::EthApiClient, - eth_filter::EthFilterApiClient, ganache::GanacheApiClient, hardhat::HardhatApiClient, mev::MevApiClient, @@ -86,4 +79,7 @@ pub mod clients { validation::BlockSubmissionValidationApiClient, web3::Web3ApiClient, }; + pub use reth_rpc_eth_api::{ + EthApiClient, EthBundleApiClient, EthCallBundleApiClient, EthFilterApiClient, + }; } diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 1e8ef8f56a18..30d5f3389d04 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -7,7 +7,7 @@ use jsonrpsee::{ Methods, }; use reth_engine_primitives::EngineTypes; -use reth_rpc::EthSubscriptionIdProvider; +use reth_rpc::eth::EthSubscriptionIdProvider; use reth_rpc_api::servers::*; use reth_rpc_layer::{ secret_to_bearer_header, AuthClientLayer, AuthClientService, AuthLayer, JwtAuthValidator, diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index 45cad81cd7f0..89ce742e38e4 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -4,7 +4,7 @@ use crate::{ }; use jsonrpsee::server::ServerBuilder; use reth_node_core::{args::RpcServerArgs, utils::get_or_create_jwt_secret_from_path}; -use reth_rpc::eth::{cache::EthStateCacheConfig, gas_oracle::GasPriceOracleConfig}; +use reth_rpc::eth::{EthStateCacheConfig, GasPriceOracleConfig}; use reth_rpc_layer::{JwtError, JwtSecret}; use reth_rpc_server_types::RpcModuleSelection; use std::{net::SocketAddr, path::PathBuf}; diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 224301966b23..62e857e0267e 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -1,19 +1,16 @@ -use crate::RpcModuleConfig; +use std::sync::Arc; + use reth_evm::ConfigureEvm; use reth_network_api::{NetworkInfo, Peers}; use reth_provider::{ AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, StateProviderFactory, }; -use reth_rpc::{ - eth::{ - cache::{cache_new_blocks_task, EthStateCache, EthStateCacheConfig}, - fee_history_cache_new_blocks_task, - gas_oracle::{GasPriceOracle, GasPriceOracleConfig}, - traits::RawTransactionForwarder, - EthFilterConfig, FeeHistoryCache, FeeHistoryCacheConfig, RPC_DEFAULT_GAS_CAP, - }, - EthApi, EthFilter, EthPubSub, +use reth_rpc::eth::{ + cache::cache_new_blocks_task, fee_history::fee_history_cache_new_blocks_task, + servers::RawTransactionForwarder, EthApi, EthFilter, EthFilterConfig, EthPubSub, EthStateCache, + EthStateCacheConfig, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, + GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP, }; use reth_rpc_server_types::constants::{ default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, @@ -21,7 +18,8 @@ use reth_rpc_server_types::constants::{ use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; use serde::{Deserialize, Serialize}; -use std::sync::Arc; + +use crate::RpcModuleConfig; /// All handlers for the `eth` namespace #[derive(Debug, Clone)] diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 7257b3be35ab..883409331601 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -47,7 +47,7 @@ //! Pool: TransactionPool + Clone + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static, -//! EvmConfig: ConfigureEvm + 'static, +//! EvmConfig: ConfigureEvm, //! { //! // configure the rpc module per transport //! let transports = TransportRpcModuleConfig::default().with_http(vec![ @@ -115,7 +115,7 @@ //! Events: CanonStateSubscriptions + Clone + 'static, //! EngineApi: EngineApiServer, //! EngineT: EngineTypes + 'static, -//! EvmConfig: ConfigureEvm + 'static, +//! EvmConfig: ConfigureEvm, //! { //! // configure the rpc module per transport //! let transports = TransportRpcModuleConfig::default().with_http(vec![ @@ -178,9 +178,12 @@ use reth_provider::{ ChangeSetReader, EvmEnvProvider, StateProviderFactory, }; use reth_rpc::{ - eth::{cache::EthStateCache, traits::RawTransactionForwarder, EthBundle}, - AdminApi, DebugApi, EngineEthApi, EthApi, EthSubscriptionIdProvider, NetApi, OtterscanApi, - RPCApi, RethApi, TraceApi, TxPoolApi, Web3Api, + eth::{ + servers::RawTransactionForwarder, EthApi, EthBundle, EthStateCache, + EthSubscriptionIdProvider, + }, + AdminApi, DebugApi, EngineEthApi, NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi, + Web3Api, }; use reth_rpc_api::servers::*; use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret}; @@ -250,7 +253,7 @@ where Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static, - EvmConfig: ConfigureEvm + 'static, + EvmConfig: ConfigureEvm, { let module_config = module_config.into(); let server_config = server_config.into(); @@ -441,7 +444,7 @@ where Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static, - EvmConfig: ConfigureEvm + 'static, + EvmConfig: ConfigureEvm, { /// Configures all [`RpcModule`]s specific to the given [`TransportRpcModuleConfig`] which can /// be used to start the transport server(s). @@ -766,7 +769,7 @@ where Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static, - EvmConfig: ConfigureEvm + 'static, + EvmConfig: ConfigureEvm, { /// Register Eth Namespace /// diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml new file mode 100644 index 000000000000..88d3efd5fff6 --- /dev/null +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -0,0 +1,78 @@ +[package] +name = "reth-rpc-eth-api" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Reth RPC `eth_` API implementation" + +[lints] +workspace = true + +[dependencies] +# reth +revm.workspace = true +revm-inspectors = { workspace = true, features = ["js-tracer"] } +revm-primitives = { workspace = true, features = ["dev"] } +reth-errors.workspace = true +reth-evm.workspace = true +reth-metrics.workspace = true +reth-network-api.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true +reth-revm.workspace = true +reth-rpc-server-types.workspace = true +reth-rpc-types.workspace = true +reth-rpc-types-compat.workspace = true +reth-tasks = { workspace = true, features = ["rayon"] } +reth-trie.workspace = true +reth-transaction-pool.workspace = true +reth-evm-optimism = { workspace = true, optional = true } +reth-chainspec.workspace = true +reth-execution-types.workspace = true + +# ethereum +alloy-dyn-abi = { workspace = true, features = ["eip712"] } +alloy-sol-types.workspace = true +secp256k1.workspace = true + +# rpc +jsonrpsee = { workspace = true, features = ["server", "macros"] } +jsonrpsee-types = { workspace = true, optional = true } +serde_json.workspace = true + +# async +async-trait.workspace = true +futures.workspace = true +parking_lot.workspace = true +tokio.workspace = true +tokio-stream.workspace = true + +# misc +auto_impl.workspace = true +derive_more.workspace = true +dyn-clone.workspace = true +metrics.workspace = true +rand.workspace = true +schnellru.workspace = true +serde.workspace = true +thiserror.workspace = true +tracing.workspace = true + +[dev-dependencies] +reth-evm-ethereum.workspace = true +reth-testing-utils.workspace = true +reth-transaction-pool = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } + +[features] +client = ["jsonrpsee/client", "jsonrpsee/async-client"] +optimism = [ + "reth-primitives/optimism", + "reth-evm-optimism", + "revm/optimism", + "reth-provider/optimism", + "jsonrpsee-types", +] \ No newline at end of file diff --git a/crates/rpc/rpc-api/src/bundle.rs b/crates/rpc/rpc-eth-api/src/api/bundle.rs similarity index 98% rename from crates/rpc/rpc-api/src/bundle.rs rename to crates/rpc/rpc-eth-api/src/api/bundle.rs index 429f6948f8ab..f657e30c430c 100644 --- a/crates/rpc/rpc-api/src/bundle.rs +++ b/crates/rpc/rpc-eth-api/src/api/bundle.rs @@ -1,4 +1,4 @@ -//! Additional `eth_` functions for bundles +//! Additional `eth_` RPC API for bundles. //! //! See also diff --git a/crates/rpc/rpc-api/src/eth_filter.rs b/crates/rpc/rpc-eth-api/src/api/filter.rs similarity index 97% rename from crates/rpc/rpc-api/src/eth_filter.rs rename to crates/rpc/rpc-eth-api/src/api/filter.rs index 2e395d5bad76..da53b577eec5 100644 --- a/crates/rpc/rpc-api/src/eth_filter.rs +++ b/crates/rpc/rpc-eth-api/src/api/filter.rs @@ -1,5 +1,8 @@ +//! `eth_` RPC API for filtering. + use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_rpc_types::{Filter, FilterChanges, FilterId, Log, PendingTransactionFilterKind}; + /// Rpc Interface for poll-based ethereum filter API. #[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-eth-api/src/api/mod.rs similarity index 99% rename from crates/rpc/rpc-api/src/eth.rs rename to crates/rpc/rpc-eth-api/src/api/mod.rs index eb11fde824cc..63e61254afc8 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-eth-api/src/api/mod.rs @@ -1,3 +1,5 @@ +//! `eth_` RPC API. + use alloy_dyn_abi::TypedData; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; @@ -8,6 +10,11 @@ use reth_rpc_types::{ TransactionRequest, Work, }; +pub mod bundle; +pub mod filter; +pub mod pubsub; +pub mod servers; + /// Eth rpc interface: #[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] diff --git a/crates/rpc/rpc-api/src/eth_pubsub.rs b/crates/rpc/rpc-eth-api/src/api/pubsub.rs similarity index 92% rename from crates/rpc/rpc-api/src/eth_pubsub.rs rename to crates/rpc/rpc-eth-api/src/api/pubsub.rs index eaa1ef2d817e..8de125152823 100644 --- a/crates/rpc/rpc-api/src/eth_pubsub.rs +++ b/crates/rpc/rpc-eth-api/src/api/pubsub.rs @@ -1,3 +1,5 @@ +//! `eth_` RPC API for pubsub subscription. + use jsonrpsee::proc_macros::rpc; use reth_rpc_types::pubsub::{Params, SubscriptionKind}; diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc-eth-api/src/api/servers/bundle.rs similarity index 94% rename from crates/rpc/rpc/src/eth/bundle.rs rename to crates/rpc/rpc-eth-api/src/api/servers/bundle.rs index 97ea4bb4c0fe..b272d72a9548 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/bundle.rs @@ -1,19 +1,15 @@ //! `Eth` bundle implementation and helpers. -use crate::eth::{ - error::{EthApiError, EthResult, RpcInvalidTransactionError}, - revm_utils::FillableTransaction, - utils::recover_raw_transaction, - EthTransactions, -}; +use std::sync::Arc; + use jsonrpsee::core::RpcResult; +use reth_evm::ConfigureEvmEnv; use reth_primitives::{ keccak256, revm_primitives::db::{DatabaseCommit, DatabaseRef}, PooledTransactionsElement, U256, }; use reth_revm::database::StateProviderDatabase; -use reth_rpc_api::EthCallBundleApiServer; use reth_rpc_types::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; use reth_tasks::pool::BlockingTaskGuard; use revm::{ @@ -21,7 +17,12 @@ use revm::{ primitives::{ResultAndState, TxEnv}, }; use revm_primitives::{EnvKzgSettings, EnvWithHandlerCfg, MAX_BLOB_GAS_PER_BLOCK}; -use std::sync::Arc; + +use crate::{ + servers::{Call, EthTransactions, LoadPendingBlock}, + utils::recover_raw_transaction, + EthApiError, EthCallBundleApiServer, EthResult, RpcInvalidTransactionError, +}; /// `Eth` bundle implementation. pub struct EthBundle { @@ -38,7 +39,7 @@ impl EthBundle { impl EthBundle where - Eth: EthTransactions + 'static, + Eth: EthTransactions + LoadPendingBlock + Call + 'static, { /// Simulates a bundle of transactions at the top of a given block number with the state of /// another (or the same) block. This can be used to simulate future blocks with the current @@ -98,6 +99,8 @@ where // use the block number of the request block_env.number = U256::from(block_number); + let eth_api = self.inner.eth_api.clone(); + self.inner .eth_api .spawn_with_state_at_block(at, move |state| { @@ -129,13 +132,13 @@ where .map_err(|e| EthApiError::InvalidParams(e.to_string()))?; } - let tx = tx.into_ecrecovered_transaction(signer); + let tx = tx.into_transaction(); hash_bytes.extend_from_slice(tx.hash().as_slice()); let gas_price = tx .effective_tip_per_gas(basefee) .ok_or_else(|| RpcInvalidTransactionError::FeeCapTooLow)?; - tx.try_fill_tx_env(evm.tx_mut())?; + Call::evm_config(ð_api).fill_tx_env(evm.tx_mut(), &tx.clone(), signer); let ResultAndState { result, state } = evm.transact()?; let gas_used = result.gas_used(); @@ -166,7 +169,7 @@ where let tx_res = EthCallBundleTransactionResult { coinbase_diff, eth_sent_to_coinbase, - from_address: tx.signer(), + from_address: signer, gas_fees, gas_price: U256::from(gas_price), gas_used, @@ -212,7 +215,7 @@ where #[async_trait::async_trait] impl EthCallBundleApiServer for EthBundle where - Eth: EthTransactions + 'static, + Eth: EthTransactions + LoadPendingBlock + Call + 'static, { async fn call_bundle(&self, request: EthCallBundle) -> RpcResult { Ok(Self::call_bundle(self, request).await?) diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc-eth-api/src/api/servers/filter.rs similarity index 94% rename from crates/rpc/rpc/src/eth/filter.rs rename to crates/rpc/rpc-eth-api/src/api/servers/filter.rs index 1fea2df4a4b4..8f2ff382c544 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/filter.rs @@ -1,40 +1,37 @@ -use super::cache::EthStateCache; -use crate::{ - eth::{ - error::EthApiError, - logs_utils::{self, append_matching_block_logs}, - }, - result::{rpc_error_with_code, ToRpcResult}, - EthSubscriptionIdProvider, +//! `eth_` `Filter` RPC handler implementation + +use std::{ + collections::HashMap, + fmt, + iter::StepBy, + ops::RangeInclusive, + sync::Arc, + time::{Duration, Instant}, }; -use core::fmt; use async_trait::async_trait; use jsonrpsee::{core::RpcResult, server::IdProvider}; use reth_chainspec::ChainInfo; use reth_primitives::{IntoRecoveredTransaction, TxHash}; use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider, ProviderError}; -use reth_rpc_api::EthFilterApiServer; use reth_rpc_types::{ BlockNumHash, Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log, PendingTransactionFilterKind, }; - use reth_tasks::TaskSpawner; use reth_transaction_pool::{NewSubpoolTransactionStream, PoolTransaction, TransactionPool}; -use std::{ - collections::HashMap, - iter::StepBy, - ops::RangeInclusive, - sync::Arc, - time::{Duration, Instant}, -}; use tokio::{ sync::{mpsc::Receiver, Mutex}, time::MissedTickBehavior, }; use tracing::trace; +use crate::{ + logs_utils::{self, append_matching_block_logs}, + result::rpc_error_with_code, + EthApiError, EthFilterApiServer, EthStateCache, EthSubscriptionIdProvider, ToRpcResult, +}; + /// The maximum number of headers we read at once when handling a range filter. const MAX_HEADERS_RANGE: u64 = 1_000; // with ~530bytes per header this is ~500kb @@ -132,7 +129,7 @@ where ::Transaction: 'static, { /// Returns all the filter changes for the given id, if any - pub async fn filter_changes(&self, id: FilterId) -> Result { + pub async fn filter_changes(&self, id: FilterId) -> Result { let info = self.inner.provider.chain_info()?; let best_number = info.best_number; @@ -140,7 +137,7 @@ where // the last time changes were polled, in other words the best block at last poll + 1 let (start_block, kind) = { let mut filters = self.inner.active_filters.inner.lock().await; - let filter = filters.get_mut(&id).ok_or(FilterError::FilterNotFound(id))?; + let filter = filters.get_mut(&id).ok_or(EthFilterError::FilterNotFound(id))?; if filter.block > best_number { // no new blocks since the last poll @@ -204,16 +201,16 @@ where /// Returns an error if no matching log filter exists. /// /// Handler for `eth_getFilterLogs` - pub async fn filter_logs(&self, id: FilterId) -> Result, FilterError> { + pub async fn filter_logs(&self, id: FilterId) -> Result, EthFilterError> { let filter = { let filters = self.inner.active_filters.inner.lock().await; if let FilterKind::Log(ref filter) = - filters.get(&id).ok_or_else(|| FilterError::FilterNotFound(id.clone()))?.kind + filters.get(&id).ok_or_else(|| EthFilterError::FilterNotFound(id.clone()))?.kind { *filter.clone() } else { // Not a log filter - return Err(FilterError::FilterNotFound(id)) + return Err(EthFilterError::FilterNotFound(id)) } }; @@ -347,7 +344,7 @@ where Pool: TransactionPool + 'static, { /// Returns logs matching given filter object. - async fn logs_for_filter(&self, filter: Filter) -> Result, FilterError> { + async fn logs_for_filter(&self, filter: Filter) -> Result, EthFilterError> { match filter.block_option { FilterBlockOption::AtBlockHash(block_hash) => { // for all matching logs in the block @@ -428,16 +425,16 @@ where from_block: u64, to_block: u64, chain_info: ChainInfo, - ) -> Result, FilterError> { + ) -> Result, EthFilterError> { trace!(target: "rpc::eth::filter", from=from_block, to=to_block, ?filter, "finding logs in range"); let best_number = chain_info.best_number; if to_block < from_block { - return Err(FilterError::InvalidBlockRangeParams) + return Err(EthFilterError::InvalidBlockRangeParams) } if to_block - from_block > self.max_blocks_per_filter { - return Err(FilterError::QueryExceedsMaxBlocks(self.max_blocks_per_filter)) + return Err(EthFilterError::QueryExceedsMaxBlocks(self.max_blocks_per_filter)) } let mut all_logs = Vec::new(); @@ -505,7 +502,7 @@ where // logs of a single block let is_multi_block_range = from_block != to_block; if is_multi_block_range && all_logs.len() > self.max_logs_per_response { - return Err(FilterError::QueryExceedsMaxResults( + return Err(EthFilterError::QueryExceedsMaxResults( self.max_logs_per_response, )) } @@ -682,17 +679,49 @@ enum FilterKind { PendingTransaction(PendingTransactionKind), } +/// An iterator that yields _inclusive_ block ranges of a given step size +#[derive(Debug)] +struct BlockRangeInclusiveIter { + iter: StepBy>, + step: u64, + end: u64, +} + +impl BlockRangeInclusiveIter { + fn new(range: RangeInclusive, step: u64) -> Self { + Self { end: *range.end(), iter: range.step_by(step as usize + 1), step } + } +} + +impl Iterator for BlockRangeInclusiveIter { + type Item = (u64, u64); + + fn next(&mut self) -> Option { + let start = self.iter.next()?; + let end = (start + self.step).min(self.end); + if start > end { + return None + } + Some((start, end)) + } +} + /// Errors that can occur in the handler implementation #[derive(Debug, thiserror::Error)] -pub enum FilterError { +pub enum EthFilterError { + /// Filter not found. #[error("filter not found")] FilterNotFound(FilterId), + /// Invalid block range. #[error("invalid block range params")] InvalidBlockRangeParams, + /// Query scope is too broad. #[error("query exceeds max block range {0}")] QueryExceedsMaxBlocks(u64), + /// Query result is too large. #[error("query exceeds max results {0}")] QueryExceedsMaxResults(usize), + /// Error serving request in `eth_` namespace. #[error(transparent)] EthAPIError(#[from] EthApiError), /// Error thrown when a spawned task failed to deliver a response. @@ -701,59 +730,32 @@ pub enum FilterError { } // convert the error -impl From for jsonrpsee::types::error::ErrorObject<'static> { - fn from(err: FilterError) -> Self { +impl From for jsonrpsee::types::error::ErrorObject<'static> { + fn from(err: EthFilterError) -> Self { match err { - FilterError::FilterNotFound(_) => rpc_error_with_code( + EthFilterError::FilterNotFound(_) => rpc_error_with_code( jsonrpsee::types::error::INVALID_PARAMS_CODE, "filter not found", ), - err @ FilterError::InternalError => { + err @ EthFilterError::InternalError => { rpc_error_with_code(jsonrpsee::types::error::INTERNAL_ERROR_CODE, err.to_string()) } - FilterError::EthAPIError(err) => err.into(), - err @ FilterError::InvalidBlockRangeParams | - err @ FilterError::QueryExceedsMaxBlocks(_) | - err @ FilterError::QueryExceedsMaxResults(_) => { + EthFilterError::EthAPIError(err) => err.into(), + err @ EthFilterError::InvalidBlockRangeParams | + err @ EthFilterError::QueryExceedsMaxBlocks(_) | + err @ EthFilterError::QueryExceedsMaxResults(_) => { rpc_error_with_code(jsonrpsee::types::error::INVALID_PARAMS_CODE, err.to_string()) } } } } -impl From for FilterError { +impl From for EthFilterError { fn from(err: ProviderError) -> Self { Self::EthAPIError(err.into()) } } -/// An iterator that yields _inclusive_ block ranges of a given step size -#[derive(Debug)] -struct BlockRangeInclusiveIter { - iter: StepBy>, - step: u64, - end: u64, -} - -impl BlockRangeInclusiveIter { - fn new(range: RangeInclusive, step: u64) -> Self { - Self { end: *range.end(), iter: range.step_by(step as usize + 1), step } - } -} - -impl Iterator for BlockRangeInclusiveIter { - type Item = (u64, u64); - - fn next(&mut self) -> Option { - let start = self.iter.next()?; - let end = (start + self.step).min(self.end); - if start > end { - return None - } - Some((start, end)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs new file mode 100644 index 000000000000..2d583161f921 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs @@ -0,0 +1,35 @@ +//! Contains RPC handler implementations specific to blocks. + +use reth_provider::{BlockReaderIdExt, HeaderProvider}; + +use crate::{ + servers::{EthBlocks, LoadBlock, LoadPendingBlock, SpawnBlocking}, + EthApi, EthStateCache, +}; + +impl EthBlocks for EthApi +where + Self: LoadBlock, + Provider: HeaderProvider, +{ + #[inline] + fn provider(&self) -> impl reth_provider::HeaderProvider { + self.inner.provider() + } +} + +impl LoadBlock for EthApi +where + Self: LoadPendingBlock + SpawnBlocking, + Provider: BlockReaderIdExt, +{ + #[inline] + fn provider(&self) -> impl BlockReaderIdExt { + self.inner.provider() + } + + #[inline] + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs new file mode 100644 index 000000000000..151cfc91f44b --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs @@ -0,0 +1,29 @@ +//! Contains RPC handler implementations specific to endpoints that call/execute within evm. + +use reth_evm::ConfigureEvm; + +use crate::{ + servers::{Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking}, + EthApi, +}; + +impl EthCall for EthApi where + Self: Call + LoadPendingBlock +{ +} + +impl Call for EthApi +where + Self: LoadState + SpawnBlocking, + EvmConfig: ConfigureEvm, +{ + #[inline] + fn call_gas_limit(&self) -> u64 { + self.inner.gas_cap() + } + + #[inline] + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs new file mode 100644 index 000000000000..351ce9c18a31 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs @@ -0,0 +1,39 @@ +//! Contains RPC handler implementations for fee history. + +use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider}; + +use crate::{ + servers::{EthFees, LoadBlock, LoadFee}, + EthApi, EthStateCache, FeeHistoryCache, GasPriceOracle, +}; + +impl EthFees for EthApi where + Self: LoadFee +{ +} + +impl LoadFee for EthApi +where + Self: LoadBlock, + Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider, +{ + #[inline] + fn provider(&self) -> impl BlockIdReader + HeaderProvider + ChainSpecProvider { + self.inner.provider() + } + + #[inline] + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } + + #[inline] + fn gas_oracle(&self) -> &GasPriceOracle { + self.inner.gas_oracle() + } + + #[inline] + fn fee_history_cache(&self) -> &FeeHistoryCache { + self.inner.fee_history_cache() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs new file mode 100644 index 000000000000..e360df71394f --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs @@ -0,0 +1,19 @@ +//! The entire implementation of the namespace is quite large, hence it is divided across several +//! files. + +pub mod signer; +pub mod traits; + +mod block; +mod call; +mod fees; +#[cfg(feature = "optimism")] +pub mod optimism; +#[cfg(not(feature = "optimism"))] +mod pending_block; +#[cfg(not(feature = "optimism"))] +mod receipt; +mod spec; +mod state; +mod trace; +mod transaction; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs new file mode 100644 index 000000000000..4726a00e848e --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs @@ -0,0 +1,231 @@ +//! Loads and formats OP transaction RPC response. + +use jsonrpsee_types::error::ErrorObject; +use reth_evm::ConfigureEvm; +use reth_evm_optimism::RethL1BlockInfo; +use reth_primitives::{ + BlockNumber, Receipt, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, B256, +}; +use reth_provider::{ + BlockIdReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, ExecutionOutcome, + StateProviderFactory, +}; +use reth_rpc_types::{AnyTransactionReceipt, OptimismTransactionReceiptFields, ToRpcError}; +use reth_transaction_pool::TransactionPool; +use revm::L1BlockInfo; +use revm_primitives::{BlockEnv, ExecutionResult}; + +use crate::{ + result::internal_rpc_err, + servers::{LoadPendingBlock, LoadReceipt, SpawnBlocking}, + EthApi, EthApiError, EthResult, EthStateCache, PendingBlock, ReceiptBuilder, +}; + +/// L1 fee and data gas for a transaction, along with the L1 block info. +#[derive(Debug, Default, Clone)] +pub struct OptimismTxMeta { + /// The L1 block info. + pub l1_block_info: Option, + /// The L1 fee for the block. + pub l1_fee: Option, + /// The L1 data gas for the block. + pub l1_data_gas: Option, +} + +impl OptimismTxMeta { + /// Creates a new [`OptimismTxMeta`]. + pub const fn new( + l1_block_info: Option, + l1_fee: Option, + l1_data_gas: Option, + ) -> Self { + Self { l1_block_info, l1_fee, l1_data_gas } + } +} + +impl EthApi +where + Provider: BlockIdReader + ChainSpecProvider, +{ + /// Builds [`OptimismTxMeta`] object using the provided [`TransactionSigned`], L1 block + /// info and block timestamp. The [`L1BlockInfo`] is used to calculate the l1 fee and l1 data + /// gas for the transaction. If the [`L1BlockInfo`] is not provided, the meta info will be + /// empty. + pub fn build_op_tx_meta( + &self, + tx: &TransactionSigned, + l1_block_info: Option, + block_timestamp: u64, + ) -> EthResult { + let Some(l1_block_info) = l1_block_info else { return Ok(OptimismTxMeta::default()) }; + + let (l1_fee, l1_data_gas) = if !tx.is_deposit() { + let envelope_buf = tx.envelope_encoded(); + + let inner_l1_fee = l1_block_info + .l1_tx_data_fee( + &self.inner.provider().chain_spec(), + block_timestamp, + &envelope_buf, + tx.is_deposit(), + ) + .map_err(|_| OptimismEthApiError::L1BlockFeeError)?; + let inner_l1_data_gas = l1_block_info + .l1_data_gas(&self.inner.provider().chain_spec(), block_timestamp, &envelope_buf) + .map_err(|_| OptimismEthApiError::L1BlockGasError)?; + ( + Some(inner_l1_fee.saturating_to::()), + Some(inner_l1_data_gas.saturating_to::()), + ) + } else { + (None, None) + }; + + Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas)) + } +} + +impl LoadReceipt for EthApi +where + Self: Send + Sync, + Provider: BlockIdReader + ChainSpecProvider, +{ + #[inline] + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } + + async fn build_transaction_receipt( + &self, + tx: TransactionSigned, + meta: TransactionMeta, + receipt: Receipt, + ) -> EthResult { + let (block, receipts) = self + .cache() + .get_block_and_receipts(meta.block_hash) + .await? + .ok_or(EthApiError::UnknownBlockNumber)?; + + let block = block.unseal(); + let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok(); + let optimism_tx_meta = self.build_op_tx_meta(&tx, l1_block_info, block.timestamp)?; + + let resp_builder = ReceiptBuilder::new(&tx, meta, &receipt, &receipts)?; + let resp_builder = op_receipt_fields(resp_builder, &tx, &receipt, optimism_tx_meta); + + Ok(resp_builder.build()) + } +} + +/// Applies OP specific fields to a receipt. +fn op_receipt_fields( + resp_builder: ReceiptBuilder, + tx: &TransactionSigned, + receipt: &Receipt, + optimism_tx_meta: OptimismTxMeta, +) -> ReceiptBuilder { + let mut op_fields = OptimismTransactionReceiptFields::default(); + + if tx.is_deposit() { + op_fields.deposit_nonce = receipt.deposit_nonce.map(reth_primitives::U64::from); + op_fields.deposit_receipt_version = + receipt.deposit_receipt_version.map(reth_primitives::U64::from); + } else if let Some(l1_block_info) = optimism_tx_meta.l1_block_info { + op_fields.l1_fee = optimism_tx_meta.l1_fee; + op_fields.l1_gas_used = optimism_tx_meta.l1_data_gas.map(|dg| { + dg + l1_block_info.l1_fee_overhead.unwrap_or_default().saturating_to::() + }); + op_fields.l1_fee_scalar = Some(f64::from(l1_block_info.l1_base_fee_scalar) / 1_000_000.0); + op_fields.l1_gas_price = Some(l1_block_info.l1_base_fee.saturating_to()); + } + + resp_builder.add_other_fields(op_fields.into()) +} + +impl LoadPendingBlock + for EthApi +where + Self: SpawnBlocking, + Provider: BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory, + Pool: TransactionPool, + EvmConfig: ConfigureEvm, +{ + #[inline] + fn provider( + &self, + ) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory { + self.inner.provider() + } + + #[inline] + fn pool(&self) -> impl reth_transaction_pool::TransactionPool { + self.inner.pool() + } + + #[inline] + fn pending_block(&self) -> &tokio::sync::Mutex> { + self.inner.pending_block() + } + + #[inline] + fn evm_config(&self) -> &impl reth_evm::ConfigureEvm { + self.inner.evm_config() + } + + fn assemble_receipt( + &self, + tx: &TransactionSignedEcRecovered, + result: ExecutionResult, + cumulative_gas_used: u64, + ) -> Receipt { + Receipt { + tx_type: tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: None, + deposit_receipt_version: None, + } + } + + fn receipts_root( + &self, + _block_env: &BlockEnv, + execution_outcome: &ExecutionOutcome, + block_number: BlockNumber, + ) -> B256 { + execution_outcome + .optimism_receipts_root_slow( + block_number, + self.provider().chain_spec().as_ref(), + _block_env.timestamp.to::(), + ) + .expect("Block is present") + } +} + +/// Optimism specific errors, that extend [`EthApiError`]. +#[derive(Debug, thiserror::Error)] +pub enum OptimismEthApiError { + /// Thrown when calculating L1 gas fee. + #[error("failed to calculate l1 gas fee")] + L1BlockFeeError, + /// Thrown when calculating L1 gas used + #[error("failed to calculate l1 gas used")] + L1BlockGasError, +} + +impl ToRpcError for OptimismEthApiError { + fn to_rpc_error(&self) -> ErrorObject<'static> { + match self { + Self::L1BlockFeeError | Self::L1BlockGasError => internal_rpc_err(self.to_string()), + } + } +} + +impl From for EthApiError { + fn from(err: OptimismEthApiError) -> Self { + Self::other(err) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs new file mode 100644 index 000000000000..33db098da6d0 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs @@ -0,0 +1,41 @@ +//! Support for building a pending block with transactions from local view of mempool. + +use reth_evm::ConfigureEvm; +use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; +use reth_transaction_pool::TransactionPool; + +use crate::{ + servers::{LoadPendingBlock, SpawnBlocking}, + EthApi, PendingBlock, +}; + +impl LoadPendingBlock + for EthApi +where + Self: SpawnBlocking, + Provider: BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory, + Pool: TransactionPool, + EvmConfig: reth_evm::ConfigureEvm, +{ + #[inline] + fn provider( + &self, + ) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory { + self.inner.provider() + } + + #[inline] + fn pool(&self) -> impl TransactionPool { + self.inner.pool() + } + + #[inline] + fn pending_block(&self) -> &tokio::sync::Mutex> { + self.inner.pending_block() + } + + #[inline] + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs new file mode 100644 index 000000000000..404b526e4a9a --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs @@ -0,0 +1,13 @@ +//! Builds an RPC receipt response w.r.t. data layout of network. + +use crate::{servers::LoadReceipt, EthApi, EthStateCache}; + +impl LoadReceipt for EthApi +where + Self: Send + Sync, +{ + #[inline] + fn cache(&self) -> &EthStateCache { + &self.inner.eth_cache + } +} diff --git a/crates/rpc/rpc/src/eth/signer.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs similarity index 85% rename from crates/rpc/rpc/src/eth/signer.rs rename to crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs index 50c06159c4f0..bd2cdc244ac6 100644 --- a/crates/rpc/rpc/src/eth/signer.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs @@ -1,49 +1,23 @@ //! An abstraction over ethereum signers. -use crate::eth::error::SignError; +use std::collections::HashMap; + use alloy_dyn_abi::TypedData; use reth_primitives::{ eip191_hash_message, sign_message, Address, Signature, TransactionSigned, B256, }; use reth_rpc_types::TypedTransactionRequest; - -use dyn_clone::DynClone; use reth_rpc_types_compat::transaction::to_primitive_transaction; use secp256k1::SecretKey; -use std::collections::HashMap; - -type Result = std::result::Result; - -/// An Ethereum Signer used via RPC. -#[async_trait::async_trait] -pub(crate) trait EthSigner: Send + Sync + DynClone { - /// Returns the available accounts for this signer. - fn accounts(&self) -> Vec

; - - /// Returns `true` whether this signer can sign for this address - fn is_signer_for(&self, addr: &Address) -> bool { - self.accounts().contains(addr) - } - - /// Returns the signature - async fn sign(&self, address: Address, message: &[u8]) -> Result; - - /// signs a transaction request using the given account in request - fn sign_transaction( - &self, - request: TypedTransactionRequest, - address: &Address, - ) -> Result; - /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait. - fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result; -} - -dyn_clone::clone_trait_object!(EthSigner); +use crate::{ + servers::{helpers::traits::signer::Result, EthSigner}, + SignError, +}; /// Holds developer keys -#[derive(Clone)] -pub(crate) struct DevSigner { +#[derive(Debug, Clone)] +pub struct DevSigner { addresses: Vec
, accounts: HashMap, } @@ -121,9 +95,12 @@ impl EthSigner for DevSigner { #[cfg(test)] mod tests { - use super::*; - use reth_primitives::U256; use std::str::FromStr; + + use reth_primitives::U256; + + use super::*; + fn build_signer() -> DevSigner { let addresses = vec![]; let secret = diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs new file mode 100644 index 000000000000..f1ec323743f2 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs @@ -0,0 +1,64 @@ +use reth_chainspec::ChainInfo; +use reth_errors::{RethError, RethResult}; +use reth_evm::ConfigureEvm; +use reth_network_api::NetworkInfo; +use reth_primitives::{Address, U256, U64}; +use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; +use reth_rpc_types::{SyncInfo, SyncStatus}; +use reth_transaction_pool::TransactionPool; + +use crate::{servers::EthApiSpec, EthApi}; + +impl EthApiSpec for EthApi +where + Pool: TransactionPool + 'static, + Provider: + BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, + Network: NetworkInfo + 'static, + EvmConfig: ConfigureEvm, +{ + /// Returns the current ethereum protocol version. + /// + /// Note: This returns an [`U64`], since this should return as hex string. + async fn protocol_version(&self) -> RethResult { + let status = self.network().network_status().await.map_err(RethError::other)?; + Ok(U64::from(status.protocol_version)) + } + + /// Returns the chain id + fn chain_id(&self) -> U64 { + U64::from(self.network().chain_id()) + } + + /// Returns the current info for the chain + fn chain_info(&self) -> RethResult { + Ok(self.provider().chain_info()?) + } + + fn accounts(&self) -> Vec
{ + self.inner.signers.read().iter().flat_map(|s| s.accounts()).collect() + } + + fn is_syncing(&self) -> bool { + self.network().is_syncing() + } + + /// Returns the [`SyncStatus`] of the network + fn sync_status(&self) -> RethResult { + let status = if self.is_syncing() { + let current_block = U256::from( + self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(), + ); + SyncStatus::Info(SyncInfo { + starting_block: self.inner.starting_block, + current_block, + highest_block: current_block, + warp_chunks_amount: None, + warp_chunks_processed: None, + }) + } else { + SyncStatus::None + }; + Ok(status) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs new file mode 100644 index 000000000000..c55c695c346c --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs @@ -0,0 +1,104 @@ +//! Contains RPC handler implementations specific to state. + +use reth_provider::StateProviderFactory; +use reth_transaction_pool::TransactionPool; + +use crate::{ + servers::{EthState, LoadState, SpawnBlocking}, + EthApi, EthStateCache, +}; + +impl EthState for EthApi where + Self: LoadState + SpawnBlocking +{ +} + +impl LoadState for EthApi +where + Provider: StateProviderFactory, + Pool: TransactionPool, +{ + #[inline] + fn provider(&self) -> impl StateProviderFactory { + self.inner.provider() + } + + #[inline] + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } + + #[inline] + fn pool(&self) -> impl TransactionPool { + self.inner.pool() + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use reth_evm_ethereum::EthEvmConfig; + use reth_primitives::{ + constants::ETHEREUM_BLOCK_GAS_LIMIT, Address, StorageKey, StorageValue, U256, + }; + use reth_provider::test_utils::{ExtendedAccount, MockEthProvider, NoopProvider}; + use reth_tasks::pool::BlockingTaskPool; + use reth_transaction_pool::test_utils::testing_pool; + + use crate::{ + servers::EthState, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, + }; + + use super::*; + + #[tokio::test] + async fn test_storage() { + // === Noop === + let pool = testing_pool(); + let evm_config = EthEvmConfig::default(); + + let cache = EthStateCache::spawn(NoopProvider::default(), Default::default(), evm_config); + let eth_api = EthApi::new( + NoopProvider::default(), + pool.clone(), + (), + cache.clone(), + GasPriceOracle::new(NoopProvider::default(), Default::default(), cache.clone()), + ETHEREUM_BLOCK_GAS_LIMIT, + BlockingTaskPool::build().expect("failed to build tracing pool"), + FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), + evm_config, + None, + ); + let address = Address::random(); + let storage = eth_api.storage_at(address, U256::ZERO.into(), None).await.unwrap(); + assert_eq!(storage, U256::ZERO.to_be_bytes()); + + // === Mock === + let mock_provider = MockEthProvider::default(); + let storage_value = StorageValue::from(1337); + let storage_key = StorageKey::random(); + let storage = HashMap::from([(storage_key, storage_value)]); + let account = ExtendedAccount::new(0, U256::ZERO).extend_storage(storage); + mock_provider.add_account(address, account); + + let cache = EthStateCache::spawn(mock_provider.clone(), Default::default(), evm_config); + let eth_api = EthApi::new( + mock_provider.clone(), + pool, + (), + cache.clone(), + GasPriceOracle::new(mock_provider, Default::default(), cache.clone()), + ETHEREUM_BLOCK_GAS_LIMIT, + BlockingTaskPool::build().expect("failed to build tracing pool"), + FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), + evm_config, + None, + ); + + let storage_key: U256 = storage_key.into(); + let storage = eth_api.storage_at(address, storage_key.into(), None).await.unwrap(); + assert_eq!(storage, storage_value.to_be_bytes()); + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs new file mode 100644 index 000000000000..14b853adbc49 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs @@ -0,0 +1,19 @@ +//! Contains RPC handler implementations specific to tracing. + +use reth_evm::ConfigureEvm; + +use crate::{ + servers::{LoadState, Trace}, + EthApi, +}; + +impl Trace for EthApi +where + Self: LoadState, + EvmConfig: ConfigureEvm, +{ + #[inline] + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs new file mode 100644 index 000000000000..804069f6e617 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs @@ -0,0 +1,244 @@ +//! Database access for `eth_` block RPC methods. Loads block and receipt data w.r.t. network. + +use std::sync::Arc; + +use futures::Future; +use reth_primitives::{BlockId, Receipt, SealedBlock, SealedBlockWithSenders, TransactionMeta}; +use reth_provider::{BlockIdReader, BlockReader, BlockReaderIdExt, HeaderProvider}; +use reth_rpc_types::{AnyTransactionReceipt, Header, Index, RichBlock}; +use reth_rpc_types_compat::block::{from_block, uncle_block_from_header}; + +use crate::{ + servers::{LoadPendingBlock, LoadReceipt, SpawnBlocking}, + EthApiError, EthResult, EthStateCache, ReceiptBuilder, +}; + +/// Block related functions for the [`EthApiServer`](crate::EthApiServer) trait in the +/// `eth_` namespace. +pub trait EthBlocks: LoadBlock { + /// Returns a handle for reading data from disk. + /// + /// Data access in default (L1) trait method implementations. + fn provider(&self) -> impl HeaderProvider; + + /// Returns the block header for the given block id. + fn rpc_block_header( + &self, + block_id: BlockId, + ) -> impl Future>> + Send + where + Self: LoadPendingBlock + SpawnBlocking, + { + async move { Ok(self.rpc_block(block_id, false).await?.map(|block| block.inner.header)) } + } + + /// Returns the populated rpc block object for the given block id. + /// + /// If `full` is true, the block object will contain all transaction objects, otherwise it will + /// only contain the transaction hashes. + fn rpc_block( + &self, + block_id: BlockId, + full: bool, + ) -> impl Future>> + Send + where + Self: LoadPendingBlock + SpawnBlocking, + { + async move { + let block = match self.block_with_senders(block_id).await? { + Some(block) => block, + None => return Ok(None), + }; + let block_hash = block.hash(); + let total_difficulty = EthBlocks::provider(self) + .header_td_by_number(block.number)? + .ok_or(EthApiError::UnknownBlockNumber)?; + let block = + from_block(block.unseal(), total_difficulty, full.into(), Some(block_hash))?; + Ok(Some(block.into())) + } + } + + /// Returns the number transactions in the given block. + /// + /// Returns `None` if the block does not exist + fn block_transaction_count( + &self, + block_id: impl Into, + ) -> impl Future>> + Send { + let block_id = block_id.into(); + + async move { + if block_id.is_pending() { + // Pending block can be fetched directly without need for caching + return Ok(LoadBlock::provider(self).pending_block()?.map(|block| block.body.len())) + } + + let block_hash = match LoadBlock::provider(self).block_hash_for_id(block_id)? { + Some(block_hash) => block_hash, + None => return Ok(None), + }; + + Ok(self.cache().get_block_transactions(block_hash).await?.map(|txs| txs.len())) + } + } + + /// Helper function for `eth_getBlockReceipts`. + /// + /// Returns all transaction receipts in block, or `None` if block wasn't found. + fn block_receipts( + &self, + block_id: BlockId, + ) -> impl Future>>> + Send + where + Self: LoadReceipt, + { + async move { + if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? { + let block_number = block.number; + let base_fee = block.base_fee_per_gas; + let block_hash = block.hash(); + let excess_blob_gas = block.excess_blob_gas; + let timestamp = block.timestamp; + let block = block.unseal(); + + let receipts = block + .body + .into_iter() + .zip(receipts.iter()) + .enumerate() + .map(|(idx, (tx, receipt))| { + let meta = TransactionMeta { + tx_hash: tx.hash, + index: idx as u64, + block_hash, + block_number, + base_fee, + excess_blob_gas, + timestamp, + }; + + ReceiptBuilder::new(&tx, meta, receipt, &receipts) + .map(|builder| builder.build()) + }) + .collect::>>(); + return receipts.map(Some) + } + + Ok(None) + } + } + + /// Helper method that loads a bock and all its receipts. + fn load_block_and_receipts( + &self, + block_id: BlockId, + ) -> impl Future>)>>> + Send + where + Self: LoadReceipt, + { + async move { + if block_id.is_pending() { + return Ok(LoadBlock::provider(self) + .pending_block_and_receipts()? + .map(|(sb, receipts)| (sb, Arc::new(receipts)))) + } + + if let Some(block_hash) = LoadBlock::provider(self).block_hash_for_id(block_id)? { + return Ok(LoadReceipt::cache(self).get_block_and_receipts(block_hash).await?) + } + + Ok(None) + } + } + + /// Returns uncle headers of given block. + /// + /// Returns an empty vec if there are none. + fn ommers( + &self, + block_id: impl Into, + ) -> EthResult>> { + let block_id = block_id.into(); + Ok(LoadBlock::provider(self).ommers_by_id(block_id)?) + } + + /// Returns uncle block at given index in given block. + /// + /// Returns `None` if index out of range. + fn ommer_by_block_and_index( + &self, + block_id: impl Into, + index: Index, + ) -> impl Future>> + Send { + let block_id = block_id.into(); + + async move { + let uncles = if block_id.is_pending() { + // Pending block can be fetched directly without need for caching + LoadBlock::provider(self).pending_block()?.map(|block| block.ommers) + } else { + LoadBlock::provider(self).ommers_by_id(block_id)? + } + .unwrap_or_default(); + + let index = usize::from(index); + let uncle = + uncles.into_iter().nth(index).map(|header| uncle_block_from_header(header).into()); + Ok(uncle) + } + } +} + +/// Loads a block from database. +/// +/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods. +pub trait LoadBlock: LoadPendingBlock + SpawnBlocking { + // Returns a handle for reading data from disk. + /// + /// Data access in default (L1) trait method implementations. + fn provider(&self) -> impl BlockReaderIdExt; + + /// Returns a handle for reading data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn cache(&self) -> &EthStateCache; + + /// Returns the block object for the given block id. + fn block( + &self, + block_id: BlockId, + ) -> impl Future>> + Send { + async move { + self.block_with_senders(block_id) + .await + .map(|maybe_block| maybe_block.map(|block| block.block)) + } + } + + /// Returns the block object for the given block id. + fn block_with_senders( + &self, + block_id: BlockId, + ) -> impl Future>> + Send { + async move { + if block_id.is_pending() { + // Pending block can be fetched directly without need for caching + let maybe_pending = + LoadPendingBlock::provider(self).pending_block_with_senders()?; + return if maybe_pending.is_some() { + Ok(maybe_pending) + } else { + self.local_pending_block().await + } + } + + let block_hash = match LoadPendingBlock::provider(self).block_hash_for_id(block_id)? { + Some(block_hash) => block_hash, + None => return Ok(None), + }; + + Ok(self.cache().get_sealed_block_with_senders(block_hash).await?) + } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs new file mode 100644 index 000000000000..1dae8dc6bb1e --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs @@ -0,0 +1,55 @@ +//! Spawns a blocking task. CPU heavy tasks are executed with the `rayon` library. IO heavy tasks +//! are executed on the `tokio` runtime. + +use futures::Future; +use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; +use tokio::sync::oneshot; + +use crate::{EthApiError, EthResult}; + +/// Executes code on a blocking thread. +pub trait SpawnBlocking: Clone + Send + Sync + 'static { + /// Returns a handle for spawning IO heavy blocking tasks. + /// + /// Runtime access in default trait method implementations. + fn io_task_spawner(&self) -> impl TaskSpawner; + + /// Returns a handle for spawning CPU heavy blocking tasks. + /// + /// Thread pool access in default trait method implementations. + fn tracing_task_pool(&self) -> &BlockingTaskPool; + + /// Executes the future on a new blocking task. + /// + /// Note: This is expected for futures that are dominated by blocking IO operations, for tracing + /// or CPU bound operations in general use [`spawn_tracing`](Self::spawn_tracing). + fn spawn_blocking_io(&self, f: F) -> impl Future> + Send + where + F: FnOnce(Self) -> EthResult + Send + 'static, + R: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + let this = self.clone(); + self.io_task_spawner().spawn_blocking(Box::pin(async move { + let res = async move { f(this) }.await; + let _ = tx.send(res); + })); + + async move { rx.await.map_err(|_| EthApiError::InternalEthError)? } + } + + /// Executes a blocking task on the tracing pool. + /// + /// Note: This is expected for futures that are predominantly CPU bound, as it uses `rayon` + /// under the hood, for blocking IO futures use [`spawn_blocking`](Self::spawn_blocking_io). See + /// . + fn spawn_tracing(&self, f: F) -> impl Future> + Send + where + F: FnOnce(Self) -> EthResult + Send + 'static, + R: Send + 'static, + { + let this = self.clone(); + let fut = self.tracing_task_pool().spawn(move || f(this)); + async move { fut.await.map_err(|_| EthApiError::InternalBlockingTaskError)? } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs new file mode 100644 index 000000000000..ee531b7f9e3f --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs @@ -0,0 +1,775 @@ +//! Loads a pending block from database. Helper trait for `eth_` transaction, call and trace RPC +//! methods. + +use futures::Future; +use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; +use reth_primitives::{ + revm::env::tx_env_with_recovered, + revm_primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason, + ResultAndState, TransactTo, + }, + Bytes, TransactionSignedEcRecovered, TxKind, B256, U256, +}; +use reth_provider::StateProvider; +use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef}; +use reth_rpc_types::{ + state::{EvmOverrides, StateOverride}, + AccessListWithGasUsed, BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo, + TransactionRequest, +}; +use revm::{Database, DatabaseCommit}; +use revm_inspectors::access_list::AccessListInspector; +use tracing::trace; + +use crate::{ + cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, + error::ensure_success, + revm_utils::{ + apply_state_overrides, build_call_evm_env, caller_gas_allowance, + cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env, + }, + servers::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace}, + EthApiError, EthResult, RevertError, RpcInvalidTransactionError, StateCacheDb, + ESTIMATE_GAS_ERROR_RATIO, MIN_TRANSACTION_GAS, +}; + +/// Execution related functions for the [`EthApiServer`](crate::EthApiServer) trait in +/// the `eth_` namespace. +pub trait EthCall: Call + LoadPendingBlock { + /// Estimate gas needed for execution of the `request` at the [`BlockId`]. + fn estimate_gas_at( + &self, + request: TransactionRequest, + at: BlockId, + state_override: Option, + ) -> impl Future> + Send { + Call::estimate_gas_at(self, request, at, state_override) + } + + /// Executes the call request (`eth_call`) and returns the output + fn call( + &self, + request: TransactionRequest, + block_number: Option, + overrides: EvmOverrides, + ) -> impl Future> + Send { + async move { + let (res, _env) = + self.transact_call_at(request, block_number.unwrap_or_default(), overrides).await?; + + ensure_success(res.result) + } + } + + /// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the + /// optionality of state overrides + fn call_many( + &self, + bundle: Bundle, + state_context: Option, + mut state_override: Option, + ) -> impl Future>> + Send + where + Self: LoadBlock, + { + async move { + let Bundle { transactions, block_override } = bundle; + if transactions.is_empty() { + return Err(EthApiError::InvalidParams(String::from("transactions are empty."))) + } + + let StateContext { transaction_index, block_number } = + state_context.unwrap_or_default(); + let transaction_index = transaction_index.unwrap_or_default(); + + let target_block = block_number.unwrap_or_default(); + let is_block_target_pending = target_block.is_pending(); + + let ((cfg, block_env, _), block) = futures::try_join!( + self.evm_env_at(target_block), + self.block_with_senders(target_block) + )?; + + let Some(block) = block else { return Err(EthApiError::UnknownBlockNumber) }; + let gas_limit = self.call_gas_limit(); + + // we're essentially replaying the transactions in the block here, hence we need the + // state that points to the beginning of the block, which is the state at + // the parent block + let mut at = block.parent_hash; + let mut replay_block_txs = true; + + let num_txs = transaction_index.index().unwrap_or(block.body.len()); + // but if all transactions are to be replayed, we can use the state at the block itself, + // however only if we're not targeting the pending block, because for pending we can't + // rely on the block's state being available + if !is_block_target_pending && num_txs == block.body.len() { + at = block.hash(); + replay_block_txs = false; + } + + let this = self.clone(); + self.spawn_with_state_at_block(at.into(), move |state| { + let mut results = Vec::with_capacity(transactions.len()); + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + + if replay_block_txs { + // only need to replay the transactions in the block if not all transactions are + // to be replayed + let transactions = block.into_transactions_ecrecovered().take(num_txs); + for tx in transactions { + let tx = tx_env_with_recovered(&tx); + let env = + EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx); + let (res, _) = this.transact(&mut db, env)?; + db.commit(res.state); + } + } + + let block_overrides = block_override.map(Box::new); + + let mut transactions = transactions.into_iter().peekable(); + while let Some(tx) = transactions.next() { + // apply state overrides only once, before the first transaction + let state_overrides = state_override.take(); + let overrides = EvmOverrides::new(state_overrides, block_overrides.clone()); + + let env = prepare_call_env( + cfg.clone(), + block_env.clone(), + tx, + gas_limit, + &mut db, + overrides, + )?; + let (res, _) = this.transact(&mut db, env)?; + + match ensure_success(res.result) { + Ok(output) => { + results.push(EthCallResponse { value: Some(output), error: None }); + } + Err(err) => { + results.push(EthCallResponse { + value: None, + error: Some(err.to_string()), + }); + } + } + + if transactions.peek().is_some() { + // need to apply the state changes of this call before executing the next + // call + db.commit(res.state); + } + } + + Ok(results) + }) + .await + } + } + + /// Creates [`AccessListWithGasUsed`] for the [`TransactionRequest`] at the given + /// [`BlockId`], or latest block. + fn create_access_list_at( + &self, + request: TransactionRequest, + block_number: Option, + ) -> impl Future> + Send + where + Self: Trace, + { + async move { + let block_id = block_number.unwrap_or_default(); + let (cfg, block, at) = self.evm_env_at(block_id).await?; + + self.spawn_blocking_io(move |this| { + this.create_access_list_with(cfg, block, at, request) + }) + .await + } + } + + /// Creates [`AccessListWithGasUsed`] for the [`TransactionRequest`] at the given + /// [`BlockId`]. + fn create_access_list_with( + &self, + cfg: CfgEnvWithHandlerCfg, + block: BlockEnv, + at: BlockId, + mut request: TransactionRequest, + ) -> EthResult + where + Self: Trace, + { + let state = self.state_at_block_id(at)?; + + let mut env = build_call_evm_env(cfg, block, request.clone())?; + + // we want to disable this in eth_createAccessList, since this is common practice used by + // other node impls and providers + env.cfg.disable_block_gas_limit = true; + + // The basefee should be ignored for eth_createAccessList + // See: + // + env.cfg.disable_base_fee = true; + + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + + if request.gas.is_none() && env.tx.gas_price > U256::ZERO { + // no gas limit was provided in the request, so we need to cap the request's gas limit + cap_tx_gas_limit_with_caller_allowance(&mut db, &mut env.tx)?; + } + + let from = request.from.unwrap_or_default(); + let to = if let Some(TxKind::Call(to)) = request.to { + to + } else { + let nonce = db.basic_ref(from)?.unwrap_or_default().nonce; + from.create(nonce) + }; + + // can consume the list since we're not using the request anymore + let initial = request.access_list.take().unwrap_or_default(); + + let precompiles = get_precompiles(env.handler_cfg.spec_id); + let mut inspector = AccessListInspector::new(initial, from, to, precompiles); + let (result, env) = self.inspect(&mut db, env, &mut inspector)?; + + match result.result { + ExecutionResult::Halt { reason, .. } => Err(match reason { + HaltReason::NonceOverflow => RpcInvalidTransactionError::NonceMaxValue, + halt => RpcInvalidTransactionError::EvmHalt(halt), + }), + ExecutionResult::Revert { output, .. } => { + Err(RpcInvalidTransactionError::Revert(RevertError::new(output))) + } + ExecutionResult::Success { .. } => Ok(()), + }?; + + let access_list = inspector.into_access_list(); + + let cfg_with_spec_id = + CfgEnvWithHandlerCfg { cfg_env: env.cfg.clone(), handler_cfg: env.handler_cfg }; + + // calculate the gas used using the access list + request.access_list = Some(access_list.clone()); + let gas_used = + self.estimate_gas_with(cfg_with_spec_id, env.block.clone(), request, &*db.db, None)?; + + Ok(AccessListWithGasUsed { access_list, gas_used }) + } +} + +/// Executes code on state. +pub trait Call: LoadState + SpawnBlocking { + /// Returns default gas limit to use for `eth_call` and tracing RPC methods. + /// + /// Data access in default trait method implementations. + fn call_gas_limit(&self) -> u64; + + /// Returns a handle for reading evm config. + /// + /// Data access in default (L1) trait method implementations. + fn evm_config(&self) -> &impl ConfigureEvm; + + /// Executes the closure with the state that corresponds to the given [`BlockId`]. + fn with_state_at_block(&self, at: BlockId, f: F) -> EthResult + where + F: FnOnce(StateProviderTraitObjWrapper<'_>) -> EthResult, + { + let state = self.state_at_block_id(at)?; + f(StateProviderTraitObjWrapper(&state)) + } + + /// Executes the [`EnvWithHandlerCfg`] against the given [Database] without committing state + /// changes. + fn transact( + &self, + db: DB, + env: EnvWithHandlerCfg, + ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> + where + DB: Database, + ::Error: Into, + { + let mut evm = self.evm_config().evm_with_env(db, env); + let res = evm.transact()?; + let (_, env) = evm.into_db_and_env_with_handler_cfg(); + Ok((res, env)) + } + + /// Executes the call request at the given [`BlockId`]. + fn transact_call_at( + &self, + request: TransactionRequest, + at: BlockId, + overrides: EvmOverrides, + ) -> impl Future> + Send + where + Self: LoadPendingBlock, + { + let this = self.clone(); + self.spawn_with_call_at(request, at, overrides, move |db, env| this.transact(db, env)) + } + + /// Executes the closure with the state that corresponds to the given [`BlockId`] on a new task + fn spawn_with_state_at_block( + &self, + at: BlockId, + f: F, + ) -> impl Future> + Send + where + F: FnOnce(StateProviderTraitObjWrapper<'_>) -> EthResult + Send + 'static, + T: Send + 'static, + { + self.spawn_tracing(move |this| { + let state = this.state_at_block_id(at)?; + f(StateProviderTraitObjWrapper(&state)) + }) + } + + /// Prepares the state and env for the given [`TransactionRequest`] at the given [`BlockId`] and + /// executes the closure on a new task returning the result of the closure. + /// + /// This returns the configured [`EnvWithHandlerCfg`] for the given [`TransactionRequest`] at + /// the given [`BlockId`] and with configured call settings: `prepare_call_env`. + fn spawn_with_call_at( + &self, + request: TransactionRequest, + at: BlockId, + overrides: EvmOverrides, + f: F, + ) -> impl Future> + Send + where + Self: LoadPendingBlock, + F: FnOnce(StateCacheDbRefMutWrapper<'_, '_>, EnvWithHandlerCfg) -> EthResult + + Send + + 'static, + R: Send + 'static, + { + async move { + let (cfg, block_env, at) = self.evm_env_at(at).await?; + let this = self.clone(); + self.spawn_tracing(move |_| { + let state = this.state_at_block_id(at)?; + let mut db = + CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state))); + + let env = prepare_call_env( + cfg, + block_env, + request, + this.call_gas_limit(), + &mut db, + overrides, + )?; + + f(StateCacheDbRefMutWrapper(&mut db), env) + }) + .await + .map_err(|_| EthApiError::InternalBlockingTaskError) + } + } + + /// Retrieves the transaction if it exists and executes it. + /// + /// Before the transaction is executed, all previous transaction in the block are applied to the + /// state by executing them first. + /// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed + /// and the database that points to the beginning of the transaction. + /// + /// Note: Implementers should use a threadpool where blocking is allowed, such as + /// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool). + fn spawn_replay_transaction( + &self, + hash: B256, + f: F, + ) -> impl Future>> + Send + where + Self: LoadBlock + LoadPendingBlock + LoadTransaction, + F: FnOnce(TransactionInfo, ResultAndState, StateCacheDb<'_>) -> EthResult + + Send + + 'static, + R: Send + 'static, + { + async move { + let (transaction, block) = match self.transaction_and_block(hash).await? { + None => return Ok(None), + Some(res) => res, + }; + let (tx, tx_info) = transaction.split(); + + let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?; + + // we need to get the state of the parent block because we're essentially replaying the + // block the transaction is included in + let parent_block = block.parent_hash; + let block_txs = block.into_transactions_ecrecovered(); + + let this = self.clone(); + self.spawn_with_state_at_block(parent_block.into(), move |state| { + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + + // replay all transactions prior to the targeted transaction + this.replay_transactions_until( + &mut db, + cfg.clone(), + block_env.clone(), + block_txs, + tx.hash, + )?; + + let env = + EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx)); + + let (res, _) = this.transact(&mut db, env)?; + f(tx_info, res, db) + }) + .await + .map(Some) + } + } + + /// Replays all the transactions until the target transaction is found. + /// + /// All transactions before the target transaction are executed and their changes are written to + /// the _runtime_ db ([`CacheDB`]). + /// + /// Note: This assumes the target transaction is in the given iterator. + /// Returns the index of the target transaction in the given iterator. + fn replay_transactions_until( + &self, + db: &mut CacheDB, + cfg: CfgEnvWithHandlerCfg, + block_env: BlockEnv, + transactions: impl IntoIterator, + target_tx_hash: B256, + ) -> Result + where + DB: DatabaseRef, + EthApiError: From<::Error>, + { + let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default()); + + let mut evm = self.evm_config().evm_with_env(db, env); + let mut index = 0; + for tx in transactions { + if tx.hash() == target_tx_hash { + // reached the target transaction + break + } + + let sender = tx.signer(); + self.evm_config().fill_tx_env(evm.tx_mut(), &tx.into_signed(), sender); + evm.transact_commit()?; + index += 1; + } + Ok(index) + } + + /// Estimate gas needed for execution of the `request` at the [`BlockId`]. + fn estimate_gas_at( + &self, + request: TransactionRequest, + at: BlockId, + state_override: Option, + ) -> impl Future> + Send + where + Self: LoadPendingBlock, + { + async move { + let (cfg, block_env, at) = self.evm_env_at(at).await?; + + self.spawn_blocking_io(move |this| { + let state = this.state_at_block_id(at)?; + this.estimate_gas_with(cfg, block_env, request, state, state_override) + }) + .await + } + } + + /// Estimates the gas usage of the `request` with the state. + /// + /// This will execute the [`TransactionRequest`] and find the best gas limit via binary search + fn estimate_gas_with( + &self, + mut cfg: CfgEnvWithHandlerCfg, + block: BlockEnv, + request: TransactionRequest, + state: S, + state_override: Option, + ) -> EthResult + where + S: StateProvider, + { + // Disabled because eth_estimateGas is sometimes used with eoa senders + // See + cfg.disable_eip3607 = true; + + // The basefee should be ignored for eth_createAccessList + // See: + // + cfg.disable_base_fee = true; + + // Keep a copy of gas related request values + let tx_request_gas_limit = request.gas; + let tx_request_gas_price = request.gas_price; + let block_env_gas_limit = block.gas_limit; + + // Determine the highest possible gas limit, considering both the request's specified limit + // and the block's limit. + let mut highest_gas_limit = tx_request_gas_limit + .map(|tx_gas_limit| U256::from(tx_gas_limit).max(block_env_gas_limit)) + .unwrap_or(block_env_gas_limit); + + // Configure the evm env + let mut env = build_call_evm_env(cfg, block, request)?; + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + + // Apply any state overrides if specified. + if let Some(state_override) = state_override { + apply_state_overrides(state_override, &mut db)?; + } + + // Optimize for simple transfer transactions, potentially reducing the gas estimate. + if env.tx.data.is_empty() { + if let TransactTo::Call(to) = env.tx.transact_to { + if let Ok(code) = db.db.account_code(to) { + let no_code_callee = code.map(|code| code.is_empty()).unwrap_or(true); + if no_code_callee { + // If the tx is a simple transfer (call to an account with no code) we can + // shortcircuit. But simply returning + // `MIN_TRANSACTION_GAS` is dangerous because there might be additional + // field combos that bump the price up, so we try executing the function + // with the minimum gas limit to make sure. + let mut env = env.clone(); + env.tx.gas_limit = MIN_TRANSACTION_GAS; + if let Ok((res, _)) = self.transact(&mut db, env) { + if res.result.is_success() { + return Ok(U256::from(MIN_TRANSACTION_GAS)) + } + } + } + } + } + } + + // Check funds of the sender (only useful to check if transaction gas price is more than 0). + // + // The caller allowance is check by doing `(account.balance - tx.value) / tx.gas_price` + if env.tx.gas_price > U256::ZERO { + // cap the highest gas limit by max gas caller can afford with given gas price + highest_gas_limit = highest_gas_limit.min(caller_gas_allowance(&mut db, &env.tx)?); + } + + // We can now normalize the highest gas limit to a u64 + let mut highest_gas_limit: u64 = highest_gas_limit.try_into().unwrap_or(u64::MAX); + + // If the provided gas limit is less than computed cap, use that + env.tx.gas_limit = env.tx.gas_limit.min(highest_gas_limit); + + trace!(target: "rpc::eth::estimate", ?env, "Starting gas estimation"); + + // Execute the transaction with the highest possible gas limit. + let (mut res, mut env) = match self.transact(&mut db, env.clone()) { + // Handle the exceptional case where the transaction initialization uses too much gas. + // If the gas price or gas limit was specified in the request, retry the transaction + // with the block's gas limit to determine if the failure was due to + // insufficient gas. + Err(EthApiError::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh)) + if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() => + { + return Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db)) + } + // Propagate other results (successful or other errors). + ethres => ethres?, + }; + + let gas_refund = match res.result { + ExecutionResult::Success { gas_refunded, .. } => gas_refunded, + ExecutionResult::Halt { reason, gas_used } => { + // here we don't check for invalid opcode because already executed with highest gas + // limit + return Err(RpcInvalidTransactionError::halt(reason, gas_used).into()) + } + ExecutionResult::Revert { output, .. } => { + // if price or limit was included in the request then we can execute the request + // again with the block's gas limit to check if revert is gas related or not + return if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() { + Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db)) + } else { + // the transaction did revert + Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into()) + } + } + }; + + // At this point we know the call succeeded but want to find the _best_ (lowest) gas the + // transaction succeeds with. We find this by doing a binary search over the possible range. + // + // NOTE: this is the gas the transaction used, which is less than the + // transaction requires to succeed. + let mut gas_used = res.result.gas_used(); + // the lowest value is capped by the gas used by the unconstrained transaction + let mut lowest_gas_limit = gas_used.saturating_sub(1); + + // As stated in Geth, there is a good chance that the transaction will pass if we set the + // gas limit to the execution gas used plus the gas refund, so we check this first + // 1 { + // An estimation error is allowed once the current gas limit range used in the binary + // search is small enough (less than 1.5% of the highest gas limit) + // { + // Increase the lowest gas limit if gas is too high + lowest_gas_limit = mid_gas_limit; + } + // Handle other cases, including successful transactions. + ethres => { + // Unpack the result and environment if the transaction was successful. + (res, env) = ethres?; + // Update the estimated gas range based on the transaction result. + self.update_estimated_gas_range( + res.result, + mid_gas_limit, + &mut highest_gas_limit, + &mut lowest_gas_limit, + )?; + } + } + + // New midpoint + mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64; + } + + Ok(U256::from(highest_gas_limit)) + } + + /// Updates the highest and lowest gas limits for binary search based on the execution result. + /// + /// This function refines the gas limit estimates used in a binary search to find the optimal + /// gas limit for a transaction. It adjusts the highest or lowest gas limits depending on + /// whether the execution succeeded, reverted, or halted due to specific reasons. + #[inline] + fn update_estimated_gas_range( + &self, + result: ExecutionResult, + tx_gas_limit: u64, + highest_gas_limit: &mut u64, + lowest_gas_limit: &mut u64, + ) -> EthResult<()> { + match result { + ExecutionResult::Success { .. } => { + // Cap the highest gas limit with the succeeding gas limit. + *highest_gas_limit = tx_gas_limit; + } + ExecutionResult::Revert { .. } => { + // Increase the lowest gas limit. + *lowest_gas_limit = tx_gas_limit; + } + ExecutionResult::Halt { reason, .. } => { + match reason { + HaltReason::OutOfGas(_) | HaltReason::InvalidEFOpcode => { + // Both `OutOfGas` and `InvalidEFOpcode` can occur dynamically if the gas + // left is too low. Treat this as an out of gas + // condition, knowing that the call succeeds with a + // higher gas limit. + // + // Common usage of invalid opcode in OpenZeppelin: + // + + // Increase the lowest gas limit. + *lowest_gas_limit = tx_gas_limit; + } + err => { + // These cases should be unreachable because we know the transaction + // succeeds, but if they occur, treat them as an + // error. + return Err(RpcInvalidTransactionError::EvmHalt(err).into()) + } + } + } + }; + + Ok(()) + } + + /// Executes the requests again after an out of gas error to check if the error is gas related + /// or not + #[inline] + fn map_out_of_gas_err( + &self, + env_gas_limit: U256, + mut env: EnvWithHandlerCfg, + db: &mut CacheDB>, + ) -> EthApiError + where + S: StateProvider, + { + let req_gas_limit = env.tx.gas_limit; + env.tx.gas_limit = env_gas_limit.try_into().unwrap_or(u64::MAX); + let (res, _) = match self.transact(db, env) { + Ok(res) => res, + Err(err) => return err, + }; + match res.result { + ExecutionResult::Success { .. } => { + // transaction succeeded by manually increasing the gas limit to + // highest, which means the caller lacks funds to pay for the tx + RpcInvalidTransactionError::BasicOutOfGas(req_gas_limit).into() + } + ExecutionResult::Revert { output, .. } => { + // reverted again after bumping the limit + RpcInvalidTransactionError::Revert(RevertError::new(output)).into() + } + ExecutionResult::Halt { reason, .. } => { + RpcInvalidTransactionError::EvmHalt(reason).into() + } + } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs new file mode 100644 index 000000000000..9217b7d5f653 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs @@ -0,0 +1,346 @@ +//! Loads fee history from database. Helper trait for `eth_` fee and transaction RPC methods. + +use futures::Future; +use reth_primitives::U256; +use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider}; +use reth_rpc_types::{BlockNumberOrTag, FeeHistory}; +use tracing::debug; + +use crate::{ + fee_history::calculate_reward_percentiles_for_block, servers::LoadBlock, EthApiError, + EthResult, EthStateCache, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, + RpcInvalidTransactionError, +}; + +/// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the +/// `eth_` namespace. +pub trait EthFees: LoadFee { + /// Returns a suggestion for a gas price for legacy transactions. + /// + /// See also: + fn gas_price(&self) -> impl Future> + Send + where + Self: LoadBlock, + { + LoadFee::gas_price(self) + } + + /// Returns a suggestion for a base fee for blob transactions. + fn blob_base_fee(&self) -> impl Future> + Send + where + Self: LoadBlock, + { + LoadFee::blob_base_fee(self) + } + + /// Returns a suggestion for the priority fee (the tip) + fn suggested_priority_fee(&self) -> impl Future> + Send + where + Self: 'static, + { + LoadFee::suggested_priority_fee(self) + } + + /// Reports the fee history, for the given amount of blocks, up until the given newest block. + /// + /// If `reward_percentiles` are provided the [`FeeHistory`] will include the _approximated_ + /// rewards for the requested range. + fn fee_history( + &self, + mut block_count: u64, + newest_block: BlockNumberOrTag, + reward_percentiles: Option>, + ) -> impl Future> + Send { + async move { + if block_count == 0 { + return Ok(FeeHistory::default()) + } + + // See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225 + let max_fee_history = if reward_percentiles.is_none() { + self.gas_oracle().config().max_header_history + } else { + self.gas_oracle().config().max_block_history + }; + + if block_count > max_fee_history { + debug!( + requested = block_count, + truncated = max_fee_history, + "Sanitizing fee history block count" + ); + block_count = max_fee_history + } + + let Some(end_block) = + LoadFee::provider(self).block_number_for_id(newest_block.into())? + else { + return Err(EthApiError::UnknownBlockNumber) + }; + + // need to add 1 to the end block to get the correct (inclusive) range + let end_block_plus = end_block + 1; + // Ensure that we would not be querying outside of genesis + if end_block_plus < block_count { + block_count = end_block_plus; + } + + // If reward percentiles were specified, we + // need to validate that they are monotonically + // increasing and 0 <= p <= 100 + // Note: The types used ensure that the percentiles are never < 0 + if let Some(percentiles) = &reward_percentiles { + if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) { + return Err(EthApiError::InvalidRewardPercentiles) + } + } + + // Fetch the headers and ensure we got all of them + // + // Treat a request for 1 block as a request for `newest_block..=newest_block`, + // otherwise `newest_block - 2 + // NOTE: We ensured that block count is capped + let start_block = end_block_plus - block_count; + + // Collect base fees, gas usage ratios and (optionally) reward percentile data + let mut base_fee_per_gas: Vec = Vec::new(); + let mut gas_used_ratio: Vec = Vec::new(); + + let mut base_fee_per_blob_gas: Vec = Vec::new(); + let mut blob_gas_used_ratio: Vec = Vec::new(); + + let mut rewards: Vec> = Vec::new(); + + // Check if the requested range is within the cache bounds + let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await; + + if let Some(fee_entries) = fee_entries { + if fee_entries.len() != block_count as usize { + return Err(EthApiError::InvalidBlockRange) + } + + for entry in &fee_entries { + base_fee_per_gas.push(entry.base_fee_per_gas as u128); + gas_used_ratio.push(entry.gas_used_ratio); + base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default()); + blob_gas_used_ratio.push(entry.blob_gas_used_ratio); + + if let Some(percentiles) = &reward_percentiles { + let mut block_rewards = Vec::with_capacity(percentiles.len()); + for &percentile in percentiles { + block_rewards.push(self.approximate_percentile(entry, percentile)); + } + rewards.push(block_rewards); + } + } + let last_entry = fee_entries.last().expect("is not empty"); + + // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the + // next block + base_fee_per_gas + .push(last_entry.next_block_base_fee(&LoadFee::provider(self).chain_spec()) + as u128); + + base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); + } else { + // read the requested header range + let headers = LoadFee::provider(self).sealed_headers_range(start_block..=end_block)?; + if headers.len() != block_count as usize { + return Err(EthApiError::InvalidBlockRange) + } + + for header in &headers { + base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default() as u128); + gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64); + base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default()); + blob_gas_used_ratio.push( + header.blob_gas_used.unwrap_or_default() as f64 / + reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64, + ); + + // Percentiles were specified, so we need to collect reward percentile ino + if let Some(percentiles) = &reward_percentiles { + let (transactions, receipts) = LoadFee::cache(self) + .get_transactions_and_receipts(header.hash()) + .await? + .ok_or(EthApiError::InvalidBlockRange)?; + rewards.push( + calculate_reward_percentiles_for_block( + percentiles, + header.gas_used, + header.base_fee_per_gas.unwrap_or_default(), + &transactions, + &receipts, + ) + .unwrap_or_default(), + ); + } + } + + // The spec states that `base_fee_per_gas` "[..] includes the next block after the + // newest of the returned range, because this value can be derived from the + // newest block" + // + // The unwrap is safe since we checked earlier that we got at least 1 header. + let last_header = headers.last().expect("is present"); + base_fee_per_gas.push( + LoadFee::provider(self).chain_spec().base_fee_params_at_timestamp(last_header.timestamp).next_block_base_fee( + last_header.gas_used as u128, + last_header.gas_limit as u128, + last_header.base_fee_per_gas.unwrap_or_default() as u128, + )); + + // Same goes for the `base_fee_per_blob_gas`: + // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. + base_fee_per_blob_gas + .push(last_header.next_block_blob_fee().unwrap_or_default()); + }; + + Ok(FeeHistory { + base_fee_per_gas, + gas_used_ratio, + base_fee_per_blob_gas, + blob_gas_used_ratio, + oldest_block: start_block, + reward: reward_percentiles.map(|_| rewards), + }) + } + } + + /// Approximates reward at a given percentile for a specific block + /// Based on the configured resolution + fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 { + let resolution = self.fee_history_cache().resolution(); + let rounded_percentile = + (requested_percentile * resolution as f64).round() / resolution as f64; + let clamped_percentile = rounded_percentile.clamp(0.0, 100.0); + + // Calculate the index in the precomputed rewards array + let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize; + // Fetch the reward from the FeeHistoryEntry + entry.rewards.get(index).cloned().unwrap_or_default() + } +} + +/// Loads fee from database. +/// +/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` fees RPC methods. +pub trait LoadFee: LoadBlock { + // Returns a handle for reading data from disk. + /// + /// Data access in default (L1) trait method implementations. + fn provider(&self) -> impl BlockIdReader + HeaderProvider + ChainSpecProvider; + + /// Returns a handle for reading data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn cache(&self) -> &EthStateCache; + + /// Returns a handle for reading gas price. + /// + /// Data access in default (L1) trait method implementations. + fn gas_oracle(&self) -> &GasPriceOracle; + + /// Returns a handle for reading fee history data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn fee_history_cache(&self) -> &FeeHistoryCache; + + /// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy + /// transactions. + fn legacy_gas_price( + &self, + gas_price: Option, + ) -> impl Future> + Send { + async move { + match gas_price { + Some(gas_price) => Ok(gas_price), + None => { + // fetch a suggested gas price + self.gas_price().await + } + } + } + } + + /// Returns the EIP-1559 fees if they are set, otherwise fetches a suggested gas price for + /// EIP-1559 transactions. + /// + /// Returns (`max_fee`, `priority_fee`) + fn eip1559_fees( + &self, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + ) -> impl Future> + Send { + async move { + let max_fee_per_gas = match max_fee_per_gas { + Some(max_fee_per_gas) => max_fee_per_gas, + None => { + // fetch pending base fee + let base_fee = self + .block(BlockNumberOrTag::Pending.into()) + .await? + .ok_or(EthApiError::UnknownBlockNumber)? + .base_fee_per_gas + .ok_or_else(|| { + EthApiError::InvalidTransaction( + RpcInvalidTransactionError::TxTypeNotSupported, + ) + })?; + U256::from(base_fee) + } + }; + + let max_priority_fee_per_gas = match max_priority_fee_per_gas { + Some(max_priority_fee_per_gas) => max_priority_fee_per_gas, + None => self.suggested_priority_fee().await?, + }; + Ok((max_fee_per_gas, max_priority_fee_per_gas)) + } + } + + /// Returns the EIP-4844 blob fee if it is set, otherwise fetches a blob fee. + fn eip4844_blob_fee( + &self, + blob_fee: Option, + ) -> impl Future> + Send { + async move { + match blob_fee { + Some(blob_fee) => Ok(blob_fee), + None => self.blob_base_fee().await, + } + } + } + + /// Returns a suggestion for a gas price for legacy transactions. + /// + /// See also: + fn gas_price(&self) -> impl Future> + Send { + let header = self.block(BlockNumberOrTag::Latest.into()); + let suggested_tip = self.suggested_priority_fee(); + async move { + let (header, suggested_tip) = futures::try_join!(header, suggested_tip)?; + let base_fee = header.and_then(|h| h.base_fee_per_gas).unwrap_or_default(); + Ok(suggested_tip + U256::from(base_fee)) + } + } + + /// Returns a suggestion for a base fee for blob transactions. + fn blob_base_fee(&self) -> impl Future> + Send { + async move { + self.block(BlockNumberOrTag::Latest.into()) + .await? + .and_then(|h: reth_primitives::SealedBlock| h.next_block_blob_fee()) + .ok_or(EthApiError::ExcessBlobGasNotSet) + .map(U256::from) + } + } + + /// Returns a suggestion for the priority fee (the tip) + fn suggested_priority_fee(&self) -> impl Future> + Send + where + Self: 'static, + { + self.gas_oracle().suggest_tip_cap() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs new file mode 100644 index 000000000000..c714a166ddc0 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs @@ -0,0 +1,45 @@ +//! Behaviour needed to serve `eth_` RPC requests, divided into general database reads and +//! specific database access. +//! +//! Traits with `Load` prefix, read atomic data from database, e.g. a block or transaction. Any +//! database read done in more than one default `Eth` trait implementation, is defined in a `Load` +//! trait. +//! +//! Traits with `Eth` prefix, compose specific data needed to serve RPC requests in the `eth` +//! namespace. They use `Load` traits as building blocks. +//! [`EthTransactions`](crate::servers::EthTransactions) also writes data (submits transactions). +//! Based on the `eth_` request method semantics, request methods are divided into: +//! [`EthTransactions`](crate::servers::EthTransactions), [`EthBlocks`](crate::servers::EthBlocks), +//! [`EthFees`](crate::servers::EthFees), [`EthState`](crate::servers::EthState) and +//! [`EthCall`](crate::servers::EthCall). Default implementation of the `Eth` traits, is done w.r.t. +//! L1. +//! +//! [`EthApiServer`](crate::EthApiServer), is implemented for any type that implements +//! all the `Eth` traits, e.g. [`EthApi`](crate::EthApi). + +pub mod block; +pub mod blocking_task; +pub mod call; +pub mod fee; +pub mod pending_block; +pub mod receipt; +pub mod signer; +pub mod spec; +pub mod state; +pub mod trace; +pub mod transaction; + +use block::LoadBlock; +use blocking_task::SpawnBlocking; +use call::Call; +use pending_block::LoadPendingBlock; +use trace::Trace; +use transaction::LoadTransaction; + +/// Extension trait that bundles traits needed for tracing transactions. +pub trait TraceExt: + LoadTransaction + LoadBlock + LoadPendingBlock + SpawnBlocking + Trace + Call +{ +} + +impl TraceExt for T where T: LoadTransaction + LoadBlock + LoadPendingBlock + Trace + Call {} diff --git a/crates/rpc/rpc/src/eth/api/pending_block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs similarity index 53% rename from crates/rpc/rpc/src/eth/api/pending_block.rs rename to crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs index 4f9a616dece8..f167a82fef20 100644 --- a/crates/rpc/rpc/src/eth/api/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs @@ -1,63 +1,209 @@ -//! Support for building a pending block via local txpool. +//! Loads a pending block from database. Helper trait for `eth_` block, transaction, call and trace +//! RPC methods. -use crate::eth::error::{EthApiError, EthResult}; -use reth_chainspec::ChainSpec; -use reth_errors::ProviderError; +use std::time::{Duration, Instant}; + +use futures::Future; +use reth_evm::ConfigureEvm; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH}, - proofs, + proofs::calculate_transaction_root, revm::env::tx_env_with_recovered, revm_primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EVMError, Env, InvalidTransaction, ResultAndState, SpecId, + BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EVMError, Env, ExecutionResult, InvalidTransaction, + ResultAndState, SpecId, }, - Block, BlockId, BlockNumberOrTag, Header, IntoRecoveredTransaction, Receipt, Requests, - SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256, + Block, BlockNumber, Header, IntoRecoveredTransaction, Receipt, Requests, + SealedBlockWithSenders, SealedHeader, TransactionSignedEcRecovered, B256, + EMPTY_OMMER_ROOT_HASH, U256, +}; +use reth_provider::{ + BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory, }; -use reth_provider::{ChainSpecProvider, StateProviderFactory}; use reth_revm::{ - database::StateProviderDatabase, - state_change::{ - apply_beacon_root_contract_call, apply_blockhashes_update, - post_block_withdrawals_balance_increments, - }, + database::StateProviderDatabase, state_change::post_block_withdrawals_balance_increments, }; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; -use revm::{db::states::bundle_state::BundleRetention, Database, DatabaseCommit, State}; -use revm_primitives::EnvWithHandlerCfg; -use std::time::Instant; - -/// Configured [`BlockEnv`] and [`CfgEnvWithHandlerCfg`] for a pending block -#[derive(Debug, Clone)] -pub(crate) struct PendingBlockEnv { - /// Configured [`CfgEnvWithHandlerCfg`] for the pending block. - pub(crate) cfg: CfgEnvWithHandlerCfg, - /// Configured [`BlockEnv`] for the pending block. - pub(crate) block_env: BlockEnv, - /// Origin block for the config - pub(crate) origin: PendingBlockEnvOrigin, -} +use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State}; +use tokio::sync::Mutex; +use tracing::debug; + +use crate::{ + pending_block::{pre_block_beacon_root_contract_call, pre_block_blockhashes_update}, + servers::SpawnBlocking, + EthApiError, EthResult, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin, +}; -impl PendingBlockEnv { - /// Builds a pending block using the given client and pool. +/// Loads a pending block from database. +/// +/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods. +#[auto_impl::auto_impl(&, Arc)] +pub trait LoadPendingBlock { + /// Returns a handle for reading data from disk. + /// + /// Data access in default (L1) trait method implementations. + fn provider( + &self, + ) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory; + + /// Returns a handle for reading data from transaction pool. + /// + /// Data access in default (L1) trait method implementations. + fn pool(&self) -> impl TransactionPool; + + /// Returns a handle to the pending block. + /// + /// Data access in default (L1) trait method implementations. + fn pending_block(&self) -> &Mutex>; + + /// Returns a handle for reading evm config. + /// + /// Data access in default (L1) trait method implementations. + fn evm_config(&self) -> &impl ConfigureEvm; + + /// Configures the [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the pending block + /// + /// If no pending block is available, this will derive it from the `latest` block + fn pending_block_env_and_cfg(&self) -> EthResult { + let origin: PendingBlockEnvOrigin = if let Some(pending) = + self.provider().pending_block_with_senders()? + { + PendingBlockEnvOrigin::ActualPending(pending) + } else { + // no pending block from the CL yet, so we use the latest block and modify the env + // values that we can + let latest = + self.provider().latest_header()?.ok_or_else(|| EthApiError::UnknownBlockNumber)?; + + let (mut latest_header, block_hash) = latest.split(); + // child block + latest_header.number += 1; + // assumed child block is in the next slot: 12s + latest_header.timestamp += 12; + // base fee of the child block + let chain_spec = self.provider().chain_spec(); + + latest_header.base_fee_per_gas = latest_header.next_block_base_fee( + chain_spec.base_fee_params_at_timestamp(latest_header.timestamp), + ); + + // update excess blob gas consumed above target + latest_header.excess_blob_gas = latest_header.next_block_excess_blob_gas(); + + // we're reusing the same block hash because we need this to lookup the block's state + let latest = SealedHeader::new(latest_header, block_hash); + + PendingBlockEnvOrigin::DerivedFromLatest(latest) + }; + + let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::LATEST); + + let mut block_env = BlockEnv::default(); + // Note: for the PENDING block we assume it is past the known merge block and thus this will + // not fail when looking up the total difficulty value for the blockenv. + self.provider().fill_env_with_header( + &mut cfg, + &mut block_env, + origin.header(), + self.evm_config().clone(), + )?; + + Ok(PendingBlockEnv::new(cfg, block_env, origin)) + } + + /// Returns the locally built pending block + fn local_pending_block( + &self, + ) -> impl Future>> + Send + where + Self: SpawnBlocking, + { + async move { + let pending = self.pending_block_env_and_cfg()?; + if pending.origin.is_actual_pending() { + return Ok(pending.origin.into_actual_pending()) + } + + let mut lock = self.pending_block().lock().await; + + let now = Instant::now(); + + // check if the block is still good + if let Some(pending_block) = lock.as_ref() { + // this is guaranteed to be the `latest` header + if pending.block_env.number.to::() == pending_block.block.number && + pending.origin.header().hash() == pending_block.block.parent_hash && + now <= pending_block.expires_at + { + return Ok(Some(pending_block.block.clone())) + } + } + + // no pending block from the CL yet, so we need to build it ourselves via txpool + let pending_block = match self + .spawn_blocking_io(move |this| { + // we rebuild the block + this.build_block(pending) + }) + .await + { + Ok(block) => block, + Err(err) => { + debug!(target: "rpc", "Failed to build pending block: {:?}", err); + return Ok(None) + } + }; + + let now = Instant::now(); + *lock = Some(PendingBlock::new(pending_block.clone(), now + Duration::from_secs(1))); + + Ok(Some(pending_block)) + } + } + + /// Assembles a [`Receipt`] for a transaction, based on its [`ExecutionResult`]. + fn assemble_receipt( + &self, + tx: &TransactionSignedEcRecovered, + result: ExecutionResult, + cumulative_gas_used: u64, + ) -> Receipt { + Receipt { + tx_type: tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + #[cfg(feature = "optimism")] + deposit_nonce: None, + #[cfg(feature = "optimism")] + deposit_receipt_version: None, + } + } + + /// Calculates receipts root in block building. + /// + /// Panics if block is not in the [`ExecutionOutcome`]'s block range. + fn receipts_root( + &self, + _block_env: &BlockEnv, + execution_outcome: &ExecutionOutcome, + block_number: BlockNumber, + ) -> B256 { + execution_outcome.receipts_root_slow(block_number).expect("Block is present") + } + + /// Builds a pending block using the configured provider and pool. /// /// If the origin is the actual pending block, the block is built with withdrawals. /// /// After Cancun, if the origin is the actual pending block, the block includes the EIP-4788 pre /// block contract call using the parent beacon block root received from the CL. - pub(crate) fn build_block( - self, - client: &Client, - pool: &Pool, - ) -> EthResult - where - Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool, - { - let Self { cfg, block_env, origin } = self; + fn build_block(&self, env: PendingBlockEnv) -> EthResult { + let PendingBlockEnv { cfg, block_env, origin } = env; let parent_hash = origin.build_target_hash(); - let state_provider = client.history_by_block_hash(parent_hash)?; + let state_provider = self.provider().history_by_block_hash(parent_hash)?; let state = StateProviderDatabase::new(state_provider); let mut db = State::builder().with_database(state).with_bundle_update().build(); @@ -69,10 +215,11 @@ impl PendingBlockEnv { let mut executed_txs = Vec::new(); let mut senders = Vec::new(); - let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new( - base_fee, - block_env.get_blob_gasprice().map(|gasprice| gasprice as u64), - )); + let mut best_txs = + self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new( + base_fee, + block_env.get_blob_gasprice().map(|gasprice| gasprice as u64), + )); let (withdrawals, withdrawals_root) = match origin { PendingBlockEnvOrigin::ActualPending(ref block) => { @@ -81,7 +228,7 @@ impl PendingBlockEnv { PendingBlockEnvOrigin::DerivedFromLatest(_) => (None, None), }; - let chain_spec = client.chain_spec(); + let chain_spec = self.provider().chain_spec(); let parent_beacon_block_root = if origin.is_actual_pending() { // apply eip-4788 pre block contract call if we got the block from the CL with the real @@ -192,16 +339,7 @@ impl PendingBlockEnv { cumulative_gas_used += gas_used; // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Some(Receipt { - tx_type: tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - #[cfg(feature = "optimism")] - deposit_nonce: None, - #[cfg(feature = "optimism")] - deposit_receipt_version: None, - })); + receipts.push(Some(self.assemble_receipt(&tx, result, cumulative_gas_used))); // append transaction to the list of executed transactions let (tx, sender) = tx.to_components(); @@ -229,18 +367,7 @@ impl PendingBlockEnv { Vec::new(), ); - #[cfg(feature = "optimism")] - let receipts_root = execution_outcome - .optimism_receipts_root_slow( - block_number, - chain_spec.as_ref(), - block_env.timestamp.to::(), - ) - .expect("Block is present"); - - #[cfg(not(feature = "optimism"))] - let receipts_root = - execution_outcome.receipts_root_slow(block_number).expect("Block is present"); + let receipts_root = self.receipts_root(&block_env, &execution_outcome, block_number); let logs_bloom = execution_outcome.block_logs_bloom(block_number).expect("Block is present"); @@ -250,7 +377,7 @@ impl PendingBlockEnv { let state_root = state_provider.state_root(execution_outcome.state())?; // create the block header - let transactions_root = proofs::calculate_transaction_root(&executed_txs); + let transactions_root = calculate_transaction_root(&executed_txs); // check if cancun is activated to set eip4844 header fields correctly let blob_gas_used = @@ -294,137 +421,3 @@ impl PendingBlockEnv { Ok(SealedBlockWithSenders { block: block.seal_slow(), senders }) } } - -/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call. -/// -/// This constructs a new [Evm](revm::Evm) with the given DB, and environment -/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] to execute the pre block contract call. -/// -/// This uses [`apply_beacon_root_contract_call`] to ultimately apply the beacon root contract state -/// change. -fn pre_block_beacon_root_contract_call( - db: &mut DB, - chain_spec: &ChainSpec, - block_number: u64, - initialized_cfg: &CfgEnvWithHandlerCfg, - initialized_block_env: &BlockEnv, - parent_beacon_block_root: Option, -) -> EthResult<()> -where - DB::Error: std::fmt::Display, -{ - // apply pre-block EIP-4788 contract call - let mut evm_pre_block = revm::Evm::builder() - .with_db(db) - .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - Default::default(), - )) - .build(); - - // initialize a block from the env, because the pre block call needs the block itself - apply_beacon_root_contract_call( - chain_spec, - initialized_block_env.timestamp.to::(), - block_number, - parent_beacon_block_root, - &mut evm_pre_block, - ) - .map_err(|err| EthApiError::Internal(err.into())) -} - -/// Apply the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) pre block state transitions. -/// -/// This constructs a new [Evm](revm::Evm) with the given DB, and environment -/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`]. -/// -/// This uses [`apply_blockhashes_update`]. -fn pre_block_blockhashes_update + DatabaseCommit>( - db: &mut DB, - chain_spec: &ChainSpec, - initialized_block_env: &BlockEnv, - block_number: u64, - parent_block_hash: B256, -) -> EthResult<()> -where - DB::Error: std::fmt::Display, -{ - apply_blockhashes_update( - db, - chain_spec, - initialized_block_env.timestamp.to::(), - block_number, - parent_block_hash, - ) - .map_err(|err| EthApiError::Internal(err.into())) -} - -/// The origin for a configured [`PendingBlockEnv`] -#[derive(Clone, Debug)] -pub(crate) enum PendingBlockEnvOrigin { - /// The pending block as received from the CL. - ActualPending(SealedBlockWithSenders), - /// The _modified_ header of the latest block. - /// - /// This derives the pending state based on the latest header by modifying: - /// - the timestamp - /// - the block number - /// - fees - DerivedFromLatest(SealedHeader), -} - -impl PendingBlockEnvOrigin { - /// Returns true if the origin is the actual pending block as received from the CL. - pub(crate) const fn is_actual_pending(&self) -> bool { - matches!(self, Self::ActualPending(_)) - } - - /// Consumes the type and returns the actual pending block. - pub(crate) fn into_actual_pending(self) -> Option { - match self { - Self::ActualPending(block) => Some(block), - _ => None, - } - } - - /// Returns the [`BlockId`] that represents the state of the block. - /// - /// If this is the actual pending block, the state is the "Pending" tag, otherwise we can safely - /// identify the block by its hash (latest block). - pub(crate) fn state_block_id(&self) -> BlockId { - match self { - Self::ActualPending(_) => BlockNumberOrTag::Pending.into(), - Self::DerivedFromLatest(header) => BlockId::Hash(header.hash().into()), - } - } - - /// Returns the hash of the block the pending block should be built on. - /// - /// For the [`PendingBlockEnvOrigin::ActualPending`] this is the parent hash of the block. - /// For the [`PendingBlockEnvOrigin::DerivedFromLatest`] this is the hash of the _latest_ - /// header. - fn build_target_hash(&self) -> B256 { - match self { - Self::ActualPending(block) => block.parent_hash, - Self::DerivedFromLatest(header) => header.hash(), - } - } - - /// Returns the header this pending block is based on. - pub(crate) fn header(&self) -> &SealedHeader { - match self { - Self::ActualPending(block) => &block.header, - Self::DerivedFromLatest(header) => header, - } - } -} - -/// In memory pending block for `pending` tag -#[derive(Debug)] -pub(crate) struct PendingBlock { - /// The cached pending block - pub(crate) block: SealedBlockWithSenders, - /// Timestamp when the pending block is considered outdated - pub(crate) expires_at: Instant, -} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs new file mode 100644 index 000000000000..83c4b9d03d8a --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs @@ -0,0 +1,37 @@ +//! Loads a receipt from database. Helper trait for `eth_` block and transaction RPC methods, that +//! loads receipt data w.r.t. network. + +use futures::Future; +use reth_primitives::{Receipt, TransactionMeta, TransactionSigned}; +use reth_rpc_types::AnyTransactionReceipt; + +use crate::{EthApiError, EthResult, EthStateCache, ReceiptBuilder}; + +/// Assembles transaction receipt data w.r.t to network. +/// +/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` receipts RPC methods. +#[auto_impl::auto_impl(&, Arc)] +pub trait LoadReceipt: Send + Sync { + /// Returns a handle for reading data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn cache(&self) -> &EthStateCache; + + /// Helper method for `eth_getBlockReceipts` and `eth_getTransactionReceipt`. + fn build_transaction_receipt( + &self, + tx: TransactionSigned, + meta: TransactionMeta, + receipt: Receipt, + ) -> impl Future> + Send { + async move { + // get all receipts for the block + let all_receipts = match self.cache().get_receipts(meta.block_hash).await? { + Some(recpts) => recpts, + None => return Err(EthApiError::UnknownBlockNumber), + }; + + Ok(ReceiptBuilder::new(&tx, meta, &receipt, &all_receipts)?.build()) + } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs new file mode 100644 index 000000000000..637708eb7c44 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs @@ -0,0 +1,40 @@ +//! An abstraction over ethereum signers. + +use std::result; + +use alloy_dyn_abi::TypedData; +use dyn_clone::DynClone; +use reth_primitives::{Address, Signature, TransactionSigned}; +use reth_rpc_types::TypedTransactionRequest; + +use crate::SignError; + +/// Result returned by [`EthSigner`] methods. +pub type Result = result::Result; + +/// An Ethereum Signer used via RPC. +#[async_trait::async_trait] +pub trait EthSigner: Send + Sync + DynClone { + /// Returns the available accounts for this signer. + fn accounts(&self) -> Vec
; + + /// Returns `true` whether this signer can sign for this address + fn is_signer_for(&self, addr: &Address) -> bool { + self.accounts().contains(addr) + } + + /// Returns the signature + async fn sign(&self, address: Address, message: &[u8]) -> Result; + + /// signs a transaction request using the given account in request + fn sign_transaction( + &self, + request: TypedTransactionRequest, + address: &Address, + ) -> Result; + + /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait. + fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result; +} + +dyn_clone::clone_trait_object!(EthSigner); diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs new file mode 100644 index 000000000000..63722e376e64 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs @@ -0,0 +1,31 @@ +//! Loads chain metadata. + +use futures::Future; +use reth_chainspec::ChainInfo; +use reth_errors::RethResult; +use reth_primitives::{Address, U64}; +use reth_rpc_types::SyncStatus; + +/// `Eth` API trait. +/// +/// Defines core functionality of the `eth` API implementation. +#[auto_impl::auto_impl(&, Arc)] +pub trait EthApiSpec: Send + Sync { + /// Returns the current ethereum protocol version. + fn protocol_version(&self) -> impl Future> + Send; + + /// Returns the chain id + fn chain_id(&self) -> U64; + + /// Returns provider chain info + fn chain_info(&self) -> RethResult; + + /// Returns a list of addresses owned by provider. + fn accounts(&self) -> Vec
; + + /// Returns `true` if the network is undergoing sync. + fn is_syncing(&self) -> bool; + + /// Returns the [`SyncStatus`] of the network + fn sync_status(&self) -> RethResult; +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs new file mode 100644 index 000000000000..220a33e91683 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs @@ -0,0 +1,247 @@ +//! Loads a pending block from database. Helper trait for `eth_` block, transaction, call and trace +//! RPC methods. + +use futures::Future; +use reth_primitives::{ + revm::env::fill_block_env_with_coinbase, Address, BlockId, BlockNumberOrTag, Bytes, Header, + B256, U256, +}; +use reth_provider::{BlockIdReader, StateProvider, StateProviderBox, StateProviderFactory}; +use reth_rpc_types::{serde_helpers::JsonStorageKey, EIP1186AccountProofResponse}; +use reth_rpc_types_compat::proof::from_primitive_account_proof; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, SpecId}; + +use crate::{ + servers::{EthApiSpec, LoadPendingBlock, SpawnBlocking}, + EthApiError, EthResult, EthStateCache, PendingBlockEnv, RpcInvalidTransactionError, +}; + +/// Helper methods for `eth_` methods relating to state (accounts). +pub trait EthState: LoadState + SpawnBlocking { + /// Returns the number of transactions sent from an address at the given block identifier. + /// + /// If this is [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this will + /// look up the highest transaction in pool and return the next nonce (highest + 1). + fn transaction_count( + &self, + address: Address, + block_id: Option, + ) -> impl Future> + Send { + LoadState::transaction_count(self, address, block_id) + } + + /// Returns code of given account, at given blocknumber. + fn get_code( + &self, + address: Address, + block_id: Option, + ) -> impl Future> + Send { + self.spawn_blocking_io(move |this| { + Ok(this + .state_at_block_id_or_latest(block_id)? + .account_code(address)? + .unwrap_or_default() + .original_bytes()) + }) + } + + /// Returns balance of given account, at given blocknumber. + fn balance( + &self, + address: Address, + block_id: Option, + ) -> impl Future> + Send { + self.spawn_blocking_io(move |this| { + Ok(this + .state_at_block_id_or_latest(block_id)? + .account_balance(address)? + .unwrap_or_default()) + }) + } + + /// Returns values stored of given account, at given blocknumber. + fn storage_at( + &self, + address: Address, + index: JsonStorageKey, + block_id: Option, + ) -> impl Future> + Send { + self.spawn_blocking_io(move |this| { + Ok(B256::new( + this.state_at_block_id_or_latest(block_id)? + .storage(address, index.0)? + .unwrap_or_default() + .to_be_bytes(), + )) + }) + } + + /// Returns values stored of given account, with Merkle-proof, at given blocknumber. + fn get_proof( + &self, + address: Address, + keys: Vec, + block_id: Option, + ) -> EthResult> + Send> + where + Self: EthApiSpec, + { + let chain_info = self.chain_info()?; + let block_id = block_id.unwrap_or_default(); + + // if we are trying to create a proof for the latest block, but have a BlockId as input + // that is not BlockNumberOrTag::Latest, then we need to figure out whether or not the + // BlockId corresponds to the latest block + let is_latest_block = match block_id { + BlockId::Number(BlockNumberOrTag::Number(num)) => num == chain_info.best_number, + BlockId::Hash(hash) => hash == chain_info.best_hash.into(), + BlockId::Number(BlockNumberOrTag::Latest) => true, + _ => false, + }; + + // TODO: remove when HistoricalStateProviderRef::proof is implemented + if !is_latest_block { + return Err(EthApiError::InvalidBlockRange) + } + + Ok(self.spawn_tracing(move |this| { + let state = this.state_at_block_id(block_id)?; + let storage_keys = keys.iter().map(|key| key.0).collect::>(); + let proof = state.proof(address, &storage_keys)?; + Ok(from_primitive_account_proof(proof)) + })) + } +} + +/// Loads state from database. +/// +/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` state RPC methods. +pub trait LoadState { + /// Returns a handle for reading state from database. + /// + /// Data access in default trait method implementations. + fn provider(&self) -> impl StateProviderFactory; + + /// Returns a handle for reading data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn cache(&self) -> &EthStateCache; + + /// Returns a handle for reading data from transaction pool. + /// + /// Data access in default trait method implementations. + fn pool(&self) -> impl TransactionPool; + + /// Returns the state at the given block number + fn state_at_hash(&self, block_hash: B256) -> EthResult { + Ok(self.provider().history_by_block_hash(block_hash)?) + } + + /// Returns the state at the given [`BlockId`] enum. + /// + /// Note: if not [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this + /// will only return canonical state. See also + fn state_at_block_id(&self, at: BlockId) -> EthResult { + Ok(self.provider().state_by_block_id(at)?) + } + + /// Returns the _latest_ state + fn latest_state(&self) -> EthResult { + Ok(self.provider().latest()?) + } + + /// Returns the state at the given [`BlockId`] enum or the latest. + /// + /// Convenience function to interprets `None` as `BlockId::Number(BlockNumberOrTag::Latest)` + fn state_at_block_id_or_latest( + &self, + block_id: Option, + ) -> EthResult { + if let Some(block_id) = block_id { + self.state_at_block_id(block_id) + } else { + Ok(self.latest_state()?) + } + } + + /// Returns the revm evm env for the requested [`BlockId`] + /// + /// If the [`BlockId`] this will return the [`BlockId`] of the block the env was configured + /// for. + /// If the [`BlockId`] is pending, this will return the "Pending" tag, otherwise this returns + /// the hash of the exact block. + fn evm_env_at( + &self, + at: BlockId, + ) -> impl Future> + Send + where + Self: LoadPendingBlock + SpawnBlocking, + { + async move { + if at.is_pending() { + let PendingBlockEnv { cfg, block_env, origin } = + self.pending_block_env_and_cfg()?; + Ok((cfg, block_env, origin.state_block_id())) + } else { + // Use cached values if there is no pending block + let block_hash = LoadPendingBlock::provider(self) + .block_hash_for_id(at)? + .ok_or_else(|| EthApiError::UnknownBlockNumber)?; + let (cfg, env) = self.cache().get_evm_env(block_hash).await?; + Ok((cfg, env, block_hash.into())) + } + } + } + + /// Returns the revm evm env for the raw block header + /// + /// This is used for tracing raw blocks + fn evm_env_for_raw_block( + &self, + header: &Header, + ) -> impl Future> + Send + where + Self: LoadPendingBlock + SpawnBlocking, + { + async move { + // get the parent config first + let (cfg, mut block_env, _) = self.evm_env_at(header.parent_hash.into()).await?; + + let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE; + fill_block_env_with_coinbase(&mut block_env, header, after_merge, header.beneficiary); + + Ok((cfg, block_env)) + } + } + + /// Returns the number of transactions sent from an address at the given block identifier. + /// + /// If this is [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this will + /// look up the highest transaction in pool and return the next nonce (highest + 1). + fn transaction_count( + &self, + address: Address, + block_id: Option, + ) -> impl Future> + Send + where + Self: SpawnBlocking, + { + self.spawn_blocking_io(move |this| { + if block_id == Some(BlockId::pending()) { + let address_txs = this.pool().get_transactions_by_sender(address); + if let Some(highest_nonce) = + address_txs.iter().map(|item| item.transaction.nonce()).max() + { + let tx_count = highest_nonce + .checked_add(1) + .ok_or(RpcInvalidTransactionError::NonceMaxValue)?; + return Ok(U256::from(tx_count)) + } + } + + let state = this.state_at_block_id_or_latest(block_id)?; + Ok(U256::from(state.account_nonce(address)?.unwrap_or_default())) + }) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs new file mode 100644 index 000000000000..a1eedf5dc1bf --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs @@ -0,0 +1,412 @@ +//! Loads a pending block from database. Helper trait for `eth_` call and trace RPC methods. + +use futures::Future; +use reth_evm::ConfigureEvm; +use reth_primitives::{revm::env::tx_env_with_recovered, B256}; +use reth_revm::database::StateProviderDatabase; +use reth_rpc_types::{BlockId, TransactionInfo}; +use revm::{db::CacheDB, Database, DatabaseCommit, GetInspector, Inspector}; +use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; +use revm_primitives::{EnvWithHandlerCfg, EvmState, ExecutionResult, ResultAndState}; + +use crate::{ + cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, + servers::{Call, LoadBlock, LoadPendingBlock, LoadState, LoadTransaction}, + EthApiError, EthResult, StateCacheDb, +}; + +/// Executes CPU heavy tasks. +pub trait Trace: LoadState { + /// Returns a handle for reading evm config. + /// + /// Data access in default (L1) trait method implementations. + fn evm_config(&self) -> &impl ConfigureEvm; + + /// Executes the [`EnvWithHandlerCfg`] against the given [Database] without committing state + /// changes. + fn inspect( + &self, + db: DB, + env: EnvWithHandlerCfg, + inspector: I, + ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> + where + DB: Database, + ::Error: Into, + I: GetInspector, + { + self.inspect_and_return_db(db, env, inspector).map(|(res, env, _)| (res, env)) + } + + /// Same as [`inspect`](Self::inspect) but also returns the database again. + /// + /// Even though [Database] is also implemented on `&mut` + /// this is still useful if there are certain trait bounds on the Inspector's database generic + /// type + fn inspect_and_return_db( + &self, + db: DB, + env: EnvWithHandlerCfg, + inspector: I, + ) -> EthResult<(ResultAndState, EnvWithHandlerCfg, DB)> + where + DB: Database, + ::Error: Into, + I: GetInspector, + { + let mut evm = self.evm_config().evm_with_env_and_inspector(db, env, inspector); + let res = evm.transact()?; + let (db, env) = evm.into_db_and_env_with_handler_cfg(); + Ok((res, env, db)) + } + + /// Executes the transaction on top of the given [`BlockId`] with a tracer configured by the + /// config. + /// + /// The callback is then called with the [`TracingInspector`] and the [`ResultAndState`] after + /// the configured [`EnvWithHandlerCfg`] was inspected. + /// + /// Caution: this is blocking + fn trace_at( + &self, + env: EnvWithHandlerCfg, + config: TracingInspectorConfig, + at: BlockId, + f: F, + ) -> EthResult + where + Self: Call, + F: FnOnce(TracingInspector, ResultAndState) -> EthResult, + { + self.with_state_at_block(at, |state| { + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + let mut inspector = TracingInspector::new(config); + let (res, _) = self.inspect(&mut db, env, &mut inspector)?; + f(inspector, res) + }) + } + + /// Same as [`trace_at`](Self::trace_at) but also provides the used database to the callback. + /// + /// Executes the transaction on top of the given [`BlockId`] with a tracer configured by the + /// config. + /// + /// The callback is then called with the [`TracingInspector`] and the [`ResultAndState`] after + /// the configured [`EnvWithHandlerCfg`] was inspected. + fn spawn_trace_at_with_state( + &self, + env: EnvWithHandlerCfg, + config: TracingInspectorConfig, + at: BlockId, + f: F, + ) -> impl Future> + Send + where + Self: LoadPendingBlock + Call, + F: FnOnce(TracingInspector, ResultAndState, StateCacheDb<'_>) -> EthResult + + Send + + 'static, + R: Send + 'static, + { + let this = self.clone(); + self.spawn_with_state_at_block(at, move |state| { + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + let mut inspector = TracingInspector::new(config); + let (res, _) = this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?; + f(inspector, res, db) + }) + } + + /// Retrieves the transaction if it exists and returns its trace. + /// + /// Before the transaction is traced, all previous transaction in the block are applied to the + /// state by executing them first. + /// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed + /// and the database that points to the beginning of the transaction. + /// + /// Note: Implementers should use a threadpool where blocking is allowed, such as + /// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool). + fn spawn_trace_transaction_in_block( + &self, + hash: B256, + config: TracingInspectorConfig, + f: F, + ) -> impl Future>> + Send + where + Self: LoadPendingBlock + LoadTransaction + Call, + F: FnOnce( + TransactionInfo, + TracingInspector, + ResultAndState, + StateCacheDb<'_>, + ) -> EthResult + + Send + + 'static, + R: Send + 'static, + { + self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f) + } + + /// Retrieves the transaction if it exists and returns its trace. + /// + /// Before the transaction is traced, all previous transaction in the block are applied to the + /// state by executing them first. + /// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed + /// and the database that points to the beginning of the transaction. + /// + /// Note: Implementers should use a threadpool where blocking is allowed, such as + /// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool). + fn spawn_trace_transaction_in_block_with_inspector( + &self, + hash: B256, + mut inspector: Insp, + f: F, + ) -> impl Future>> + Send + where + Self: LoadPendingBlock + LoadTransaction + Call, + F: FnOnce(TransactionInfo, Insp, ResultAndState, StateCacheDb<'_>) -> EthResult + + Send + + 'static, + Insp: for<'a, 'b> Inspector> + Send + 'static, + R: Send + 'static, + { + async move { + let (transaction, block) = match self.transaction_and_block(hash).await? { + None => return Ok(None), + Some(res) => res, + }; + let (tx, tx_info) = transaction.split(); + + let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?; + + // we need to get the state of the parent block because we're essentially replaying the + // block the transaction is included in + let parent_block = block.parent_hash; + let block_txs = block.into_transactions_ecrecovered(); + + let this = self.clone(); + self.spawn_with_state_at_block(parent_block.into(), move |state| { + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + + // replay all transactions prior to the targeted transaction + this.replay_transactions_until( + &mut db, + cfg.clone(), + block_env.clone(), + block_txs, + tx.hash, + )?; + + let env = + EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx)); + let (res, _) = + this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?; + f(tx_info, inspector, res, db) + }) + .await + .map(Some) + } + } + + /// Executes all transactions of a block up to a given index. + /// + /// If a `highest_index` is given, this will only execute the first `highest_index` + /// transactions, in other words, it will stop executing transactions after the + /// `highest_index`th transaction. If `highest_index` is `None`, all transactions + /// are executed. + fn trace_block_until( + &self, + block_id: BlockId, + highest_index: Option, + config: TracingInspectorConfig, + f: F, + ) -> impl Future>>> + Send + where + Self: LoadBlock, + F: Fn( + TransactionInfo, + TracingInspector, + ExecutionResult, + &EvmState, + &StateCacheDb<'_>, + ) -> EthResult + + Send + + 'static, + R: Send + 'static, + { + self.trace_block_until_with_inspector( + block_id, + highest_index, + move || TracingInspector::new(config), + f, + ) + } + + /// Executes all transactions of a block. + /// + /// If a `highest_index` is given, this will only execute the first `highest_index` + /// transactions, in other words, it will stop executing transactions after the + /// `highest_index`th transaction. + /// + /// Note: This expect tx index to be 0-indexed, so the first transaction is at index 0. + /// + /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing + /// the transactions. + fn trace_block_until_with_inspector( + &self, + block_id: BlockId, + highest_index: Option, + mut inspector_setup: Setup, + f: F, + ) -> impl Future>>> + Send + where + Self: LoadBlock, + F: Fn(TransactionInfo, Insp, ExecutionResult, &EvmState, &StateCacheDb<'_>) -> EthResult + + Send + + 'static, + Setup: FnMut() -> Insp + Send + 'static, + Insp: for<'a, 'b> Inspector> + Send + 'static, + R: Send + 'static, + { + async move { + let ((cfg, block_env, _), block) = + futures::try_join!(self.evm_env_at(block_id), self.block_with_senders(block_id))?; + + let Some(block) = block else { return Ok(None) }; + + if block.body.is_empty() { + // nothing to trace + return Ok(Some(Vec::new())) + } + + // replay all transactions of the block + self.spawn_tracing(move |this| { + // we need to get the state of the parent block because we're replaying this block + // on top of its parent block's state + let state_at = block.parent_hash; + let block_hash = block.hash(); + + let block_number = block_env.number.saturating_to::(); + let base_fee = block_env.basefee.saturating_to::(); + + // prepare transactions, we do everything upfront to reduce time spent with open + // state + let max_transactions = highest_index.map_or(block.body.len(), |highest| { + // we need + 1 because the index is 0-based + highest as usize + 1 + }); + let mut results = Vec::with_capacity(max_transactions); + + let mut transactions = block + .into_transactions_ecrecovered() + .take(max_transactions) + .enumerate() + .map(|(idx, tx)| { + let tx_info = TransactionInfo { + hash: Some(tx.hash()), + index: Some(idx as u64), + block_hash: Some(block_hash), + block_number: Some(block_number), + base_fee: Some(base_fee), + }; + let tx_env = tx_env_with_recovered(&tx); + (tx_info, tx_env) + }) + .peekable(); + + // now get the state + let state = this.state_at_block_id(state_at.into())?; + let mut db = + CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state))); + + while let Some((tx_info, tx)) = transactions.next() { + let env = + EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx); + + let mut inspector = inspector_setup(); + let (res, _) = + this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?; + let ResultAndState { result, state } = res; + results.push(f(tx_info, inspector, result, &state, &db)?); + + // need to apply the state changes of this transaction before executing the + // next transaction, but only if there's a next transaction + if transactions.peek().is_some() { + // commit the state changes to the DB + db.commit(state) + } + } + + Ok(Some(results)) + }) + .await + } + } + + /// Executes all transactions of a block and returns a list of callback results invoked for each + /// transaction in the block. + /// + /// This + /// 1. fetches all transactions of the block + /// 2. configures the EVM evn + /// 3. loops over all transactions and executes them + /// 4. calls the callback with the transaction info, the execution result, the changed state + /// _after_ the transaction [`StateProviderDatabase`] and the database that points to the + /// state right _before_ the transaction. + fn trace_block_with( + &self, + block_id: BlockId, + config: TracingInspectorConfig, + f: F, + ) -> impl Future>>> + Send + where + Self: LoadBlock, + // This is the callback that's invoked for each transaction with the inspector, the result, + // state and db + F: Fn( + TransactionInfo, + TracingInspector, + ExecutionResult, + &EvmState, + &StateCacheDb<'_>, + ) -> EthResult + + Send + + 'static, + R: Send + 'static, + { + self.trace_block_until(block_id, None, config, f) + } + + /// Executes all transactions of a block and returns a list of callback results invoked for each + /// transaction in the block. + /// + /// This + /// 1. fetches all transactions of the block + /// 2. configures the EVM evn + /// 3. loops over all transactions and executes them + /// 4. calls the callback with the transaction info, the execution result, the changed state + /// _after_ the transaction [`EvmState`] and the database that points to the state right + /// _before_ the transaction, in other words the state the transaction was executed on: + /// `changed_state = tx(cached_state)` + /// + /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing + /// a transaction. This is invoked for each transaction. + fn trace_block_inspector( + &self, + block_id: BlockId, + insp_setup: Setup, + f: F, + ) -> impl Future>>> + Send + where + Self: LoadBlock, + // This is the callback that's invoked for each transaction with the inspector, the result, + // state and db + F: Fn(TransactionInfo, Insp, ExecutionResult, &EvmState, &StateCacheDb<'_>) -> EthResult + + Send + + 'static, + Setup: FnMut() -> Insp + Send + 'static, + Insp: for<'a, 'b> Inspector> + Send + 'static, + R: Send + 'static, + { + self.trace_block_until_with_inspector(block_id, None, insp_setup, f) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs new file mode 100644 index 000000000000..5c87de1d5ed6 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs @@ -0,0 +1,651 @@ +//! Database access for `eth_` transaction RPC methods. Loads transaction and receipt data w.r.t. +//! network. + +use std::{fmt, sync::Arc}; + +use alloy_dyn_abi::TypedData; +use futures::Future; +use reth_primitives::{ + Address, BlockId, Bytes, FromRecoveredPooledTransaction, IntoRecoveredTransaction, Receipt, + SealedBlockWithSenders, TransactionMeta, TransactionSigned, TxHash, TxKind, B256, U256, +}; +use reth_provider::{BlockReaderIdExt, ReceiptProvider, TransactionsProvider}; +use reth_rpc_types::{ + transaction::{ + EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest, + LegacyTransactionRequest, + }, + AnyTransactionReceipt, Index, Transaction, TransactionRequest, TypedTransactionRequest, +}; +use reth_rpc_types_compat::transaction::from_recovered_with_block_context; +use reth_transaction_pool::{TransactionOrigin, TransactionPool}; + +use crate::{ + servers::{ + Call, EthApiSpec, EthSigner, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, + SpawnBlocking, + }, + utils::recover_raw_transaction, + EthApiError, EthResult, EthStateCache, SignError, TransactionSource, +}; + +/// Transaction related functions for the [`EthApiServer`](crate::EthApiServer) trait in +/// the `eth_` namespace. +/// +/// This includes utilities for transaction tracing, transacting and inspection. +/// +/// Async functions that are spawned onto the +/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool) begin with `spawn_` +/// +/// ## Calls +/// +/// There are subtle differences between when transacting [`TransactionRequest`]: +/// +/// The endpoints `eth_call` and `eth_estimateGas` and `eth_createAccessList` should always +/// __disable__ the base fee check in the +/// [`EnvWithHandlerCfg`](revm_primitives::CfgEnvWithHandlerCfg). +/// +/// The behaviour for tracing endpoints is not consistent across clients. +/// Geth also disables the basefee check for tracing: +/// Erigon does not: +/// +/// See also +/// +/// This implementation follows the behaviour of Geth and disables the basefee check for tracing. +pub trait EthTransactions: LoadTransaction { + /// Returns a handle for reading data from disk. + /// + /// Data access in default (L1) trait method implementations. + fn provider(&self) -> impl BlockReaderIdExt; + + /// Returns a handle for forwarding received raw transactions. + /// + /// Access to transaction forwarder in default (L1) trait method implementations. + fn raw_tx_forwarder(&self) -> Option>; + + /// Returns a handle for signing data. + /// + /// Singer access in default (L1) trait method implementations. + fn signers(&self) -> &parking_lot::RwLock>>; + + /// Returns the transaction by hash. + /// + /// Checks the pool and state. + /// + /// Returns `Ok(None)` if no matching transaction was found. + fn transaction_by_hash( + &self, + hash: B256, + ) -> impl Future>> + Send { + LoadTransaction::transaction_by_hash(self, hash) + } + + /// Get all transactions in the block with the given hash. + /// + /// Returns `None` if block does not exist. + fn transactions_by_block( + &self, + block: B256, + ) -> impl Future>>> + Send { + async move { Ok(self.cache().get_block_transactions(block).await?) } + } + + /// Returns the EIP-2718 encoded transaction by hash. + /// + /// If this is a pooled EIP-4844 transaction, the blob sidecar is included. + /// + /// Checks the pool and state. + /// + /// Returns `Ok(None)` if no matching transaction was found. + fn raw_transaction_by_hash( + &self, + hash: B256, + ) -> impl Future>> + Send { + async move { + // Note: this is mostly used to fetch pooled transactions so we check the pool first + if let Some(tx) = + self.pool().get_pooled_transaction_element(hash).map(|tx| tx.envelope_encoded()) + { + return Ok(Some(tx)) + } + + self.spawn_blocking_io(move |ref this| { + Ok(LoadTransaction::provider(this) + .transaction_by_hash(hash)? + .map(|tx| tx.envelope_encoded())) + }) + .await + } + } + + /// Returns the _historical_ transaction and the block it was mined in + fn historical_transaction_by_hash_at( + &self, + hash: B256, + ) -> impl Future>> + Send { + async move { + match self.transaction_by_hash_at(hash).await? { + None => Ok(None), + Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))), + } + } + } + + /// Returns the transaction receipt for the given hash. + /// + /// Returns None if the transaction does not exist or is pending + /// Note: The tx receipt is not available for pending transactions. + fn transaction_receipt( + &self, + hash: B256, + ) -> impl Future>> + Send + where + Self: LoadReceipt + 'static, + { + async move { + let result = self.load_transaction_and_receipt(hash).await?; + + let (tx, meta, receipt) = match result { + Some((tx, meta, receipt)) => (tx, meta, receipt), + None => return Ok(None), + }; + + self.build_transaction_receipt(tx, meta, receipt).await.map(Some) + } + } + + /// Helper method that loads a transaction and its receipt. + fn load_transaction_and_receipt( + &self, + hash: TxHash, + ) -> impl Future>> + Send + where + Self: 'static, + { + let this = self.clone(); + self.spawn_blocking_io(move |_| { + let (tx, meta) = + match LoadTransaction::provider(&this).transaction_by_hash_with_meta(hash)? { + Some((tx, meta)) => (tx, meta), + None => return Ok(None), + }; + + let receipt = match EthTransactions::provider(&this).receipt_by_hash(hash)? { + Some(recpt) => recpt, + None => return Ok(None), + }; + + Ok(Some((tx, meta, receipt))) + }) + } + + /// Get [`Transaction`] by [`BlockId`] and index of transaction within that block. + /// + /// Returns `Ok(None)` if the block does not exist, or index is out of range. + fn transaction_by_block_and_tx_index( + &self, + block_id: BlockId, + index: Index, + ) -> impl Future>> + Send + where + Self: LoadBlock, + { + async move { + if let Some(block) = self.block_with_senders(block_id).await? { + let block_hash = block.hash(); + let block_number = block.number; + let base_fee_per_gas = block.base_fee_per_gas; + if let Some(tx) = block.into_transactions_ecrecovered().nth(index.into()) { + return Ok(Some(from_recovered_with_block_context( + tx, + block_hash, + block_number, + base_fee_per_gas, + index.into(), + ))) + } + } + + Ok(None) + } + } + + /// Get transaction, as raw bytes, by [`BlockId`] and index of transaction within that block. + /// + /// Returns `Ok(None)` if the block does not exist, or index is out of range. + fn raw_transaction_by_block_and_tx_index( + &self, + block_id: BlockId, + index: Index, + ) -> impl Future>> + Send + where + Self: LoadBlock, + { + async move { + if let Some(block) = self.block_with_senders(block_id).await? { + if let Some(tx) = block.transactions().nth(index.into()) { + return Ok(Some(tx.envelope_encoded())) + } + } + + Ok(None) + } + } + + /// Decodes and recovers the transaction and submits it to the pool. + /// + /// Returns the hash of the transaction. + fn send_raw_transaction(&self, tx: Bytes) -> impl Future> + Send { + async move { + // On optimism, transactions are forwarded directly to the sequencer to be included in + // blocks that it builds. + if let Some(client) = self.raw_tx_forwarder().as_ref() { + tracing::debug!( target: "rpc::eth", "forwarding raw transaction to"); + client.forward_raw_transaction(&tx).await?; + } + + let recovered = recover_raw_transaction(tx)?; + let pool_transaction = + ::Transaction::from_recovered_pooled_transaction( + recovered, + ); + + // submit the transaction to the pool with a `Local` origin + let hash = + self.pool().add_transaction(TransactionOrigin::Local, pool_transaction).await?; + + Ok(hash) + } + } + + /// Signs transaction with a matching signer, if any and submits the transaction to the pool. + /// Returns the hash of the signed transaction. + fn send_transaction( + &self, + mut request: TransactionRequest, + ) -> impl Future> + Send + where + Self: EthApiSpec + LoadBlock + LoadPendingBlock + LoadFee + Call, + { + async move { + let from = match request.from { + Some(from) => from, + None => return Err(SignError::NoAccount.into()), + }; + + // set nonce if not already set before + if request.nonce.is_none() { + let nonce = self.transaction_count(from, Some(BlockId::pending())).await?; + // note: `.to()` can't panic because the nonce is constructed from a `u64` + request.nonce = Some(nonce.to::()); + } + + let chain_id = self.chain_id(); + + let estimated_gas = + self.estimate_gas_at(request.clone(), BlockId::pending(), None).await?; + let gas_limit = estimated_gas; + + let TransactionRequest { + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + input: data, + nonce, + mut access_list, + max_fee_per_blob_gas, + blob_versioned_hashes, + sidecar, + .. + } = request; + + // todo: remove this inlining after https://github.com/alloy-rs/alloy/pull/183#issuecomment-1928161285 + let transaction = match ( + gas_price, + max_fee_per_gas, + access_list.take(), + max_fee_per_blob_gas, + blob_versioned_hashes, + sidecar, + ) { + // legacy transaction + // gas price required + (Some(_), None, None, None, None, None) => { + Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest { + nonce: nonce.unwrap_or_default(), + gas_price: U256::from(gas_price.unwrap_or_default()), + gas_limit: U256::from(gas.unwrap_or_default()), + value: value.unwrap_or_default(), + input: data.into_input().unwrap_or_default(), + kind: to.unwrap_or(TxKind::Create), + chain_id: None, + })) + } + // EIP2930 + // if only accesslist is set, and no eip1599 fees + (_, None, Some(access_list), None, None, None) => { + Some(TypedTransactionRequest::EIP2930(EIP2930TransactionRequest { + nonce: nonce.unwrap_or_default(), + gas_price: U256::from(gas_price.unwrap_or_default()), + gas_limit: U256::from(gas.unwrap_or_default()), + value: value.unwrap_or_default(), + input: data.into_input().unwrap_or_default(), + kind: to.unwrap_or(TxKind::Create), + chain_id: 0, + access_list, + })) + } + // EIP1559 + // if 4844 fields missing + // gas_price, max_fee_per_gas, access_list, max_fee_per_blob_gas, + // blob_versioned_hashes, sidecar, + (None, _, _, None, None, None) => { + // Empty fields fall back to the canonical transaction schema. + Some(TypedTransactionRequest::EIP1559(EIP1559TransactionRequest { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()), + max_priority_fee_per_gas: U256::from( + max_priority_fee_per_gas.unwrap_or_default(), + ), + gas_limit: U256::from(gas.unwrap_or_default()), + value: value.unwrap_or_default(), + input: data.into_input().unwrap_or_default(), + kind: to.unwrap_or(TxKind::Create), + chain_id: 0, + access_list: access_list.unwrap_or_default(), + })) + } + // EIP4884 + // all blob fields required + ( + None, + _, + _, + Some(max_fee_per_blob_gas), + Some(blob_versioned_hashes), + Some(sidecar), + ) => { + // As per the EIP, we follow the same semantics as EIP-1559. + Some(TypedTransactionRequest::EIP4844(EIP4844TransactionRequest { + chain_id: 0, + nonce: nonce.unwrap_or_default(), + max_priority_fee_per_gas: U256::from( + max_priority_fee_per_gas.unwrap_or_default(), + ), + max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(gas.unwrap_or_default()), + value: value.unwrap_or_default(), + input: data.into_input().unwrap_or_default(), + #[allow(clippy::manual_unwrap_or_default)] // clippy is suggesting here unwrap_or_default + to: match to { + Some(TxKind::Call(to)) => to, + _ => Address::default(), + }, + access_list: access_list.unwrap_or_default(), + + // eip-4844 specific. + max_fee_per_blob_gas: U256::from(max_fee_per_blob_gas), + blob_versioned_hashes, + sidecar, + })) + } + + _ => None, + }; + + let transaction = match transaction { + Some(TypedTransactionRequest::Legacy(mut req)) => { + req.chain_id = Some(chain_id.to()); + req.gas_limit = gas_limit.saturating_to(); + req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?; + + TypedTransactionRequest::Legacy(req) + } + Some(TypedTransactionRequest::EIP2930(mut req)) => { + req.chain_id = chain_id.to(); + req.gas_limit = gas_limit.saturating_to(); + req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?; + + TypedTransactionRequest::EIP2930(req) + } + Some(TypedTransactionRequest::EIP1559(mut req)) => { + let (max_fee_per_gas, max_priority_fee_per_gas) = self + .eip1559_fees( + max_fee_per_gas.map(U256::from), + max_priority_fee_per_gas.map(U256::from), + ) + .await?; + + req.chain_id = chain_id.to(); + req.gas_limit = gas_limit.saturating_to(); + req.max_fee_per_gas = max_fee_per_gas.saturating_to(); + req.max_priority_fee_per_gas = max_priority_fee_per_gas.saturating_to(); + + TypedTransactionRequest::EIP1559(req) + } + Some(TypedTransactionRequest::EIP4844(mut req)) => { + let (max_fee_per_gas, max_priority_fee_per_gas) = self + .eip1559_fees( + max_fee_per_gas.map(U256::from), + max_priority_fee_per_gas.map(U256::from), + ) + .await?; + + req.max_fee_per_gas = max_fee_per_gas; + req.max_priority_fee_per_gas = max_priority_fee_per_gas; + req.max_fee_per_blob_gas = + self.eip4844_blob_fee(max_fee_per_blob_gas.map(U256::from)).await?; + + req.chain_id = chain_id.to(); + req.gas_limit = gas_limit; + + TypedTransactionRequest::EIP4844(req) + } + None => return Err(EthApiError::ConflictingFeeFieldsInRequest), + }; + + let signed_tx = self.sign_request(&from, transaction)?; + + let recovered = + signed_tx.into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?; + + let pool_transaction = match recovered.try_into() { + Ok(converted) => <::Pool as TransactionPool>::Transaction::from_recovered_pooled_transaction(converted), + Err(_) => return Err(EthApiError::TransactionConversionError), + }; + + // submit the transaction to the pool with a `Local` origin + let hash = LoadTransaction::pool(self) + .add_transaction(TransactionOrigin::Local, pool_transaction) + .await?; + + Ok(hash) + } + } + + /// Signs a transaction, with configured signers. + fn sign_request( + &self, + from: &Address, + request: TypedTransactionRequest, + ) -> EthResult { + for signer in self.signers().read().iter() { + if signer.is_signer_for(from) { + return match signer.sign_transaction(request, from) { + Ok(tx) => Ok(tx), + Err(e) => Err(e.into()), + } + } + } + Err(EthApiError::InvalidTransactionSignature) + } + + /// Signs given message. Returns the signature. + fn sign( + &self, + account: Address, + message: Bytes, + ) -> impl Future> + Send { + async move { Ok(self.find_signer(&account)?.sign(account, &message).await?.to_hex_bytes()) } + } + + /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait. + fn sign_typed_data(&self, data: &TypedData, account: Address) -> EthResult { + Ok(self.find_signer(&account)?.sign_typed_data(account, data)?.to_hex_bytes()) + } + + /// Returns the signer for the given account, if found in configured signers. + fn find_signer(&self, account: &Address) -> Result, SignError> { + self.signers() + .read() + .iter() + .find(|signer| signer.is_signer_for(account)) + .map(|signer| dyn_clone::clone_box(&**signer)) + .ok_or(SignError::NoAccount) + } +} + +/// Loads a transaction from database. +/// +/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` transactions RPC +/// methods. +pub trait LoadTransaction: SpawnBlocking { + /// Transaction pool with pending transactions. [`TransactionPool::Transaction`] is the + /// supported transaction type. + type Pool: TransactionPool; + + /// Returns a handle for reading data from disk. + /// + /// Data access in default (L1) trait method implementations. + fn provider(&self) -> impl TransactionsProvider; + + /// Returns a handle for reading data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn cache(&self) -> &EthStateCache; + + /// Returns a handle for reading data from pool. + /// + /// Data access in default (L1) trait method implementations. + fn pool(&self) -> &Self::Pool; + + /// Returns the transaction by hash. + /// + /// Checks the pool and state. + /// + /// Returns `Ok(None)` if no matching transaction was found. + fn transaction_by_hash( + &self, + hash: B256, + ) -> impl Future>> + Send { + async move { + // Try to find the transaction on disk + let mut resp = self + .spawn_blocking_io(move |this| { + match this.provider().transaction_by_hash_with_meta(hash)? { + None => Ok(None), + Some((tx, meta)) => { + // Note: we assume this transaction is valid, because it's mined (or + // part of pending block) and already. We don't need to + // check for pre EIP-2 because this transaction could be pre-EIP-2. + let transaction = tx + .into_ecrecovered_unchecked() + .ok_or(EthApiError::InvalidTransactionSignature)?; + + let tx = TransactionSource::Block { + transaction, + index: meta.index, + block_hash: meta.block_hash, + block_number: meta.block_number, + base_fee: meta.base_fee, + }; + Ok(Some(tx)) + } + } + }) + .await?; + + if resp.is_none() { + // tx not found on disk, check pool + if let Some(tx) = + self.pool().get(&hash).map(|tx| tx.transaction.to_recovered_transaction()) + { + resp = Some(TransactionSource::Pool(tx)); + } + } + + Ok(resp) + } + } + + /// Returns the transaction by including its corresponding [`BlockId`]. + /// + /// Note: this supports pending transactions + fn transaction_by_hash_at( + &self, + transaction_hash: B256, + ) -> impl Future>> + Send { + async move { + match self.transaction_by_hash(transaction_hash).await? { + None => Ok(None), + Some(tx) => { + let res = match tx { + tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()), + TransactionSource::Block { + transaction, + index, + block_hash, + block_number, + base_fee, + } => { + let at = BlockId::Hash(block_hash.into()); + let tx = TransactionSource::Block { + transaction, + index, + block_hash, + block_number, + base_fee, + }; + (tx, at) + } + }; + Ok(Some(res)) + } + } + } + } + + /// Fetches the transaction and the transaction's block + fn transaction_and_block( + &self, + hash: B256, + ) -> impl Future>> + Send + { + async move { + let (transaction, at) = match self.transaction_by_hash_at(hash).await? { + None => return Ok(None), + Some(res) => res, + }; + + // Note: this is always either hash or pending + let block_hash = match at { + BlockId::Hash(hash) => hash.block_hash, + _ => return Ok(None), + }; + let block = self.cache().get_block_with_senders(block_hash).await?; + Ok(block.map(|block| (transaction, block.seal(block_hash)))) + } + } +} + +/// A trait that allows for forwarding raw transactions. +/// +/// For example to a sequencer. +#[async_trait::async_trait] +pub trait RawTransactionForwarder: fmt::Debug + Send + Sync + 'static { + /// Forwards raw transaction bytes for `eth_sendRawTransaction` + async fn forward_raw_transaction(&self, raw: &[u8]) -> EthResult<()>; +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs new file mode 100644 index 000000000000..1f4afa7a9b0e --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs @@ -0,0 +1,126 @@ +//! Contains RPC handler implementations specific to transactions + +use reth_provider::{BlockReaderIdExt, TransactionsProvider}; +use reth_transaction_pool::TransactionPool; + +use crate::{ + servers::{ + EthSigner, EthTransactions, LoadTransaction, RawTransactionForwarder, SpawnBlocking, + }, + EthApi, EthStateCache, +}; + +impl EthTransactions + for EthApi +where + Self: LoadTransaction, + Pool: TransactionPool + 'static, + Provider: BlockReaderIdExt, +{ + #[inline] + fn provider(&self) -> impl BlockReaderIdExt { + self.inner.provider() + } + + #[inline] + fn raw_tx_forwarder(&self) -> Option> { + self.inner.raw_tx_forwarder() + } + + #[inline] + fn signers(&self) -> &parking_lot::RwLock>> { + self.inner.signers() + } +} + +impl LoadTransaction + for EthApi +where + Self: SpawnBlocking, + Provider: TransactionsProvider, + Pool: TransactionPool, +{ + type Pool = Pool; + + #[inline] + fn provider(&self) -> impl reth_provider::TransactionsProvider { + self.inner.provider() + } + + #[inline] + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } + + #[inline] + fn pool(&self) -> &Self::Pool { + self.inner.pool() + } +} + +#[cfg(test)] +mod tests { + use reth_evm_ethereum::EthEvmConfig; + use reth_network_api::noop::NoopNetwork; + use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, hex_literal::hex, Bytes}; + use reth_provider::test_utils::NoopProvider; + use reth_tasks::pool::BlockingTaskPool; + use reth_transaction_pool::{test_utils::testing_pool, TransactionPool}; + + use crate::{ + servers::EthTransactions, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, + GasPriceOracle, + }; + + use super::*; + + #[tokio::test] + async fn send_raw_transaction() { + let noop_provider = NoopProvider::default(); + let noop_network_provider = NoopNetwork::default(); + + let pool = testing_pool(); + + let evm_config = EthEvmConfig::default(); + let cache = EthStateCache::spawn(noop_provider, Default::default(), evm_config); + let fee_history_cache = + FeeHistoryCache::new(cache.clone(), FeeHistoryCacheConfig::default()); + let eth_api = EthApi::new( + noop_provider, + pool.clone(), + noop_network_provider, + cache.clone(), + GasPriceOracle::new(noop_provider, Default::default(), cache.clone()), + ETHEREUM_BLOCK_GAS_LIMIT, + BlockingTaskPool::build().expect("failed to build tracing pool"), + fee_history_cache, + evm_config, + None, + ); + + // https://etherscan.io/tx/0xa694b71e6c128a2ed8e2e0f6770bddbe52e3bb8f10e8472f9a79ab81497a8b5d + let tx_1 = Bytes::from(hex!("02f871018303579880850555633d1b82520894eee27662c2b8eba3cd936a23f039f3189633e4c887ad591c62bdaeb180c080a07ea72c68abfb8fca1bd964f0f99132ed9280261bdca3e549546c0205e800f7d0a05b4ef3039e9c9b9babc179a1878fb825b5aaf5aed2fa8744854150157b08d6f3")); + + let tx_1_result = eth_api.send_raw_transaction(tx_1).await.unwrap(); + assert_eq!( + pool.len(), + 1, + "expect 1 transactions in the pool, but pool size is {}", + pool.len() + ); + + // https://etherscan.io/tx/0x48816c2f32c29d152b0d86ff706f39869e6c1f01dc2fe59a3c1f9ecf39384694 + let tx_2 = Bytes::from(hex!("02f9043c018202b7843b9aca00850c807d37a08304d21d94ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b881bc16d674ec80000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000063e2d99f00000000000000000000000000000000000000000000000000000000000000030b000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000065717fe021ea67801d1088cc80099004b05b64600000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e95fd5965fd1f1a6f0d4600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000428dca9537116148616a5a3e44035af17238fe9dc080a0c6ec1e41f5c0b9511c49b171ad4e04c6bb419c74d99fe9891d74126ec6e4e879a032069a753d7a2cfa158df95421724d24c0e9501593c09905abf3699b4a4405ce")); + + let tx_2_result = eth_api.send_raw_transaction(tx_2).await.unwrap(); + assert_eq!( + pool.len(), + 2, + "expect 2 transactions in the pool, but pool size is {}", + pool.len() + ); + + assert!(pool.get(&tx_1_result).is_some(), "tx1 not found in the pool"); + assert!(pool.get(&tx_2_result).is_some(), "tx2 not found in the pool"); + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/mod.rs b/crates/rpc/rpc-eth-api/src/api/servers/mod.rs new file mode 100644 index 000000000000..5be35304a2e4 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/mod.rs @@ -0,0 +1,323 @@ +//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`](crate::EthApi) trait +//! Handles RPC requests for the `eth_` namespace. + +use std::sync::Arc; + +use reth_primitives::{BlockNumberOrTag, U256}; +use reth_provider::{BlockReaderIdExt, ChainSpecProvider}; + +pub mod bundle; +pub mod filter; +pub mod helpers; +pub mod pubsub; + +mod server; + +pub use helpers::{ + signer::DevSigner, + traits::{ + block::{EthBlocks, LoadBlock}, + blocking_task::SpawnBlocking, + call::{Call, EthCall}, + fee::{EthFees, LoadFee}, + pending_block::LoadPendingBlock, + receipt::LoadReceipt, + signer::EthSigner, + spec::EthApiSpec, + state::{EthState, LoadState}, + trace::Trace, + transaction::{EthTransactions, LoadTransaction, RawTransactionForwarder}, + TraceExt, + }, +}; +use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor}; +use tokio::sync::Mutex; + +use crate::{EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock}; + +/// `Eth` API implementation. +/// +/// This type provides the functionality for handling `eth_` related requests. +/// These are implemented two-fold: Core functionality is implemented as [`EthApiSpec`] +/// trait. Additionally, the required server implementations (e.g. +/// [`EthApiServer`](crate::EthApiServer)) are implemented separately in submodules. The rpc handler +/// implementation can then delegate to the main impls. This way [`EthApi`] is not limited to +/// [`jsonrpsee`] and can be used standalone or in other network handlers (for example ipc). +pub struct EthApi { + /// All nested fields bundled together. + inner: Arc>, +} + +impl EthApi { + /// Sets a forwarder for `eth_sendRawTransaction` + /// + /// Note: this might be removed in the future in favor of a more generic approach. + pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { + self.inner.raw_transaction_forwarder.write().replace(forwarder); + } +} + +impl EthApi +where + Provider: BlockReaderIdExt + ChainSpecProvider, +{ + /// Creates a new, shareable instance using the default tokio task spawner. + #[allow(clippy::too_many_arguments)] + pub fn new( + provider: Provider, + pool: Pool, + network: Network, + eth_cache: EthStateCache, + gas_oracle: GasPriceOracle, + gas_cap: impl Into, + blocking_task_pool: BlockingTaskPool, + fee_history_cache: FeeHistoryCache, + evm_config: EvmConfig, + raw_transaction_forwarder: Option>, + ) -> Self { + Self::with_spawner( + provider, + pool, + network, + eth_cache, + gas_oracle, + gas_cap.into().into(), + Box::::default(), + blocking_task_pool, + fee_history_cache, + evm_config, + raw_transaction_forwarder, + ) + } + + /// Creates a new, shareable instance. + #[allow(clippy::too_many_arguments)] + pub fn with_spawner( + provider: Provider, + pool: Pool, + network: Network, + eth_cache: EthStateCache, + gas_oracle: GasPriceOracle, + gas_cap: u64, + task_spawner: Box, + blocking_task_pool: BlockingTaskPool, + fee_history_cache: FeeHistoryCache, + evm_config: EvmConfig, + raw_transaction_forwarder: Option>, + ) -> Self { + // get the block number of the latest block + let latest_block = provider + .header_by_number_or_tag(BlockNumberOrTag::Latest) + .ok() + .flatten() + .map(|header| header.number) + .unwrap_or_default(); + + let inner = EthApiInner { + provider, + pool, + network, + signers: parking_lot::RwLock::new(Default::default()), + eth_cache, + gas_oracle, + gas_cap, + starting_block: U256::from(latest_block), + task_spawner, + pending_block: Default::default(), + blocking_task_pool, + fee_history_cache, + evm_config, + raw_transaction_forwarder: parking_lot::RwLock::new(raw_transaction_forwarder), + }; + + Self { inner: Arc::new(inner) } + } + + /// Returns the state cache frontend + pub fn cache(&self) -> &EthStateCache { + &self.inner.eth_cache + } + + /// Returns the gas oracle frontend + pub fn gas_oracle(&self) -> &GasPriceOracle { + &self.inner.gas_oracle + } + + /// Returns the configured gas limit cap for `eth_call` and tracing related calls + pub fn gas_cap(&self) -> u64 { + self.inner.gas_cap + } + + /// Returns the inner `Provider` + pub fn provider(&self) -> &Provider { + &self.inner.provider + } + + /// Returns the inner `Network` + pub fn network(&self) -> &Network { + &self.inner.network + } + + /// Returns the inner `Pool` + pub fn pool(&self) -> &Pool { + &self.inner.pool + } + + /// Returns fee history cache + pub fn fee_history_cache(&self) -> &FeeHistoryCache { + &self.inner.fee_history_cache + } +} + +impl std::fmt::Debug + for EthApi +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EthApi").finish_non_exhaustive() + } +} + +impl Clone for EthApi { + fn clone(&self) -> Self { + Self { inner: Arc::clone(&self.inner) } + } +} + +/// Implements [`SpawnBlocking`] for a type, that has similar data layout to [`EthApi`]. +#[macro_export] +macro_rules! spawn_blocking_impl { + ($network_api:ty) => { + impl $crate::servers::SpawnBlocking for $network_api + where + Self: Clone + Send + Sync + 'static, + { + #[inline] + fn io_task_spawner(&self) -> impl reth_tasks::TaskSpawner { + self.inner.task_spawner() + } + + #[inline] + fn tracing_task_pool(&self) -> &reth_tasks::pool::BlockingTaskPool { + self.inner.blocking_task_pool() + } + } + }; +} + +spawn_blocking_impl!(EthApi); + +impl EthApi { + /// Generates 20 random developer accounts. + /// Used in DEV mode. + pub fn with_dev_accounts(&self) { + let mut signers = self.inner.signers.write(); + *signers = DevSigner::random_signers(20); + } +} + +/// Container type `EthApi` +#[allow(missing_debug_implementations)] +pub struct EthApiInner { + /// The transaction pool. + pool: Pool, + /// The provider that can interact with the chain. + provider: Provider, + /// An interface to interact with the network + network: Network, + /// All configured Signers + signers: parking_lot::RwLock>>, + /// The async cache frontend for eth related data + eth_cache: EthStateCache, + /// The async gas oracle frontend for gas price suggestions + gas_oracle: GasPriceOracle, + /// Maximum gas limit for `eth_call` and call tracing RPC methods. + gas_cap: u64, + /// The block number at which the node started + starting_block: U256, + /// The type that can spawn tasks which would otherwise block. + task_spawner: Box, + /// Cached pending block if any + pending_block: Mutex>, + /// A pool dedicated to CPU heavy blocking tasks. + blocking_task_pool: BlockingTaskPool, + /// Cache for block fees history + fee_history_cache: FeeHistoryCache, + /// The type that defines how to configure the EVM + evm_config: EvmConfig, + /// Allows forwarding received raw transactions + raw_transaction_forwarder: parking_lot::RwLock>>, +} + +impl EthApiInner { + /// Returns a handle to data on disk. + #[inline] + pub const fn provider(&self) -> &Provider { + &self.provider + } + + /// Returns a handle to data in memory. + #[inline] + pub const fn cache(&self) -> &EthStateCache { + &self.eth_cache + } + + /// Returns a handle to the pending block. + #[inline] + pub const fn pending_block(&self) -> &Mutex> { + &self.pending_block + } + + /// Returns a handle to the task spawner. + #[inline] + pub const fn task_spawner(&self) -> &dyn TaskSpawner { + &*self.task_spawner + } + + /// Returns a handle to the blocking thread pool. + #[inline] + pub const fn blocking_task_pool(&self) -> &BlockingTaskPool { + &self.blocking_task_pool + } + + /// Returns a handle to the EVM config. + #[inline] + pub const fn evm_config(&self) -> &EvmConfig { + &self.evm_config + } + + /// Returns a handle to the transaction pool. + #[inline] + pub const fn pool(&self) -> &Pool { + &self.pool + } + + /// Returns a handle to the transaction forwarder. + #[inline] + pub fn raw_tx_forwarder(&self) -> Option> { + self.raw_transaction_forwarder.read().clone() + } + + /// Returns the gas cap. + #[inline] + pub const fn gas_cap(&self) -> u64 { + self.gas_cap + } + + /// Returns a handle to the gas oracle. + #[inline] + pub const fn gas_oracle(&self) -> &GasPriceOracle { + &self.gas_oracle + } + + /// Returns a handle to the fee history cache. + #[inline] + pub const fn fee_history_cache(&self) -> &FeeHistoryCache { + &self.fee_history_cache + } + + /// Returns a handle to the signers. + #[inline] + pub const fn signers(&self) -> &parking_lot::RwLock>> { + &self.signers + } +} diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs similarity index 98% rename from crates/rpc/rpc/src/eth/pubsub.rs rename to crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs index fdfa836b91ff..a0e886f6d947 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs @@ -1,9 +1,7 @@ //! `eth_` `PubSub` RPC handler implementation -use crate::{ - eth::logs_utils, - result::{internal_rpc_err, invalid_params_rpc_err}, -}; +use std::sync::Arc; + use futures::StreamExt; use jsonrpsee::{ server::SubscriptionMessage, types::ErrorObject, PendingSubscriptionSink, SubscriptionSink, @@ -11,7 +9,6 @@ use jsonrpsee::{ use reth_network_api::NetworkInfo; use reth_primitives::{IntoRecoveredTransaction, TxHash}; use reth_provider::{BlockReader, CanonStateSubscriptions, EvmEnvProvider}; -use reth_rpc_api::EthPubSubApiServer; use reth_rpc_types::{ pubsub::{ Params, PubSubSyncStatus, SubscriptionKind, SubscriptionResult as EthSubscriptionResult, @@ -22,12 +19,17 @@ use reth_rpc_types::{ use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{NewTransactionEvent, TransactionPool}; use serde::Serialize; -use std::sync::Arc; use tokio_stream::{ wrappers::{BroadcastStream, ReceiverStream}, Stream, }; +use crate::{ + logs_utils, + result::{internal_rpc_err, invalid_params_rpc_err}, + EthPubSubApiServer, +}; + /// `Eth` pubsub RPC implementation. /// /// This handles `eth_subscribe` RPC calls. @@ -197,10 +199,10 @@ where /// Helper to convert a serde error into an [`ErrorObject`] #[derive(Debug, thiserror::Error)] #[error("Failed to serialize subscription item: {0}")] -pub(crate) struct SubscriptionSerializeError(#[from] serde_json::Error); +pub struct SubscriptionSerializeError(#[from] serde_json::Error); impl SubscriptionSerializeError { - pub(crate) const fn new(err: serde_json::Error) -> Self { + const fn new(err: serde_json::Error) -> Self { Self(err) } } diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc-eth-api/src/api/servers/server.rs similarity index 85% rename from crates/rpc/rpc/src/eth/api/server.rs rename to crates/rpc/rpc-eth-api/src/api/servers/server.rs index f238b4da079a..0701ff70db48 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/server.rs @@ -1,49 +1,37 @@ -//! Implementation of the [`jsonrpsee`] generated [`reth_rpc_api::EthApiServer`] trait -//! Handles RPC requests for the `eth_` namespace. +//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`] trait. Handles RPC requests for +//! the `eth_` namespace. -use super::EthApiSpec; -use crate::{ - eth::{ - api::{EthApi, EthTransactions}, - error::EthApiError, - }, - result::{internal_rpc_err, ToRpcResult}, -}; use alloy_dyn_abi::TypedData; use jsonrpsee::core::RpcResult as Result; -use reth_evm::ConfigureEvm; -use reth_network_api::NetworkInfo; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; -use reth_provider::{ - BlockIdReader, BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, - HeaderProvider, StateProviderFactory, -}; -use reth_rpc_api::EthApiServer; use reth_rpc_types::{ serde_helpers::JsonStorageKey, state::{EvmOverrides, StateOverride}, AccessListWithGasUsed, AnyTransactionReceipt, BlockOverrides, Bundle, EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, Index, RichBlock, - StateContext, SyncStatus, TransactionRequest, Work, + StateContext, SyncStatus, Transaction, TransactionRequest, Work, }; -use reth_transaction_pool::TransactionPool; use tracing::trace; +use crate::{ + result::internal_rpc_err, + servers::{ + EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadReceipt, Trace, + }, + EthApiError, EthApiServer, ToRpcResult, +}; + #[async_trait::async_trait] -impl EthApiServer for EthApi +impl EthApiServer for T where - Self: EthApiSpec + EthTransactions, - Pool: TransactionPool + 'static, - Provider: BlockReader - + BlockIdReader - + BlockReaderIdExt - + ChainSpecProvider - + HeaderProvider - + StateProviderFactory - + EvmEnvProvider - + 'static, - Network: NetworkInfo + Send + Sync + 'static, - EvmConfig: ConfigureEvm + 'static, + Self: EthApiSpec + + EthTransactions + + EthBlocks + + EthState + + EthCall + + EthFees + + Trace + + LoadReceipt, { /// Handler for: `eth_protocolVersion` async fn protocol_version(&self) -> Result { @@ -85,7 +73,7 @@ where /// Handler for: `eth_getBlockByHash` async fn block_by_hash(&self, hash: B256, full: bool) -> Result> { trace!(target: "rpc::eth", ?hash, ?full, "Serving eth_getBlockByHash"); - Ok(Self::rpc_block(self, hash, full).await?) + Ok(EthBlocks::rpc_block(self, hash.into(), full).await?) } /// Handler for: `eth_getBlockByNumber` @@ -95,13 +83,13 @@ where full: bool, ) -> Result> { trace!(target: "rpc::eth", ?number, ?full, "Serving eth_getBlockByNumber"); - Ok(Self::rpc_block(self, number, full).await?) + Ok(EthBlocks::rpc_block(self, number.into(), full).await?) } /// Handler for: `eth_getBlockTransactionCountByHash` async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { trace!(target: "rpc::eth", ?hash, "Serving eth_getBlockTransactionCountByHash"); - Ok(Self::block_transaction_count(self, hash).await?.map(U256::from)) + Ok(EthBlocks::block_transaction_count(self, hash).await?.map(U256::from)) } /// Handler for: `eth_getBlockTransactionCountByNumber` @@ -110,19 +98,19 @@ where number: BlockNumberOrTag, ) -> Result> { trace!(target: "rpc::eth", ?number, "Serving eth_getBlockTransactionCountByNumber"); - Ok(Self::block_transaction_count(self, number).await?.map(U256::from)) + Ok(EthBlocks::block_transaction_count(self, number).await?.map(U256::from)) } /// Handler for: `eth_getUncleCountByBlockHash` async fn block_uncles_count_by_hash(&self, hash: B256) -> Result> { trace!(target: "rpc::eth", ?hash, "Serving eth_getUncleCountByBlockHash"); - Ok(Self::ommers(self, hash)?.map(|ommers| U256::from(ommers.len()))) + Ok(EthBlocks::ommers(self, hash)?.map(|ommers| U256::from(ommers.len()))) } /// Handler for: `eth_getUncleCountByBlockNumber` async fn block_uncles_count_by_number(&self, number: BlockNumberOrTag) -> Result> { trace!(target: "rpc::eth", ?number, "Serving eth_getUncleCountByBlockNumber"); - Ok(Self::ommers(self, number)?.map(|ommers| U256::from(ommers.len()))) + Ok(EthBlocks::ommers(self, number)?.map(|ommers| U256::from(ommers.len()))) } /// Handler for: `eth_getBlockReceipts` @@ -131,7 +119,7 @@ where block_id: BlockId, ) -> Result>> { trace!(target: "rpc::eth", ?block_id, "Serving eth_getBlockReceipts"); - Ok(Self::block_receipts(self, block_id).await?) + Ok(EthBlocks::block_receipts(self, block_id).await?) } /// Handler for: `eth_getUncleByBlockHashAndIndex` @@ -141,7 +129,7 @@ where index: Index, ) -> Result> { trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getUncleByBlockHashAndIndex"); - Ok(Self::ommer_by_block_and_index(self, hash, index).await?) + Ok(EthBlocks::ommer_by_block_and_index(self, hash, index).await?) } /// Handler for: `eth_getUncleByBlockNumberAndIndex` @@ -151,7 +139,7 @@ where index: Index, ) -> Result> { trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getUncleByBlockNumberAndIndex"); - Ok(Self::ommer_by_block_and_index(self, number, index).await?) + Ok(EthBlocks::ommer_by_block_and_index(self, number, index).await?) } /// Handler for: `eth_getRawTransactionByHash` @@ -161,7 +149,7 @@ where } /// Handler for: `eth_getTransactionByHash` - async fn transaction_by_hash(&self, hash: B256) -> Result> { + async fn transaction_by_hash(&self, hash: B256) -> Result> { trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionByHash"); Ok(EthTransactions::transaction_by_hash(self, hash).await?.map(Into::into)) } @@ -173,7 +161,7 @@ where index: Index, ) -> Result> { trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getRawTransactionByBlockHashAndIndex"); - Ok(Self::raw_transaction_by_block_and_tx_index(self, hash, index).await?) + Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, hash.into(), index).await?) } /// Handler for: `eth_getTransactionByBlockHashAndIndex` @@ -183,7 +171,7 @@ where index: Index, ) -> Result> { trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getTransactionByBlockHashAndIndex"); - Ok(Self::transaction_by_block_and_tx_index(self, hash, index).await?) + Ok(EthTransactions::transaction_by_block_and_tx_index(self, hash.into(), index).await?) } /// Handler for: `eth_getRawTransactionByBlockNumberAndIndex` @@ -193,7 +181,8 @@ where index: Index, ) -> Result> { trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getRawTransactionByBlockNumberAndIndex"); - Ok(Self::raw_transaction_by_block_and_tx_index(self, number, index).await?) + Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, number.into(), index) + .await?) } /// Handler for: `eth_getTransactionByBlockNumberAndIndex` @@ -203,7 +192,7 @@ where index: Index, ) -> Result> { trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getTransactionByBlockNumberAndIndex"); - Ok(Self::transaction_by_block_and_tx_index(self, number, index).await?) + Ok(EthTransactions::transaction_by_block_and_tx_index(self, number.into(), index).await?) } /// Handler for: `eth_getTransactionReceipt` @@ -215,7 +204,7 @@ where /// Handler for: `eth_getBalance` async fn balance(&self, address: Address, block_number: Option) -> Result { trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getBalance"); - Ok(self.on_blocking_task(|this| async move { this.balance(address, block_number) }).await?) + Ok(EthState::balance(self, address, block_number).await?) } /// Handler for: `eth_getStorageAt` @@ -226,9 +215,8 @@ where block_number: Option, ) -> Result { trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getStorageAt"); - Ok(self - .on_blocking_task(|this| async move { this.storage_at(address, index, block_number) }) - .await?) + let res: B256 = EthState::storage_at(self, address, index, block_number).await?; + Ok(res) } /// Handler for: `eth_getTransactionCount` @@ -238,31 +226,25 @@ where block_number: Option, ) -> Result { trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getTransactionCount"); - Ok(self - .on_blocking_task( - |this| async move { this.get_transaction_count(address, block_number) }, - ) - .await?) + Ok(EthState::transaction_count(self, address, block_number).await?) } /// Handler for: `eth_getCode` async fn get_code(&self, address: Address, block_number: Option) -> Result { trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getCode"); - Ok(self - .on_blocking_task(|this| async move { this.get_code(address, block_number) }) - .await?) + Ok(EthState::get_code(self, address, block_number).await?) } /// Handler for: `eth_getHeaderByNumber` async fn header_by_number(&self, block_number: BlockNumberOrTag) -> Result> { trace!(target: "rpc::eth", ?block_number, "Serving eth_getHeaderByNumber"); - Ok(Self::rpc_block_header(self, block_number).await?) + Ok(EthBlocks::rpc_block_header(self, block_number.into()).await?) } /// Handler for: `eth_getHeaderByHash` async fn header_by_hash(&self, hash: B256) -> Result> { trace!(target: "rpc::eth", ?hash, "Serving eth_getHeaderByHash"); - Ok(Self::rpc_block_header(self, hash).await?) + Ok(EthBlocks::rpc_block_header(self, hash.into()).await?) } /// Handler for: `eth_call` @@ -274,9 +256,13 @@ where block_overrides: Option>, ) -> Result { trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, ?block_overrides, "Serving eth_call"); - Ok(self - .call(request, block_number, EvmOverrides::new(state_overrides, block_overrides)) - .await?) + Ok(EthCall::call( + self, + request, + block_number, + EvmOverrides::new(state_overrides, block_overrides), + ) + .await?) } /// Handler for: `eth_callMany` @@ -287,7 +273,7 @@ where state_override: Option, ) -> Result> { trace!(target: "rpc::eth", ?bundle, ?state_context, ?state_override, "Serving eth_callMany"); - Ok(Self::call_many(self, bundle, state_context, state_override).await?) + Ok(EthCall::call_many(self, bundle, state_context, state_override).await?) } /// Handler for: `eth_createAccessList` @@ -297,7 +283,8 @@ where block_number: Option, ) -> Result { trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_createAccessList"); - let access_list_with_gas_used = self.create_access_list_at(request, block_number).await?; + let access_list_with_gas_used = + EthCall::create_access_list_at(self, request, block_number).await?; Ok(access_list_with_gas_used) } @@ -310,25 +297,31 @@ where state_override: Option, ) -> Result { trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_estimateGas"); - Ok(self.estimate_gas_at(request, block_number.unwrap_or_default(), state_override).await?) + Ok(EthCall::estimate_gas_at( + self, + request, + block_number.unwrap_or_default(), + state_override, + ) + .await?) } /// Handler for: `eth_gasPrice` async fn gas_price(&self) -> Result { trace!(target: "rpc::eth", "Serving eth_gasPrice"); - return Ok(Self::gas_price(self).await?) + return Ok(EthFees::gas_price(self).await?) } /// Handler for: `eth_maxPriorityFeePerGas` async fn max_priority_fee_per_gas(&self) -> Result { trace!(target: "rpc::eth", "Serving eth_maxPriorityFeePerGas"); - return Ok(Self::suggested_priority_fee(self).await?) + return Ok(EthFees::suggested_priority_fee(self).await?) } /// Handler for: `eth_blobBaseFee` async fn blob_base_fee(&self) -> Result { trace!(target: "rpc::eth", "Serving eth_blobBaseFee"); - return Ok(Self::blob_base_fee(self).await?) + return Ok(EthFees::blob_base_fee(self).await?) } // FeeHistory is calculated based on lazy evaluation of fees for historical blocks, and further @@ -347,7 +340,9 @@ where reward_percentiles: Option>, ) -> Result { trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory"); - return Ok(Self::fee_history(self, block_count.to(), newest_block, reward_percentiles).await?) + return Ok( + EthFees::fee_history(self, block_count.to(), newest_block, reward_percentiles).await? + ) } /// Handler for: `eth_mining` @@ -390,7 +385,7 @@ where /// Handler for: `eth_sign` async fn sign(&self, address: Address, message: Bytes) -> Result { trace!(target: "rpc::eth", ?address, ?message, "Serving eth_sign"); - Ok(Self::sign(self, address, &message).await?) + Ok(EthTransactions::sign(self, address, message).await?) } /// Handler for: `eth_signTransaction` @@ -401,7 +396,7 @@ where /// Handler for: `eth_signTypedData` async fn sign_typed_data(&self, address: Address, data: TypedData) -> Result { trace!(target: "rpc::eth", ?address, ?data, "Serving eth_signTypedData"); - Ok(Self::sign_typed_data(self, &data, address)?) + Ok(EthTransactions::sign_typed_data(self, &data, address)?) } /// Handler for: `eth_getProof` @@ -412,7 +407,7 @@ where block_number: Option, ) -> Result { trace!(target: "rpc::eth", ?address, ?keys, ?block_number, "Serving eth_getProof"); - let res = Self::get_proof(self, address, keys, block_number).await; + let res = EthState::get_proof(self, address, keys, block_number)?.await; Ok(res.map_err(|e| match e { EthApiError::InvalidBlockRange => { @@ -425,13 +420,6 @@ where #[cfg(test)] mod tests { - use crate::{ - eth::{ - cache::EthStateCache, gas_oracle::GasPriceOracle, FeeHistoryCache, - FeeHistoryCacheConfig, - }, - EthApi, - }; use jsonrpsee::types::error::INVALID_PARAMS_CODE; use reth_chainspec::BaseFeeParams; use reth_evm_ethereum::EthEvmConfig; @@ -444,12 +432,15 @@ mod tests { test_utils::{MockEthProvider, NoopProvider}, BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory, }; - use reth_rpc_api::EthApiServer; use reth_rpc_types::FeeHistory; use reth_tasks::pool::BlockingTaskPool; use reth_testing_utils::{generators, generators::Rng}; use reth_transaction_pool::test_utils::{testing_pool, TestPool}; + use crate::{ + EthApi, EthApiServer, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, + }; + fn build_test_eth_api< P: BlockReaderIdExt + BlockReader @@ -661,7 +652,8 @@ mod tests { let (eth_api, base_fees_per_gas, gas_used_ratios) = prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - let fee_history = eth_api.fee_history(1, newest_block.into(), None).await.unwrap(); + let fee_history = + eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap(); assert_eq!( fee_history.base_fee_per_gas, &base_fees_per_gas[base_fees_per_gas.len() - 2..], @@ -695,7 +687,7 @@ mod tests { prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); let fee_history = - eth_api.fee_history(block_count, newest_block.into(), None).await.unwrap(); + eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap(); assert_eq!( &fee_history.base_fee_per_gas, &base_fees_per_gas, diff --git a/crates/rpc/rpc/src/eth/cache/config.rs b/crates/rpc/rpc-eth-api/src/cache/config.rs similarity index 91% rename from crates/rpc/rpc/src/eth/cache/config.rs rename to crates/rpc/rpc-eth-api/src/cache/config.rs index 5dc989e8e637..93207c930f5c 100644 --- a/crates/rpc/rpc/src/eth/cache/config.rs +++ b/crates/rpc/rpc-eth-api/src/cache/config.rs @@ -1,7 +1,9 @@ +//! Configuration for RPC cache. + use reth_rpc_server_types::constants::cache::*; use serde::{Deserialize, Serialize}; -/// Settings for the [`EthStateCache`](crate::eth::cache::EthStateCache). +/// Settings for the [`EthStateCache`](crate::EthStateCache). #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EthStateCacheConfig { diff --git a/crates/rpc/rpc-eth-api/src/cache/db.rs b/crates/rpc/rpc-eth-api/src/cache/db.rs new file mode 100644 index 000000000000..74b20defbc60 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/cache/db.rs @@ -0,0 +1,169 @@ +//! Helper types to workaround 'higher-ranked lifetime error' +//! in default implementation of +//! [`Call`](crate::servers::Call). + +use reth_primitives::{B256, U256}; +use reth_provider::StateProvider; +use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef}; +use revm::Database; + +/// Helper alias type for the state's [`CacheDB`] +pub type StateCacheDb<'a> = CacheDB>>; + +/// Hack to get around 'higher-ranked lifetime error', see +/// +#[allow(missing_debug_implementations)] +pub struct StateProviderTraitObjWrapper<'a>(pub &'a dyn StateProvider); + +impl<'a> reth_provider::StateRootProvider for StateProviderTraitObjWrapper<'a> { + fn state_root( + &self, + bundle_state: &revm::db::BundleState, + ) -> reth_errors::ProviderResult { + self.0.state_root(bundle_state) + } + + fn state_root_with_updates( + &self, + bundle_state: &revm::db::BundleState, + ) -> reth_errors::ProviderResult<(B256, reth_trie::updates::TrieUpdates)> { + self.0.state_root_with_updates(bundle_state) + } +} + +impl<'a> reth_provider::AccountReader for StateProviderTraitObjWrapper<'a> { + fn basic_account( + &self, + address: revm_primitives::Address, + ) -> reth_errors::ProviderResult> { + self.0.basic_account(address) + } +} + +impl<'a> reth_provider::BlockHashReader for StateProviderTraitObjWrapper<'a> { + fn block_hash( + &self, + block_number: reth_primitives::BlockNumber, + ) -> reth_errors::ProviderResult> { + self.0.block_hash(block_number) + } + + fn canonical_hashes_range( + &self, + start: reth_primitives::BlockNumber, + end: reth_primitives::BlockNumber, + ) -> reth_errors::ProviderResult> { + self.0.canonical_hashes_range(start, end) + } + + fn convert_block_hash( + &self, + hash_or_number: reth_rpc_types::BlockHashOrNumber, + ) -> reth_errors::ProviderResult> { + self.0.convert_block_hash(hash_or_number) + } +} + +impl<'a> StateProvider for StateProviderTraitObjWrapper<'a> { + fn account_balance( + &self, + addr: revm_primitives::Address, + ) -> reth_errors::ProviderResult> { + self.0.account_balance(addr) + } + + fn account_code( + &self, + addr: revm_primitives::Address, + ) -> reth_errors::ProviderResult> { + self.0.account_code(addr) + } + + fn account_nonce( + &self, + addr: revm_primitives::Address, + ) -> reth_errors::ProviderResult> { + self.0.account_nonce(addr) + } + + fn bytecode_by_hash( + &self, + code_hash: B256, + ) -> reth_errors::ProviderResult> { + self.0.bytecode_by_hash(code_hash) + } + + fn proof( + &self, + address: revm_primitives::Address, + keys: &[B256], + ) -> reth_errors::ProviderResult { + self.0.proof(address, keys) + } + + fn storage( + &self, + account: revm_primitives::Address, + storage_key: reth_primitives::StorageKey, + ) -> reth_errors::ProviderResult> { + self.0.storage(account, storage_key) + } +} + +/// Hack to get around 'higher-ranked lifetime error', see +/// +#[allow(missing_debug_implementations)] +pub struct StateCacheDbRefMutWrapper<'a, 'b>(pub &'b mut StateCacheDb<'a>); + +impl<'a, 'b> Database for StateCacheDbRefMutWrapper<'a, 'b> { + type Error = as Database>::Error; + fn basic( + &mut self, + address: revm_primitives::Address, + ) -> Result, Self::Error> { + self.0.basic(address) + } + + fn block_hash(&mut self, number: U256) -> Result { + self.0.block_hash(number) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + self.0.code_by_hash(code_hash) + } + + fn storage( + &mut self, + address: revm_primitives::Address, + index: U256, + ) -> Result { + self.0.storage(address, index) + } +} + +impl<'a, 'b> DatabaseRef for StateCacheDbRefMutWrapper<'a, 'b> { + type Error = as Database>::Error; + + fn basic_ref( + &self, + address: revm_primitives::Address, + ) -> Result, Self::Error> { + self.0.basic_ref(address) + } + + fn block_hash_ref(&self, number: U256) -> Result { + self.0.block_hash_ref(number) + } + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + self.0.code_by_hash_ref(code_hash) + } + + fn storage_ref( + &self, + address: revm_primitives::Address, + index: U256, + ) -> Result { + self.0.storage_ref(address, index) + } +} diff --git a/crates/rpc/rpc/src/eth/cache/metrics.rs b/crates/rpc/rpc-eth-api/src/cache/metrics.rs similarity index 93% rename from crates/rpc/rpc/src/eth/cache/metrics.rs rename to crates/rpc/rpc-eth-api/src/cache/metrics.rs index c9b18a299da3..d87a35e03170 100644 --- a/crates/rpc/rpc/src/eth/cache/metrics.rs +++ b/crates/rpc/rpc-eth-api/src/cache/metrics.rs @@ -1,3 +1,5 @@ +//! Tracks state of RPC cache. + use metrics::Counter; use reth_metrics::{metrics::Gauge, Metrics}; diff --git a/crates/rpc/rpc/src/eth/cache/mod.rs b/crates/rpc/rpc-eth-api/src/cache/mod.rs similarity index 99% rename from crates/rpc/rpc/src/eth/cache/mod.rs rename to crates/rpc/rpc-eth-api/src/cache/mod.rs index cfbe68311ee3..92e96806bc9b 100644 --- a/crates/rpc/rpc/src/eth/cache/mod.rs +++ b/crates/rpc/rpc-eth-api/src/cache/mod.rs @@ -26,13 +26,12 @@ use tokio::sync::{ }; use tokio_stream::wrappers::UnboundedReceiverStream; -mod config; -pub use config::*; +use crate::{EthStateCacheConfig, MultiConsumerLruCache}; -mod metrics; - -mod multi_consumer; -pub use multi_consumer::MultiConsumerLruCache; +pub mod config; +pub mod db; +pub mod metrics; +pub mod multi_consumer; /// The type that can send the response to a requested [Block] type BlockTransactionsResponseSender = @@ -107,7 +106,7 @@ impl EthStateCache { ) -> Self where Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, - EvmConfig: ConfigureEvm + 'static, + EvmConfig: ConfigureEvm, { Self::spawn_with(provider, config, TokioTaskExecutor::default(), evm_config) } @@ -125,7 +124,7 @@ impl EthStateCache { where Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, - EvmConfig: ConfigureEvm + 'static, + EvmConfig: ConfigureEvm, { let EthStateCacheConfig { max_blocks, max_receipts, max_envs, max_concurrent_db_requests } = config; @@ -316,7 +315,7 @@ impl EthStateCacheService>) { if let Some(queued) = self.full_block_cache.remove(&block_hash) { @@ -403,7 +402,7 @@ impl Future for EthStateCacheService, { - /// The LRU cache for the + /// The LRU cache. cache: LruMap, - /// All queued consumers + /// All queued consumers. queued: HashMap>, /// Cache metrics metrics: CacheMetrics, diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc-eth-api/src/error.rs similarity index 100% rename from crates/rpc/rpc/src/eth/error.rs rename to crates/rpc/rpc-eth-api/src/error.rs diff --git a/crates/rpc/rpc/src/eth/api/fee_history.rs b/crates/rpc/rpc-eth-api/src/fee_history.rs similarity index 99% rename from crates/rpc/rpc/src/eth/api/fee_history.rs rename to crates/rpc/rpc-eth-api/src/fee_history.rs index 626c670376c8..10c06eab06c6 100644 --- a/crates/rpc/rpc/src/eth/api/fee_history.rs +++ b/crates/rpc/rpc-eth-api/src/fee_history.rs @@ -1,6 +1,11 @@ //! Consist of types adjacent to the fee history cache and its configs -use crate::eth::{cache::EthStateCache, error::EthApiError}; +use std::{ + collections::{BTreeMap, VecDeque}, + fmt::Debug, + sync::{atomic::Ordering::SeqCst, Arc}, +}; + use futures::{ future::{Fuse, FusedFuture}, FutureExt, Stream, StreamExt, @@ -16,13 +21,10 @@ use reth_provider::{BlockReaderIdExt, CanonStateNotification, ChainSpecProvider} use reth_rpc_server_types::constants::gas_oracle::MAX_HEADER_HISTORY; use reth_rpc_types::TxGasAndReward; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, VecDeque}, - fmt::Debug, - sync::{atomic::Ordering::SeqCst, Arc}, -}; use tracing::trace; +use crate::{EthApiError, EthStateCache}; + /// Contains cached fee history entries for blocks. /// /// Purpose for this is to provide cached data for `eth_feeHistory`. diff --git a/crates/rpc/rpc/src/eth/gas_oracle.rs b/crates/rpc/rpc-eth-api/src/gas_oracle.rs similarity index 89% rename from crates/rpc/rpc/src/eth/gas_oracle.rs rename to crates/rpc/rpc-eth-api/src/gas_oracle.rs index bb44af67b5f7..3cc55eb2b341 100644 --- a/crates/rpc/rpc/src/eth/gas_oracle.rs +++ b/crates/rpc/rpc-eth-api/src/gas_oracle.rs @@ -1,20 +1,33 @@ //! An implementation of the eth gas price oracle, used for providing gas price estimates based on //! previous blocks. -use crate::eth::{ - cache::EthStateCache, - error::{EthApiError, EthResult, RpcInvalidTransactionError}, -}; +use std::fmt::{self, Debug, Formatter}; + use derive_more::{Deref, DerefMut}; use reth_primitives::{constants::GWEI_TO_WEI, BlockNumberOrTag, B256, U256}; use reth_provider::BlockReaderIdExt; use reth_rpc_server_types::constants::gas_oracle::*; use schnellru::{ByLength, LruMap}; use serde::{Deserialize, Serialize}; -use std::fmt::{self, Debug, Formatter}; use tokio::sync::Mutex; use tracing::warn; +use crate::{EthApiError, EthResult, EthStateCache, RpcInvalidTransactionError}; + +/// The default gas limit for `eth_call` and adjacent calls. +/// +/// This is different from the default to regular 30M block gas limit +/// [`ETHEREUM_BLOCK_GAS_LIMIT`](reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT) to allow for +/// more complex calls. +pub const RPC_DEFAULT_GAS_CAP: GasCap = GasCap(50_000_000); + +/// Gas per transaction not creating a contract. +pub const MIN_TRANSACTION_GAS: u64 = 21_000u64; +/// Allowed error ratio for gas estimation +/// Taken from Geth's implementation in order to pass the hive tests +/// +pub const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015; + /// Settings for the [`GasPriceOracle`] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -73,7 +86,7 @@ pub struct GasPriceOracle { impl GasPriceOracle where - Provider: BlockReaderIdExt + 'static, + Provider: BlockReaderIdExt, { /// Creates and returns the [`GasPriceOracle`]. pub fn new( @@ -286,6 +299,28 @@ impl Default for GasPriceOracleResult { } } +/// The wrapper type for gas limit +#[derive(Debug, Clone, Copy)] +pub struct GasCap(u64); + +impl Default for GasCap { + fn default() -> Self { + RPC_DEFAULT_GAS_CAP + } +} + +impl From for GasCap { + fn from(gas_cap: u64) -> Self { + Self(gas_cap) + } +} + +impl From for u64 { + fn from(gas_cap: GasCap) -> Self { + gas_cap.0 + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc/src/eth/id_provider.rs b/crates/rpc/rpc-eth-api/src/id_provider.rs similarity index 93% rename from crates/rpc/rpc/src/eth/id_provider.rs rename to crates/rpc/rpc-eth-api/src/id_provider.rs index 6691e13a9f4b..0b63da39f425 100644 --- a/crates/rpc/rpc/src/eth/id_provider.rs +++ b/crates/rpc/rpc-eth-api/src/id_provider.rs @@ -1,6 +1,11 @@ -use jsonrpsee::types::SubscriptionId; +//! Helper type for [`EthPubSubApiServer`](crate::EthPubSubApiServer) implementation. +//! +//! Generates IDs for tracking subscriptions. + use std::fmt::Write; +use jsonrpsee::types::SubscriptionId; + /// An [`IdProvider`](jsonrpsee::core::traits::IdProvider) for ethereum subscription ids. /// /// Returns new hex-string [QUANTITY](https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding) ids diff --git a/crates/rpc/rpc-eth-api/src/lib.rs b/crates/rpc/rpc-eth-api/src/lib.rs new file mode 100644 index 000000000000..51cf5dafc07b --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/lib.rs @@ -0,0 +1,62 @@ +//! Reth RPC `eth_` API implementation +//! +//! ## Feature Flags +//! +//! - `client`: Enables JSON-RPC client support. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub mod api; +pub mod cache; +pub mod error; +pub mod fee_history; +pub mod gas_oracle; +pub mod id_provider; +pub mod logs_utils; +pub mod pending_block; +pub mod receipt; +pub mod result; +pub mod revm_utils; +pub mod transaction; +pub mod utils; + +#[cfg(feature = "client")] +pub use api::{ + bundle::{EthBundleApiClient, EthCallBundleApiClient}, + filter::EthFilterApiClient, + EthApiClient, +}; +pub use api::{ + bundle::{EthBundleApiServer, EthCallBundleApiServer}, + filter::EthFilterApiServer, + pubsub::EthPubSubApiServer, + servers::{ + self, + bundle::EthBundle, + filter::{EthFilter, EthFilterConfig}, + pubsub::EthPubSub, + EthApi, + }, + EthApiServer, +}; +pub use cache::{ + config::EthStateCacheConfig, db::StateCacheDb, multi_consumer::MultiConsumerLruCache, + EthStateCache, +}; +pub use error::{EthApiError, EthResult, RevertError, RpcInvalidTransactionError, SignError}; +pub use fee_history::{FeeHistoryCache, FeeHistoryCacheConfig, FeeHistoryEntry}; +pub use gas_oracle::{ + GasCap, GasPriceOracle, GasPriceOracleConfig, GasPriceOracleResult, ESTIMATE_GAS_ERROR_RATIO, + MIN_TRANSACTION_GAS, RPC_DEFAULT_GAS_CAP, +}; +pub use id_provider::EthSubscriptionIdProvider; +pub use pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; +pub use receipt::ReceiptBuilder; +pub use result::ToRpcResult; +pub use transaction::TransactionSource; diff --git a/crates/rpc/rpc/src/eth/logs_utils.rs b/crates/rpc/rpc-eth-api/src/logs_utils.rs similarity index 96% rename from crates/rpc/rpc/src/eth/logs_utils.rs rename to crates/rpc/rpc-eth-api/src/logs_utils.rs index c57ce5fcb986..f98567c4ed9b 100644 --- a/crates/rpc/rpc/src/eth/logs_utils.rs +++ b/crates/rpc/rpc-eth-api/src/logs_utils.rs @@ -1,10 +1,14 @@ -use super::filter::FilterError; -use alloy_primitives::TxHash; +//! Helper functions for [`EthFilterApiServer`](crate::EthFilterApiServer) implementation. +//! +//! Log parsing for building filter. + use reth_chainspec::ChainInfo; -use reth_primitives::{BlockNumHash, Receipt}; +use reth_primitives::{BlockNumHash, Receipt, TxHash}; use reth_provider::{BlockReader, ProviderError}; use reth_rpc_types::{FilteredParams, Log}; +use crate::servers::filter::EthFilterError; + /// Returns all matching of a block's receipts when the transaction hashes are known. pub(crate) fn matching_block_logs_with_tx_hashes<'a, I>( filter: &FilteredParams, @@ -51,7 +55,7 @@ pub(crate) fn append_matching_block_logs( receipts: &[Receipt], removed: bool, block_timestamp: u64, -) -> Result<(), FilterError> { +) -> Result<(), EthFilterError> { // Tracks the index of a log in the entire block. let mut log_index: u64 = 0; diff --git a/crates/rpc/rpc-eth-api/src/pending_block.rs b/crates/rpc/rpc-eth-api/src/pending_block.rs new file mode 100644 index 000000000000..a1671b2833ac --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/pending_block.rs @@ -0,0 +1,162 @@ +//! Helper types for [`EthApiServer`](crate::EthApiServer) implementation. +//! +//! Types used in block building. + +use std::{fmt, time::Instant}; + +use derive_more::Constructor; +use reth_chainspec::ChainSpec; +use reth_primitives::{BlockId, BlockNumberOrTag, SealedBlockWithSenders, SealedHeader, B256}; +use reth_provider::ProviderError; +use reth_revm::state_change::{apply_beacon_root_contract_call, apply_blockhashes_update}; +use revm_primitives::{ + db::{Database, DatabaseCommit}, + BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, +}; + +use crate::{EthApiError, EthResult}; + +/// Configured [`BlockEnv`] and [`CfgEnvWithHandlerCfg`] for a pending block +#[derive(Debug, Clone, Constructor)] +pub struct PendingBlockEnv { + /// Configured [`CfgEnvWithHandlerCfg`] for the pending block. + pub cfg: CfgEnvWithHandlerCfg, + /// Configured [`BlockEnv`] for the pending block. + pub block_env: BlockEnv, + /// Origin block for the config + pub origin: PendingBlockEnvOrigin, +} + +/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call. +/// +/// This constructs a new [Evm](revm::Evm) with the given DB, and environment +/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] to execute the pre block contract call. +/// +/// This uses [`apply_beacon_root_contract_call`] to ultimately apply the beacon root contract state +/// change. +pub fn pre_block_beacon_root_contract_call( + db: &mut DB, + chain_spec: &ChainSpec, + block_number: u64, + initialized_cfg: &CfgEnvWithHandlerCfg, + initialized_block_env: &BlockEnv, + parent_beacon_block_root: Option, +) -> EthResult<()> +where + DB::Error: fmt::Display, +{ + // apply pre-block EIP-4788 contract call + let mut evm_pre_block = revm::Evm::builder() + .with_db(db) + .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + Default::default(), + )) + .build(); + + // initialize a block from the env, because the pre block call needs the block itself + apply_beacon_root_contract_call( + chain_spec, + initialized_block_env.timestamp.to::(), + block_number, + parent_beacon_block_root, + &mut evm_pre_block, + ) + .map_err(|err| EthApiError::Internal(err.into())) +} + +/// Apply the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) pre block state transitions. +/// +/// This constructs a new [Evm](revm::Evm) with the given DB, and environment +/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`]. +/// +/// This uses [`apply_blockhashes_update`]. +pub fn pre_block_blockhashes_update + DatabaseCommit>( + db: &mut DB, + chain_spec: &ChainSpec, + initialized_block_env: &BlockEnv, + block_number: u64, + parent_block_hash: B256, +) -> EthResult<()> +where + DB::Error: fmt::Display, +{ + apply_blockhashes_update( + db, + chain_spec, + initialized_block_env.timestamp.to::(), + block_number, + parent_block_hash, + ) + .map_err(|err| EthApiError::Internal(err.into())) +} + +/// The origin for a configured [`PendingBlockEnv`] +#[derive(Clone, Debug)] +pub enum PendingBlockEnvOrigin { + /// The pending block as received from the CL. + ActualPending(SealedBlockWithSenders), + /// The _modified_ header of the latest block. + /// + /// This derives the pending state based on the latest header by modifying: + /// - the timestamp + /// - the block number + /// - fees + DerivedFromLatest(SealedHeader), +} + +impl PendingBlockEnvOrigin { + /// Returns true if the origin is the actual pending block as received from the CL. + pub const fn is_actual_pending(&self) -> bool { + matches!(self, Self::ActualPending(_)) + } + + /// Consumes the type and returns the actual pending block. + pub fn into_actual_pending(self) -> Option { + match self { + Self::ActualPending(block) => Some(block), + _ => None, + } + } + + /// Returns the [`BlockId`] that represents the state of the block. + /// + /// If this is the actual pending block, the state is the "Pending" tag, otherwise we can safely + /// identify the block by its hash (latest block). + pub fn state_block_id(&self) -> BlockId { + match self { + Self::ActualPending(_) => BlockNumberOrTag::Pending.into(), + Self::DerivedFromLatest(header) => BlockId::Hash(header.hash().into()), + } + } + + /// Returns the hash of the block the pending block should be built on. + /// + /// For the [`PendingBlockEnvOrigin::ActualPending`] this is the parent hash of the block. + /// For the [`PendingBlockEnvOrigin::DerivedFromLatest`] this is the hash of the _latest_ + /// header. + pub fn build_target_hash(&self) -> B256 { + match self { + Self::ActualPending(block) => block.parent_hash, + Self::DerivedFromLatest(header) => header.hash(), + } + } + + /// Returns the header this pending block is based on. + pub fn header(&self) -> &SealedHeader { + match self { + Self::ActualPending(block) => &block.header, + Self::DerivedFromLatest(header) => header, + } + } +} + +/// In memory pending block for `pending` tag +#[derive(Debug, Constructor)] +pub struct PendingBlock { + /// The cached pending block + pub block: SealedBlockWithSenders, + /// Timestamp when the pending block is considered outdated + pub expires_at: Instant, +} diff --git a/crates/rpc/rpc-eth-api/src/receipt.rs b/crates/rpc/rpc-eth-api/src/receipt.rs new file mode 100644 index 000000000000..ac9944ab8825 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/receipt.rs @@ -0,0 +1,126 @@ +//! RPC receipt response builder, extends a layer one receipt with layer two data. + +use reth_primitives::{Address, Receipt, TransactionMeta, TransactionSigned, TxKind}; +use reth_rpc_types::{ + AnyReceiptEnvelope, AnyTransactionReceipt, Log, OtherFields, ReceiptWithBloom, + TransactionReceipt, WithOtherFields, +}; +use revm_primitives::calc_blob_gasprice; + +use crate::{EthApiError, EthResult}; + +/// Receipt response builder. +#[derive(Debug)] +pub struct ReceiptBuilder { + /// The base response body, contains L1 fields. + base: TransactionReceipt>, + /// Additional L2 fields. + other: OtherFields, +} + +impl ReceiptBuilder { + /// Returns a new builder with the base response body (L1 fields) set. + /// + /// Note: This requires _all_ block receipts because we need to calculate the gas used by the + /// transaction. + pub fn new( + transaction: &TransactionSigned, + meta: TransactionMeta, + receipt: &Receipt, + all_receipts: &[Receipt], + ) -> EthResult { + // Note: we assume this transaction is valid, because it's mined (or part of pending block) + // and we don't need to check for pre EIP-2 + let from = transaction + .recover_signer_unchecked() + .ok_or(EthApiError::InvalidTransactionSignature)?; + + // get the previous transaction cumulative gas used + let gas_used = if meta.index == 0 { + receipt.cumulative_gas_used + } else { + let prev_tx_idx = (meta.index - 1) as usize; + all_receipts + .get(prev_tx_idx) + .map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used) + .unwrap_or_default() + }; + + let blob_gas_used = transaction.transaction.blob_gas_used(); + // Blob gas price should only be present if the transaction is a blob transaction + let blob_gas_price = + blob_gas_used.and_then(|_| meta.excess_blob_gas.map(calc_blob_gasprice)); + let logs_bloom = receipt.bloom_slow(); + + // get number of logs in the block + let mut num_logs = 0; + for prev_receipt in all_receipts.iter().take(meta.index as usize) { + num_logs += prev_receipt.logs.len(); + } + + let mut logs = Vec::with_capacity(receipt.logs.len()); + for (tx_log_idx, log) in receipt.logs.iter().enumerate() { + let rpclog = Log { + inner: log.clone(), + block_hash: Some(meta.block_hash), + block_number: Some(meta.block_number), + block_timestamp: Some(meta.timestamp), + transaction_hash: Some(meta.tx_hash), + transaction_index: Some(meta.index), + log_index: Some((num_logs + tx_log_idx) as u64), + removed: false, + }; + logs.push(rpclog); + } + + let rpc_receipt = reth_rpc_types::Receipt { + status: receipt.success.into(), + cumulative_gas_used: receipt.cumulative_gas_used as u128, + logs, + }; + + let (contract_address, to) = match transaction.transaction.kind() { + TxKind::Create => (Some(from.create(transaction.transaction.nonce())), None), + TxKind::Call(addr) => (None, Some(Address(*addr))), + }; + + #[allow(clippy::needless_update)] + let base = TransactionReceipt { + inner: AnyReceiptEnvelope { + inner: ReceiptWithBloom { receipt: rpc_receipt, logs_bloom }, + r#type: transaction.transaction.tx_type().into(), + }, + transaction_hash: meta.tx_hash, + transaction_index: Some(meta.index), + block_hash: Some(meta.block_hash), + block_number: Some(meta.block_number), + from, + to, + gas_used: gas_used as u128, + contract_address, + effective_gas_price: transaction.effective_gas_price(meta.base_fee), + // TODO pre-byzantium receipts have a post-transaction state root + state_root: None, + // EIP-4844 fields + blob_gas_price, + blob_gas_used: blob_gas_used.map(u128::from), + }; + + Ok(Self { base, other: Default::default() }) + } + + /// Adds fields to response body. + pub fn add_other_fields(mut self, mut fields: OtherFields) -> Self { + self.other.append(&mut fields); + self + } + + /// Builds a receipt response from the base response body, and any set additional fields. + pub fn build(self) -> AnyTransactionReceipt { + let Self { base, other } = self; + let mut res = WithOtherFields::new(base); + res.other = other; + + res + } +} diff --git a/crates/rpc/rpc/src/result.rs b/crates/rpc/rpc-eth-api/src/result.rs similarity index 95% rename from crates/rpc/rpc/src/result.rs rename to crates/rpc/rpc-eth-api/src/result.rs index f00c9e279939..ac55bb3fefea 100644 --- a/crates/rpc/rpc/src/result.rs +++ b/crates/rpc/rpc-eth-api/src/result.rs @@ -1,8 +1,9 @@ //! Additional helpers for converting errors. +use std::fmt::Display; + use jsonrpsee::core::RpcResult; use reth_rpc_types::engine::PayloadError; -use std::fmt::Display; /// Helper trait to easily convert various `Result` types into [`RpcResult`] pub trait ToRpcResult: Sized { @@ -104,21 +105,19 @@ impl_to_rpc_result!(reth_errors::ProviderError); impl_to_rpc_result!(reth_network_api::NetworkError); /// Constructs an invalid params JSON-RPC error. -pub(crate) fn invalid_params_rpc_err( +pub fn invalid_params_rpc_err( msg: impl Into, ) -> jsonrpsee::types::error::ErrorObject<'static> { rpc_err(jsonrpsee::types::error::INVALID_PARAMS_CODE, msg, None) } /// Constructs an internal JSON-RPC error. -pub(crate) fn internal_rpc_err( - msg: impl Into, -) -> jsonrpsee::types::error::ErrorObject<'static> { +pub fn internal_rpc_err(msg: impl Into) -> jsonrpsee::types::error::ErrorObject<'static> { rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, None) } /// Constructs an internal JSON-RPC error with data -pub(crate) fn internal_rpc_err_with_data( +pub fn internal_rpc_err_with_data( msg: impl Into, data: &[u8], ) -> jsonrpsee::types::error::ErrorObject<'static> { @@ -126,7 +125,7 @@ pub(crate) fn internal_rpc_err_with_data( } /// Constructs an internal JSON-RPC error with code and message -pub(crate) fn rpc_error_with_code( +pub fn rpc_error_with_code( code: i32, msg: impl Into, ) -> jsonrpsee::types::error::ErrorObject<'static> { @@ -134,7 +133,7 @@ pub(crate) fn rpc_error_with_code( } /// Constructs a JSON-RPC error, consisting of `code`, `message` and optional `data`. -pub(crate) fn rpc_err( +pub fn rpc_err( code: i32, msg: impl Into, data: Option<&[u8]>, diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc-eth-api/src/revm_utils.rs similarity index 90% rename from crates/rpc/rpc/src/eth/revm_utils.rs rename to crates/rpc/rpc-eth-api/src/revm_utils.rs index 4f4b9d7f02e4..b0d0e6a882d3 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc-eth-api/src/revm_utils.rs @@ -1,14 +1,8 @@ //! utilities for working with revm -use crate::eth::error::{EthApiError, EthResult, RpcInvalidTransactionError}; -#[cfg(feature = "optimism")] -use reth_primitives::revm::env::fill_op_tx_env; -#[cfg(not(feature = "optimism"))] -use reth_primitives::revm::env::fill_tx_env; -use reth_primitives::{ - revm::env::fill_tx_env_with_recovered, Address, TransactionSigned, - TransactionSignedEcRecovered, TxHash, TxKind, B256, U256, -}; +use std::cmp::min; + +use reth_primitives::{Address, TxKind, B256, U256}; use reth_rpc_types::{ state::{AccountOverride, EvmOverrides, StateOverride}, BlockOverrides, TransactionRequest, @@ -23,58 +17,9 @@ use revm::{ }, Database, }; -use std::cmp::min; use tracing::trace; -/// Helper type to work with different transaction types when configuring the EVM env. -/// -/// This makes it easier to handle errors. -pub trait FillableTransaction { - /// Returns the hash of the transaction. - fn hash(&self) -> TxHash; - - /// Fill the transaction environment with the given transaction. - fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()>; -} - -impl FillableTransaction for TransactionSignedEcRecovered { - fn hash(&self) -> TxHash { - self.hash - } - - fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> { - #[cfg(not(feature = "optimism"))] - fill_tx_env_with_recovered(tx_env, self); - - #[cfg(feature = "optimism")] - { - let mut envelope_buf = Vec::with_capacity(self.length_without_header()); - self.encode_enveloped(&mut envelope_buf); - fill_tx_env_with_recovered(tx_env, self, envelope_buf.into()); - } - Ok(()) - } -} -impl FillableTransaction for TransactionSigned { - fn hash(&self) -> TxHash { - self.hash - } - - fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> { - let signer = - self.recover_signer().ok_or_else(|| EthApiError::InvalidTransactionSignature)?; - #[cfg(not(feature = "optimism"))] - fill_tx_env(tx_env, self, signer); - - #[cfg(feature = "optimism")] - { - let mut envelope_buf = Vec::with_capacity(self.length_without_header()); - self.encode_enveloped(&mut envelope_buf); - fill_op_tx_env(tx_env, self, signer, envelope_buf.into()); - } - Ok(()) - } -} +use crate::{EthApiError, EthResult, RpcInvalidTransactionError}; /// Returns the addresses of the precompiles corresponding to the `SpecId`. #[inline] diff --git a/crates/rpc/rpc-eth-api/src/transaction.rs b/crates/rpc/rpc-eth-api/src/transaction.rs new file mode 100644 index 000000000000..685f37b93d79 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/transaction.rs @@ -0,0 +1,96 @@ +//! Helper types for [`EthApiServer`](crate::EthApiServer) implementation. +//! +//! Transaction wrapper that labels transaction with its origin. + +use reth_primitives::{TransactionSignedEcRecovered, B256}; +use reth_rpc_types::{Transaction, TransactionInfo}; +use reth_rpc_types_compat::transaction::from_recovered_with_block_context; + +/// Represents from where a transaction was fetched. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum TransactionSource { + /// Transaction exists in the pool (Pending) + Pool(TransactionSignedEcRecovered), + /// Transaction already included in a block + /// + /// This can be a historical block or a pending block (received from the CL) + Block { + /// Transaction fetched via provider + transaction: TransactionSignedEcRecovered, + /// Index of the transaction in the block + index: u64, + /// Hash of the block. + block_hash: B256, + /// Number of the block. + block_number: u64, + /// base fee of the block. + base_fee: Option, + }, +} + +// === impl TransactionSource === + +impl TransactionSource { + /// Consumes the type and returns the wrapped transaction. + pub fn into_recovered(self) -> TransactionSignedEcRecovered { + self.into() + } + + /// Returns the transaction and block related info, if not pending + pub fn split(self) -> (TransactionSignedEcRecovered, TransactionInfo) { + match self { + Self::Pool(tx) => { + let hash = tx.hash(); + ( + tx, + TransactionInfo { + hash: Some(hash), + index: None, + block_hash: None, + block_number: None, + base_fee: None, + }, + ) + } + Self::Block { transaction, index, block_hash, block_number, base_fee } => { + let hash = transaction.hash(); + ( + transaction, + TransactionInfo { + hash: Some(hash), + index: Some(index), + block_hash: Some(block_hash), + block_number: Some(block_number), + base_fee: base_fee.map(u128::from), + }, + ) + } + } + } +} + +impl From for TransactionSignedEcRecovered { + fn from(value: TransactionSource) -> Self { + match value { + TransactionSource::Pool(tx) => tx, + TransactionSource::Block { transaction, .. } => transaction, + } + } +} + +impl From for Transaction { + fn from(value: TransactionSource) -> Self { + match value { + TransactionSource::Pool(tx) => reth_rpc_types_compat::transaction::from_recovered(tx), + TransactionSource::Block { transaction, index, block_hash, block_number, base_fee } => { + from_recovered_with_block_context( + transaction, + block_hash, + block_number, + base_fee, + index as usize, + ) + } + } + } +} diff --git a/crates/rpc/rpc/src/eth/utils.rs b/crates/rpc/rpc-eth-api/src/utils.rs similarity index 79% rename from crates/rpc/rpc/src/eth/utils.rs rename to crates/rpc/rpc-eth-api/src/utils.rs index a4291c4b933d..6573fb6b77cc 100644 --- a/crates/rpc/rpc/src/eth/utils.rs +++ b/crates/rpc/rpc-eth-api/src/utils.rs @@ -1,14 +1,13 @@ //! Commonly used code snippets -use crate::eth::error::{EthApiError, EthResult}; use reth_primitives::{Bytes, PooledTransactionsElement, PooledTransactionsElementEcRecovered}; +use crate::{EthApiError, EthResult}; + /// Recovers a [`PooledTransactionsElementEcRecovered`] from an enveloped encoded byte stream. /// /// See [`PooledTransactionsElement::decode_enveloped`] -pub(crate) fn recover_raw_transaction( - data: Bytes, -) -> EthResult { +pub fn recover_raw_transaction(data: Bytes) -> EthResult { if data.is_empty() { return Err(EthApiError::EmptyRawTransactionData) } diff --git a/crates/rpc/rpc-testing-util/Cargo.toml b/crates/rpc/rpc-testing-util/Cargo.toml index 898fec038f70..8ab37d1b18d0 100644 --- a/crates/rpc/rpc-testing-util/Cargo.toml +++ b/crates/rpc/rpc-testing-util/Cargo.toml @@ -29,3 +29,4 @@ similar-asserts.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros", "rt"] } +reth-rpc-eth-api.workspace = true diff --git a/crates/rpc/rpc-testing-util/tests/it/trace.rs b/crates/rpc/rpc-testing-util/tests/it/trace.rs index a6439f0744c4..029e9fbbc8b5 100644 --- a/crates/rpc/rpc-testing-util/tests/it/trace.rs +++ b/crates/rpc/rpc-testing-util/tests/it/trace.rs @@ -1,7 +1,7 @@ use futures::StreamExt; use jsonrpsee::http_client::HttpClientBuilder; -use reth_rpc_api::EthApiClient; use reth_rpc_api_testing_util::{debug::DebugApiExt, trace::TraceApiExt, utils::parse_env_url}; +use reth_rpc_eth_api::EthApiClient; use reth_rpc_types::trace::{ filter::TraceFilter, parity::TraceType, tracerequest::TraceCallRequest, }; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 87539eab4ae5..7082a75fcc8e 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -16,7 +16,7 @@ workspace = true reth-chainspec.workspace = true reth-primitives.workspace = true reth-rpc-api.workspace = true -reth-rpc-server-types.workspace = true +reth-rpc-eth-api.workspace = true reth-rpc-types.workspace = true reth-errors.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } @@ -28,17 +28,11 @@ reth-tasks = { workspace = true, features = ["rayon"] } reth-consensus-common.workspace = true reth-rpc-types-compat.workspace = true revm-inspectors = { workspace = true, features = ["js-tracer"] } -reth-evm.workspace = true reth-network-peers.workspace = true -reth-execution-types.workspace = true - -reth-evm-optimism = { workspace = true, optional = true } # eth alloy-rlp.workspace = true -alloy-dyn-abi = { workspace = true, features = ["eip712"] } alloy-primitives.workspace = true -alloy-sol-types.workspace = true alloy-genesis.workspace = true revm = { workspace = true, features = [ "optional_block_gas_limit", @@ -58,30 +52,12 @@ jsonwebtoken.workspace = true async-trait.workspace = true tokio = { workspace = true, features = ["sync"] } tower.workspace = true -tokio-stream = { workspace = true, features = ["sync"] } pin-project.workspace = true -parking_lot.workspace = true - -# metrics -reth-metrics.workspace = true -metrics.workspace = true # misc -secp256k1 = { workspace = true, features = [ - "global-context", - "rand-std", - "recovery", -] } -serde = { workspace = true, features = ["derive"] } -serde_json.workspace = true -thiserror.workspace = true -rand.workspace = true tracing.workspace = true tracing-futures = "0.2" -schnellru.workspace = true futures.workspace = true -derive_more.workspace = true -dyn-clone.workspace = true [dev-dependencies] reth-evm-ethereum.workspace = true @@ -96,7 +72,7 @@ optimism = [ "reth-primitives/optimism", "reth-rpc-types-compat/optimism", "reth-provider/optimism", - "dep:reth-evm-optimism", - "reth-evm-optimism/optimism", + "reth-rpc-api/optimism", + "reth-rpc-eth-api/optimism", "reth-revm/optimism", ] diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 71f95fedec9a..0e1e13a0c013 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -1,4 +1,5 @@ -use crate::result::ToRpcResult; +use std::sync::Arc; + use alloy_genesis::ChainConfig; use alloy_primitives::B256; use async_trait::async_trait; @@ -11,7 +12,8 @@ use reth_rpc_types::{ admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo}, PeerEthProtocolInfo, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, }; -use std::sync::Arc; + +use crate::result::ToRpcResult; /// `admin` API implementation. /// diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index b2d524a6325b..8fd7c582a115 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,12 +1,5 @@ -use crate::{ - eth::{ - error::{EthApiError, EthResult}, - revm_utils::prepare_call_env, - EthTransactions, - }, - result::{internal_rpc_err, ToRpcResult}, - EthApiSpec, -}; +use std::sync::Arc; + use alloy_rlp::{Decodable, Encodable}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; @@ -15,10 +8,17 @@ use reth_primitives::{ TransactionSignedEcRecovered, Withdrawals, B256, U256, }; use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StateProviderBox, TransactionVariant, + BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, + TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; +use reth_rpc_eth_api::{ + result::internal_rpc_err, + revm_utils::prepare_call_env, + servers::{EthApiSpec, EthTransactions, TraceExt}, + EthApiError, EthResult, StateCacheDb, ToRpcResult, +}; use reth_rpc_types::{ state::EvmOverrides, trace::geth::{ @@ -36,7 +36,6 @@ use revm_inspectors::tracing::{ js::{JsInspector, TransactionContext}, FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, }; -use std::sync::Arc; use tokio::sync::{AcquireError, OwnedSemaphorePermit}; /// `debug` API implementation. @@ -65,8 +64,13 @@ impl DebugApi { impl DebugApi where - Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static, - Eth: EthTransactions + 'static, + Provider: BlockReaderIdExt + + HeaderProvider + + ChainSpecProvider + + StateProviderFactory + + EvmEnvProvider + + 'static, + Eth: TraceExt + 'static, { /// Acquires a permit to execute a tracing call. async fn acquire_trace_permit(&self) -> Result { @@ -74,7 +78,7 @@ where } /// Trace the entire block asynchronously - async fn trace_block_with( + async fn trace_block( &self, at: BlockId, transactions: Vec, @@ -165,7 +169,7 @@ where .collect::>>()? }; - self.trace_block_with(parent.into(), transactions, cfg, block_env, opts).await + self.trace_block(parent.into(), transactions, cfg, block_env, opts).await } /// Replays a block and returns the trace of each transaction. @@ -182,7 +186,7 @@ where let ((cfg, block_env, _), block) = futures::try_join!( self.inner.eth_api.evm_env_at(block_hash.into()), - self.inner.eth_api.block_by_id_with_senders(block_id), + self.inner.eth_api.block_with_senders(block_id), )?; let block = block.ok_or_else(|| EthApiError::UnknownBlockNumber)?; @@ -190,7 +194,7 @@ where // its parent block's state let state_at = block.parent_hash; - self.trace_block_with( + self.trace_block( state_at.into(), block.into_transactions_ecrecovered().collect(), cfg, @@ -324,6 +328,10 @@ where self.inner .eth_api .spawn_with_call_at(call, at, overrides, move |db, env| { + // wrapper is hack to get around 'higher-ranked lifetime error', + // see + let db = db.0; + let (res, _) = this.eth_api().inspect(&mut *db, env, &mut inspector)?; let frame = inspector @@ -346,6 +354,10 @@ where .inner .eth_api .spawn_with_call_at(call, at, overrides, move |db, env| { + // wrapper is hack to get around 'higher-ranked lifetime error', see + // + let db = db.0; + let (res, _) = this.eth_api().inspect(&mut *db, env, &mut inspector)?; let frame = inspector.try_into_mux_frame(&res, db)?; @@ -364,6 +376,10 @@ where .inner .eth_api .spawn_with_call_at(call, at, overrides, move |db, env| { + // wrapper is hack to get around 'higher-ranked lifetime error', see + // + let db = db.0; + let mut inspector = JsInspector::new(code, config)?; let (res, _) = this.eth_api().inspect(&mut *db, env.clone(), &mut inspector)?; @@ -415,7 +431,7 @@ where let target_block = block_number.unwrap_or_default(); let ((cfg, mut block_env, _), block) = futures::try_join!( self.inner.eth_api.evm_env_at(target_block), - self.inner.eth_api.block_by_id_with_senders(target_block), + self.inner.eth_api.block_with_senders(target_block), )?; let opts = opts.unwrap_or_default(); @@ -518,7 +534,7 @@ where &self, opts: GethDebugTracingOptions, env: EnvWithHandlerCfg, - db: &mut CacheDB>, + db: &mut StateCacheDb<'_>, transaction_context: Option, ) -> EthResult<(GethTrace, revm_primitives::EvmState)> { let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; @@ -614,8 +630,13 @@ where #[async_trait] impl DebugApiServer for DebugApi where - Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static, - Eth: EthApiSpec + 'static, + Provider: BlockReaderIdExt + + HeaderProvider + + ChainSpecProvider + + StateProviderFactory + + EvmEnvProvider + + 'static, + Eth: EthApiSpec + EthTransactions + TraceExt + 'static, { /// Handler for `debug_getRawHeader` async fn raw_header(&self, block_id: BlockId) -> RpcResult { diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs deleted file mode 100644 index 18a547faf9e6..000000000000 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! Contains RPC handler implementations specific to blocks. - -use crate::{ - eth::{ - api::transactions::build_transaction_receipt_with_block_receipts, - error::{EthApiError, EthResult}, - }, - EthApi, -}; -use reth_evm::ConfigureEvm; -use reth_network_api::NetworkInfo; -use reth_primitives::{BlockId, TransactionMeta}; -use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; -use reth_rpc_types::{AnyTransactionReceipt, Header, Index, RichBlock}; -use reth_rpc_types_compat::block::{from_block, uncle_block_from_header}; -use reth_transaction_pool::TransactionPool; -use std::sync::Arc; - -impl EthApi -where - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Pool: TransactionPool + Clone + 'static, - Network: NetworkInfo + Send + Sync + 'static, - EvmConfig: ConfigureEvm + 'static, -{ - /// Returns the uncle headers of the given block - /// - /// Returns an empty vec if there are none. - pub(crate) fn ommers( - &self, - block_id: impl Into, - ) -> EthResult>> { - let block_id = block_id.into(); - Ok(self.provider().ommers_by_id(block_id)?) - } - - pub(crate) async fn ommer_by_block_and_index( - &self, - block_id: impl Into, - index: Index, - ) -> EthResult> { - let block_id = block_id.into(); - - let uncles = if block_id.is_pending() { - // Pending block can be fetched directly without need for caching - self.provider().pending_block()?.map(|block| block.ommers) - } else { - self.provider().ommers_by_id(block_id)? - } - .unwrap_or_default(); - - let index = usize::from(index); - let uncle = - uncles.into_iter().nth(index).map(|header| uncle_block_from_header(header).into()); - Ok(uncle) - } - - /// Returns all transaction receipts in the block. - /// - /// Returns `None` if the block wasn't found. - pub(crate) async fn block_receipts( - &self, - block_id: BlockId, - ) -> EthResult>> { - // Fetch block and receipts based on block_id - let block_and_receipts = if block_id.is_pending() { - self.provider() - .pending_block_and_receipts()? - .map(|(sb, receipts)| (sb, Arc::new(receipts))) - } else if let Some(block_hash) = self.provider().block_hash_for_id(block_id)? { - self.cache().get_block_and_receipts(block_hash).await? - } else { - None - }; - - // If no block and receipts found, return None - let Some((block, receipts)) = block_and_receipts else { - return Ok(None); - }; - - // Extract block details - let block_number = block.number; - let base_fee = block.base_fee_per_gas; - let block_hash = block.hash(); - let excess_blob_gas = block.excess_blob_gas; - let timestamp = block.timestamp; - let block = block.unseal(); - - #[cfg(feature = "optimism")] - let (block_timestamp, l1_block_info) = { - let body = reth_evm_optimism::extract_l1_info(&block); - (block.timestamp, body.ok()) - }; - - // Build transaction receipts - block - .body - .into_iter() - .zip(receipts.iter()) - .enumerate() - .map(|(idx, (tx, receipt))| { - let meta = TransactionMeta { - tx_hash: tx.hash, - index: idx as u64, - block_hash, - block_number, - base_fee, - excess_blob_gas, - timestamp, - }; - - #[cfg(feature = "optimism")] - let op_tx_meta = - self.build_op_tx_meta(&tx, l1_block_info.clone(), block_timestamp)?; - - build_transaction_receipt_with_block_receipts( - tx, - meta, - receipt.clone(), - &receipts, - #[cfg(feature = "optimism")] - op_tx_meta, - ) - }) - .collect::>>() - .map(Some) - } - - /// Returns the number transactions in the given block. - /// - /// Returns `None` if the block does not exist - pub(crate) async fn block_transaction_count( - &self, - block_id: impl Into, - ) -> EthResult> { - let block_id = block_id.into(); - - if block_id.is_pending() { - // Pending block can be fetched directly without need for caching - return Ok(self.provider().pending_block()?.map(|block| block.body.len())) - } - - let block_hash = match self.provider().block_hash_for_id(block_id)? { - Some(block_hash) => block_hash, - None => return Ok(None), - }; - - Ok(self.cache().get_block_transactions(block_hash).await?.map(|txs| txs.len())) - } - - /// Returns the block object for the given block id. - pub(crate) async fn block( - &self, - block_id: impl Into, - ) -> EthResult> { - self.block_with_senders(block_id) - .await - .map(|maybe_block| maybe_block.map(|block| block.block)) - } - - /// Returns the block object for the given block id. - pub(crate) async fn block_with_senders( - &self, - block_id: impl Into, - ) -> EthResult> { - let block_id = block_id.into(); - - if block_id.is_pending() { - // Pending block can be fetched directly without need for caching - let maybe_pending = self.provider().pending_block_with_senders()?; - return if maybe_pending.is_some() { - Ok(maybe_pending) - } else { - self.local_pending_block().await - } - } - - let block_hash = match self.provider().block_hash_for_id(block_id)? { - Some(block_hash) => block_hash, - None => return Ok(None), - }; - - Ok(self.cache().get_sealed_block_with_senders(block_hash).await?) - } - - /// Returns the populated rpc block object for the given block id. - /// - /// If `full` is true, the block object will contain all transaction objects, otherwise it will - /// only contain the transaction hashes. - pub(crate) async fn rpc_block( - &self, - block_id: impl Into, - full: bool, - ) -> EthResult> { - let block = match self.block_with_senders(block_id).await? { - Some(block) => block, - None => return Ok(None), - }; - let block_hash = block.hash(); - let total_difficulty = self - .provider() - .header_td_by_number(block.number)? - .ok_or(EthApiError::UnknownBlockNumber)?; - let block = from_block(block.unseal(), total_difficulty, full.into(), Some(block_hash))?; - Ok(Some(block.into())) - } - - /// Returns the block header for the given block id. - pub(crate) async fn rpc_block_header( - &self, - block_id: impl Into, - ) -> EthResult> { - let header = self.rpc_block(block_id, false).await?.map(|block| block.inner.header); - Ok(header) - } -} diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs deleted file mode 100644 index 907065e476d4..000000000000 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ /dev/null @@ -1,530 +0,0 @@ -//! Contains RPC handler implementations specific to endpoints that call/execute within evm. - -use crate::{ - eth::{ - error::{ensure_success, EthApiError, EthResult, RevertError, RpcInvalidTransactionError}, - revm_utils::{ - apply_state_overrides, build_call_evm_env, caller_gas_allowance, - cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env, - }, - EthTransactions, - }, - EthApi, -}; -use reth_evm::ConfigureEvm; -use reth_network_api::NetworkInfo; -use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, TxKind, U256}; -use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProvider, StateProviderFactory, -}; -use reth_revm::database::StateProviderDatabase; -use reth_rpc_types::{ - state::{EvmOverrides, StateOverride}, - AccessListWithGasUsed, Bundle, EthCallResponse, StateContext, TransactionRequest, -}; -use reth_transaction_pool::TransactionPool; -use revm::{ - db::{CacheDB, DatabaseRef}, - primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason}, - DatabaseCommit, -}; -use revm_inspectors::access_list::AccessListInspector; -use tracing::trace; - -// Gas per transaction not creating a contract. -const MIN_TRANSACTION_GAS: u64 = 21_000u64; -/// Allowed error ratio for gas estimation -/// Taken from Geth's implementation in order to pass the hive tests -/// -const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015; - -impl EthApi -where - Pool: TransactionPool + Clone + 'static, - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: NetworkInfo + Send + Sync + 'static, - EvmConfig: ConfigureEvm + 'static, -{ - /// Estimate gas needed for execution of the `request` at the [`BlockId`]. - pub async fn estimate_gas_at( - &self, - request: TransactionRequest, - at: BlockId, - state_override: Option, - ) -> EthResult { - let (cfg, block_env, at) = self.evm_env_at(at).await?; - - self.on_blocking_task(|this| async move { - let state = this.state_at(at)?; - this.estimate_gas_with(cfg, block_env, request, state, state_override) - }) - .await - } - - /// Executes the call request (`eth_call`) and returns the output - pub async fn call( - &self, - request: TransactionRequest, - block_number: Option, - overrides: EvmOverrides, - ) -> EthResult { - let (res, _env) = - self.transact_call_at(request, block_number.unwrap_or_default(), overrides).await?; - - ensure_success(res.result) - } - - /// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the - /// optionality of state overrides - pub async fn call_many( - &self, - bundle: Bundle, - state_context: Option, - mut state_override: Option, - ) -> EthResult> { - let Bundle { transactions, block_override } = bundle; - if transactions.is_empty() { - return Err(EthApiError::InvalidParams(String::from("transactions are empty."))) - } - - let StateContext { transaction_index, block_number } = state_context.unwrap_or_default(); - let transaction_index = transaction_index.unwrap_or_default(); - - let target_block = block_number.unwrap_or_default(); - let is_block_target_pending = target_block.is_pending(); - - let ((cfg, block_env, _), block) = futures::try_join!( - self.evm_env_at(target_block), - self.block_with_senders(target_block) - )?; - - let Some(block) = block else { return Err(EthApiError::UnknownBlockNumber) }; - let gas_limit = self.inner.gas_cap; - - // we're essentially replaying the transactions in the block here, hence we need the state - // that points to the beginning of the block, which is the state at the parent block - let mut at = block.parent_hash; - let mut replay_block_txs = true; - - let num_txs = transaction_index.index().unwrap_or(block.body.len()); - // but if all transactions are to be replayed, we can use the state at the block itself, - // however only if we're not targeting the pending block, because for pending we can't rely - // on the block's state being available - if !is_block_target_pending && num_txs == block.body.len() { - at = block.hash(); - replay_block_txs = false; - } - - let this = self.clone(); - self.spawn_with_state_at_block(at.into(), move |state| { - let mut results = Vec::with_capacity(transactions.len()); - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - if replay_block_txs { - // only need to replay the transactions in the block if not all transactions are - // to be replayed - let transactions = block.into_transactions_ecrecovered().take(num_txs); - for tx in transactions { - let tx = tx_env_with_recovered(&tx); - let env = - EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx); - let (res, _) = this.transact(&mut db, env)?; - db.commit(res.state); - } - } - - let block_overrides = block_override.map(Box::new); - - let mut transactions = transactions.into_iter().peekable(); - while let Some(tx) = transactions.next() { - // apply state overrides only once, before the first transaction - let state_overrides = state_override.take(); - let overrides = EvmOverrides::new(state_overrides, block_overrides.clone()); - - let env = prepare_call_env( - cfg.clone(), - block_env.clone(), - tx, - gas_limit, - &mut db, - overrides, - )?; - let (res, _) = this.transact(&mut db, env)?; - - match ensure_success(res.result) { - Ok(output) => { - results.push(EthCallResponse { value: Some(output), error: None }); - } - Err(err) => { - results.push(EthCallResponse { value: None, error: Some(err.to_string()) }); - } - } - - if transactions.peek().is_some() { - // need to apply the state changes of this call before executing the next call - db.commit(res.state); - } - } - - Ok(results) - }) - .await - } - - /// Estimates the gas usage of the `request` with the state. - /// - /// This will execute the [`TransactionRequest`] and find the best gas limit via binary search - pub fn estimate_gas_with( - &self, - mut cfg: CfgEnvWithHandlerCfg, - block: BlockEnv, - request: TransactionRequest, - state: S, - state_override: Option, - ) -> EthResult - where - S: StateProvider, - { - // Disabled because eth_estimateGas is sometimes used with eoa senders - // See - cfg.disable_eip3607 = true; - - // The basefee should be ignored for eth_createAccessList - // See: - // - cfg.disable_base_fee = true; - - // Keep a copy of gas related request values - let tx_request_gas_limit = request.gas; - let tx_request_gas_price = request.gas_price; - let block_env_gas_limit = block.gas_limit; - - // Determine the highest possible gas limit, considering both the request's specified limit - // and the block's limit. - let mut highest_gas_limit = tx_request_gas_limit - .map(|tx_gas_limit| U256::from(tx_gas_limit).max(block_env_gas_limit)) - .unwrap_or(block_env_gas_limit); - - // Configure the evm env - let mut env = build_call_evm_env(cfg, block, request)?; - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - // Apply any state overrides if specified. - if let Some(state_override) = state_override { - apply_state_overrides(state_override, &mut db)?; - } - - // Optimize for simple transfer transactions, potentially reducing the gas estimate. - if env.tx.data.is_empty() { - if let TxKind::Call(to) = env.tx.transact_to { - if let Ok(code) = db.db.account_code(to) { - let no_code_callee = code.map(|code| code.is_empty()).unwrap_or(true); - if no_code_callee { - // If the tx is a simple transfer (call to an account with no code) we can - // shortcircuit. But simply returning - // `MIN_TRANSACTION_GAS` is dangerous because there might be additional - // field combos that bump the price up, so we try executing the function - // with the minimum gas limit to make sure. - let mut env = env.clone(); - env.tx.gas_limit = MIN_TRANSACTION_GAS; - if let Ok((res, _)) = self.transact(&mut db, env) { - if res.result.is_success() { - return Ok(U256::from(MIN_TRANSACTION_GAS)) - } - } - } - } - } - } - - // Check funds of the sender (only useful to check if transaction gas price is more than 0). - // - // The caller allowance is check by doing `(account.balance - tx.value) / tx.gas_price` - if env.tx.gas_price > U256::ZERO { - // cap the highest gas limit by max gas caller can afford with given gas price - highest_gas_limit = highest_gas_limit.min(caller_gas_allowance(&mut db, &env.tx)?); - } - - // We can now normalize the highest gas limit to a u64 - let mut highest_gas_limit: u64 = highest_gas_limit.try_into().unwrap_or(u64::MAX); - - // If the provided gas limit is less than computed cap, use that - env.tx.gas_limit = env.tx.gas_limit.min(highest_gas_limit); - - trace!(target: "rpc::eth::estimate", ?env, "Starting gas estimation"); - - // Execute the transaction with the highest possible gas limit. - let (mut res, mut env) = match self.transact(&mut db, env.clone()) { - // Handle the exceptional case where the transaction initialization uses too much gas. - // If the gas price or gas limit was specified in the request, retry the transaction - // with the block's gas limit to determine if the failure was due to - // insufficient gas. - Err(EthApiError::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh)) - if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() => - { - return Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db)) - } - // Propagate other results (successful or other errors). - ethres => ethres?, - }; - - let gas_refund = match res.result { - ExecutionResult::Success { gas_refunded, .. } => gas_refunded, - ExecutionResult::Halt { reason, gas_used } => { - // here we don't check for invalid opcode because already executed with highest gas - // limit - return Err(RpcInvalidTransactionError::halt(reason, gas_used).into()) - } - ExecutionResult::Revert { output, .. } => { - // if price or limit was included in the request then we can execute the request - // again with the block's gas limit to check if revert is gas related or not - return if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() { - Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db)) - } else { - // the transaction did revert - Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into()) - } - } - }; - - // At this point we know the call succeeded but want to find the _best_ (lowest) gas the - // transaction succeeds with. We find this by doing a binary search over the possible range. - // - // NOTE: this is the gas the transaction used, which is less than the - // transaction requires to succeed. - let mut gas_used = res.result.gas_used(); - // the lowest value is capped by the gas used by the unconstrained transaction - let mut lowest_gas_limit = gas_used.saturating_sub(1); - - // As stated in Geth, there is a good chance that the transaction will pass if we set the - // gas limit to the execution gas used plus the gas refund, so we check this first - // 1 { - // An estimation error is allowed once the current gas limit range used in the binary - // search is small enough (less than 1.5% of the highest gas limit) - // { - // Increase the lowest gas limit if gas is too high - lowest_gas_limit = mid_gas_limit; - } - // Handle other cases, including successful transactions. - ethres => { - // Unpack the result and environment if the transaction was successful. - (res, env) = ethres?; - // Update the estimated gas range based on the transaction result. - update_estimated_gas_range( - res.result, - mid_gas_limit, - &mut highest_gas_limit, - &mut lowest_gas_limit, - )?; - } - } - - // New midpoint - mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64; - } - - Ok(U256::from(highest_gas_limit)) - } - - /// Creates the `AccessList` for the `request` at the [`BlockId`] or latest. - pub(crate) async fn create_access_list_at( - &self, - request: TransactionRequest, - block_number: Option, - ) -> EthResult { - self.on_blocking_task(|this| async move { - this.create_access_list_with(request, block_number).await - }) - .await - } - - async fn create_access_list_with( - &self, - mut request: TransactionRequest, - at: Option, - ) -> EthResult { - let block_id = at.unwrap_or_default(); - let (cfg, block, at) = self.evm_env_at(block_id).await?; - let state = self.state_at(at)?; - - let mut env = build_call_evm_env(cfg, block, request.clone())?; - - // we want to disable this in eth_createAccessList, since this is common practice used by - // other node impls and providers - env.cfg.disable_block_gas_limit = true; - - // The basefee should be ignored for eth_createAccessList - // See: - // - env.cfg.disable_base_fee = true; - - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - if request.gas.is_none() && env.tx.gas_price > U256::ZERO { - // no gas limit was provided in the request, so we need to cap the request's gas limit - cap_tx_gas_limit_with_caller_allowance(&mut db, &mut env.tx)?; - } - - let from = request.from.unwrap_or_default(); - let to = if let Some(TxKind::Call(to)) = request.to { - to - } else { - let nonce = db.basic_ref(from)?.unwrap_or_default().nonce; - from.create(nonce) - }; - - // can consume the list since we're not using the request anymore - let initial = request.access_list.take().unwrap_or_default(); - - let precompiles = get_precompiles(env.handler_cfg.spec_id); - let mut inspector = AccessListInspector::new(initial, from, to, precompiles); - let (result, env) = self.inspect(&mut db, env, &mut inspector)?; - - match result.result { - ExecutionResult::Halt { reason, .. } => Err(match reason { - HaltReason::NonceOverflow => RpcInvalidTransactionError::NonceMaxValue, - halt => RpcInvalidTransactionError::EvmHalt(halt), - }), - ExecutionResult::Revert { output, .. } => { - Err(RpcInvalidTransactionError::Revert(RevertError::new(output))) - } - ExecutionResult::Success { .. } => Ok(()), - }?; - - let access_list = inspector.into_access_list(); - - let cfg_with_spec_id = - CfgEnvWithHandlerCfg { cfg_env: env.cfg.clone(), handler_cfg: env.handler_cfg }; - - // calculate the gas used using the access list - request.access_list = Some(access_list.clone()); - let gas_used = - self.estimate_gas_with(cfg_with_spec_id, env.block.clone(), request, &*db.db, None)?; - - Ok(AccessListWithGasUsed { access_list, gas_used }) - } - - /// Executes the requests again after an out of gas error to check if the error is gas related - /// or not - #[inline] - fn map_out_of_gas_err( - &self, - env_gas_limit: U256, - mut env: EnvWithHandlerCfg, - db: &mut CacheDB>, - ) -> EthApiError - where - S: StateProvider, - { - let req_gas_limit = env.tx.gas_limit; - env.tx.gas_limit = env_gas_limit.try_into().unwrap_or(u64::MAX); - let (res, _) = match self.transact(db, env) { - Ok(res) => res, - Err(err) => return err, - }; - match res.result { - ExecutionResult::Success { .. } => { - // transaction succeeded by manually increasing the gas limit to - // highest, which means the caller lacks funds to pay for the tx - RpcInvalidTransactionError::BasicOutOfGas(req_gas_limit).into() - } - ExecutionResult::Revert { output, .. } => { - // reverted again after bumping the limit - RpcInvalidTransactionError::Revert(RevertError::new(output)).into() - } - ExecutionResult::Halt { reason, .. } => { - RpcInvalidTransactionError::EvmHalt(reason).into() - } - } - } -} - -/// Updates the highest and lowest gas limits for binary search based on the execution result. -/// -/// This function refines the gas limit estimates used in a binary search to find the optimal gas -/// limit for a transaction. It adjusts the highest or lowest gas limits depending on whether the -/// execution succeeded, reverted, or halted due to specific reasons. -#[inline] -fn update_estimated_gas_range( - result: ExecutionResult, - tx_gas_limit: u64, - highest_gas_limit: &mut u64, - lowest_gas_limit: &mut u64, -) -> EthResult<()> { - match result { - ExecutionResult::Success { .. } => { - // Cap the highest gas limit with the succeeding gas limit. - *highest_gas_limit = tx_gas_limit; - } - ExecutionResult::Revert { .. } => { - // Increase the lowest gas limit. - *lowest_gas_limit = tx_gas_limit; - } - ExecutionResult::Halt { reason, .. } => { - match reason { - HaltReason::OutOfGas(_) | HaltReason::InvalidEFOpcode => { - // Both `OutOfGas` and `InvalidFEOpcode` can occur dynamically if the gas left - // is too low. Treat this as an out of gas condition, - // knowing that the call succeeds with a higher gas limit. - // - // Common usage of invalid opcode in OpenZeppelin: - // - - // Increase the lowest gas limit. - *lowest_gas_limit = tx_gas_limit; - } - err => { - // These cases should be unreachable because we know the transaction succeeds, - // but if they occur, treat them as an error. - return Err(RpcInvalidTransactionError::EvmHalt(err).into()) - } - } - } - }; - Ok(()) -} diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs deleted file mode 100644 index 2493d6055778..000000000000 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Contains RPC handler implementations for fee history. - -use crate::{ - eth::{ - api::fee_history::{calculate_reward_percentiles_for_block, FeeHistoryEntry}, - error::{EthApiError, EthResult}, - }, - EthApi, -}; -use reth_evm::ConfigureEvm; -use reth_network_api::NetworkInfo; -use reth_primitives::{BlockNumberOrTag, U256}; -use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; -use reth_rpc_types::FeeHistory; -use reth_transaction_pool::TransactionPool; -use tracing::debug; - -impl EthApi -where - Pool: TransactionPool + Clone + 'static, - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: NetworkInfo + Send + Sync + 'static, - EvmConfig: ConfigureEvm + 'static, -{ - /// Returns a suggestion for a gas price for legacy transactions. - /// - /// See also: - pub(crate) async fn gas_price(&self) -> EthResult { - let header = self.block(BlockNumberOrTag::Latest); - let suggested_tip = self.suggested_priority_fee(); - let (header, suggested_tip) = futures::try_join!(header, suggested_tip)?; - let base_fee = header.and_then(|h| h.base_fee_per_gas).unwrap_or_default(); - Ok(suggested_tip + U256::from(base_fee)) - } - - /// Returns a suggestion for a base fee for blob transactions. - pub(crate) async fn blob_base_fee(&self) -> EthResult { - self.block(BlockNumberOrTag::Latest) - .await? - .and_then(|h: reth_primitives::SealedBlock| h.next_block_blob_fee()) - .ok_or(EthApiError::ExcessBlobGasNotSet) - .map(U256::from) - } - - /// Returns a suggestion for the priority fee (the tip) - pub(crate) async fn suggested_priority_fee(&self) -> EthResult { - self.gas_oracle().suggest_tip_cap().await - } - - /// Reports the fee history, for the given amount of blocks, up until the given newest block. - /// - /// If `reward_percentiles` are provided the [`FeeHistory`] will include the _approximated_ - /// rewards for the requested range. - pub(crate) async fn fee_history( - &self, - mut block_count: u64, - newest_block: BlockNumberOrTag, - reward_percentiles: Option>, - ) -> EthResult { - if block_count == 0 { - return Ok(FeeHistory::default()) - } - - // See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225 - let max_fee_history = if reward_percentiles.is_none() { - self.gas_oracle().config().max_header_history - } else { - self.gas_oracle().config().max_block_history - }; - - if block_count > max_fee_history { - debug!( - requested = block_count, - truncated = max_fee_history, - "Sanitizing fee history block count" - ); - block_count = max_fee_history - } - - let Some(end_block) = self.provider().block_number_for_id(newest_block.into())? else { - return Err(EthApiError::UnknownBlockNumber) - }; - - // need to add 1 to the end block to get the correct (inclusive) range - let end_block_plus = end_block + 1; - // Ensure that we would not be querying outside of genesis - if end_block_plus < block_count { - block_count = end_block_plus; - } - - // If reward percentiles were specified, we - // need to validate that they are monotonically - // increasing and 0 <= p <= 100 - // Note: The types used ensure that the percentiles are never < 0 - if let Some(percentiles) = &reward_percentiles { - if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) { - return Err(EthApiError::InvalidRewardPercentiles) - } - } - - // Fetch the headers and ensure we got all of them - // - // Treat a request for 1 block as a request for `newest_block..=newest_block`, - // otherwise `newest_block - 2 - // NOTE: We ensured that block count is capped - let start_block = end_block_plus - block_count; - - // Collect base fees, gas usage ratios and (optionally) reward percentile data - let mut base_fee_per_gas: Vec = Vec::new(); - let mut gas_used_ratio: Vec = Vec::new(); - - let mut base_fee_per_blob_gas: Vec = Vec::new(); - let mut blob_gas_used_ratio: Vec = Vec::new(); - - let mut rewards: Vec> = Vec::new(); - - // Check if the requested range is within the cache bounds - let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await; - - if let Some(fee_entries) = fee_entries { - if fee_entries.len() != block_count as usize { - return Err(EthApiError::InvalidBlockRange) - } - - for entry in &fee_entries { - base_fee_per_gas.push(entry.base_fee_per_gas as u128); - gas_used_ratio.push(entry.gas_used_ratio); - base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default()); - blob_gas_used_ratio.push(entry.blob_gas_used_ratio); - - if let Some(percentiles) = &reward_percentiles { - let mut block_rewards = Vec::with_capacity(percentiles.len()); - for &percentile in percentiles { - block_rewards.push(self.approximate_percentile(entry, percentile)); - } - rewards.push(block_rewards); - } - } - let last_entry = fee_entries.last().expect("is not empty"); - - // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the next - // block - base_fee_per_gas - .push(last_entry.next_block_base_fee(&self.provider().chain_spec()) as u128); - - base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); - } else { - // read the requested header range - let headers = self.provider().sealed_headers_range(start_block..=end_block)?; - if headers.len() != block_count as usize { - return Err(EthApiError::InvalidBlockRange) - } - - for header in &headers { - let ratio = if header.gas_limit > 0 {header.gas_used as f64 / header.gas_limit as f64} else {1.0}; - - base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default() as u128); - gas_used_ratio.push(ratio); - base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default()); - blob_gas_used_ratio.push( - header.blob_gas_used.unwrap_or_default() as f64 / - reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64, - ); - - // Percentiles were specified, so we need to collect reward percentile ino - if let Some(percentiles) = &reward_percentiles { - let (transactions, receipts) = self - .cache() - .get_transactions_and_receipts(header.hash()) - .await? - .ok_or(EthApiError::InvalidBlockRange)?; - rewards.push( - calculate_reward_percentiles_for_block( - percentiles, - header.gas_used, - header.base_fee_per_gas.unwrap_or_default(), - &transactions, - &receipts, - ) - .unwrap_or_default(), - ); - } - } - - // The spec states that `base_fee_per_gas` "[..] includes the next block after the - // newest of the returned range, because this value can be derived from the - // newest block" - // - // The unwrap is safe since we checked earlier that we got at least 1 header. - let last_header = headers.last().expect("is present"); - base_fee_per_gas.push( - self.provider().chain_spec().base_fee_params_at_timestamp(last_header.timestamp).next_block_base_fee( - last_header.gas_used as u128, - last_header.gas_limit as u128, - last_header.base_fee_per_gas.unwrap_or_default() as u128, - )); - - // Same goes for the `base_fee_per_blob_gas`: - // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. - base_fee_per_blob_gas - .push(last_header.next_block_blob_fee().unwrap_or_default()); - }; - - Ok(FeeHistory { - base_fee_per_gas, - gas_used_ratio, - base_fee_per_blob_gas, - blob_gas_used_ratio, - oldest_block: start_block, - reward: reward_percentiles.map(|_| rewards), - }) - } - - /// Approximates reward at a given percentile for a specific block - /// Based on the configured resolution - fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 { - let resolution = self.fee_history_cache().resolution(); - let rounded_percentile = - (requested_percentile * resolution as f64).round() / resolution as f64; - let clamped_percentile = rounded_percentile.clamp(0.0, 100.0); - - // Calculate the index in the precomputed rewards array - let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize; - // Fetch the reward from the FeeHistoryEntry - entry.rewards.get(index).cloned().unwrap_or_default() - } -} diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs deleted file mode 100644 index 364a55842d3c..000000000000 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ /dev/null @@ -1,503 +0,0 @@ -//! The entire implementation of the namespace is quite large, hence it is divided across several -//! files. - -use crate::eth::{ - api::{ - fee_history::FeeHistoryCache, - pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}, - }, - cache::EthStateCache, - error::{EthApiError, EthResult}, - gas_oracle::GasPriceOracle, - signer::EthSigner, - traits::RawTransactionForwarder, -}; -use async_trait::async_trait; -use reth_chainspec::ChainInfo; -use reth_errors::{RethError, RethResult}; -use reth_evm::ConfigureEvm; -use reth_network_api::NetworkInfo; -use reth_primitives::{ - revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg}, - Address, BlockId, BlockNumberOrTag, SealedBlockWithSenders, SealedHeader, B256, U256, U64, -}; -use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory, -}; -use reth_rpc_types::{SyncInfo, SyncStatus}; -use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor}; -use reth_transaction_pool::TransactionPool; -use revm_primitives::{CfgEnv, SpecId}; -use std::{ - fmt::Debug, - future::Future, - sync::Arc, - time::{Duration, Instant}, -}; -use tokio::sync::{oneshot, Mutex}; - -mod block; -mod call; -pub(crate) mod fee_history; - -mod fees; -#[cfg(feature = "optimism")] -mod optimism; -mod pending_block; -mod server; -mod sign; -mod state; -mod transactions; - -pub use transactions::{EthTransactions, TransactionSource}; - -/// `Eth` API trait. -/// -/// Defines core functionality of the `eth` API implementation. -#[async_trait] -pub trait EthApiSpec: EthTransactions + Send + Sync { - /// Returns the current ethereum protocol version. - async fn protocol_version(&self) -> RethResult; - - /// Returns the chain id - fn chain_id(&self) -> U64; - - /// Returns provider chain info - fn chain_info(&self) -> RethResult; - - /// Returns a list of addresses owned by provider. - fn accounts(&self) -> Vec
; - - /// Returns `true` if the network is undergoing sync. - fn is_syncing(&self) -> bool; - - /// Returns the [SyncStatus] of the network - fn sync_status(&self) -> RethResult; -} - -/// `Eth` API implementation. -/// -/// This type provides the functionality for handling `eth_` related requests. -/// These are implemented two-fold: Core functionality is implemented as [`EthApiSpec`] -/// trait. Additionally, the required server implementations (e.g. [`reth_rpc_api::EthApiServer`]) -/// are implemented separately in submodules. The rpc handler implementation can then delegate to -/// the main impls. This way [`EthApi`] is not limited to [`jsonrpsee`] and can be used standalone -/// or in other network handlers (for example ipc). -pub struct EthApi { - /// All nested fields bundled together. - inner: Arc>, -} - -impl EthApi { - /// Sets a forwarder for `eth_sendRawTransaction` - /// - /// Note: this might be removed in the future in favor of a more generic approach. - pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { - self.inner.raw_transaction_forwarder.write().replace(forwarder); - } -} - -impl EthApi -where - Provider: BlockReaderIdExt + ChainSpecProvider, -{ - /// Creates a new, shareable instance using the default tokio task spawner. - #[allow(clippy::too_many_arguments)] - pub fn new( - provider: Provider, - pool: Pool, - network: Network, - eth_cache: EthStateCache, - gas_oracle: GasPriceOracle, - gas_cap: impl Into, - blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, - evm_config: EvmConfig, - raw_transaction_forwarder: Option>, - ) -> Self { - Self::with_spawner( - provider, - pool, - network, - eth_cache, - gas_oracle, - gas_cap.into().into(), - Box::::default(), - blocking_task_pool, - fee_history_cache, - evm_config, - raw_transaction_forwarder, - ) - } - - /// Creates a new, shareable instance. - #[allow(clippy::too_many_arguments)] - pub fn with_spawner( - provider: Provider, - pool: Pool, - network: Network, - eth_cache: EthStateCache, - gas_oracle: GasPriceOracle, - gas_cap: u64, - task_spawner: Box, - blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, - evm_config: EvmConfig, - raw_transaction_forwarder: Option>, - ) -> Self { - // get the block number of the latest block - let latest_block = provider - .header_by_number_or_tag(BlockNumberOrTag::Latest) - .ok() - .flatten() - .map(|header| header.number) - .unwrap_or_default(); - - let inner = EthApiInner { - provider, - pool, - network, - signers: parking_lot::RwLock::new(Default::default()), - eth_cache, - gas_oracle, - gas_cap, - starting_block: U256::from(latest_block), - task_spawner, - pending_block: Default::default(), - blocking_task_pool, - fee_history_cache, - evm_config, - raw_transaction_forwarder: parking_lot::RwLock::new(raw_transaction_forwarder), - }; - - Self { inner: Arc::new(inner) } - } - - /// Executes the future on a new blocking task. - /// - /// This accepts a closure that creates a new future using a clone of this type and spawns the - /// future onto a new task that is allowed to block. - /// - /// Note: This is expected for futures that are dominated by blocking IO operations. - pub(crate) async fn on_blocking_task(&self, c: C) -> EthResult - where - C: FnOnce(Self) -> F, - F: Future> + Send + 'static, - R: Send + 'static, - { - let (tx, rx) = oneshot::channel(); - let this = self.clone(); - let f = c(this); - self.inner.task_spawner.spawn_blocking(Box::pin(async move { - let res = f.await; - let _ = tx.send(res); - })); - rx.await.map_err(|_| EthApiError::InternalEthError)? - } - - /// Returns the state cache frontend - pub(crate) fn cache(&self) -> &EthStateCache { - &self.inner.eth_cache - } - - /// Returns the gas oracle frontend - pub(crate) fn gas_oracle(&self) -> &GasPriceOracle { - &self.inner.gas_oracle - } - - /// Returns the configured gas limit cap for `eth_call` and tracing related calls - pub fn gas_cap(&self) -> u64 { - self.inner.gas_cap - } - - /// Returns the inner `Provider` - pub fn provider(&self) -> &Provider { - &self.inner.provider - } - - /// Returns the inner `Network` - pub fn network(&self) -> &Network { - &self.inner.network - } - - /// Returns the inner `Pool` - pub fn pool(&self) -> &Pool { - &self.inner.pool - } - - /// Returns fee history cache - pub fn fee_history_cache(&self) -> &FeeHistoryCache { - &self.inner.fee_history_cache - } -} - -// === State access helpers === - -impl EthApi -where - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, -{ - /// Returns the state at the given [`BlockId`] enum. - /// - /// Note: if not [`BlockNumberOrTag::Pending`] then this will only return canonical state. See also - pub fn state_at_block_id(&self, at: BlockId) -> EthResult { - Ok(self.provider().state_by_block_id(at)?) - } - - /// Returns the state at the given [`BlockId`] enum or the latest. - /// - /// Convenience function to interprets `None` as `BlockId::Number(BlockNumberOrTag::Latest)` - pub fn state_at_block_id_or_latest( - &self, - block_id: Option, - ) -> EthResult { - if let Some(block_id) = block_id { - self.state_at_block_id(block_id) - } else { - Ok(self.latest_state()?) - } - } - - /// Returns the state at the given block number - pub fn state_at_hash(&self, block_hash: B256) -> RethResult { - Ok(self.provider().history_by_block_hash(block_hash)?) - } - - /// Returns the _latest_ state - pub fn latest_state(&self) -> RethResult { - Ok(self.provider().latest()?) - } -} - -impl EthApi -where - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Pool: TransactionPool + Clone + 'static, - Network: NetworkInfo + Send + Sync + 'static, - EvmConfig: ConfigureEvm + Clone + 'static, -{ - /// Configures the [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the pending block - /// - /// If no pending block is available, this will derive it from the `latest` block - pub(crate) fn pending_block_env_and_cfg(&self) -> EthResult { - let origin: PendingBlockEnvOrigin = if let Some(pending) = - self.provider().pending_block_with_senders()? - { - PendingBlockEnvOrigin::ActualPending(pending) - } else { - // no pending block from the CL yet, so we use the latest block and modify the env - // values that we can - let latest = - self.provider().latest_header()?.ok_or_else(|| EthApiError::UnknownBlockNumber)?; - - let (mut latest_header, block_hash) = latest.split(); - // child block - latest_header.number += 1; - // assumed child block is in the next slot: 12s - latest_header.timestamp += 12; - // base fee of the child block - let chain_spec = self.provider().chain_spec(); - - latest_header.base_fee_per_gas = latest_header.next_block_base_fee( - chain_spec.base_fee_params_at_timestamp(latest_header.timestamp), - ); - - // update excess blob gas consumed above target - latest_header.excess_blob_gas = latest_header.next_block_excess_blob_gas(); - - // we're reusing the same block hash because we need this to lookup the block's state - let latest = SealedHeader::new(latest_header, block_hash); - - PendingBlockEnvOrigin::DerivedFromLatest(latest) - }; - - let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::LATEST); - - let mut block_env = BlockEnv::default(); - // Note: for the PENDING block we assume it is past the known merge block and thus this will - // not fail when looking up the total difficulty value for the blockenv. - self.provider().fill_env_with_header( - &mut cfg, - &mut block_env, - origin.header(), - self.inner.evm_config.clone(), - )?; - - Ok(PendingBlockEnv { cfg, block_env, origin }) - } - - /// Returns the locally built pending block - pub(crate) async fn local_pending_block(&self) -> EthResult> { - let pending = self.pending_block_env_and_cfg()?; - if pending.origin.is_actual_pending() { - return Ok(pending.origin.into_actual_pending()) - } - - // no pending block from the CL yet, so we need to build it ourselves via txpool - self.on_blocking_task(|this| async move { - let mut lock = this.inner.pending_block.lock().await; - let now = Instant::now(); - - // check if the block is still good - if let Some(pending_block) = lock.as_ref() { - // this is guaranteed to be the `latest` header - if pending.block_env.number.to::() == pending_block.block.number && - pending.origin.header().hash() == pending_block.block.parent_hash && - now <= pending_block.expires_at - { - return Ok(Some(pending_block.block.clone())) - } - } - - // we rebuild the block - let pending_block = match pending.build_block(this.provider(), this.pool()) { - Ok(block) => block, - Err(err) => { - tracing::debug!(target: "rpc", "Failed to build pending block: {:?}", err); - return Ok(None) - } - }; - - let now = Instant::now(); - *lock = Some(PendingBlock { - block: pending_block.clone(), - expires_at: now + Duration::from_secs(1), - }); - - Ok(Some(pending_block)) - }) - .await - } -} - -impl std::fmt::Debug - for EthApi -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EthApi").finish_non_exhaustive() - } -} - -impl Clone for EthApi { - fn clone(&self) -> Self { - Self { inner: Arc::clone(&self.inner) } - } -} - -#[async_trait] -impl EthApiSpec for EthApi -where - Pool: TransactionPool + Clone + 'static, - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: NetworkInfo + 'static, - EvmConfig: ConfigureEvm + 'static, -{ - /// Returns the current ethereum protocol version. - /// - /// Note: This returns an `U64`, since this should return as hex string. - async fn protocol_version(&self) -> RethResult { - let status = self.network().network_status().await.map_err(RethError::other)?; - Ok(U64::from(status.protocol_version)) - } - - /// Returns the chain id - fn chain_id(&self) -> U64 { - U64::from(self.network().chain_id()) - } - - /// Returns the current info for the chain - fn chain_info(&self) -> RethResult { - Ok(self.provider().chain_info()?) - } - - fn accounts(&self) -> Vec
{ - self.inner.signers.read().iter().flat_map(|s| s.accounts()).collect() - } - - fn is_syncing(&self) -> bool { - self.network().is_syncing() - } - - /// Returns the [SyncStatus] of the network - fn sync_status(&self) -> RethResult { - let status = if self.is_syncing() { - let current_block = U256::from( - self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(), - ); - SyncStatus::Info(SyncInfo { - starting_block: self.inner.starting_block, - current_block, - highest_block: current_block, - warp_chunks_amount: None, - warp_chunks_processed: None, - }) - } else { - SyncStatus::None - }; - Ok(status) - } -} - -/// The default gas limit for `eth_call` and adjacent calls. -/// -/// This is different from the default to regular 30M block gas limit -/// [`ETHEREUM_BLOCK_GAS_LIMIT`](reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT) to allow for -/// more complex calls. -pub const RPC_DEFAULT_GAS_CAP: GasCap = GasCap(50_000_000); - -/// The wrapper type for gas limit -#[derive(Debug, Clone, Copy)] -pub struct GasCap(u64); - -impl Default for GasCap { - fn default() -> Self { - RPC_DEFAULT_GAS_CAP - } -} - -impl From for GasCap { - fn from(gas_cap: u64) -> Self { - Self(gas_cap) - } -} - -impl From for u64 { - fn from(gas_cap: GasCap) -> Self { - gas_cap.0 - } -} - -/// Container type `EthApi` -struct EthApiInner { - /// The transaction pool. - pool: Pool, - /// The provider that can interact with the chain. - provider: Provider, - /// An interface to interact with the network - network: Network, - /// All configured Signers - signers: parking_lot::RwLock>>, - /// The async cache frontend for eth related data - eth_cache: EthStateCache, - /// The async gas oracle frontend for gas price suggestions - gas_oracle: GasPriceOracle, - /// Maximum gas limit for `eth_call` and call tracing RPC methods. - gas_cap: u64, - /// The block number at which the node started - starting_block: U256, - /// The type that can spawn tasks which would otherwise block. - task_spawner: Box, - /// Cached pending block if any - pending_block: Mutex>, - /// A pool dedicated to blocking tasks. - blocking_task_pool: BlockingTaskPool, - /// Cache for block fees history - fee_history_cache: FeeHistoryCache, - /// The type that defines how to configure the EVM - evm_config: EvmConfig, - /// Allows forwarding received raw transactions - raw_transaction_forwarder: parking_lot::RwLock>>, -} diff --git a/crates/rpc/rpc/src/eth/api/optimism.rs b/crates/rpc/rpc/src/eth/api/optimism.rs deleted file mode 100644 index af58450145b8..000000000000 --- a/crates/rpc/rpc/src/eth/api/optimism.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Optimism helpers. - -use revm::L1BlockInfo; - -/// Optimism Transaction Metadata -/// -/// Includes the L1 fee and data gas for the tx along with the L1 -/// block info. In order to pass the [`OptimismTxMeta`] into the -/// async colored `build_transaction_receipt_with_block_receipts` -/// function, a reference counter for the L1 block info is -/// used so the L1 block info can be shared between receipts. -#[derive(Debug, Default, Clone)] -pub(crate) struct OptimismTxMeta { - /// The L1 block info. - pub(crate) l1_block_info: Option, - /// The L1 fee for the block. - pub(crate) l1_fee: Option, - /// The L1 data gas for the block. - pub(crate) l1_data_gas: Option, -} - -impl OptimismTxMeta { - /// Creates a new [`OptimismTxMeta`]. - pub(crate) const fn new( - l1_block_info: Option, - l1_fee: Option, - l1_data_gas: Option, - ) -> Self { - Self { l1_block_info, l1_fee, l1_data_gas } - } -} diff --git a/crates/rpc/rpc/src/eth/api/sign.rs b/crates/rpc/rpc/src/eth/api/sign.rs deleted file mode 100644 index 5256de4a4dae..000000000000 --- a/crates/rpc/rpc/src/eth/api/sign.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Contains RPC handler implementations specific to sign endpoints - -use crate::{ - eth::{ - error::{EthResult, SignError}, - signer::{DevSigner, EthSigner}, - }, - EthApi, -}; -use alloy_dyn_abi::TypedData; -use reth_primitives::{Address, Bytes}; - -impl EthApi { - pub(crate) async fn sign(&self, account: Address, message: &[u8]) -> EthResult { - Ok(self.find_signer(&account)?.sign(account, message).await?.to_hex_bytes()) - } - - pub(crate) fn sign_typed_data(&self, data: &TypedData, account: Address) -> EthResult { - Ok(self.find_signer(&account)?.sign_typed_data(account, data)?.to_hex_bytes()) - } - - pub(crate) fn find_signer( - &self, - account: &Address, - ) -> Result, SignError> { - self.inner - .signers - .read() - .iter() - .find(|signer| signer.is_signer_for(account)) - .map(|signer| dyn_clone::clone_box(&**signer)) - .ok_or(SignError::NoAccount) - } - - /// Generates 20 random developer accounts. - /// Used in DEV mode. - pub fn with_dev_accounts(&self) { - let mut signers = self.inner.signers.write(); - *signers = DevSigner::random_signers(20); - } -} diff --git a/crates/rpc/rpc/src/eth/api/state.rs b/crates/rpc/rpc/src/eth/api/state.rs deleted file mode 100644 index d7c1bafacf9f..000000000000 --- a/crates/rpc/rpc/src/eth/api/state.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! Contains RPC handler implementations specific to state. - -use crate::{ - eth::error::{EthApiError, EthResult, RpcInvalidTransactionError}, - EthApi, -}; -use reth_evm::ConfigureEvm; -use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, U256}; -use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProvider, StateProviderFactory, -}; -use reth_rpc_types::{serde_helpers::JsonStorageKey, EIP1186AccountProofResponse}; -use reth_rpc_types_compat::proof::from_primitive_account_proof; -use reth_transaction_pool::{PoolTransaction, TransactionPool}; - -impl EthApi -where - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Pool: TransactionPool + Clone + 'static, - Network: Send + Sync + 'static, - EvmConfig: ConfigureEvm + 'static, -{ - pub(crate) fn get_code(&self, address: Address, block_id: Option) -> EthResult { - Ok(self - .state_at_block_id_or_latest(block_id)? - .account_code(address)? - .unwrap_or_default() - .original_bytes()) - } - - pub(crate) fn balance(&self, address: Address, block_id: Option) -> EthResult { - Ok(self - .state_at_block_id_or_latest(block_id)? - .account_balance(address)? - .unwrap_or_default()) - } - - /// Returns the number of transactions sent from an address at the given block identifier. - /// - /// If this is [`BlockNumberOrTag::Pending`] then this will look up the highest transaction in - /// pool and return the next nonce (highest + 1). - pub(crate) fn get_transaction_count( - &self, - address: Address, - block_id: Option, - ) -> EthResult { - if block_id == Some(BlockId::pending()) { - let address_txs = self.pool().get_transactions_by_sender(address); - if let Some(highest_nonce) = - address_txs.iter().map(|item| item.transaction.nonce()).max() - { - let tx_count = highest_nonce - .checked_add(1) - .ok_or(RpcInvalidTransactionError::NonceMaxValue)?; - return Ok(U256::from(tx_count)) - } - } - - let state = self.state_at_block_id_or_latest(block_id)?; - Ok(U256::from(state.account_nonce(address)?.unwrap_or_default())) - } - - pub(crate) fn storage_at( - &self, - address: Address, - index: JsonStorageKey, - block_id: Option, - ) -> EthResult { - Ok(B256::new( - self.state_at_block_id_or_latest(block_id)? - .storage(address, index.0)? - .unwrap_or_default() - .to_be_bytes(), - )) - } - - pub(crate) async fn get_proof( - &self, - address: Address, - keys: Vec, - block_id: Option, - ) -> EthResult { - let chain_info = self.provider().chain_info()?; - let block_id = block_id.unwrap_or_default(); - - // if we are trying to create a proof for the latest block, but have a BlockId as input - // that is not BlockNumberOrTag::Latest, then we need to figure out whether or not the - // BlockId corresponds to the latest block - let is_latest_block = match block_id { - BlockId::Number(BlockNumberOrTag::Number(num)) => num == chain_info.best_number, - BlockId::Hash(hash) => hash == chain_info.best_hash.into(), - BlockId::Number(BlockNumberOrTag::Latest) => true, - _ => false, - }; - - // TODO: remove when HistoricalStateProviderRef::proof is implemented - if !is_latest_block { - return Err(EthApiError::InvalidBlockRange) - } - - let this = self.clone(); - self.inner - .blocking_task_pool - .spawn(move || { - let state = this.state_at_block_id(block_id)?; - let storage_keys = keys.iter().map(|key| key.0).collect::>(); - let proof = state.proof(address, &storage_keys)?; - Ok(from_primitive_account_proof(proof)) - }) - .await - .map_err(|_| EthApiError::InternalBlockingTaskError)? - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::eth::{ - cache::EthStateCache, gas_oracle::GasPriceOracle, FeeHistoryCache, FeeHistoryCacheConfig, - }; - use reth_evm_ethereum::EthEvmConfig; - use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, StorageKey, StorageValue}; - use reth_provider::test_utils::{ExtendedAccount, MockEthProvider, NoopProvider}; - use reth_tasks::pool::BlockingTaskPool; - use reth_transaction_pool::test_utils::testing_pool; - use std::collections::HashMap; - - #[tokio::test] - async fn test_storage() { - // === Noop === - let pool = testing_pool(); - let evm_config = EthEvmConfig::default(); - - let cache = EthStateCache::spawn(NoopProvider::default(), Default::default(), evm_config); - let eth_api = EthApi::new( - NoopProvider::default(), - pool.clone(), - (), - cache.clone(), - GasPriceOracle::new(NoopProvider::default(), Default::default(), cache.clone()), - ETHEREUM_BLOCK_GAS_LIMIT, - BlockingTaskPool::build().expect("failed to build tracing pool"), - FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), - evm_config, - None, - ); - let address = Address::random(); - let storage = eth_api.storage_at(address, U256::ZERO.into(), None).unwrap(); - assert_eq!(storage, U256::ZERO.to_be_bytes()); - - // === Mock === - let mock_provider = MockEthProvider::default(); - let storage_value = StorageValue::from(1337); - let storage_key = StorageKey::random(); - let storage = HashMap::from([(storage_key, storage_value)]); - let account = ExtendedAccount::new(0, U256::ZERO).extend_storage(storage); - mock_provider.add_account(address, account); - - let cache = EthStateCache::spawn(mock_provider.clone(), Default::default(), evm_config); - let eth_api = EthApi::new( - mock_provider.clone(), - pool, - (), - cache.clone(), - GasPriceOracle::new(mock_provider, Default::default(), cache.clone()), - ETHEREUM_BLOCK_GAS_LIMIT, - BlockingTaskPool::build().expect("failed to build tracing pool"), - FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), - evm_config, - None, - ); - - let storage_key: U256 = storage_key.into(); - let storage = eth_api.storage_at(address, storage_key.into(), None).unwrap(); - assert_eq!(storage, storage_value.to_be_bytes()); - } -} diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs deleted file mode 100644 index 8829a0434e49..000000000000 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ /dev/null @@ -1,1861 +0,0 @@ -//! Contains RPC handler implementations specific to transactions -use crate::{ - eth::{ - api::pending_block::PendingBlockEnv, - error::{EthApiError, EthResult, RpcInvalidTransactionError, SignError}, - revm_utils::prepare_call_env, - utils::recover_raw_transaction, - }, - EthApi, EthApiSpec, -}; -use alloy_primitives::TxKind as RpcTransactionKind; -use async_trait::async_trait; -use reth_evm::ConfigureEvm; -use reth_network_api::NetworkInfo; -use reth_primitives::{ - eip4844::calc_blob_gasprice, - revm::env::{fill_block_env_with_coinbase, tx_env_with_recovered}, - Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredPooledTransaction, Header, - IntoRecoveredTransaction, Receipt, SealedBlock, SealedBlockWithSenders, TransactionMeta, - TransactionSigned, TransactionSignedEcRecovered, - TxKind::{Call, Create}, - B256, U256, -}; -use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory, -}; -use reth_revm::database::StateProviderDatabase; -use reth_rpc_types::{ - state::EvmOverrides, - transaction::{ - EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest, - LegacyTransactionRequest, - }, - AnyReceiptEnvelope, AnyTransactionReceipt, Index, Log, ReceiptWithBloom, Transaction, - TransactionInfo, TransactionReceipt, TransactionRequest, TypedTransactionRequest, - WithOtherFields, -}; -use reth_rpc_types_compat::transaction::from_recovered_with_block_context; -use reth_transaction_pool::{TransactionOrigin, TransactionPool}; -use revm::{ - db::CacheDB, - primitives::{ - db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, EvmState, - ExecutionResult, ResultAndState, SpecId, - }, - GetInspector, Inspector, -}; -use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; -use std::future::Future; - -use crate::eth::revm_utils::FillableTransaction; -#[cfg(feature = "optimism")] -use reth_rpc_types::OptimismTransactionReceiptFields; -use revm_primitives::db::{Database, DatabaseRef}; - -/// Helper alias type for the state's [`CacheDB`] -pub(crate) type StateCacheDB = CacheDB>; - -/// Commonly used transaction related functions for the [EthApi] type in the `eth_` namespace. -/// -/// This includes utilities for transaction tracing, transacting and inspection. -/// -/// Async functions that are spawned onto the -/// [BlockingTaskPool](reth_tasks::pool::BlockingTaskPool) begin with `spawn_` -/// -/// ## Calls -/// -/// There are subtle differences between when transacting [TransactionRequest]: -/// -/// The endpoints `eth_call` and `eth_estimateGas` and `eth_createAccessList` should always -/// __disable__ the base fee check in the [EnvWithHandlerCfg] -/// [Cfg](revm_primitives::CfgEnvWithHandlerCfg). -/// -/// The behaviour for tracing endpoints is not consistent across clients. -/// Geth also disables the basefee check for tracing: -/// Erigon does not: -/// -/// See also -/// -/// This implementation follows the behaviour of Geth and disables the basefee check for tracing. -#[async_trait::async_trait] -pub trait EthTransactions: Send + Sync { - /// Executes the [EnvWithHandlerCfg] against the given [Database] without committing state - /// changes. - fn transact( - &self, - db: DB, - env: EnvWithHandlerCfg, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into; - - /// Executes the [EnvWithHandlerCfg] against the given [Database] without committing state - /// changes. - fn inspect( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into, - I: GetInspector; - - /// Same as [Self::inspect] but also returns the database again. - /// - /// Even though [Database] is also implemented on `&mut` - /// this is still useful if there are certain trait bounds on the Inspector's database generic - /// type - fn inspect_and_return_db( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg, DB)> - where - DB: Database, - ::Error: Into, - I: GetInspector; - - /// Replays all the transactions until the target transaction is found. - /// - /// All transactions before the target transaction are executed and their changes are written to - /// the _runtime_ db ([CacheDB]). - /// - /// Note: This assumes the target transaction is in the given iterator. - /// Returns the index of the target transaction in the given iterator. - fn replay_transactions_until( - &self, - db: &mut CacheDB, - cfg: CfgEnvWithHandlerCfg, - block_env: BlockEnv, - transactions: I, - target_tx_hash: B256, - ) -> Result - where - DB: DatabaseRef, - EthApiError: From<::Error>, - I: IntoIterator, - Tx: FillableTransaction; - - /// Returns default gas limit to use for `eth_call` and tracing RPC methods. - fn call_gas_limit(&self) -> u64; - - /// Executes the future on a new blocking task. - /// - /// Note: This is expected for futures that are dominated by blocking IO operations, for tracing - /// or CPU bound operations in general use [Self::spawn_blocking]. - async fn spawn_blocking_future(&self, c: F) -> EthResult - where - F: Future> + Send + 'static, - R: Send + 'static; - - /// Executes a blocking on the tracing pol. - /// - /// Note: This is expected for futures that are predominantly CPU bound, for blocking IO futures - /// use [Self::spawn_blocking_future]. - async fn spawn_blocking(&self, c: F) -> EthResult - where - F: FnOnce() -> EthResult + Send + 'static, - R: Send + 'static; - - /// Returns the state at the given [BlockId] - fn state_at(&self, at: BlockId) -> EthResult; - - /// Executes the closure with the state that corresponds to the given [BlockId]. - fn with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult; - - /// Executes the closure with the state that corresponds to the given [BlockId] on a new task - async fn spawn_with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult + Send + 'static, - T: Send + 'static; - - /// Returns the revm evm env for the requested [BlockId] - /// - /// If the [BlockId] this will return the [BlockId] of the block the env was configured - /// for. - /// If the [BlockId] is pending, this will return the "Pending" tag, otherwise this returns the - /// hash of the exact block. - async fn evm_env_at(&self, at: BlockId) - -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv, BlockId)>; - - /// Returns the revm evm env for the raw block header - /// - /// This is used for tracing raw blocks - async fn evm_env_for_raw_block( - &self, - at: &Header, - ) -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv)>; - - /// Get all transactions in the block with the given hash. - /// - /// Returns `None` if block does not exist. - async fn transactions_by_block(&self, block: B256) - -> EthResult>>; - - /// Get the entire block for the given id. - /// - /// Returns `None` if block does not exist. - async fn block_by_id(&self, id: BlockId) -> EthResult>; - - /// Get the entire block for the given id. - /// - /// Returns `None` if block does not exist. - async fn block_by_id_with_senders( - &self, - id: BlockId, - ) -> EthResult>; - - /// Get all transactions in the block with the given hash. - /// - /// Returns `None` if block does not exist. - async fn transactions_by_block_id( - &self, - block: BlockId, - ) -> EthResult>>; - - /// Returns the EIP-2718 encoded transaction by hash. - /// - /// If this is a pooled EIP-4844 transaction, the blob sidecar is included. - /// - /// Checks the pool and state. - /// - /// Returns `Ok(None)` if no matching transaction was found. - async fn raw_transaction_by_hash(&self, hash: B256) -> EthResult>; - - /// Returns the transaction by hash. - /// - /// Checks the pool and state. - /// - /// Returns `Ok(None)` if no matching transaction was found. - async fn transaction_by_hash(&self, hash: B256) -> EthResult>; - - /// Returns the transaction by including its corresponding [BlockId] - /// - /// Note: this supports pending transactions - async fn transaction_by_hash_at( - &self, - hash: B256, - ) -> EthResult>; - - /// Returns the _historical_ transaction and the block it was mined in - async fn historical_transaction_by_hash_at( - &self, - hash: B256, - ) -> EthResult>; - - /// Returns the transaction receipt for the given hash. - /// - /// Returns None if the transaction does not exist or is pending - /// Note: The tx receipt is not available for pending transactions. - async fn transaction_receipt(&self, hash: B256) -> EthResult>; - - /// Decodes and recovers the transaction and submits it to the pool. - /// - /// Returns the hash of the transaction. - async fn send_raw_transaction(&self, tx: Bytes) -> EthResult; - - /// Signs transaction with a matching signer, if any and submits the transaction to the pool. - /// Returns the hash of the signed transaction. - async fn send_transaction(&self, request: TransactionRequest) -> EthResult; - - /// Prepares the state and env for the given [TransactionRequest] at the given [BlockId] and - /// executes the closure on a new task returning the result of the closure. - /// - /// This returns the configured [EnvWithHandlerCfg] for the given [TransactionRequest] at the - /// given [BlockId] and with configured call settings: `prepare_call_env`. - async fn spawn_with_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - f: F, - ) -> EthResult - where - F: FnOnce(&mut StateCacheDB, EnvWithHandlerCfg) -> EthResult + Send + 'static, - R: Send + 'static; - - /// Executes the call request at the given [BlockId]. - async fn transact_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)>; - - /// Executes the call request at the given [BlockId] on a new task and returns the result of the - /// inspect call. - async fn spawn_inspect_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - I: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static; - - /// Executes the transaction on top of the given [BlockId] with a tracer configured by the - /// config. - /// - /// The callback is then called with the [TracingInspector] and the [ResultAndState] after the - /// configured [EnvWithHandlerCfg] was inspected. - /// - /// Caution: this is blocking - fn trace_at( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState) -> EthResult; - - /// Same as [Self::trace_at] but also provides the used database to the callback. - /// - /// Executes the transaction on top of the given [BlockId] with a tracer configured by the - /// config. - /// - /// The callback is then called with the [TracingInspector] and the [ResultAndState] after the - /// configured [EnvWithHandlerCfg] was inspected. - async fn spawn_trace_at_with_state( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, - R: Send + 'static; - - /// Fetches the transaction and the transaction's block - async fn transaction_and_block( - &self, - hash: B256, - ) -> EthResult>; - - /// Retrieves the transaction if it exists and returns its trace. - /// - /// Before the transaction is traced, all previous transaction in the block are applied to the - /// state by executing them first. - /// The callback `f` is invoked with the [ResultAndState] after the transaction was executed and - /// the database that points to the beginning of the transaction. - /// - /// Note: Implementers should use a threadpool where blocking is allowed, such as - /// [BlockingTaskPool](reth_tasks::pool::BlockingTaskPool). - async fn spawn_trace_transaction_in_block( - &self, - hash: B256, - config: TracingInspectorConfig, - f: F, - ) -> EthResult> - where - F: FnOnce(TransactionInfo, TracingInspector, ResultAndState, StateCacheDB) -> EthResult - + Send - + 'static, - R: Send + 'static, - { - self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f) - .await - } - - /// Retrieves the transaction if it exists and returns its trace. - /// - /// Before the transaction is traced, all previous transaction in the block are applied to the - /// state by executing them first. - /// The callback `f` is invoked with the [ResultAndState] after the transaction was executed and - /// the database that points to the beginning of the transaction. - /// - /// Note: Implementers should use a threadpool where blocking is allowed, such as - /// [BlockingTaskPool](reth_tasks::pool::BlockingTaskPool). - async fn spawn_replay_transaction(&self, hash: B256, f: F) -> EthResult> - where - F: FnOnce(TransactionInfo, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, - R: Send + 'static; - - /// Retrieves the transaction if it exists and returns its trace. - /// - /// Before the transaction is traced, all previous transaction in the block are applied to the - /// state by executing them first. - /// The callback `f` is invoked with the [ResultAndState] after the transaction was executed and - /// the database that points to the beginning of the transaction. - /// - /// Note: Implementers should use a threadpool where blocking is allowed, such as - /// [BlockingTaskPool](reth_tasks::pool::BlockingTaskPool). - async fn spawn_trace_transaction_in_block_with_inspector( - &self, - hash: B256, - inspector: Insp, - f: F, - ) -> EthResult> - where - F: FnOnce(TransactionInfo, Insp, ResultAndState, StateCacheDB) -> EthResult - + Send - + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static; - - /// Executes all transactions of a block and returns a list of callback results invoked for each - /// transaction in the block. - /// - /// This - /// 1. fetches all transactions of the block - /// 2. configures the EVM evn - /// 3. loops over all transactions and executes them - /// 4. calls the callback with the transaction info, the execution result, the changed state - /// _after_ the transaction [StateProviderDatabase] and the database that points to the state - /// right _before_ the transaction. - async fn trace_block_with( - &self, - block_id: BlockId, - config: TracingInspectorConfig, - f: F, - ) -> EthResult>> - where - // This is the callback that's invoked for each transaction with the inspector, the result, - // state and db - F: for<'a> Fn( - TransactionInfo, - TracingInspector, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - R: Send + 'static, - { - self.trace_block_until(block_id, None, config, f).await - } - - /// Executes all transactions of a block and returns a list of callback results invoked for each - /// transaction in the block. - /// - /// This - /// 1. fetches all transactions of the block - /// 2. configures the EVM evn - /// 3. loops over all transactions and executes them - /// 4. calls the callback with the transaction info, the execution result, the changed state - /// _after_ the transaction [EvmState] and the database that points to the state - /// right _before_ the transaction, in other words the state the transaction was - /// executed on: `changed_state = tx(cached_state)` - /// - /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing - /// a transaction. This is invoked for each transaction. - async fn trace_block_with_inspector( - &self, - block_id: BlockId, - insp_setup: Setup, - f: F, - ) -> EthResult>> - where - // This is the callback that's invoked for each transaction with the inspector, the result, - // state and db - F: for<'a> Fn( - TransactionInfo, - Insp, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static, - { - self.trace_block_until_with_inspector(block_id, None, insp_setup, f).await - } - - /// Executes all transactions of a block. - /// - /// If a `highest_index` is given, this will only execute the first `highest_index` - /// transactions, in other words, it will stop executing transactions after the - /// `highest_index`th transaction. - async fn trace_block_until( - &self, - block_id: BlockId, - highest_index: Option, - config: TracingInspectorConfig, - f: F, - ) -> EthResult>> - where - F: for<'a> Fn( - TransactionInfo, - TracingInspector, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - R: Send + 'static, - { - self.trace_block_until_with_inspector( - block_id, - highest_index, - move || TracingInspector::new(config), - f, - ) - .await - } - - /// Executes all transactions of a block. - /// - /// If a `highest_index` is given, this will only execute the first `highest_index` - /// transactions, in other words, it will stop executing transactions after the - /// `highest_index`th transaction. - /// - /// Note: This expect tx index to be 0-indexed, so the first transaction is at index 0. - /// - /// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing - /// the transactions. - async fn trace_block_until_with_inspector( - &self, - block_id: BlockId, - highest_index: Option, - inspector_setup: Setup, - f: F, - ) -> EthResult>> - where - F: for<'a> Fn( - TransactionInfo, - Insp, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static; -} - -#[async_trait] -impl EthTransactions - for EthApi -where - Pool: TransactionPool + Clone + 'static, - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: NetworkInfo + Send + Sync + 'static, - EvmConfig: ConfigureEvm + 'static, -{ - fn transact( - &self, - db: DB, - env: EnvWithHandlerCfg, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into, - { - let mut evm = self.inner.evm_config.evm_with_env(db, env); - let res = evm.transact()?; - let (_, env) = evm.into_db_and_env_with_handler_cfg(); - Ok((res, env)) - } - - fn inspect( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into, - I: GetInspector, - { - self.inspect_and_return_db(db, env, inspector).map(|(res, env, _)| (res, env)) - } - - fn inspect_and_return_db( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg, DB)> - where - DB: Database, - ::Error: Into, - I: GetInspector, - { - let mut evm = self.inner.evm_config.evm_with_env_and_inspector(db, env, inspector); - let res = evm.transact()?; - let (db, env) = evm.into_db_and_env_with_handler_cfg(); - Ok((res, env, db)) - } - - fn replay_transactions_until( - &self, - db: &mut CacheDB, - cfg: CfgEnvWithHandlerCfg, - block_env: BlockEnv, - transactions: I, - target_tx_hash: B256, - ) -> Result - where - DB: DatabaseRef, - EthApiError: From<::Error>, - I: IntoIterator, - Tx: FillableTransaction, - { - let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default()); - - let mut evm = self.inner.evm_config.evm_with_env(db, env); - let mut index = 0; - for tx in transactions { - if tx.hash() == target_tx_hash { - // reached the target transaction - break - } - - tx.try_fill_tx_env(evm.tx_mut())?; - evm.transact_commit()?; - index += 1; - } - Ok(index) - } - - fn call_gas_limit(&self) -> u64 { - self.inner.gas_cap - } - - async fn spawn_blocking_future(&self, c: F) -> EthResult - where - F: Future> + Send + 'static, - R: Send + 'static, - { - self.on_blocking_task(|_| c).await - } - - async fn spawn_blocking(&self, c: F) -> EthResult - where - F: FnOnce() -> EthResult + Send + 'static, - R: Send + 'static, - { - self.spawn_tracing_task_with(move |_| c()).await - } - - fn state_at(&self, at: BlockId) -> EthResult { - self.state_at_block_id(at) - } - - fn with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult, - { - let state = self.state_at(at)?; - f(state) - } - - async fn spawn_with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult + Send + 'static, - T: Send + 'static, - { - self.spawn_tracing_task_with(move |this| { - let state = this.state_at(at)?; - f(state) - }) - .await - } - - async fn evm_env_at( - &self, - at: BlockId, - ) -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv, BlockId)> { - if at.is_pending() { - let PendingBlockEnv { cfg, block_env, origin } = self.pending_block_env_and_cfg()?; - Ok((cfg, block_env, origin.state_block_id())) - } else { - // Use cached values if there is no pending block - let block_hash = self - .provider() - .block_hash_for_id(at)? - .ok_or_else(|| EthApiError::UnknownBlockNumber)?; - let (cfg, env) = self.cache().get_evm_env(block_hash).await?; - Ok((cfg, env, block_hash.into())) - } - } - - async fn evm_env_for_raw_block( - &self, - header: &Header, - ) -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv)> { - // get the parent config first - let (cfg, mut block_env, _) = self.evm_env_at(header.parent_hash.into()).await?; - - let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE; - fill_block_env_with_coinbase(&mut block_env, header, after_merge, header.beneficiary); - - Ok((cfg, block_env)) - } - - async fn transactions_by_block( - &self, - block: B256, - ) -> EthResult>> { - Ok(self.cache().get_block_transactions(block).await?) - } - - async fn block_by_id(&self, id: BlockId) -> EthResult> { - self.block(id).await - } - - async fn block_by_id_with_senders( - &self, - id: BlockId, - ) -> EthResult> { - self.block_with_senders(id).await - } - - async fn transactions_by_block_id( - &self, - block: BlockId, - ) -> EthResult>> { - self.block_by_id(block).await.map(|block| block.map(|block| block.body)) - } - - async fn raw_transaction_by_hash(&self, hash: B256) -> EthResult> { - // Note: this is mostly used to fetch pooled transactions so we check the pool first - if let Some(tx) = - self.pool().get_pooled_transaction_element(hash).map(|tx| tx.envelope_encoded()) - { - return Ok(Some(tx)) - } - - self.on_blocking_task(|this| async move { - Ok(this.provider().transaction_by_hash(hash)?.map(|tx| tx.envelope_encoded())) - }) - .await - } - - async fn transaction_by_hash(&self, hash: B256) -> EthResult> { - // Try to find the transaction on disk - let mut resp = self - .on_blocking_task(|this| async move { - match this.provider().transaction_by_hash_with_meta(hash)? { - None => Ok(None), - Some((tx, meta)) => { - // Note: we assume this transaction is valid, because it's mined (or part of - // pending block) and already. We don't need to - // check for pre EIP-2 because this transaction could be pre-EIP-2. - let transaction = tx - .into_ecrecovered_unchecked() - .ok_or(EthApiError::InvalidTransactionSignature)?; - - let tx = TransactionSource::Block { - transaction, - index: meta.index, - block_hash: meta.block_hash, - block_number: meta.block_number, - base_fee: meta.base_fee, - }; - Ok(Some(tx)) - } - } - }) - .await?; - - if resp.is_none() { - // tx not found on disk, check pool - if let Some(tx) = - self.pool().get(&hash).map(|tx| tx.transaction.to_recovered_transaction()) - { - resp = Some(TransactionSource::Pool(tx)); - } - } - - Ok(resp) - } - - async fn transaction_by_hash_at( - &self, - transaction_hash: B256, - ) -> EthResult> { - match self.transaction_by_hash(transaction_hash).await? { - None => return Ok(None), - Some(tx) => { - let res = match tx { - tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()), - TransactionSource::Block { - transaction, - index, - block_hash, - block_number, - base_fee, - } => { - let at = BlockId::Hash(block_hash.into()); - let tx = TransactionSource::Block { - transaction, - index, - block_hash, - block_number, - base_fee, - }; - (tx, at) - } - }; - Ok(Some(res)) - } - } - } - - async fn historical_transaction_by_hash_at( - &self, - hash: B256, - ) -> EthResult> { - match self.transaction_by_hash_at(hash).await? { - None => Ok(None), - Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))), - } - } - - async fn transaction_receipt(&self, hash: B256) -> EthResult> { - let result = self - .on_blocking_task(|this| async move { - let (tx, meta) = match this.provider().transaction_by_hash_with_meta(hash)? { - Some((tx, meta)) => (tx, meta), - None => return Ok(None), - }; - - let receipt = match this.provider().receipt_by_hash(hash)? { - Some(recpt) => recpt, - None => return Ok(None), - }; - - Ok(Some((tx, meta, receipt))) - }) - .await?; - - let (tx, meta, receipt) = match result { - Some((tx, meta, receipt)) => (tx, meta, receipt), - None => return Ok(None), - }; - - self.build_transaction_receipt(tx, meta, receipt).await.map(Some) - } - - async fn send_raw_transaction(&self, tx: Bytes) -> EthResult { - // On optimism, transactions are forwarded directly to the sequencer to be included in - // blocks that it builds. - let maybe_forwarder = self.inner.raw_transaction_forwarder.read().clone(); - if let Some(client) = maybe_forwarder { - tracing::debug!( target: "rpc::eth", "forwarding raw transaction to"); - client.forward_raw_transaction(&tx).await?; - } - - let recovered = recover_raw_transaction(tx)?; - let pool_transaction = ::from_recovered_pooled_transaction(recovered); - - // submit the transaction to the pool with a `Local` origin - let hash = self.pool().add_transaction(TransactionOrigin::Local, pool_transaction).await?; - - Ok(hash) - } - - async fn send_transaction(&self, mut request: TransactionRequest) -> EthResult { - let from = match request.from { - Some(from) => from, - None => return Err(SignError::NoAccount.into()), - }; - - // set nonce if not already set before - if request.nonce.is_none() { - let nonce = self.get_transaction_count(from, Some(BlockId::pending()))?; - // note: `.to()` can't panic because the nonce is constructed from a `u64` - request.nonce = Some(nonce.to::()); - } - - let chain_id = self.chain_id(); - - let estimated_gas = self.estimate_gas_at(request.clone(), BlockId::pending(), None).await?; - let gas_limit = estimated_gas; - - let TransactionRequest { - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - input: data, - nonce, - mut access_list, - max_fee_per_blob_gas, - blob_versioned_hashes, - sidecar, - .. - } = request; - - // todo: remove this inlining after https://github.com/alloy-rs/alloy/pull/183#issuecomment-1928161285 - let transaction = match ( - gas_price, - max_fee_per_gas, - access_list.take(), - max_fee_per_blob_gas, - blob_versioned_hashes, - sidecar, - ) { - // legacy transaction - // gas price required - (Some(_), None, None, None, None, None) => { - Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest { - nonce: nonce.unwrap_or_default(), - gas_price: U256::from(gas_price.unwrap_or_default()), - gas_limit: U256::from(gas.unwrap_or_default()), - value: value.unwrap_or_default(), - input: data.into_input().unwrap_or_default(), - kind: to.unwrap_or(RpcTransactionKind::Create), - chain_id: None, - })) - } - // EIP2930 - // if only accesslist is set, and no eip1599 fees - (_, None, Some(access_list), None, None, None) => { - Some(TypedTransactionRequest::EIP2930(EIP2930TransactionRequest { - nonce: nonce.unwrap_or_default(), - gas_price: U256::from(gas_price.unwrap_or_default()), - gas_limit: U256::from(gas.unwrap_or_default()), - value: value.unwrap_or_default(), - input: data.into_input().unwrap_or_default(), - kind: to.unwrap_or(RpcTransactionKind::Create), - chain_id: 0, - access_list, - })) - } - // EIP1559 - // if 4844 fields missing - // gas_price, max_fee_per_gas, access_list, max_fee_per_blob_gas, blob_versioned_hashes, - // sidecar, - (None, _, _, None, None, None) => { - // Empty fields fall back to the canonical transaction schema. - Some(TypedTransactionRequest::EIP1559(EIP1559TransactionRequest { - nonce: nonce.unwrap_or_default(), - max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()), - max_priority_fee_per_gas: U256::from( - max_priority_fee_per_gas.unwrap_or_default(), - ), - gas_limit: U256::from(gas.unwrap_or_default()), - value: value.unwrap_or_default(), - input: data.into_input().unwrap_or_default(), - kind: to.unwrap_or(RpcTransactionKind::Create), - chain_id: 0, - access_list: access_list.unwrap_or_default(), - })) - } - // EIP4884 - // all blob fields required - ( - None, - _, - _, - Some(max_fee_per_blob_gas), - Some(blob_versioned_hashes), - Some(sidecar), - ) => { - // As per the EIP, we follow the same semantics as EIP-1559. - Some(TypedTransactionRequest::EIP4844(EIP4844TransactionRequest { - chain_id: 0, - nonce: nonce.unwrap_or_default(), - max_priority_fee_per_gas: U256::from( - max_priority_fee_per_gas.unwrap_or_default(), - ), - max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()), - gas_limit: U256::from(gas.unwrap_or_default()), - value: value.unwrap_or_default(), - input: data.into_input().unwrap_or_default(), - #[allow(clippy::manual_unwrap_or_default)] // clippy is suggesting here unwrap_or_default - to: match to { - Some(RpcTransactionKind::Call(to)) => to, - _ => Address::default(), - }, - access_list: access_list.unwrap_or_default(), - - // eip-4844 specific. - max_fee_per_blob_gas: U256::from(max_fee_per_blob_gas), - blob_versioned_hashes, - sidecar, - })) - } - - _ => None, - }; - - let transaction = match transaction { - Some(TypedTransactionRequest::Legacy(mut req)) => { - req.chain_id = Some(chain_id.to()); - req.gas_limit = gas_limit.saturating_to(); - req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?; - - TypedTransactionRequest::Legacy(req) - } - Some(TypedTransactionRequest::EIP2930(mut req)) => { - req.chain_id = chain_id.to(); - req.gas_limit = gas_limit.saturating_to(); - req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?; - - TypedTransactionRequest::EIP2930(req) - } - Some(TypedTransactionRequest::EIP1559(mut req)) => { - let (max_fee_per_gas, max_priority_fee_per_gas) = self - .eip1559_fees( - max_fee_per_gas.map(U256::from), - max_priority_fee_per_gas.map(U256::from), - ) - .await?; - - req.chain_id = chain_id.to(); - req.gas_limit = gas_limit.saturating_to(); - req.max_fee_per_gas = max_fee_per_gas.saturating_to(); - req.max_priority_fee_per_gas = max_priority_fee_per_gas.saturating_to(); - - TypedTransactionRequest::EIP1559(req) - } - Some(TypedTransactionRequest::EIP4844(mut req)) => { - let (max_fee_per_gas, max_priority_fee_per_gas) = self - .eip1559_fees( - max_fee_per_gas.map(U256::from), - max_priority_fee_per_gas.map(U256::from), - ) - .await?; - - req.max_fee_per_gas = max_fee_per_gas; - req.max_priority_fee_per_gas = max_priority_fee_per_gas; - req.max_fee_per_blob_gas = - self.eip4844_blob_fee(max_fee_per_blob_gas.map(U256::from)).await?; - - req.chain_id = chain_id.to(); - req.gas_limit = gas_limit; - - TypedTransactionRequest::EIP4844(req) - } - None => return Err(EthApiError::ConflictingFeeFieldsInRequest), - }; - - let signed_tx = self.sign_request(&from, transaction)?; - - let recovered = - signed_tx.into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?; - - let pool_transaction = match recovered.try_into() { - Ok(converted) => ::from_recovered_pooled_transaction(converted), - Err(_) => return Err(EthApiError::TransactionConversionError), - }; - - // submit the transaction to the pool with a `Local` origin - let hash = self.pool().add_transaction(TransactionOrigin::Local, pool_transaction).await?; - - Ok(hash) - } - - async fn spawn_with_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - f: F, - ) -> EthResult - where - F: FnOnce(&mut StateCacheDB, EnvWithHandlerCfg) -> EthResult + Send + 'static, - R: Send + 'static, - { - let (cfg, block_env, at) = self.evm_env_at(at).await?; - let this = self.clone(); - self.inner - .blocking_task_pool - .spawn(move || { - let state = this.state_at(at)?; - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - let env = prepare_call_env( - cfg, - block_env, - request, - this.call_gas_limit(), - &mut db, - overrides, - )?; - f(&mut db, env) - }) - .await - .map_err(|_| EthApiError::InternalBlockingTaskError)? - } - - async fn transact_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> { - let this = self.clone(); - self.spawn_with_call_at(request, at, overrides, move |db, env| this.transact(db, env)).await - } - - async fn spawn_inspect_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - I: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - { - let this = self.clone(); - self.spawn_with_call_at(request, at, overrides, move |db, env| { - this.inspect(db, env, inspector) - }) - .await - } - - fn trace_at( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState) -> EthResult, - { - let this = self.clone(); - self.with_state_at_block(at, |state| { - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - let mut inspector = TracingInspector::new(config); - let (res, _) = this.inspect(&mut db, env, &mut inspector)?; - f(inspector, res) - }) - } - - async fn spawn_trace_at_with_state( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, - R: Send + 'static, - { - let this = self.clone(); - self.spawn_with_state_at_block(at, move |state| { - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - let mut inspector = TracingInspector::new(config); - let (res, _) = this.inspect(&mut db, env, &mut inspector)?; - f(inspector, res, db) - }) - .await - } - - async fn transaction_and_block( - &self, - hash: B256, - ) -> EthResult> { - let (transaction, at) = match self.transaction_by_hash_at(hash).await? { - None => return Ok(None), - Some(res) => res, - }; - - // Note: this is always either hash or pending - let block_hash = match at { - BlockId::Hash(hash) => hash.block_hash, - _ => return Ok(None), - }; - let block = self.cache().get_block_with_senders(block_hash).await?; - Ok(block.map(|block| (transaction, block.seal(block_hash)))) - } - - async fn spawn_replay_transaction(&self, hash: B256, f: F) -> EthResult> - where - F: FnOnce(TransactionInfo, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, - R: Send + 'static, - { - let (transaction, block) = match self.transaction_and_block(hash).await? { - None => return Ok(None), - Some(res) => res, - }; - let (tx, tx_info) = transaction.split(); - - let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?; - - // we need to get the state of the parent block because we're essentially replaying the - // block the transaction is included in - let parent_block = block.parent_hash; - let block_txs = block.into_transactions_ecrecovered(); - - let this = self.clone(); - self.spawn_with_state_at_block(parent_block.into(), move |state| { - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - // replay all transactions prior to the targeted transaction - this.replay_transactions_until( - &mut db, - cfg.clone(), - block_env.clone(), - block_txs, - tx.hash, - )?; - - let env = - EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx)); - - let (res, _) = this.transact(&mut db, env)?; - f(tx_info, res, db) - }) - .await - .map(Some) - } - - async fn spawn_trace_transaction_in_block_with_inspector( - &self, - hash: B256, - mut inspector: Insp, - f: F, - ) -> EthResult> - where - F: FnOnce(TransactionInfo, Insp, ResultAndState, StateCacheDB) -> EthResult - + Send - + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static, - { - let (transaction, block) = match self.transaction_and_block(hash).await? { - None => return Ok(None), - Some(res) => res, - }; - let (tx, tx_info) = transaction.split(); - - let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?; - - // we need to get the state of the parent block because we're essentially replaying the - // block the transaction is included in - let parent_block = block.parent_hash; - let block_txs = block.into_transactions_ecrecovered(); - - let this = self.clone(); - self.spawn_with_state_at_block(parent_block.into(), move |state| { - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - // replay all transactions prior to the targeted transaction - this.replay_transactions_until( - &mut db, - cfg.clone(), - block_env.clone(), - block_txs, - tx.hash, - )?; - - let env = - EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx)); - - let (res, _) = this.inspect(&mut db, env, &mut inspector)?; - f(tx_info, inspector, res, db) - }) - .await - .map(Some) - } - - async fn trace_block_until_with_inspector( - &self, - block_id: BlockId, - highest_index: Option, - mut inspector_setup: Setup, - f: F, - ) -> EthResult>> - where - F: for<'a> Fn( - TransactionInfo, - Insp, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static, - { - let ((cfg, block_env, _), block) = - futures::try_join!(self.evm_env_at(block_id), self.block_with_senders(block_id))?; - - let Some(block) = block else { return Ok(None) }; - - if block.body.is_empty() { - // nothing to trace - return Ok(Some(Vec::new())) - } - - // replay all transactions of the block - self.spawn_tracing_task_with(move |this| { - // we need to get the state of the parent block because we're replaying this block on - // top of its parent block's state - let state_at = block.parent_hash; - let block_hash = block.hash(); - - let block_number = block_env.number.saturating_to::(); - let base_fee = block_env.basefee.saturating_to::(); - - // prepare transactions, we do everything upfront to reduce time spent with open state - let max_transactions = highest_index.map_or(block.body.len(), |highest| { - // we need + 1 because the index is 0-based - highest as usize + 1 - }); - let mut results = Vec::with_capacity(max_transactions); - - let mut transactions = block - .into_transactions_ecrecovered() - .take(max_transactions) - .enumerate() - .map(|(idx, tx)| { - let tx_info = TransactionInfo { - hash: Some(tx.hash()), - index: Some(idx as u64), - block_hash: Some(block_hash), - block_number: Some(block_number), - base_fee: Some(base_fee), - }; - let tx_env = tx_env_with_recovered(&tx); - (tx_info, tx_env) - }) - .peekable(); - - // now get the state - let state = this.state_at(state_at.into())?; - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - while let Some((tx_info, tx)) = transactions.next() { - let env = EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx); - - let mut inspector = inspector_setup(); - let (res, _) = this.inspect(&mut db, env, &mut inspector)?; - let ResultAndState { result, state } = res; - results.push(f(tx_info, inspector, result, &state, &db)?); - - // need to apply the state changes of this transaction before executing the - // next transaction, but only if there's a next transaction - if transactions.peek().is_some() { - // commit the state changes to the DB - db.commit(state) - } - } - - Ok(results) - }) - .await - .map(Some) - } -} - -// === impl EthApi === - -impl EthApi -where - Self: Send + Sync + 'static, -{ - /// Spawns the given closure on a new blocking tracing task - async fn spawn_tracing_task_with(&self, f: F) -> EthResult - where - F: FnOnce(Self) -> EthResult + Send + 'static, - T: Send + 'static, - { - let this = self.clone(); - self.inner - .blocking_task_pool - .spawn(move || f(this)) - .await - .map_err(|_| EthApiError::InternalBlockingTaskError)? - } -} - -impl EthApi -where - Pool: TransactionPool + 'static, - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: NetworkInfo + 'static, - EvmConfig: ConfigureEvm, -{ - /// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy - /// transactions. - pub(crate) async fn legacy_gas_price(&self, gas_price: Option) -> EthResult { - match gas_price { - Some(gas_price) => Ok(gas_price), - None => { - // fetch a suggested gas price - self.gas_price().await - } - } - } - - /// Returns the EIP-1559 fees if they are set, otherwise fetches a suggested gas price for - /// EIP-1559 transactions. - /// - /// Returns (`max_fee`, `priority_fee`) - pub(crate) async fn eip1559_fees( - &self, - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - ) -> EthResult<(U256, U256)> { - let max_fee_per_gas = match max_fee_per_gas { - Some(max_fee_per_gas) => max_fee_per_gas, - None => { - // fetch pending base fee - let base_fee = self - .block(BlockNumberOrTag::Pending) - .await? - .ok_or(EthApiError::UnknownBlockNumber)? - .base_fee_per_gas - .ok_or_else(|| { - EthApiError::InvalidTransaction( - RpcInvalidTransactionError::TxTypeNotSupported, - ) - })?; - U256::from(base_fee) - } - }; - - let max_priority_fee_per_gas = match max_priority_fee_per_gas { - Some(max_priority_fee_per_gas) => max_priority_fee_per_gas, - None => self.suggested_priority_fee().await?, - }; - Ok((max_fee_per_gas, max_priority_fee_per_gas)) - } - - /// Returns the EIP-4844 blob fee if it is set, otherwise fetches a blob fee. - pub(crate) async fn eip4844_blob_fee(&self, blob_fee: Option) -> EthResult { - match blob_fee { - Some(blob_fee) => Ok(blob_fee), - None => self.blob_base_fee().await, - } - } - - pub(crate) fn sign_request( - &self, - from: &Address, - request: TypedTransactionRequest, - ) -> EthResult { - for signer in self.inner.signers.read().iter() { - if signer.is_signer_for(from) { - return match signer.sign_transaction(request, from) { - Ok(tx) => Ok(tx), - Err(e) => Err(e.into()), - } - } - } - Err(EthApiError::InvalidTransactionSignature) - } - - /// Get Transaction by [`BlockId`] and the index of the transaction within that Block. - /// - /// Returns `Ok(None)` if the block does not exist, or the block as fewer transactions - pub(crate) async fn transaction_by_block_and_tx_index( - &self, - block_id: impl Into, - index: Index, - ) -> EthResult> { - if let Some(block) = self.block_with_senders(block_id.into()).await? { - let block_hash = block.hash(); - let block_number = block.number; - let base_fee_per_gas = block.base_fee_per_gas; - if let Some(tx) = block.into_transactions_ecrecovered().nth(index.into()) { - return Ok(Some(from_recovered_with_block_context( - tx, - block_hash, - block_number, - base_fee_per_gas, - index.into(), - ))) - } - } - - Ok(None) - } - - pub(crate) async fn raw_transaction_by_block_and_tx_index( - &self, - block_id: impl Into, - index: Index, - ) -> EthResult> { - if let Some(block) = self.block_with_senders(block_id.into()).await? { - if let Some(tx) = block.transactions().nth(index.into()) { - return Ok(Some(tx.envelope_encoded())) - } - } - - Ok(None) - } -} - -impl EthApi -where - Provider: BlockReaderIdExt + ChainSpecProvider, -{ - /// Helper function for `eth_getTransactionReceipt` - /// - /// Returns the receipt - #[cfg(not(feature = "optimism"))] - pub(crate) async fn build_transaction_receipt( - &self, - tx: TransactionSigned, - meta: TransactionMeta, - receipt: Receipt, - ) -> EthResult { - // get all receipts for the block - let all_receipts = match self.cache().get_receipts(meta.block_hash).await? { - Some(recpts) => recpts, - None => return Err(EthApiError::UnknownBlockNumber), - }; - build_transaction_receipt_with_block_receipts(tx, meta, receipt, &all_receipts) - } - - /// Helper function for `eth_getTransactionReceipt` (optimism) - /// - /// Returns the receipt - #[cfg(feature = "optimism")] - pub(crate) async fn build_transaction_receipt( - &self, - tx: TransactionSigned, - meta: TransactionMeta, - receipt: Receipt, - ) -> EthResult { - let (block, receipts) = self - .cache() - .get_block_and_receipts(meta.block_hash) - .await? - .ok_or(EthApiError::UnknownBlockNumber)?; - - let block = block.unseal(); - let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok(); - let optimism_tx_meta = self.build_op_tx_meta(&tx, l1_block_info, block.timestamp)?; - - build_transaction_receipt_with_block_receipts( - tx, - meta, - receipt, - &receipts, - optimism_tx_meta, - ) - } - - /// Builds op metadata object using the provided [`TransactionSigned`], L1 block info and - /// `block_timestamp`. The `L1BlockInfo` is used to calculate the l1 fee and l1 data gas for the - /// transaction. If the `L1BlockInfo` is not provided, the meta info will be empty. - #[cfg(feature = "optimism")] - pub(crate) fn build_op_tx_meta( - &self, - tx: &TransactionSigned, - l1_block_info: Option, - block_timestamp: u64, - ) -> EthResult { - use crate::eth::{api::optimism::OptimismTxMeta, optimism::OptimismEthApiError}; - use reth_evm_optimism::RethL1BlockInfo; - - let Some(l1_block_info) = l1_block_info else { return Ok(OptimismTxMeta::default()) }; - - let (l1_fee, l1_data_gas) = if !tx.is_deposit() { - let envelope_buf = tx.envelope_encoded(); - - let inner_l1_fee = l1_block_info - .l1_tx_data_fee( - &self.inner.provider.chain_spec(), - block_timestamp, - &envelope_buf, - tx.is_deposit(), - ) - .map_err(|_| OptimismEthApiError::L1BlockFeeError)?; - let inner_l1_data_gas = l1_block_info - .l1_data_gas(&self.inner.provider.chain_spec(), block_timestamp, &envelope_buf) - .map_err(|_| OptimismEthApiError::L1BlockGasError)?; - ( - Some(inner_l1_fee.saturating_to::()), - Some(inner_l1_data_gas.saturating_to::()), - ) - } else { - (None, None) - }; - - Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas)) - } -} - -/// Represents from where a transaction was fetched. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum TransactionSource { - /// Transaction exists in the pool (Pending) - Pool(TransactionSignedEcRecovered), - /// Transaction already included in a block - /// - /// This can be a historical block or a pending block (received from the CL) - Block { - /// Transaction fetched via provider - transaction: TransactionSignedEcRecovered, - /// Index of the transaction in the block - index: u64, - /// Hash of the block. - block_hash: B256, - /// Number of the block. - block_number: u64, - /// base fee of the block. - base_fee: Option, - }, -} - -// === impl TransactionSource === - -impl TransactionSource { - /// Consumes the type and returns the wrapped transaction. - pub fn into_recovered(self) -> TransactionSignedEcRecovered { - self.into() - } - - /// Returns the transaction and block related info, if not pending - pub fn split(self) -> (TransactionSignedEcRecovered, TransactionInfo) { - match self { - Self::Pool(tx) => { - let hash = tx.hash(); - ( - tx, - TransactionInfo { - hash: Some(hash), - index: None, - block_hash: None, - block_number: None, - base_fee: None, - }, - ) - } - Self::Block { transaction, index, block_hash, block_number, base_fee } => { - let hash = transaction.hash(); - ( - transaction, - TransactionInfo { - hash: Some(hash), - index: Some(index), - block_hash: Some(block_hash), - block_number: Some(block_number), - base_fee: base_fee.map(u128::from), - }, - ) - } - } - } -} - -impl From for TransactionSignedEcRecovered { - fn from(value: TransactionSource) -> Self { - match value { - TransactionSource::Pool(tx) => tx, - TransactionSource::Block { transaction, .. } => transaction, - } - } -} - -impl From for Transaction { - fn from(value: TransactionSource) -> Self { - match value { - TransactionSource::Pool(tx) => reth_rpc_types_compat::transaction::from_recovered(tx), - TransactionSource::Block { transaction, index, block_hash, block_number, base_fee } => { - from_recovered_with_block_context( - transaction, - block_hash, - block_number, - base_fee, - index as usize, - ) - } - } - } -} - -/// Helper function to construct a transaction receipt -/// -/// Note: This requires _all_ block receipts because we need to calculate the gas used by the -/// transaction. -pub(crate) fn build_transaction_receipt_with_block_receipts( - transaction: TransactionSigned, - meta: TransactionMeta, - receipt: Receipt, - all_receipts: &[Receipt], - #[cfg(feature = "optimism")] optimism_tx_meta: crate::eth::api::optimism::OptimismTxMeta, -) -> EthResult { - // Note: we assume this transaction is valid, because it's mined (or part of pending block) and - // we don't need to check for pre EIP-2 - let from = - transaction.recover_signer_unchecked().ok_or(EthApiError::InvalidTransactionSignature)?; - - // get the previous transaction cumulative gas used - let gas_used = if meta.index == 0 { - receipt.cumulative_gas_used - } else { - let prev_tx_idx = (meta.index - 1) as usize; - all_receipts - .get(prev_tx_idx) - .map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used) - .unwrap_or_default() - }; - - let blob_gas_used = transaction.transaction.blob_gas_used(); - // Blob gas price should only be present if the transaction is a blob transaction - let blob_gas_price = blob_gas_used.and_then(|_| meta.excess_blob_gas.map(calc_blob_gasprice)); - let logs_bloom = receipt.bloom_slow(); - - // get number of logs in the block - let mut num_logs = 0; - for prev_receipt in all_receipts.iter().take(meta.index as usize) { - num_logs += prev_receipt.logs.len(); - } - - let mut logs = Vec::with_capacity(receipt.logs.len()); - for (tx_log_idx, log) in receipt.logs.into_iter().enumerate() { - let rpclog = Log { - inner: log, - block_hash: Some(meta.block_hash), - block_number: Some(meta.block_number), - block_timestamp: Some(meta.timestamp), - transaction_hash: Some(meta.tx_hash), - transaction_index: Some(meta.index), - log_index: Some((num_logs + tx_log_idx) as u64), - removed: false, - }; - logs.push(rpclog); - } - - let rpc_receipt = reth_rpc_types::Receipt { - status: receipt.success.into(), - cumulative_gas_used: receipt.cumulative_gas_used as u128, - logs, - }; - - #[allow(clippy::needless_update)] - let res_receipt = TransactionReceipt { - inner: AnyReceiptEnvelope { - inner: ReceiptWithBloom { receipt: rpc_receipt, logs_bloom }, - r#type: transaction.transaction.tx_type().into(), - }, - transaction_hash: meta.tx_hash, - transaction_index: Some(meta.index), - block_hash: Some(meta.block_hash), - block_number: Some(meta.block_number), - from, - to: None, - gas_used: gas_used as u128, - contract_address: None, - effective_gas_price: transaction.effective_gas_price(meta.base_fee), - // TODO pre-byzantium receipts have a post-transaction state root - state_root: None, - // EIP-4844 fields - blob_gas_price, - blob_gas_used: blob_gas_used.map(u128::from), - }; - let mut res_receipt = WithOtherFields::new(res_receipt); - - #[cfg(feature = "optimism")] - { - let mut op_fields = OptimismTransactionReceiptFields::default(); - - if transaction.is_deposit() { - op_fields.deposit_nonce = receipt.deposit_nonce.map(reth_primitives::U64::from); - op_fields.deposit_receipt_version = - receipt.deposit_receipt_version.map(reth_primitives::U64::from); - } else if let Some(l1_block_info) = optimism_tx_meta.l1_block_info { - op_fields.l1_fee = optimism_tx_meta.l1_fee; - op_fields.l1_gas_used = optimism_tx_meta.l1_data_gas.map(|dg| { - dg + l1_block_info.l1_fee_overhead.unwrap_or_default().saturating_to::() - }); - op_fields.l1_fee_scalar = - Some(f64::from(l1_block_info.l1_base_fee_scalar) / 1_000_000.0); - op_fields.l1_gas_price = Some(l1_block_info.l1_base_fee.saturating_to()); - } - - res_receipt.other = op_fields.into(); - } - - match transaction.transaction.kind() { - Create => { - res_receipt.contract_address = Some(from.create(transaction.transaction.nonce())); - } - Call(addr) => { - res_receipt.to = Some(Address(*addr)); - } - } - - Ok(res_receipt) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::eth::{ - cache::EthStateCache, gas_oracle::GasPriceOracle, FeeHistoryCache, FeeHistoryCacheConfig, - }; - use reth_evm_ethereum::EthEvmConfig; - use reth_network_api::noop::NoopNetwork; - use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, hex_literal::hex}; - use reth_provider::test_utils::NoopProvider; - use reth_tasks::pool::BlockingTaskPool; - use reth_transaction_pool::test_utils::testing_pool; - - #[tokio::test] - async fn send_raw_transaction() { - let noop_provider = NoopProvider::default(); - let noop_network_provider = NoopNetwork::default(); - - let pool = testing_pool(); - - let evm_config = EthEvmConfig::default(); - let cache = EthStateCache::spawn(noop_provider, Default::default(), evm_config); - let fee_history_cache = - FeeHistoryCache::new(cache.clone(), FeeHistoryCacheConfig::default()); - let eth_api = EthApi::new( - noop_provider, - pool.clone(), - noop_network_provider, - cache.clone(), - GasPriceOracle::new(noop_provider, Default::default(), cache.clone()), - ETHEREUM_BLOCK_GAS_LIMIT, - BlockingTaskPool::build().expect("failed to build tracing pool"), - fee_history_cache, - evm_config, - None, - ); - - // https://etherscan.io/tx/0xa694b71e6c128a2ed8e2e0f6770bddbe52e3bb8f10e8472f9a79ab81497a8b5d - let tx_1 = Bytes::from(hex!("02f871018303579880850555633d1b82520894eee27662c2b8eba3cd936a23f039f3189633e4c887ad591c62bdaeb180c080a07ea72c68abfb8fca1bd964f0f99132ed9280261bdca3e549546c0205e800f7d0a05b4ef3039e9c9b9babc179a1878fb825b5aaf5aed2fa8744854150157b08d6f3")); - - let tx_1_result = eth_api.send_raw_transaction(tx_1).await.unwrap(); - assert_eq!( - pool.len(), - 1, - "expect 1 transactions in the pool, but pool size is {}", - pool.len() - ); - - // https://etherscan.io/tx/0x48816c2f32c29d152b0d86ff706f39869e6c1f01dc2fe59a3c1f9ecf39384694 - let tx_2 = Bytes::from(hex!("02f9043c018202b7843b9aca00850c807d37a08304d21d94ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b881bc16d674ec80000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000063e2d99f00000000000000000000000000000000000000000000000000000000000000030b000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000065717fe021ea67801d1088cc80099004b05b64600000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e95fd5965fd1f1a6f0d4600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000428dca9537116148616a5a3e44035af17238fe9dc080a0c6ec1e41f5c0b9511c49b171ad4e04c6bb419c74d99fe9891d74126ec6e4e879a032069a753d7a2cfa158df95421724d24c0e9501593c09905abf3699b4a4405ce")); - - let tx_2_result = eth_api.send_raw_transaction(tx_2).await.unwrap(); - assert_eq!( - pool.len(), - 2, - "expect 2 transactions in the pool, but pool size is {}", - pool.len() - ); - - assert!(pool.get(&tx_1_result).is_some(), "tx1 not found in the pool"); - assert!(pool.get(&tx_2_result).is_some(), "tx2 not found in the pool"); - } -} diff --git a/crates/rpc/rpc/src/eth/mod.rs b/crates/rpc/rpc/src/eth/mod.rs deleted file mode 100644 index 8d8e982c2c79..000000000000 --- a/crates/rpc/rpc/src/eth/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! `eth` namespace handler implementation. - -mod api; -pub mod bundle; -pub mod cache; -pub mod error; -mod filter; -pub mod gas_oracle; -mod id_provider; -mod logs_utils; -mod pubsub; -pub mod revm_utils; -mod signer; -pub mod traits; -pub(crate) mod utils; - -#[cfg(feature = "optimism")] -pub mod optimism; - -pub use api::{ - fee_history::{fee_history_cache_new_blocks_task, FeeHistoryCache, FeeHistoryCacheConfig}, - EthApi, EthApiSpec, EthTransactions, TransactionSource, RPC_DEFAULT_GAS_CAP, -}; - -pub use bundle::EthBundle; -pub use filter::{EthFilter, EthFilterConfig}; -pub use id_provider::EthSubscriptionIdProvider; -pub use pubsub::EthPubSub; diff --git a/crates/rpc/rpc/src/eth/optimism.rs b/crates/rpc/rpc/src/eth/optimism.rs deleted file mode 100644 index fb1665b95785..000000000000 --- a/crates/rpc/rpc/src/eth/optimism.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Optimism specific types. - -use jsonrpsee::types::ErrorObject; -use reth_rpc_types::ToRpcError; - -use crate::{eth::error::EthApiError, result::internal_rpc_err}; - -/// Eth Optimism Api Error -#[cfg(feature = "optimism")] -#[derive(Debug, thiserror::Error)] -pub enum OptimismEthApiError { - /// Thrown when calculating L1 gas fee - #[error("failed to calculate l1 gas fee")] - L1BlockFeeError, - /// Thrown when calculating L1 gas used - #[error("failed to calculate l1 gas used")] - L1BlockGasError, -} - -impl ToRpcError for OptimismEthApiError { - fn to_rpc_error(&self) -> ErrorObject<'static> { - match self { - Self::L1BlockFeeError | Self::L1BlockGasError => internal_rpc_err(self.to_string()), - } - } -} - -impl From for EthApiError { - fn from(err: OptimismEthApiError) -> Self { - Self::other(err) - } -} diff --git a/crates/rpc/rpc/src/eth/traits.rs b/crates/rpc/rpc/src/eth/traits.rs deleted file mode 100644 index 0f73ded3c82f..000000000000 --- a/crates/rpc/rpc/src/eth/traits.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Additional helper traits that allow for more customization. - -use crate::eth::error::EthResult; -use std::fmt; - -/// A trait that allows for forwarding raw transactions. -/// -/// For example to a sequencer. -#[async_trait::async_trait] -pub trait RawTransactionForwarder: fmt::Debug + Send + Sync + 'static { - /// Forwards raw transaction bytes for `eth_sendRawTransaction` - async fn forward_raw_transaction(&self, raw: &[u8]) -> EthResult<()>; -} diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index 17dc8fcb809f..fbd9244734b2 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -11,11 +11,12 @@ //! and can reduce overall performance of all concurrent requests handled via the jsonrpsee server. //! //! To avoid this, all blocking or CPU intensive handlers must be spawned to a separate task. See -//! the [`EthApi`] handler implementations for examples. The rpc-api traits make no use of the -//! available jsonrpsee `blocking` attribute to give implementers more freedom because the -//! `blocking` attribute and async handlers are mutually exclusive. However, as mentioned above, a -//! lot of handlers make use of async functions, caching for example, but are also using blocking -//! disk-io, hence these calls are spawned as futures to a blocking task manually. +//! the [`EthApi`](reth_rpc_eth_api::EthApi) handler implementations for examples. The rpc-api +//! traits make no use of the available jsonrpsee `blocking` attribute to give implementers more +//! freedom because the `blocking` attribute and async handlers are mutually exclusive. However, as +//! mentioned above, a lot of handlers make use of async functions, caching for example, but are +//! also using blocking disk-io, hence these calls are spawned as futures to a blocking task +//! manually. #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", @@ -35,7 +36,6 @@ use tower as _; mod admin; mod debug; mod engine; -pub mod eth; mod net; mod otterscan; mod reth; @@ -46,7 +46,6 @@ mod web3; pub use admin::AdminApi; pub use debug::DebugApi; pub use engine::{EngineApi, EngineEthApi}; -pub use eth::{EthApi, EthApiSpec, EthFilter, EthPubSub, EthSubscriptionIdProvider}; pub use net::NetApi; pub use otterscan::OtterscanApi; pub use reth::RethApi; @@ -54,4 +53,6 @@ pub use rpc::RPCApi; pub use trace::TraceApi; pub use txpool::TxPoolApi; pub use web3::Web3Api; -pub mod result; + +pub use reth_rpc_eth_api as eth; +pub use reth_rpc_eth_api::result; diff --git a/crates/rpc/rpc/src/net.rs b/crates/rpc/rpc/src/net.rs index 8e6615a281bf..c3a1229a5c39 100644 --- a/crates/rpc/rpc/src/net.rs +++ b/crates/rpc/rpc/src/net.rs @@ -1,8 +1,8 @@ -use crate::eth::EthApiSpec; use jsonrpsee::core::RpcResult as Result; use reth_network_api::PeersInfo; use reth_primitives::U64; use reth_rpc_api::NetApiServer; +use reth_rpc_eth_api::servers::EthApiSpec; use reth_rpc_types::PeerCount; /// `Net` API implementation. diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index e658402f3a94..9405ffd3e1c1 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -1,11 +1,9 @@ use alloy_primitives::Bytes; use async_trait::async_trait; use jsonrpsee::core::RpcResult; -use revm_inspectors::transfer::{TransferInspector, TransferKind}; -use revm_primitives::ExecutionResult; - use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256}; use reth_rpc_api::{EthApiServer, OtterscanServer}; +use reth_rpc_eth_api::{result::internal_rpc_err, servers::TraceExt}; use reth_rpc_types::{ trace::otterscan::{ BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions, @@ -13,8 +11,8 @@ use reth_rpc_types::{ }, BlockTransactions, Transaction, }; - -use crate::{eth::EthTransactions, result::internal_rpc_err}; +use revm_inspectors::transfer::{TransferInspector, TransferKind}; +use revm_primitives::ExecutionResult; const API_LEVEL: u64 = 8; @@ -34,7 +32,7 @@ impl OtterscanApi { #[async_trait] impl OtterscanServer for OtterscanApi where - Eth: EthApiServer + EthTransactions, + Eth: EthApiServer + TraceExt + 'static, { /// Handler for `ots_hasCode` async fn has_code(&self, address: Address, block_number: Option) -> RpcResult { diff --git a/crates/rpc/rpc/src/reth.rs b/crates/rpc/rpc/src/reth.rs index 17925b5ab8bf..11c437e696a6 100644 --- a/crates/rpc/rpc/src/reth.rs +++ b/crates/rpc/rpc/src/reth.rs @@ -1,12 +1,13 @@ -use crate::eth::error::{EthApiError, EthResult}; +use std::{collections::HashMap, future::Future, sync::Arc}; + use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_errors::RethResult; use reth_primitives::{Address, BlockId, U256}; use reth_provider::{BlockReaderIdExt, ChangeSetReader, StateProviderFactory}; use reth_rpc_api::RethApiServer; +use reth_rpc_eth_api::{EthApiError, EthResult}; use reth_tasks::TaskSpawner; -use std::{collections::HashMap, future::Future, sync::Arc}; use tokio::sync::oneshot; /// `reth` API implementation. diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index e959a367e3f3..2c3e9334a43f 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -1,9 +1,5 @@ -use crate::eth::{ - error::{EthApiError, EthResult}, - revm_utils::prepare_call_env, - utils::recover_raw_transaction, - EthTransactions, -}; +use std::{collections::HashSet, sync::Arc}; + use async_trait::async_trait; use jsonrpsee::core::RpcResult as Result; use reth_consensus_common::calc::{ @@ -13,6 +9,12 @@ use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, Header, use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::TraceApiServer; +use reth_rpc_eth_api::{ + error::{EthApiError, EthResult}, + revm_utils::prepare_call_env, + servers::TraceExt, + utils::recover_raw_transaction, +}; use reth_rpc_types::{ state::{EvmOverrides, StateOverride}, trace::{ @@ -32,7 +34,6 @@ use revm_inspectors::{ opcode::OpcodeGasInspector, tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig}, }; -use std::{collections::HashSet, sync::Arc}; use tokio::sync::{AcquireError, OwnedSemaphorePermit}; /// `trace` API implementation. @@ -74,7 +75,7 @@ impl TraceApi { impl TraceApi where Provider: BlockReader + StateProviderFactory + EvmEnvProvider + ChainSpecProvider + 'static, - Eth: EthTransactions + 'static, + Eth: TraceExt + 'static, { /// Executes the given call and returns a number of possible traces for it. pub async fn trace_call(&self, trace_request: TraceCallRequest) -> EthResult { @@ -86,6 +87,10 @@ where let this = self.clone(); self.eth_api() .spawn_with_call_at(trace_request.call, at, overrides, move |db, env| { + // wrapper is hack to get around 'higher-ranked lifetime error', see + // + let db = db.0; + let (res, _) = this.eth_api().inspect(&mut *db, env, &mut inspector)?; let trace_res = inspector.into_parity_builder().into_trace_results_with_state( &res, @@ -372,7 +377,7 @@ where }, ); - let block = self.inner.eth_api.block_by_id(block_id); + let block = self.inner.eth_api.block(block_id); let (maybe_traces, maybe_block) = futures::try_join!(traces, block)?; let mut maybe_traces = @@ -455,7 +460,7 @@ where let res = self .inner .eth_api - .trace_block_with_inspector( + .trace_block_inspector( block_id, OpcodeGasInspector::default, move |tx_info, inspector, _res, _, _| { @@ -470,7 +475,7 @@ where let Some(transactions) = res else { return Ok(None) }; - let Some(block) = self.inner.eth_api.block_by_id(block_id).await? else { return Ok(None) }; + let Some(block) = self.inner.eth_api.block(block_id).await? else { return Ok(None) }; Ok(Some(BlockOpcodeGas { block_hash: block.hash(), @@ -548,7 +553,7 @@ where impl TraceApiServer for TraceApi where Provider: BlockReader + StateProviderFactory + EvmEnvProvider + ChainSpecProvider + 'static, - Eth: EthTransactions + 'static, + Eth: TraceExt + 'static, { /// Executes the given call and returns a number of possible traces for it. /// diff --git a/crates/rpc/rpc/src/web3.rs b/crates/rpc/rpc/src/web3.rs index 4ed94ac85523..78464327aa2c 100644 --- a/crates/rpc/rpc/src/web3.rs +++ b/crates/rpc/rpc/src/web3.rs @@ -1,10 +1,11 @@ -use crate::result::ToRpcResult; use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_network_api::NetworkInfo; use reth_primitives::{keccak256, Bytes, B256}; use reth_rpc_api::Web3ApiServer; +use crate::result::ToRpcResult; + /// `web3` API implementation. /// /// This type provides the functionality for handling `web3` related requests. diff --git a/crates/storage/db-api/Cargo.toml b/crates/storage/db-api/Cargo.toml index 07d402d4afa8..7286e03f2da4 100644 --- a/crates/storage/db-api/Cargo.toml +++ b/crates/storage/db-api/Cargo.toml @@ -71,4 +71,4 @@ arbitrary = [ "dep:arbitrary", "dep:proptest", ] -optimism = [] +optimism = ["reth-primitives/optimism"] diff --git a/crates/storage/db-api/src/models/mod.rs b/crates/storage/db-api/src/models/mod.rs index f5ef4ea5fcce..df6467336507 100644 --- a/crates/storage/db-api/src/models/mod.rs +++ b/crates/storage/db-api/src/models/mod.rs @@ -314,70 +314,69 @@ mod tests { // // this check is to ensure we do not inadvertently add too many fields to a struct which would // expand the flags field and break backwards compatibility + #[cfg(not(feature = "optimism"))] #[test] fn test_ensure_backwards_compatibility() { - #[cfg(not(feature = "optimism"))] - { - assert_eq!(Account::bitflag_encoded_bytes(), 2); - assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); - assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); - assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); - assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); - assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(Header::bitflag_encoded_bytes(), 4); - assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); - assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); - assert_eq!(Receipt::bitflag_encoded_bytes(), 1); - assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); - assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); - assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); - assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); - assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); - assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); - assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); - assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); - assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); - } - - #[cfg(feature = "optimism")] - { - assert_eq!(Account::bitflag_encoded_bytes(), 2); - assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); - assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); - assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); - assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); - assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(Header::bitflag_encoded_bytes(), 4); - assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); - assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); - assert_eq!(Receipt::bitflag_encoded_bytes(), 2); - assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); - assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); - assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); - assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); - assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); - assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); - assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); - assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); - assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); - } + assert_eq!(Account::bitflag_encoded_bytes(), 2); + assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); + assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); + assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); + assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); + assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(Header::bitflag_encoded_bytes(), 4); + assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); + assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); + assert_eq!(Receipt::bitflag_encoded_bytes(), 1); + assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); + assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); + assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); + assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); + assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); + assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); + assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); + assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); + assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); + } + + #[cfg(feature = "optimism")] + #[test] + fn test_ensure_backwards_compatibility() { + assert_eq!(Account::bitflag_encoded_bytes(), 2); + assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); + assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); + assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); + assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); + assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(Header::bitflag_encoded_bytes(), 4); + assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); + assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); + assert_eq!(Receipt::bitflag_encoded_bytes(), 2); + assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); + assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); + assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); + assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); + assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); + assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); + assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); + assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); + assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); } } diff --git a/crates/storage/nippy-jar/src/error.rs b/crates/storage/nippy-jar/src/error.rs index a440a9cb3e30..225d4fba30fb 100644 --- a/crates/storage/nippy-jar/src/error.rs +++ b/crates/storage/nippy-jar/src/error.rs @@ -24,7 +24,7 @@ pub enum NippyJarError { #[error("unexpected missing value: row:col {0}:{1}")] UnexpectedMissingValue(u64, u64), #[error(transparent)] - FilterError(#[from] cuckoofilter::CuckooError), + EthFilterError(#[from] cuckoofilter::CuckooError), #[error("nippy jar initialized without filter")] FilterMissing, #[error("filter has reached max capacity")] diff --git a/crates/storage/provider/src/traits/spec.rs b/crates/storage/provider/src/traits/spec.rs index 917051d97aca..798bfeae16fd 100644 --- a/crates/storage/provider/src/traits/spec.rs +++ b/crates/storage/provider/src/traits/spec.rs @@ -2,6 +2,7 @@ use reth_chainspec::ChainSpec; use std::sync::Arc; /// A trait for reading the current chainspec. +#[auto_impl::auto_impl(&, Arc)] pub trait ChainSpecProvider: Send + Sync { /// Get an [`Arc`] to the chainspec. fn chain_spec(&self) -> Arc; diff --git a/crates/storage/storage-api/src/receipts.rs b/crates/storage/storage-api/src/receipts.rs index b050ca3e248c..04eb81aad02d 100644 --- a/crates/storage/storage-api/src/receipts.rs +++ b/crates/storage/storage-api/src/receipts.rs @@ -38,6 +38,7 @@ pub trait ReceiptProvider: Send + Sync { /// so this trait can only be implemented for types that implement `BlockIdReader`. The /// `BlockIdReader` methods should be used to resolve `BlockId`s to block numbers or hashes, and /// retrieving the receipts should be done using the type's `ReceiptProvider` methods. +#[auto_impl::auto_impl(&, Arc)] pub trait ReceiptProviderIdExt: ReceiptProvider + BlockIdReader { /// Get receipt by block id fn receipts_by_block_id(&self, block: BlockId) -> ProviderResult>> { diff --git a/crates/tasks/Cargo.toml b/crates/tasks/Cargo.toml index 63eb870fc15e..82c80c0932b8 100644 --- a/crates/tasks/Cargo.toml +++ b/crates/tasks/Cargo.toml @@ -23,6 +23,7 @@ reth-metrics.workspace = true metrics.workspace = true # misc +auto_impl.workspace = true tracing.workspace = true thiserror.workspace = true dyn-clone.workspace = true diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index 2d3f5f41b2b6..a0070698fcff 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -84,6 +84,7 @@ pub mod pool; /// ``` /// /// The [`TaskSpawner`] trait is [`DynClone`] so `Box` are also `Clone`. +#[auto_impl::auto_impl(&, Arc)] pub trait TaskSpawner: Send + Sync + Unpin + std::fmt::Debug + DynClone { /// Spawns the task onto the runtime. /// See also [`Handle::spawn`]. @@ -580,6 +581,7 @@ impl TaskSpawner for TaskExecutor { } /// `TaskSpawner` with extended behaviour +#[auto_impl::auto_impl(&, Arc)] pub trait TaskSpawnerExt: Send + Sync + Unpin + std::fmt::Debug + DynClone { /// This spawns a critical task onto the runtime. /// diff --git a/crates/tasks/src/pool.rs b/crates/tasks/src/pool.rs index dbb4e19de98d..10fedccedd1f 100644 --- a/crates/tasks/src/pool.rs +++ b/crates/tasks/src/pool.rs @@ -43,7 +43,8 @@ impl BlockingTaskGuard { /// /// This is a dedicated threadpool for blocking tasks which are CPU bound. /// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio -/// runtime's blocking pool, which performs poorly with CPU bound tasks. Once the tokio blocking +/// runtime's blocking pool, which performs poorly with CPU bound tasks (see +/// ). Once the tokio blocking /// pool is saturated it is converted into a queue, blocking tasks could then interfere with the /// queue and block other RPC calls. /// diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index 41d57e94165e..e43c45e17e82 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -39,7 +39,7 @@ pub type PeerId = reth_primitives::B512; /// /// Note: This requires `Clone` for convenience, since it is assumed that this will be implemented /// for a wrapped `Arc` type, see also [`Pool`](crate::Pool). -#[auto_impl::auto_impl(Arc)] +#[auto_impl::auto_impl(&, Arc)] pub trait TransactionPool: Send + Sync + Clone { /// The transaction type of the pool type Transaction: PoolTransaction; @@ -388,7 +388,7 @@ pub trait TransactionPool: Send + Sync + Clone { } /// Extension for [TransactionPool] trait that allows to set the current block info. -#[auto_impl::auto_impl(Arc)] +#[auto_impl::auto_impl(&, Arc)] pub trait TransactionPoolExt: TransactionPool { /// Sets the current block info for the pool. fn set_block_info(&self, info: BlockInfo); diff --git a/examples/custom-dev-node/src/main.rs b/examples/custom-dev-node/src/main.rs index 498971dbd1b6..5f346e7a2932 100644 --- a/examples/custom-dev-node/src/main.rs +++ b/examples/custom-dev-node/src/main.rs @@ -7,7 +7,7 @@ use futures_util::StreamExt; use reth::{ builder::{NodeBuilder, NodeHandle}, providers::CanonStateSubscriptions, - rpc::eth::EthTransactions, + rpc::eth::servers::EthTransactions, tasks::TaskManager, }; use reth_chainspec::ChainSpec; diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 9d394126fde2..638a2dc172cb 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -2,13 +2,12 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use alloy_genesis::Genesis; use reth::{ builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, primitives::{ address, - revm_primitives::{CfgEnvWithHandlerCfg, Env, PrecompileResult, TxEnv}, - Address, Bytes, U256, + revm_primitives::{Env, PrecompileResult}, + Bytes, }, revm::{ handler::register::EvmHandler, @@ -21,8 +20,8 @@ use reth::{ use reth_chainspec::{Chain, ChainSpec}; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; -use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider, EthereumNode}; -use reth_primitives::{Header, TransactionSigned}; +use reth_node_ethereum::{EthExecutorProvider, EthereumNode}; +use reth_primitives::Genesis; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; @@ -62,20 +61,7 @@ impl MyEvmConfig { } } -impl ConfigureEvmEnv for MyEvmConfig { - fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { - EthEvmConfig::fill_tx_env(tx_env, transaction, sender) - } - - fn fill_cfg_env( - cfg_env: &mut CfgEnvWithHandlerCfg, - chain_spec: &ChainSpec, - header: &Header, - total_difficulty: U256, - ) { - EthEvmConfig::fill_cfg_env(cfg_env, chain_spec, header, total_difficulty) - } -} +impl ConfigureEvmEnv for MyEvmConfig {} impl ConfigureEvm for MyEvmConfig { type DefaultExternalContext<'a> = (); diff --git a/examples/custom-inspector/src/main.rs b/examples/custom-inspector/src/main.rs index c1aae0227fc0..b3f33af07437 100644 --- a/examples/custom-inspector/src/main.rs +++ b/examples/custom-inspector/src/main.rs @@ -21,7 +21,7 @@ use reth::{ interpreter::{Interpreter, OpCode}, Database, Evm, EvmContext, Inspector, }, - rpc::{compat::transaction::transaction_to_call_request, eth::EthTransactions}, + rpc::{compat::transaction::transaction_to_call_request, eth::servers::Call}, transaction_pool::TransactionPool, }; use reth_node_ethereum::node::EthereumNode; diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index 8eaecb29ac73..adca439d764a 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -133,8 +133,8 @@ impl StatefulPrecompileMut for WrappedPrecompile { } impl ConfigureEvmEnv for MyEvmConfig { - fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { - EthEvmConfig::fill_tx_env(tx_env, transaction, sender) + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + EthEvmConfig::default().fill_tx_env(tx_env, transaction, sender) } fn fill_cfg_env( From 1b9f5871f551613f61ebd988c73e2ea5380ac31f Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 27 Jun 2024 14:34:17 +0200 Subject: [PATCH 237/405] chore(rpc): move impl of eth api server out of `reth-rpc-eth-api` (#9135) Co-authored-by: Matthias Seitz --- Cargo.lock | 63 +- Cargo.toml | 3 + bin/reth/Cargo.toml | 3 + bin/reth/src/lib.rs | 13 +- crates/e2e-test-utils/src/rpc.rs | 9 +- crates/ethereum/node/tests/e2e/dev.rs | 8 +- crates/node-core/Cargo.toml | 2 + crates/node-core/src/args/gas_price_oracle.rs | 2 +- crates/node-core/src/args/rpc_server.rs | 22 +- crates/node-core/src/lib.rs | 2 +- crates/optimism/node/Cargo.toml | 8 +- crates/optimism/node/src/rpc.rs | 17 +- crates/rpc/rpc-api/src/lib.rs | 2 +- crates/rpc/rpc-builder/Cargo.toml | 1 + crates/rpc/rpc-builder/src/auth.rs | 7 +- crates/rpc/rpc-builder/src/config.rs | 10 +- crates/rpc/rpc-builder/src/eth.rs | 13 +- crates/rpc/rpc-builder/src/lib.rs | 10 +- crates/rpc/rpc-eth-api/Cargo.toml | 29 +- crates/rpc/rpc-eth-api/src/api/mod.rs | 318 -------- crates/rpc/rpc-eth-api/src/api/servers/mod.rs | 323 -------- .../rpc/rpc-eth-api/src/api/servers/server.rs | 715 ----------------- .../rpc/rpc-eth-api/src/{api => }/bundle.rs | 0 crates/rpc/rpc-eth-api/src/core.rs | 727 ++++++++++++++++++ .../rpc/rpc-eth-api/src/{api => }/filter.rs | 0 .../helpers/traits => helpers}/block.rs | 20 +- .../rpc-eth-api/src/helpers/blocking_task.rs | 54 ++ .../helpers/traits => helpers}/call.rs | 22 +- .../servers/helpers/traits => helpers}/fee.rs | 10 +- .../servers/helpers/traits => helpers}/mod.rs | 30 +- .../traits => helpers}/pending_block.rs | 10 +- .../helpers/traits => helpers}/receipt.rs | 3 +- .../helpers/traits => helpers}/signer.rs | 3 +- .../helpers/traits => helpers}/spec.rs | 0 .../helpers/traits => helpers}/state.rs | 8 +- .../helpers/traits => helpers}/trace.rs | 10 +- .../helpers/traits => helpers}/transaction.rs | 15 +- crates/rpc/rpc-eth-api/src/lib.rs | 63 +- .../rpc/rpc-eth-api/src/{api => }/pubsub.rs | 0 crates/rpc/rpc-eth-types/Cargo.toml | 68 ++ .../src/cache/config.rs | 8 +- .../src/cache/db.rs | 2 +- .../src/cache/metrics.rs | 0 .../src/cache/mod.rs | 4 +- .../src/cache/multi_consumer.rs | 6 +- .../src/error.rs | 27 +- .../src/fee_history.rs | 7 +- .../src/gas_oracle.rs | 41 +- .../src/id_provider.rs | 8 +- crates/rpc/rpc-eth-types/src/lib.rs | 34 + .../src/logs_utils.rs | 64 +- .../src/pending_block.rs | 4 +- .../src/receipt.rs | 2 +- .../src/revm_utils.rs | 2 +- .../src/transaction.rs | 2 +- .../src/utils.rs | 2 +- crates/rpc/rpc-server-types/Cargo.toml | 11 + crates/rpc/rpc-server-types/src/constants.rs | 14 + crates/rpc/rpc-server-types/src/lib.rs | 3 + .../src/result.rs | 34 +- crates/rpc/rpc-types/Cargo.toml | 8 +- crates/rpc/rpc/Cargo.toml | 17 + crates/rpc/rpc/src/admin.rs | 3 +- crates/rpc/rpc/src/debug.rs | 9 +- .../src/api/servers => rpc/src/eth}/bundle.rs | 10 +- crates/rpc/rpc/src/eth/core.rs | 601 +++++++++++++++ .../src/api/servers => rpc/src/eth}/filter.rs | 62 +- .../servers => rpc/src/eth}/helpers/block.rs | 7 +- .../src/eth/helpers}/blocking_task.rs | 0 .../servers => rpc/src/eth}/helpers/call.rs | 6 +- crates/rpc/rpc/src/eth/helpers/fee.rs | 346 +++++++++ .../servers => rpc/src/eth}/helpers/fees.rs | 8 +- .../servers => rpc/src/eth}/helpers/mod.rs | 1 - .../src/eth}/helpers/optimism.rs | 10 +- .../src/eth}/helpers/pending_block.rs | 7 +- .../src/eth}/helpers/receipt.rs | 7 +- .../servers => rpc/src/eth}/helpers/signer.rs | 7 +- .../servers => rpc/src/eth}/helpers/spec.rs | 7 +- .../servers => rpc/src/eth}/helpers/state.rs | 16 +- .../servers => rpc/src/eth}/helpers/trace.rs | 6 +- .../src/eth}/helpers/transaction.rs | 21 +- crates/rpc/rpc/src/eth/mod.rs | 17 + .../src/api/servers => rpc/src/eth}/pubsub.rs | 9 +- crates/rpc/rpc/src/lib.rs | 16 +- crates/rpc/rpc/src/net.rs | 2 +- crates/rpc/rpc/src/otterscan.rs | 3 +- crates/rpc/rpc/src/reth.rs | 2 +- crates/rpc/rpc/src/trace.rs | 4 +- crates/rpc/rpc/src/web3.rs | 3 +- examples/custom-dev-node/src/main.rs | 5 +- examples/custom-inspector/src/main.rs | 2 +- examples/rpc-db/src/myrpc_ext.rs | 2 +- 92 files changed, 2325 insertions(+), 1797 deletions(-) delete mode 100644 crates/rpc/rpc-eth-api/src/api/mod.rs delete mode 100644 crates/rpc/rpc-eth-api/src/api/servers/mod.rs delete mode 100644 crates/rpc/rpc-eth-api/src/api/servers/server.rs rename crates/rpc/rpc-eth-api/src/{api => }/bundle.rs (100%) create mode 100644 crates/rpc/rpc-eth-api/src/core.rs rename crates/rpc/rpc-eth-api/src/{api => }/filter.rs (100%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/block.rs (94%) create mode 100644 crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/call.rs (99%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/fee.rs (98%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/mod.rs (55%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/pending_block.rs (99%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/receipt.rs (94%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/signer.rs (97%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/spec.rs (100%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/state.rs (99%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/trace.rs (98%) rename crates/rpc/rpc-eth-api/src/{api/servers/helpers/traits => helpers}/transaction.rs (99%) rename crates/rpc/rpc-eth-api/src/{api => }/pubsub.rs (100%) create mode 100644 crates/rpc/rpc-eth-types/Cargo.toml rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/cache/config.rs (80%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/cache/db.rs (99%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/cache/metrics.rs (100%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/cache/mod.rs (99%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/cache/multi_consumer.rs (99%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/error.rs (97%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/fee_history.rs (99%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/gas_oracle.rs (90%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/id_provider.rs (86%) create mode 100644 crates/rpc/rpc-eth-types/src/lib.rs rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/logs_utils.rs (79%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/pending_block.rs (97%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/receipt.rs (99%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/revm_utils.rs (99%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/transaction.rs (97%) rename crates/rpc/{rpc-eth-api => rpc-eth-types}/src/utils.rs (95%) rename crates/rpc/{rpc-eth-api => rpc-server-types}/src/result.rs (80%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/bundle.rs (97%) create mode 100644 crates/rpc/rpc/src/eth/core.rs rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/filter.rs (93%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/block.rs (83%) rename crates/rpc/{rpc-eth-api/src/api/servers/helpers/traits => rpc/src/eth/helpers}/blocking_task.rs (100%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/call.rs (84%) create mode 100644 crates/rpc/rpc/src/eth/helpers/fee.rs rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/fees.rs (85%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/mod.rs (95%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/optimism.rs (96%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/pending_block.rs (89%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/receipt.rs (66%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/signer.rs (98%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/spec.rs (90%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/state.rs (92%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/trace.rs (83%) rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/helpers/transaction.rs (94%) create mode 100644 crates/rpc/rpc/src/eth/mod.rs rename crates/rpc/{rpc-eth-api/src/api/servers => rpc/src/eth}/pubsub.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 4c2408d2ac58..326850d6eb8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6345,6 +6345,8 @@ dependencies = [ "reth-rpc", "reth-rpc-api", "reth-rpc-builder", + "reth-rpc-eth-types", + "reth-rpc-server-types", "reth-rpc-types", "reth-rpc-types-compat", "reth-stages", @@ -7604,6 +7606,7 @@ dependencies = [ "reth-prune-types", "reth-rpc-api", "reth-rpc-eth-api", + "reth-rpc-eth-types", "reth-rpc-server-types", "reth-rpc-types", "reth-rpc-types-compat", @@ -7687,6 +7690,7 @@ dependencies = [ "clap", "eyre", "jsonrpsee", + "jsonrpsee-types", "parking_lot 0.12.3", "reqwest", "reth", @@ -7710,6 +7714,8 @@ dependencies = [ "reth-provider", "reth-revm", "reth-rpc", + "reth-rpc-eth-api", + "reth-rpc-eth-types", "reth-rpc-types", "reth-rpc-types-compat", "reth-tracing", @@ -7996,6 +8002,7 @@ dependencies = [ name = "reth-rpc" version = "1.0.0" dependencies = [ + "alloy-dyn-abi", "alloy-genesis", "alloy-primitives", "alloy-rlp", @@ -8006,12 +8013,17 @@ dependencies = [ "http-body", "hyper", "jsonrpsee", + "jsonrpsee-types", "jsonwebtoken", + "parking_lot 0.12.3", "pin-project", + "rand 0.8.5", "reth-chainspec", "reth-consensus-common", "reth-errors", + "reth-evm", "reth-evm-ethereum", + "reth-evm-optimism", "reth-network-api", "reth-network-peers", "reth-primitives", @@ -8020,6 +8032,8 @@ dependencies = [ "reth-rpc-api", "reth-rpc-engine-api", "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-server-types", "reth-rpc-types", "reth-rpc-types-compat", "reth-tasks", @@ -8028,8 +8042,13 @@ dependencies = [ "revm", "revm-inspectors", "revm-primitives", + "secp256k1", + "serde", + "serde_json", "tempfile", + "thiserror", "tokio", + "tokio-stream", "tower", "tracing", "tracing-futures", @@ -8089,6 +8108,7 @@ dependencies = [ "reth-rpc", "reth-rpc-api", "reth-rpc-engine-api", + "reth-rpc-eth-types", "reth-rpc-layer", "reth-rpc-server-types", "reth-rpc-types", @@ -8144,25 +8164,48 @@ name = "reth-rpc-eth-api" version = "1.0.0" dependencies = [ "alloy-dyn-abi", - "alloy-sol-types", "async-trait", "auto_impl", - "derive_more", "dyn-clone", "futures", "jsonrpsee", + "parking_lot 0.12.3", + "reth-chainspec", + "reth-errors", + "reth-evm", + "reth-execution-types", + "reth-primitives", + "reth-provider", + "reth-revm", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-rpc-types", + "reth-rpc-types-compat", + "reth-tasks", + "reth-transaction-pool", + "revm", + "revm-inspectors", + "revm-primitives", + "tokio", + "tracing", +] + +[[package]] +name = "reth-rpc-eth-types" +version = "1.0.0" +dependencies = [ + "alloy-sol-types", + "derive_more", + "futures", + "jsonrpsee-core", "jsonrpsee-types", "metrics", - "parking_lot 0.12.3", "rand 0.8.5", "reth-chainspec", "reth-errors", "reth-evm", - "reth-evm-ethereum", - "reth-evm-optimism", "reth-execution-types", "reth-metrics", - "reth-network-api", "reth-primitives", "reth-provider", "reth-revm", @@ -8170,14 +8213,12 @@ dependencies = [ "reth-rpc-types", "reth-rpc-types-compat", "reth-tasks", - "reth-testing-utils", "reth-transaction-pool", "reth-trie", "revm", "revm-inspectors", "revm-primitives", "schnellru", - "secp256k1", "serde", "serde_json", "thiserror", @@ -8208,6 +8249,12 @@ name = "reth-rpc-server-types" version = "1.0.0" dependencies = [ "alloy-primitives", + "jsonrpsee-core", + "jsonrpsee-types", + "reth-errors", + "reth-network-api", + "reth-primitives", + "reth-rpc-types", "serde", "strum", ] diff --git a/Cargo.toml b/Cargo.toml index 441b69b2aa8e..32360341f024 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,8 +81,10 @@ members = [ "crates/rpc/rpc-builder/", "crates/rpc/rpc-engine-api/", "crates/rpc/rpc-eth-api/", + "crates/rpc/rpc-eth-types/", "crates/rpc/rpc-layer", "crates/rpc/rpc-testing-util/", + "crates/rpc/rpc-server-types/", "crates/rpc/rpc-types-compat/", "crates/rpc/rpc-types/", "crates/rpc/rpc/", @@ -342,6 +344,7 @@ reth-rpc-builder = { path = "crates/rpc/rpc-builder" } reth-rpc-engine-api = { path = "crates/rpc/rpc-engine-api" } reth-rpc-eth-api = { path = "crates/rpc/rpc-eth-api" } reth-rpc-layer = { path = "crates/rpc/rpc-layer" } +reth-rpc-eth-types = { path = "crates/rpc/rpc-eth-types" } reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-types = { path = "crates/rpc/rpc-types" } reth-rpc-types-compat = { path = "crates/rpc/rpc-types-compat" } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 88506f5637f3..65c7c4f5911e 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -37,6 +37,8 @@ reth-rpc.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true reth-rpc-api = { workspace = true, features = ["client"] } +reth-rpc-eth-types.workspace = true +reth-rpc-server-types.workspace = true reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true reth-net-banlist.workspace = true @@ -153,6 +155,7 @@ optimism = [ "reth-blockchain-tree/optimism", "dep:reth-node-optimism", "reth-node-core/optimism", + "reth-rpc-eth-types/optimism", ] # no-op feature flag for switching between the `optimism` and default functionality in CI matrices diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 35cd1357a12c..c725b033b2c0 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -148,6 +148,15 @@ pub mod rpc { pub use reth_rpc_types::*; } + /// Re-exported from `reth_rpc_server_types`. + pub mod server_types { + pub use reth_rpc_server_types::*; + /// Re-exported from `reth_rpc_eth_types`. + pub mod eth { + pub use reth_rpc_eth_types::*; + } + } + /// Re-exported from `reth_rpc_api`. pub mod api { pub use reth_rpc_api::*; @@ -159,10 +168,10 @@ pub mod rpc { /// Re-exported from `reth_rpc::rpc`. pub mod result { - pub use reth_rpc::result::*; + pub use reth_rpc_server_types::result::*; } - /// Re-exported from `reth_rpc::eth`. + /// Re-exported from `reth_rpc_types_compat`. pub mod compat { pub use reth_rpc_types_compat::*; } diff --git a/crates/e2e-test-utils/src/rpc.rs b/crates/e2e-test-utils/src/rpc.rs index 0c55e9c23c0d..b05d5df895a0 100644 --- a/crates/e2e-test-utils/src/rpc.rs +++ b/crates/e2e-test-utils/src/rpc.rs @@ -1,8 +1,13 @@ use alloy_consensus::TxEnvelope; use alloy_network::eip2718::Decodable2718; -use reth::{api::FullNodeComponents, builder::rpc::RpcRegistry, rpc::api::DebugApiServer}; +use reth::{ + builder::{rpc::RpcRegistry, FullNodeComponents}, + rpc::{ + api::{eth::helpers::EthTransactions, DebugApiServer}, + server_types::eth::EthResult, + }, +}; use reth_primitives::{Bytes, B256}; -use reth_rpc::eth::{servers::EthTransactions, EthResult}; pub struct RpcTestContext { pub inner: RpcRegistry, diff --git a/crates/ethereum/node/tests/e2e/dev.rs b/crates/ethereum/node/tests/e2e/dev.rs index 4a95231d4fb4..2b20d781f7c5 100644 --- a/crates/ethereum/node/tests/e2e/dev.rs +++ b/crates/ethereum/node/tests/e2e/dev.rs @@ -1,12 +1,14 @@ -use crate::utils::EthNode; +use std::sync::Arc; + use alloy_genesis::Genesis; use alloy_primitives::{b256, hex}; use futures::StreamExt; -use reth::rpc::eth::servers::EthTransactions; +use reth::rpc::api::eth::helpers::EthTransactions; use reth_chainspec::ChainSpec; use reth_e2e_test_utils::setup; use reth_provider::CanonStateSubscriptions; -use std::sync::Arc; + +use crate::utils::EthNode; #[tokio::test] async fn can_run_dev_node() -> eyre::Result<()> { diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index 259cfcdb5b49..ec86bb440893 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -21,6 +21,7 @@ reth-storage-errors.workspace = true reth-provider.workspace = true reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true +reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true @@ -103,6 +104,7 @@ optimism = [ "reth-rpc-types-compat/optimism", "reth-beacon-consensus/optimism", "reth-rpc-eth-api/optimism", + "reth-rpc-eth-types/optimism" ] jemalloc = ["dep:tikv-jemalloc-ctl"] diff --git a/crates/node-core/src/args/gas_price_oracle.rs b/crates/node-core/src/args/gas_price_oracle.rs index c719e9c8a5fb..abdd8e14214f 100644 --- a/crates/node-core/src/args/gas_price_oracle.rs +++ b/crates/node-core/src/args/gas_price_oracle.rs @@ -1,6 +1,6 @@ use crate::primitives::U256; use clap::Args; -use reth_rpc_eth_api::GasPriceOracleConfig; +use reth_rpc_eth_types::GasPriceOracleConfig; use reth_rpc_server_types::constants::gas_oracle::{ DEFAULT_GAS_PRICE_BLOCKS, DEFAULT_GAS_PRICE_PERCENTILE, DEFAULT_IGNORE_GAS_PRICE, DEFAULT_MAX_GAS_PRICE, diff --git a/crates/node-core/src/args/rpc_server.rs b/crates/node-core/src/args/rpc_server.rs index 62c675d50777..bad1e24213b9 100644 --- a/crates/node-core/src/args/rpc_server.rs +++ b/crates/node-core/src/args/rpc_server.rs @@ -1,22 +1,22 @@ //! clap [Args](clap::Args) for RPC related arguments. -use crate::args::{ - types::{MaxU32, ZeroAsNoneU64}, - GasPriceOracleArgs, RpcStateCacheArgs, +use std::{ + ffi::OsStr, + net::{IpAddr, Ipv4Addr}, + path::PathBuf, }; + use alloy_rpc_types_engine::JwtSecret; use clap::{ builder::{PossibleValue, RangedU64ValueParser, TypedValueParser}, Arg, Args, Command, }; use rand::Rng; -use reth_rpc_eth_api::RPC_DEFAULT_GAS_CAP; - use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection}; -use std::{ - ffi::OsStr, - net::{IpAddr, Ipv4Addr}, - path::PathBuf, + +use crate::args::{ + types::{MaxU32, ZeroAsNoneU64}, + GasPriceOracleArgs, RpcStateCacheArgs, }; /// Default max number of subscriptions per connection. @@ -152,7 +152,7 @@ pub struct RpcServerArgs { alias = "rpc-gascap", value_name = "GAS_CAP", value_parser = RangedU64ValueParser::::new().range(1..), - default_value_t = RPC_DEFAULT_GAS_CAP.into() + default_value_t = constants::gas_oracle::RPC_DEFAULT_GAS_CAP )] pub rpc_gas_cap: u64, @@ -285,7 +285,7 @@ impl Default for RpcServerArgs { rpc_max_tracing_requests: constants::default_max_tracing_requests(), rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(), rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(), - rpc_gas_cap: RPC_DEFAULT_GAS_CAP.into(), + rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP, gas_price_oracle: GasPriceOracleArgs::default(), rpc_state_cache: RpcStateCacheArgs::default(), } diff --git a/crates/node-core/src/lib.rs b/crates/node-core/src/lib.rs index 8fbc0b2f9d8b..27a81cc26e7c 100644 --- a/crates/node-core/src/lib.rs +++ b/crates/node-core/src/lib.rs @@ -43,7 +43,7 @@ pub mod rpc { /// Re-exported from `reth_rpc::rpc`. pub mod result { - pub use reth_rpc_eth_api::result::*; + pub use reth_rpc_server_types::result::*; } /// Re-exported from `reth_rpc::eth`. diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index f85e7baf696d..5d172ec93b73 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -35,6 +35,8 @@ reth-beacon-consensus.workspace = true reth-optimism-consensus.workspace = true revm-primitives.workspace = true reth-discv5.workspace = true +reth-rpc-eth-types.workspace = true +reth-rpc-eth-api.workspace = true # async async-trait.workspace = true @@ -44,11 +46,14 @@ tracing.workspace = true # misc clap.workspace = true serde.workspace = true -serde_json.workspace = true eyre.workspace = true parking_lot.workspace = true thiserror.workspace = true + +# rpc jsonrpsee.workspace = true +jsonrpsee-types.workspace = true +serde_json.workspace = true [dev-dependencies] reth.workspace = true @@ -71,4 +76,5 @@ optimism = [ "reth-beacon-consensus/optimism", "reth-revm/optimism", "reth-auto-seal-consensus/optimism", + "reth-rpc-eth-types/optimism" ] diff --git a/crates/optimism/node/src/rpc.rs b/crates/optimism/node/src/rpc.rs index 01cb08c268d6..d7c3f49efbc9 100644 --- a/crates/optimism/node/src/rpc.rs +++ b/crates/optimism/node/src/rpc.rs @@ -1,13 +1,12 @@ //! Helpers for optimism specific RPC implementations. -use jsonrpsee::types::ErrorObject; +use std::sync::{atomic::AtomicUsize, Arc}; + +use jsonrpsee_types::error::{ErrorObject, INTERNAL_ERROR_CODE}; use reqwest::Client; -use reth_rpc::eth::{ - error::{EthApiError, EthResult}, - servers::RawTransactionForwarder, -}; +use reth_rpc_eth_api::RawTransactionForwarder; +use reth_rpc_eth_types::error::{EthApiError, EthResult}; use reth_rpc_types::ToRpcError; -use std::sync::{atomic::AtomicUsize, Arc}; /// Error type when interacting with the Sequencer #[derive(Debug, thiserror::Error)] @@ -22,11 +21,7 @@ pub enum SequencerRpcError { impl ToRpcError for SequencerRpcError { fn to_rpc_error(&self) -> ErrorObject<'static> { - ErrorObject::owned( - jsonrpsee::types::error::INTERNAL_ERROR_CODE, - self.to_string(), - None::, - ) + ErrorObject::owned(INTERNAL_ERROR_CODE, self.to_string(), None::) } } diff --git a/crates/rpc/rpc-api/src/lib.rs b/crates/rpc/rpc-api/src/lib.rs index 661b7780e18c..cb84f8388788 100644 --- a/crates/rpc/rpc-api/src/lib.rs +++ b/crates/rpc/rpc-api/src/lib.rs @@ -51,7 +51,7 @@ pub mod servers { web3::Web3ApiServer, }; pub use reth_rpc_eth_api::{ - EthApiServer, EthBundleApiServer, EthCallBundleApiServer, EthFilterApiServer, + self as eth, EthApiServer, EthBundleApiServer, EthCallBundleApiServer, EthFilterApiServer, EthPubSubApiServer, }; } diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index 46105030cb63..9108525d038a 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -20,6 +20,7 @@ reth-provider.workspace = true reth-rpc.workspace = true reth-rpc-api.workspace = true reth-rpc-layer.workspace = true +reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-tasks = { workspace = true, features = ["rayon"] } reth-transaction-pool.workspace = true diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 30d5f3389d04..952362e0cfda 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -1,3 +1,5 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use crate::error::{RpcError, ServerKind}; use http::header::AUTHORIZATION; use jsonrpsee::{ @@ -7,14 +9,13 @@ use jsonrpsee::{ Methods, }; use reth_engine_primitives::EngineTypes; -use reth_rpc::eth::EthSubscriptionIdProvider; -use reth_rpc_api::servers::*; +use reth_rpc_api::*; +use reth_rpc_eth_types::EthSubscriptionIdProvider; use reth_rpc_layer::{ secret_to_bearer_header, AuthClientLayer, AuthClientService, AuthLayer, JwtAuthValidator, JwtSecret, }; use reth_rpc_server_types::constants; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use tower::layer::util::Identity; pub use jsonrpsee::server::ServerBuilder; diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index 89ce742e38e4..fbace8ec7803 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -4,7 +4,7 @@ use crate::{ }; use jsonrpsee::server::ServerBuilder; use reth_node_core::{args::RpcServerArgs, utils::get_or_create_jwt_secret_from_path}; -use reth_rpc::eth::{EthStateCacheConfig, GasPriceOracleConfig}; +use reth_rpc_eth_types::{EthStateCacheConfig, GasPriceOracleConfig}; use reth_rpc_layer::{JwtError, JwtSecret}; use reth_rpc_server_types::RpcModuleSelection; use std::{net::SocketAddr, path::PathBuf}; @@ -214,11 +214,13 @@ impl RethRpcServerConfig for RpcServerArgs { #[cfg(test)] mod tests { + use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use clap::{Args, Parser}; use reth_node_core::args::RpcServerArgs; - use reth_rpc::eth::RPC_DEFAULT_GAS_CAP; - use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection}; - use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use reth_rpc_server_types::{ + constants, constants::gas_oracle::RPC_DEFAULT_GAS_CAP, RethRpcModule, RpcModuleSelection, + }; use crate::config::RethRpcServerConfig; diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 62e857e0267e..7d177ed4302b 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -6,14 +6,15 @@ use reth_provider::{ AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, StateProviderFactory, }; -use reth_rpc::eth::{ - cache::cache_new_blocks_task, fee_history::fee_history_cache_new_blocks_task, - servers::RawTransactionForwarder, EthApi, EthFilter, EthFilterConfig, EthPubSub, EthStateCache, +use reth_rpc::eth::{EthApi, EthFilter, EthFilterConfig, EthPubSub, RawTransactionForwarder}; +use reth_rpc_eth_types::{ + cache::cache_new_blocks_task, fee_history::fee_history_cache_new_blocks_task, EthStateCache, EthStateCacheConfig, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, - GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP, + GasPriceOracleConfig, }; use reth_rpc_server_types::constants::{ - default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, + default_max_tracing_requests, gas_oracle::RPC_DEFAULT_GAS_CAP, DEFAULT_MAX_BLOCKS_PER_FILTER, + DEFAULT_MAX_LOGS_PER_RESPONSE, }; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; @@ -274,7 +275,7 @@ impl Default for EthConfig { max_tracing_requests: default_max_tracing_requests(), max_blocks_per_filter: DEFAULT_MAX_BLOCKS_PER_FILTER, max_logs_per_response: DEFAULT_MAX_LOGS_PER_RESPONSE, - rpc_gas_cap: RPC_DEFAULT_GAS_CAP.into(), + rpc_gas_cap: RPC_DEFAULT_GAS_CAP, stale_filter_ttl: DEFAULT_STALE_FILTER_TTL, fee_history_cache: FeeHistoryCacheConfig::default(), } diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 883409331601..2800e83d1154 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -178,14 +178,12 @@ use reth_provider::{ ChangeSetReader, EvmEnvProvider, StateProviderFactory, }; use reth_rpc::{ - eth::{ - servers::RawTransactionForwarder, EthApi, EthBundle, EthStateCache, - EthSubscriptionIdProvider, - }, + eth::{EthApi, EthBundle, RawTransactionForwarder}, AdminApi, DebugApi, EngineEthApi, NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi, Web3Api, }; -use reth_rpc_api::servers::*; +use reth_rpc_api::*; +use reth_rpc_eth_types::{EthStateCache, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret}; use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, TransactionPool}; @@ -998,7 +996,7 @@ where /// /// This will spawn the required service tasks for [`EthApi`] for: /// - [`EthStateCache`] - /// - [`reth_rpc::eth::FeeHistoryCache`] + /// - [`FeeHistoryCache`](reth_rpc_eth_types::FeeHistoryCache) fn with_eth(&mut self, f: F) -> R where F: FnOnce(&EthHandlers) -> R, diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index 88d3efd5fff6..fb8d84b3f4b7 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -6,7 +6,7 @@ rust-version.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true -description = "Reth RPC `eth_` API implementation" +description = "Reth RPC 'eth' namespace API" [lints] workspace = true @@ -18,61 +18,40 @@ revm-inspectors = { workspace = true, features = ["js-tracer"] } revm-primitives = { workspace = true, features = ["dev"] } reth-errors.workspace = true reth-evm.workspace = true -reth-metrics.workspace = true -reth-network-api.workspace = true reth-primitives.workspace = true reth-provider.workspace = true reth-revm.workspace = true -reth-rpc-server-types.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true reth-tasks = { workspace = true, features = ["rayon"] } -reth-trie.workspace = true reth-transaction-pool.workspace = true -reth-evm-optimism = { workspace = true, optional = true } reth-chainspec.workspace = true reth-execution-types.workspace = true +reth-rpc-eth-types.workspace = true +reth-rpc-server-types.workspace = true # ethereum alloy-dyn-abi = { workspace = true, features = ["eip712"] } -alloy-sol-types.workspace = true -secp256k1.workspace = true # rpc jsonrpsee = { workspace = true, features = ["server", "macros"] } -jsonrpsee-types = { workspace = true, optional = true } -serde_json.workspace = true # async async-trait.workspace = true futures.workspace = true parking_lot.workspace = true tokio.workspace = true -tokio-stream.workspace = true # misc auto_impl.workspace = true -derive_more.workspace = true dyn-clone.workspace = true -metrics.workspace = true -rand.workspace = true -schnellru.workspace = true -serde.workspace = true -thiserror.workspace = true tracing.workspace = true -[dev-dependencies] -reth-evm-ethereum.workspace = true -reth-testing-utils.workspace = true -reth-transaction-pool = { workspace = true, features = ["test-utils"] } -reth-provider = { workspace = true, features = ["test-utils"] } - [features] client = ["jsonrpsee/client", "jsonrpsee/async-client"] optimism = [ "reth-primitives/optimism", - "reth-evm-optimism", "revm/optimism", "reth-provider/optimism", - "jsonrpsee-types", + "reth-rpc-eth-types/optimism" ] \ No newline at end of file diff --git a/crates/rpc/rpc-eth-api/src/api/mod.rs b/crates/rpc/rpc-eth-api/src/api/mod.rs deleted file mode 100644 index 63e61254afc8..000000000000 --- a/crates/rpc/rpc-eth-api/src/api/mod.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! `eth_` RPC API. - -use alloy_dyn_abi::TypedData; -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; -use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; -use reth_rpc_types::{ - serde_helpers::JsonStorageKey, state::StateOverride, AccessListWithGasUsed, - AnyTransactionReceipt, BlockOverrides, Bundle, EIP1186AccountProofResponse, EthCallResponse, - FeeHistory, Header, Index, RichBlock, StateContext, SyncStatus, Transaction, - TransactionRequest, Work, -}; - -pub mod bundle; -pub mod filter; -pub mod pubsub; -pub mod servers; - -/// Eth rpc interface: -#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] -#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] -pub trait EthApi { - /// Returns the protocol version encoded as a string. - #[method(name = "protocolVersion")] - async fn protocol_version(&self) -> RpcResult; - - /// Returns an object with data about the sync status or false. - #[method(name = "syncing")] - fn syncing(&self) -> RpcResult; - - /// Returns the client coinbase address. - #[method(name = "coinbase")] - async fn author(&self) -> RpcResult
; - - /// Returns a list of addresses owned by client. - #[method(name = "accounts")] - fn accounts(&self) -> RpcResult>; - - /// Returns the number of most recent block. - #[method(name = "blockNumber")] - fn block_number(&self) -> RpcResult; - - /// Returns the chain ID of the current network. - #[method(name = "chainId")] - async fn chain_id(&self) -> RpcResult>; - - /// Returns information about a block by hash. - #[method(name = "getBlockByHash")] - async fn block_by_hash(&self, hash: B256, full: bool) -> RpcResult>; - - /// Returns information about a block by number. - #[method(name = "getBlockByNumber")] - async fn block_by_number( - &self, - number: BlockNumberOrTag, - full: bool, - ) -> RpcResult>; - - /// Returns the number of transactions in a block from a block matching the given block hash. - #[method(name = "getBlockTransactionCountByHash")] - async fn block_transaction_count_by_hash(&self, hash: B256) -> RpcResult>; - - /// Returns the number of transactions in a block matching the given block number. - #[method(name = "getBlockTransactionCountByNumber")] - async fn block_transaction_count_by_number( - &self, - number: BlockNumberOrTag, - ) -> RpcResult>; - - /// Returns the number of uncles in a block from a block matching the given block hash. - #[method(name = "getUncleCountByBlockHash")] - async fn block_uncles_count_by_hash(&self, hash: B256) -> RpcResult>; - - /// Returns the number of uncles in a block with given block number. - #[method(name = "getUncleCountByBlockNumber")] - async fn block_uncles_count_by_number( - &self, - number: BlockNumberOrTag, - ) -> RpcResult>; - - /// Returns all transaction receipts for a given block. - #[method(name = "getBlockReceipts")] - async fn block_receipts( - &self, - block_id: BlockId, - ) -> RpcResult>>; - - /// Returns an uncle block of the given block and index. - #[method(name = "getUncleByBlockHashAndIndex")] - async fn uncle_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> RpcResult>; - - /// Returns an uncle block of the given block and index. - #[method(name = "getUncleByBlockNumberAndIndex")] - async fn uncle_by_block_number_and_index( - &self, - number: BlockNumberOrTag, - index: Index, - ) -> RpcResult>; - - /// Returns the EIP-2718 encoded transaction if it exists. - /// - /// If this is a EIP-4844 transaction that is in the pool it will include the sidecar. - #[method(name = "getRawTransactionByHash")] - async fn raw_transaction_by_hash(&self, hash: B256) -> RpcResult>; - - /// Returns the information about a transaction requested by transaction hash. - #[method(name = "getTransactionByHash")] - async fn transaction_by_hash(&self, hash: B256) -> RpcResult>; - - /// Returns information about a raw transaction by block hash and transaction index position. - #[method(name = "getRawTransactionByBlockHashAndIndex")] - async fn raw_transaction_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> RpcResult>; - - /// Returns information about a transaction by block hash and transaction index position. - #[method(name = "getTransactionByBlockHashAndIndex")] - async fn transaction_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> RpcResult>; - - /// Returns information about a raw transaction by block number and transaction index - /// position. - #[method(name = "getRawTransactionByBlockNumberAndIndex")] - async fn raw_transaction_by_block_number_and_index( - &self, - number: BlockNumberOrTag, - index: Index, - ) -> RpcResult>; - - /// Returns information about a transaction by block number and transaction index position. - #[method(name = "getTransactionByBlockNumberAndIndex")] - async fn transaction_by_block_number_and_index( - &self, - number: BlockNumberOrTag, - index: Index, - ) -> RpcResult>; - - /// Returns the receipt of a transaction by transaction hash. - #[method(name = "getTransactionReceipt")] - async fn transaction_receipt(&self, hash: B256) -> RpcResult>; - - /// Returns the balance of the account of given address. - #[method(name = "getBalance")] - async fn balance(&self, address: Address, block_number: Option) -> RpcResult; - - /// Returns the value from a storage position at a given address - #[method(name = "getStorageAt")] - async fn storage_at( - &self, - address: Address, - index: JsonStorageKey, - block_number: Option, - ) -> RpcResult; - - /// Returns the number of transactions sent from an address at given block number. - #[method(name = "getTransactionCount")] - async fn transaction_count( - &self, - address: Address, - block_number: Option, - ) -> RpcResult; - - /// Returns code at a given address at given block number. - #[method(name = "getCode")] - async fn get_code(&self, address: Address, block_number: Option) -> RpcResult; - - /// Returns the block's header at given number. - #[method(name = "getHeaderByNumber")] - async fn header_by_number(&self, hash: BlockNumberOrTag) -> RpcResult>; - - /// Returns the block's header at given hash. - #[method(name = "getHeaderByHash")] - async fn header_by_hash(&self, hash: B256) -> RpcResult>; - - /// Executes a new message call immediately without creating a transaction on the block chain. - #[method(name = "call")] - async fn call( - &self, - request: TransactionRequest, - block_number: Option, - state_overrides: Option, - block_overrides: Option>, - ) -> RpcResult; - - /// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the - /// optionality of state overrides - #[method(name = "callMany")] - async fn call_many( - &self, - bundle: Bundle, - state_context: Option, - state_override: Option, - ) -> RpcResult>; - - /// Generates an access list for a transaction. - /// - /// This method creates an [EIP2930](https://eips.ethereum.org/EIPS/eip-2930) type accessList based on a given Transaction. - /// - /// An access list contains all storage slots and addresses touched by the transaction, except - /// for the sender account and the chain's precompiles. - /// - /// It returns list of addresses and storage keys used by the transaction, plus the gas - /// consumed when the access list is added. That is, it gives you the list of addresses and - /// storage keys that will be used by that transaction, plus the gas consumed if the access - /// list is included. Like eth_estimateGas, this is an estimation; the list could change - /// when the transaction is actually mined. Adding an accessList to your transaction does - /// not necessary result in lower gas usage compared to a transaction without an access - /// list. - #[method(name = "createAccessList")] - async fn create_access_list( - &self, - request: TransactionRequest, - block_number: Option, - ) -> RpcResult; - - /// Generates and returns an estimate of how much gas is necessary to allow the transaction to - /// complete. - #[method(name = "estimateGas")] - async fn estimate_gas( - &self, - request: TransactionRequest, - block_number: Option, - state_override: Option, - ) -> RpcResult; - - /// Returns the current price per gas in wei. - #[method(name = "gasPrice")] - async fn gas_price(&self) -> RpcResult; - - /// Introduced in EIP-1559, returns suggestion for the priority for dynamic fee transactions. - #[method(name = "maxPriorityFeePerGas")] - async fn max_priority_fee_per_gas(&self) -> RpcResult; - - /// Introduced in EIP-4844, returns the current blob base fee in wei. - #[method(name = "blobBaseFee")] - async fn blob_base_fee(&self) -> RpcResult; - - /// Returns the Transaction fee history - /// - /// Introduced in EIP-1559 for getting information on the appropriate priority fee to use. - /// - /// Returns transaction base fee per gas and effective priority fee per gas for the - /// requested/supported block range. The returned Fee history for the returned block range - /// can be a subsection of the requested range if not all blocks are available. - #[method(name = "feeHistory")] - async fn fee_history( - &self, - block_count: U64, - newest_block: BlockNumberOrTag, - reward_percentiles: Option>, - ) -> RpcResult; - - /// Returns whether the client is actively mining new blocks. - #[method(name = "mining")] - async fn is_mining(&self) -> RpcResult; - - /// Returns the number of hashes per second that the node is mining with. - #[method(name = "hashrate")] - async fn hashrate(&self) -> RpcResult; - - /// Returns the hash of the current block, the seedHash, and the boundary condition to be met - /// (“target”) - #[method(name = "getWork")] - async fn get_work(&self) -> RpcResult; - - /// Used for submitting mining hashrate. - /// - /// Can be used for remote miners to submit their hash rate. - /// It accepts the miner hash rate and an identifier which must be unique between nodes. - /// Returns `true` if the block was successfully submitted, `false` otherwise. - #[method(name = "submitHashrate")] - async fn submit_hashrate(&self, hashrate: U256, id: B256) -> RpcResult; - - /// Used for submitting a proof-of-work solution. - #[method(name = "submitWork")] - async fn submit_work(&self, nonce: B64, pow_hash: B256, mix_digest: B256) -> RpcResult; - - /// Sends transaction; will block waiting for signer to return the - /// transaction hash. - #[method(name = "sendTransaction")] - async fn send_transaction(&self, request: TransactionRequest) -> RpcResult; - - /// Sends signed transaction, returning its hash. - #[method(name = "sendRawTransaction")] - async fn send_raw_transaction(&self, bytes: Bytes) -> RpcResult; - - /// Returns an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" - /// + len(message) + message))). - #[method(name = "sign")] - async fn sign(&self, address: Address, message: Bytes) -> RpcResult; - - /// Signs a transaction that can be submitted to the network at a later time using with - /// `sendRawTransaction.` - #[method(name = "signTransaction")] - async fn sign_transaction(&self, transaction: TransactionRequest) -> RpcResult; - - /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). - #[method(name = "signTypedData")] - async fn sign_typed_data(&self, address: Address, data: TypedData) -> RpcResult; - - /// Returns the account and storage values of the specified account including the Merkle-proof. - /// This call can be used to verify that the data you are pulling from is not tampered with. - #[method(name = "getProof")] - async fn get_proof( - &self, - address: Address, - keys: Vec, - block_number: Option, - ) -> RpcResult; -} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/mod.rs b/crates/rpc/rpc-eth-api/src/api/servers/mod.rs deleted file mode 100644 index 5be35304a2e4..000000000000 --- a/crates/rpc/rpc-eth-api/src/api/servers/mod.rs +++ /dev/null @@ -1,323 +0,0 @@ -//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`](crate::EthApi) trait -//! Handles RPC requests for the `eth_` namespace. - -use std::sync::Arc; - -use reth_primitives::{BlockNumberOrTag, U256}; -use reth_provider::{BlockReaderIdExt, ChainSpecProvider}; - -pub mod bundle; -pub mod filter; -pub mod helpers; -pub mod pubsub; - -mod server; - -pub use helpers::{ - signer::DevSigner, - traits::{ - block::{EthBlocks, LoadBlock}, - blocking_task::SpawnBlocking, - call::{Call, EthCall}, - fee::{EthFees, LoadFee}, - pending_block::LoadPendingBlock, - receipt::LoadReceipt, - signer::EthSigner, - spec::EthApiSpec, - state::{EthState, LoadState}, - trace::Trace, - transaction::{EthTransactions, LoadTransaction, RawTransactionForwarder}, - TraceExt, - }, -}; -use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor}; -use tokio::sync::Mutex; - -use crate::{EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock}; - -/// `Eth` API implementation. -/// -/// This type provides the functionality for handling `eth_` related requests. -/// These are implemented two-fold: Core functionality is implemented as [`EthApiSpec`] -/// trait. Additionally, the required server implementations (e.g. -/// [`EthApiServer`](crate::EthApiServer)) are implemented separately in submodules. The rpc handler -/// implementation can then delegate to the main impls. This way [`EthApi`] is not limited to -/// [`jsonrpsee`] and can be used standalone or in other network handlers (for example ipc). -pub struct EthApi { - /// All nested fields bundled together. - inner: Arc>, -} - -impl EthApi { - /// Sets a forwarder for `eth_sendRawTransaction` - /// - /// Note: this might be removed in the future in favor of a more generic approach. - pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { - self.inner.raw_transaction_forwarder.write().replace(forwarder); - } -} - -impl EthApi -where - Provider: BlockReaderIdExt + ChainSpecProvider, -{ - /// Creates a new, shareable instance using the default tokio task spawner. - #[allow(clippy::too_many_arguments)] - pub fn new( - provider: Provider, - pool: Pool, - network: Network, - eth_cache: EthStateCache, - gas_oracle: GasPriceOracle, - gas_cap: impl Into, - blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, - evm_config: EvmConfig, - raw_transaction_forwarder: Option>, - ) -> Self { - Self::with_spawner( - provider, - pool, - network, - eth_cache, - gas_oracle, - gas_cap.into().into(), - Box::::default(), - blocking_task_pool, - fee_history_cache, - evm_config, - raw_transaction_forwarder, - ) - } - - /// Creates a new, shareable instance. - #[allow(clippy::too_many_arguments)] - pub fn with_spawner( - provider: Provider, - pool: Pool, - network: Network, - eth_cache: EthStateCache, - gas_oracle: GasPriceOracle, - gas_cap: u64, - task_spawner: Box, - blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, - evm_config: EvmConfig, - raw_transaction_forwarder: Option>, - ) -> Self { - // get the block number of the latest block - let latest_block = provider - .header_by_number_or_tag(BlockNumberOrTag::Latest) - .ok() - .flatten() - .map(|header| header.number) - .unwrap_or_default(); - - let inner = EthApiInner { - provider, - pool, - network, - signers: parking_lot::RwLock::new(Default::default()), - eth_cache, - gas_oracle, - gas_cap, - starting_block: U256::from(latest_block), - task_spawner, - pending_block: Default::default(), - blocking_task_pool, - fee_history_cache, - evm_config, - raw_transaction_forwarder: parking_lot::RwLock::new(raw_transaction_forwarder), - }; - - Self { inner: Arc::new(inner) } - } - - /// Returns the state cache frontend - pub fn cache(&self) -> &EthStateCache { - &self.inner.eth_cache - } - - /// Returns the gas oracle frontend - pub fn gas_oracle(&self) -> &GasPriceOracle { - &self.inner.gas_oracle - } - - /// Returns the configured gas limit cap for `eth_call` and tracing related calls - pub fn gas_cap(&self) -> u64 { - self.inner.gas_cap - } - - /// Returns the inner `Provider` - pub fn provider(&self) -> &Provider { - &self.inner.provider - } - - /// Returns the inner `Network` - pub fn network(&self) -> &Network { - &self.inner.network - } - - /// Returns the inner `Pool` - pub fn pool(&self) -> &Pool { - &self.inner.pool - } - - /// Returns fee history cache - pub fn fee_history_cache(&self) -> &FeeHistoryCache { - &self.inner.fee_history_cache - } -} - -impl std::fmt::Debug - for EthApi -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EthApi").finish_non_exhaustive() - } -} - -impl Clone for EthApi { - fn clone(&self) -> Self { - Self { inner: Arc::clone(&self.inner) } - } -} - -/// Implements [`SpawnBlocking`] for a type, that has similar data layout to [`EthApi`]. -#[macro_export] -macro_rules! spawn_blocking_impl { - ($network_api:ty) => { - impl $crate::servers::SpawnBlocking for $network_api - where - Self: Clone + Send + Sync + 'static, - { - #[inline] - fn io_task_spawner(&self) -> impl reth_tasks::TaskSpawner { - self.inner.task_spawner() - } - - #[inline] - fn tracing_task_pool(&self) -> &reth_tasks::pool::BlockingTaskPool { - self.inner.blocking_task_pool() - } - } - }; -} - -spawn_blocking_impl!(EthApi); - -impl EthApi { - /// Generates 20 random developer accounts. - /// Used in DEV mode. - pub fn with_dev_accounts(&self) { - let mut signers = self.inner.signers.write(); - *signers = DevSigner::random_signers(20); - } -} - -/// Container type `EthApi` -#[allow(missing_debug_implementations)] -pub struct EthApiInner { - /// The transaction pool. - pool: Pool, - /// The provider that can interact with the chain. - provider: Provider, - /// An interface to interact with the network - network: Network, - /// All configured Signers - signers: parking_lot::RwLock>>, - /// The async cache frontend for eth related data - eth_cache: EthStateCache, - /// The async gas oracle frontend for gas price suggestions - gas_oracle: GasPriceOracle, - /// Maximum gas limit for `eth_call` and call tracing RPC methods. - gas_cap: u64, - /// The block number at which the node started - starting_block: U256, - /// The type that can spawn tasks which would otherwise block. - task_spawner: Box, - /// Cached pending block if any - pending_block: Mutex>, - /// A pool dedicated to CPU heavy blocking tasks. - blocking_task_pool: BlockingTaskPool, - /// Cache for block fees history - fee_history_cache: FeeHistoryCache, - /// The type that defines how to configure the EVM - evm_config: EvmConfig, - /// Allows forwarding received raw transactions - raw_transaction_forwarder: parking_lot::RwLock>>, -} - -impl EthApiInner { - /// Returns a handle to data on disk. - #[inline] - pub const fn provider(&self) -> &Provider { - &self.provider - } - - /// Returns a handle to data in memory. - #[inline] - pub const fn cache(&self) -> &EthStateCache { - &self.eth_cache - } - - /// Returns a handle to the pending block. - #[inline] - pub const fn pending_block(&self) -> &Mutex> { - &self.pending_block - } - - /// Returns a handle to the task spawner. - #[inline] - pub const fn task_spawner(&self) -> &dyn TaskSpawner { - &*self.task_spawner - } - - /// Returns a handle to the blocking thread pool. - #[inline] - pub const fn blocking_task_pool(&self) -> &BlockingTaskPool { - &self.blocking_task_pool - } - - /// Returns a handle to the EVM config. - #[inline] - pub const fn evm_config(&self) -> &EvmConfig { - &self.evm_config - } - - /// Returns a handle to the transaction pool. - #[inline] - pub const fn pool(&self) -> &Pool { - &self.pool - } - - /// Returns a handle to the transaction forwarder. - #[inline] - pub fn raw_tx_forwarder(&self) -> Option> { - self.raw_transaction_forwarder.read().clone() - } - - /// Returns the gas cap. - #[inline] - pub const fn gas_cap(&self) -> u64 { - self.gas_cap - } - - /// Returns a handle to the gas oracle. - #[inline] - pub const fn gas_oracle(&self) -> &GasPriceOracle { - &self.gas_oracle - } - - /// Returns a handle to the fee history cache. - #[inline] - pub const fn fee_history_cache(&self) -> &FeeHistoryCache { - &self.fee_history_cache - } - - /// Returns a handle to the signers. - #[inline] - pub const fn signers(&self) -> &parking_lot::RwLock>> { - &self.signers - } -} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/server.rs b/crates/rpc/rpc-eth-api/src/api/servers/server.rs deleted file mode 100644 index 0701ff70db48..000000000000 --- a/crates/rpc/rpc-eth-api/src/api/servers/server.rs +++ /dev/null @@ -1,715 +0,0 @@ -//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`] trait. Handles RPC requests for -//! the `eth_` namespace. - -use alloy_dyn_abi::TypedData; -use jsonrpsee::core::RpcResult as Result; -use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; -use reth_rpc_types::{ - serde_helpers::JsonStorageKey, - state::{EvmOverrides, StateOverride}, - AccessListWithGasUsed, AnyTransactionReceipt, BlockOverrides, Bundle, - EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, Index, RichBlock, - StateContext, SyncStatus, Transaction, TransactionRequest, Work, -}; -use tracing::trace; - -use crate::{ - result::internal_rpc_err, - servers::{ - EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadReceipt, Trace, - }, - EthApiError, EthApiServer, ToRpcResult, -}; - -#[async_trait::async_trait] -impl EthApiServer for T -where - Self: EthApiSpec - + EthTransactions - + EthBlocks - + EthState - + EthCall - + EthFees - + Trace - + LoadReceipt, -{ - /// Handler for: `eth_protocolVersion` - async fn protocol_version(&self) -> Result { - trace!(target: "rpc::eth", "Serving eth_protocolVersion"); - EthApiSpec::protocol_version(self).await.to_rpc_result() - } - - /// Handler for: `eth_syncing` - fn syncing(&self) -> Result { - trace!(target: "rpc::eth", "Serving eth_syncing"); - EthApiSpec::sync_status(self).to_rpc_result() - } - - /// Handler for: `eth_coinbase` - async fn author(&self) -> Result
{ - Err(internal_rpc_err("unimplemented")) - } - - /// Handler for: `eth_accounts` - fn accounts(&self) -> Result> { - trace!(target: "rpc::eth", "Serving eth_accounts"); - Ok(EthApiSpec::accounts(self)) - } - - /// Handler for: `eth_blockNumber` - fn block_number(&self) -> Result { - trace!(target: "rpc::eth", "Serving eth_blockNumber"); - Ok(U256::from( - EthApiSpec::chain_info(self).with_message("failed to read chain info")?.best_number, - )) - } - - /// Handler for: `eth_chainId` - async fn chain_id(&self) -> Result> { - trace!(target: "rpc::eth", "Serving eth_chainId"); - Ok(Some(EthApiSpec::chain_id(self))) - } - - /// Handler for: `eth_getBlockByHash` - async fn block_by_hash(&self, hash: B256, full: bool) -> Result> { - trace!(target: "rpc::eth", ?hash, ?full, "Serving eth_getBlockByHash"); - Ok(EthBlocks::rpc_block(self, hash.into(), full).await?) - } - - /// Handler for: `eth_getBlockByNumber` - async fn block_by_number( - &self, - number: BlockNumberOrTag, - full: bool, - ) -> Result> { - trace!(target: "rpc::eth", ?number, ?full, "Serving eth_getBlockByNumber"); - Ok(EthBlocks::rpc_block(self, number.into(), full).await?) - } - - /// Handler for: `eth_getBlockTransactionCountByHash` - async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getBlockTransactionCountByHash"); - Ok(EthBlocks::block_transaction_count(self, hash).await?.map(U256::from)) - } - - /// Handler for: `eth_getBlockTransactionCountByNumber` - async fn block_transaction_count_by_number( - &self, - number: BlockNumberOrTag, - ) -> Result> { - trace!(target: "rpc::eth", ?number, "Serving eth_getBlockTransactionCountByNumber"); - Ok(EthBlocks::block_transaction_count(self, number).await?.map(U256::from)) - } - - /// Handler for: `eth_getUncleCountByBlockHash` - async fn block_uncles_count_by_hash(&self, hash: B256) -> Result> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getUncleCountByBlockHash"); - Ok(EthBlocks::ommers(self, hash)?.map(|ommers| U256::from(ommers.len()))) - } - - /// Handler for: `eth_getUncleCountByBlockNumber` - async fn block_uncles_count_by_number(&self, number: BlockNumberOrTag) -> Result> { - trace!(target: "rpc::eth", ?number, "Serving eth_getUncleCountByBlockNumber"); - Ok(EthBlocks::ommers(self, number)?.map(|ommers| U256::from(ommers.len()))) - } - - /// Handler for: `eth_getBlockReceipts` - async fn block_receipts( - &self, - block_id: BlockId, - ) -> Result>> { - trace!(target: "rpc::eth", ?block_id, "Serving eth_getBlockReceipts"); - Ok(EthBlocks::block_receipts(self, block_id).await?) - } - - /// Handler for: `eth_getUncleByBlockHashAndIndex` - async fn uncle_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> Result> { - trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getUncleByBlockHashAndIndex"); - Ok(EthBlocks::ommer_by_block_and_index(self, hash, index).await?) - } - - /// Handler for: `eth_getUncleByBlockNumberAndIndex` - async fn uncle_by_block_number_and_index( - &self, - number: BlockNumberOrTag, - index: Index, - ) -> Result> { - trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getUncleByBlockNumberAndIndex"); - Ok(EthBlocks::ommer_by_block_and_index(self, number, index).await?) - } - - /// Handler for: `eth_getRawTransactionByHash` - async fn raw_transaction_by_hash(&self, hash: B256) -> Result> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getRawTransactionByHash"); - Ok(EthTransactions::raw_transaction_by_hash(self, hash).await?) - } - - /// Handler for: `eth_getTransactionByHash` - async fn transaction_by_hash(&self, hash: B256) -> Result> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionByHash"); - Ok(EthTransactions::transaction_by_hash(self, hash).await?.map(Into::into)) - } - - /// Handler for: `eth_getRawTransactionByBlockHashAndIndex` - async fn raw_transaction_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> Result> { - trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getRawTransactionByBlockHashAndIndex"); - Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, hash.into(), index).await?) - } - - /// Handler for: `eth_getTransactionByBlockHashAndIndex` - async fn transaction_by_block_hash_and_index( - &self, - hash: B256, - index: Index, - ) -> Result> { - trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getTransactionByBlockHashAndIndex"); - Ok(EthTransactions::transaction_by_block_and_tx_index(self, hash.into(), index).await?) - } - - /// Handler for: `eth_getRawTransactionByBlockNumberAndIndex` - async fn raw_transaction_by_block_number_and_index( - &self, - number: BlockNumberOrTag, - index: Index, - ) -> Result> { - trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getRawTransactionByBlockNumberAndIndex"); - Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, number.into(), index) - .await?) - } - - /// Handler for: `eth_getTransactionByBlockNumberAndIndex` - async fn transaction_by_block_number_and_index( - &self, - number: BlockNumberOrTag, - index: Index, - ) -> Result> { - trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getTransactionByBlockNumberAndIndex"); - Ok(EthTransactions::transaction_by_block_and_tx_index(self, number.into(), index).await?) - } - - /// Handler for: `eth_getTransactionReceipt` - async fn transaction_receipt(&self, hash: B256) -> Result> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionReceipt"); - Ok(EthTransactions::transaction_receipt(self, hash).await?) - } - - /// Handler for: `eth_getBalance` - async fn balance(&self, address: Address, block_number: Option) -> Result { - trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getBalance"); - Ok(EthState::balance(self, address, block_number).await?) - } - - /// Handler for: `eth_getStorageAt` - async fn storage_at( - &self, - address: Address, - index: JsonStorageKey, - block_number: Option, - ) -> Result { - trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getStorageAt"); - let res: B256 = EthState::storage_at(self, address, index, block_number).await?; - Ok(res) - } - - /// Handler for: `eth_getTransactionCount` - async fn transaction_count( - &self, - address: Address, - block_number: Option, - ) -> Result { - trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getTransactionCount"); - Ok(EthState::transaction_count(self, address, block_number).await?) - } - - /// Handler for: `eth_getCode` - async fn get_code(&self, address: Address, block_number: Option) -> Result { - trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getCode"); - Ok(EthState::get_code(self, address, block_number).await?) - } - - /// Handler for: `eth_getHeaderByNumber` - async fn header_by_number(&self, block_number: BlockNumberOrTag) -> Result> { - trace!(target: "rpc::eth", ?block_number, "Serving eth_getHeaderByNumber"); - Ok(EthBlocks::rpc_block_header(self, block_number.into()).await?) - } - - /// Handler for: `eth_getHeaderByHash` - async fn header_by_hash(&self, hash: B256) -> Result> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getHeaderByHash"); - Ok(EthBlocks::rpc_block_header(self, hash.into()).await?) - } - - /// Handler for: `eth_call` - async fn call( - &self, - request: TransactionRequest, - block_number: Option, - state_overrides: Option, - block_overrides: Option>, - ) -> Result { - trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, ?block_overrides, "Serving eth_call"); - Ok(EthCall::call( - self, - request, - block_number, - EvmOverrides::new(state_overrides, block_overrides), - ) - .await?) - } - - /// Handler for: `eth_callMany` - async fn call_many( - &self, - bundle: Bundle, - state_context: Option, - state_override: Option, - ) -> Result> { - trace!(target: "rpc::eth", ?bundle, ?state_context, ?state_override, "Serving eth_callMany"); - Ok(EthCall::call_many(self, bundle, state_context, state_override).await?) - } - - /// Handler for: `eth_createAccessList` - async fn create_access_list( - &self, - request: TransactionRequest, - block_number: Option, - ) -> Result { - trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_createAccessList"); - let access_list_with_gas_used = - EthCall::create_access_list_at(self, request, block_number).await?; - - Ok(access_list_with_gas_used) - } - - /// Handler for: `eth_estimateGas` - async fn estimate_gas( - &self, - request: TransactionRequest, - block_number: Option, - state_override: Option, - ) -> Result { - trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_estimateGas"); - Ok(EthCall::estimate_gas_at( - self, - request, - block_number.unwrap_or_default(), - state_override, - ) - .await?) - } - - /// Handler for: `eth_gasPrice` - async fn gas_price(&self) -> Result { - trace!(target: "rpc::eth", "Serving eth_gasPrice"); - return Ok(EthFees::gas_price(self).await?) - } - - /// Handler for: `eth_maxPriorityFeePerGas` - async fn max_priority_fee_per_gas(&self) -> Result { - trace!(target: "rpc::eth", "Serving eth_maxPriorityFeePerGas"); - return Ok(EthFees::suggested_priority_fee(self).await?) - } - - /// Handler for: `eth_blobBaseFee` - async fn blob_base_fee(&self) -> Result { - trace!(target: "rpc::eth", "Serving eth_blobBaseFee"); - return Ok(EthFees::blob_base_fee(self).await?) - } - - // FeeHistory is calculated based on lazy evaluation of fees for historical blocks, and further - // caching of it in the LRU cache. - // When new RPC call is executed, the cache gets locked, we check it for the historical fees - // according to the requested block range, and fill any cache misses (in both RPC response - // and cache itself) with the actual data queried from the database. - // To minimize the number of database seeks required to query the missing data, we calculate the - // first non-cached block number and last non-cached block number. After that, we query this - // range of consecutive blocks from the database. - /// Handler for: `eth_feeHistory` - async fn fee_history( - &self, - block_count: U64, - newest_block: BlockNumberOrTag, - reward_percentiles: Option>, - ) -> Result { - trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory"); - return Ok( - EthFees::fee_history(self, block_count.to(), newest_block, reward_percentiles).await? - ) - } - - /// Handler for: `eth_mining` - async fn is_mining(&self) -> Result { - Err(internal_rpc_err("unimplemented")) - } - - /// Handler for: `eth_hashrate` - async fn hashrate(&self) -> Result { - Ok(U256::ZERO) - } - - /// Handler for: `eth_getWork` - async fn get_work(&self) -> Result { - Err(internal_rpc_err("unimplemented")) - } - - /// Handler for: `eth_submitHashrate` - async fn submit_hashrate(&self, _hashrate: U256, _id: B256) -> Result { - Ok(false) - } - - /// Handler for: `eth_submitWork` - async fn submit_work(&self, _nonce: B64, _pow_hash: B256, _mix_digest: B256) -> Result { - Err(internal_rpc_err("unimplemented")) - } - - /// Handler for: `eth_sendTransaction` - async fn send_transaction(&self, request: TransactionRequest) -> Result { - trace!(target: "rpc::eth", ?request, "Serving eth_sendTransaction"); - Ok(EthTransactions::send_transaction(self, request).await?) - } - - /// Handler for: `eth_sendRawTransaction` - async fn send_raw_transaction(&self, tx: Bytes) -> Result { - trace!(target: "rpc::eth", ?tx, "Serving eth_sendRawTransaction"); - Ok(EthTransactions::send_raw_transaction(self, tx).await?) - } - - /// Handler for: `eth_sign` - async fn sign(&self, address: Address, message: Bytes) -> Result { - trace!(target: "rpc::eth", ?address, ?message, "Serving eth_sign"); - Ok(EthTransactions::sign(self, address, message).await?) - } - - /// Handler for: `eth_signTransaction` - async fn sign_transaction(&self, _transaction: TransactionRequest) -> Result { - Err(internal_rpc_err("unimplemented")) - } - - /// Handler for: `eth_signTypedData` - async fn sign_typed_data(&self, address: Address, data: TypedData) -> Result { - trace!(target: "rpc::eth", ?address, ?data, "Serving eth_signTypedData"); - Ok(EthTransactions::sign_typed_data(self, &data, address)?) - } - - /// Handler for: `eth_getProof` - async fn get_proof( - &self, - address: Address, - keys: Vec, - block_number: Option, - ) -> Result { - trace!(target: "rpc::eth", ?address, ?keys, ?block_number, "Serving eth_getProof"); - let res = EthState::get_proof(self, address, keys, block_number)?.await; - - Ok(res.map_err(|e| match e { - EthApiError::InvalidBlockRange => { - internal_rpc_err("eth_getProof is unimplemented for historical blocks") - } - _ => e.into(), - })?) - } -} - -#[cfg(test)] -mod tests { - use jsonrpsee::types::error::INVALID_PARAMS_CODE; - use reth_chainspec::BaseFeeParams; - use reth_evm_ethereum::EthEvmConfig; - use reth_network_api::noop::NoopNetwork; - use reth_primitives::{ - constants::ETHEREUM_BLOCK_GAS_LIMIT, Block, BlockNumberOrTag, Header, TransactionSigned, - B256, U64, - }; - use reth_provider::{ - test_utils::{MockEthProvider, NoopProvider}, - BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory, - }; - use reth_rpc_types::FeeHistory; - use reth_tasks::pool::BlockingTaskPool; - use reth_testing_utils::{generators, generators::Rng}; - use reth_transaction_pool::test_utils::{testing_pool, TestPool}; - - use crate::{ - EthApi, EthApiServer, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, - }; - - fn build_test_eth_api< - P: BlockReaderIdExt - + BlockReader - + ChainSpecProvider - + EvmEnvProvider - + StateProviderFactory - + Unpin - + Clone - + 'static, - >( - provider: P, - ) -> EthApi { - let evm_config = EthEvmConfig::default(); - let cache = EthStateCache::spawn(provider.clone(), Default::default(), evm_config); - let fee_history_cache = - FeeHistoryCache::new(cache.clone(), FeeHistoryCacheConfig::default()); - - EthApi::new( - provider.clone(), - testing_pool(), - NoopNetwork::default(), - cache.clone(), - GasPriceOracle::new(provider, Default::default(), cache), - ETHEREUM_BLOCK_GAS_LIMIT, - BlockingTaskPool::build().expect("failed to build tracing pool"), - fee_history_cache, - evm_config, - None, - ) - } - - // Function to prepare the EthApi with mock data - fn prepare_eth_api( - newest_block: u64, - mut oldest_block: Option, - block_count: u64, - mock_provider: MockEthProvider, - ) -> (EthApi, Vec, Vec) { - let mut rng = generators::rng(); - - // Build mock data - let mut gas_used_ratios = Vec::new(); - let mut base_fees_per_gas = Vec::new(); - let mut last_header = None; - let mut parent_hash = B256::default(); - - for i in (0..block_count).rev() { - let hash = rng.gen(); - let gas_limit: u64 = rng.gen(); - let gas_used: u64 = rng.gen(); - // Note: Generates a u32 to avoid overflows later - let base_fee_per_gas: Option = rng.gen::().then(|| rng.gen::() as u64); - - let header = Header { - number: newest_block - i, - gas_limit, - gas_used, - base_fee_per_gas, - parent_hash, - ..Default::default() - }; - last_header = Some(header.clone()); - parent_hash = hash; - - let mut transactions = vec![]; - for _ in 0..100 { - let random_fee: u128 = rng.gen(); - - if let Some(base_fee_per_gas) = header.base_fee_per_gas { - let transaction = TransactionSigned { - transaction: reth_primitives::Transaction::Eip1559( - reth_primitives::TxEip1559 { - max_priority_fee_per_gas: random_fee, - max_fee_per_gas: random_fee + base_fee_per_gas as u128, - ..Default::default() - }, - ), - ..Default::default() - }; - - transactions.push(transaction); - } else { - let transaction = TransactionSigned { - transaction: reth_primitives::Transaction::Legacy(Default::default()), - ..Default::default() - }; - - transactions.push(transaction); - } - } - - mock_provider.add_block( - hash, - Block { header: header.clone(), body: transactions, ..Default::default() }, - ); - mock_provider.add_header(hash, header); - - oldest_block.get_or_insert(hash); - gas_used_ratios.push(gas_used as f64 / gas_limit as f64); - base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default()); - } - - // Add final base fee (for the next block outside of the request) - let last_header = last_header.unwrap(); - base_fees_per_gas.push(BaseFeeParams::ethereum().next_block_base_fee( - last_header.gas_used as u128, - last_header.gas_limit as u128, - last_header.base_fee_per_gas.unwrap_or_default() as u128, - )); - - let eth_api = build_test_eth_api(mock_provider); - - (eth_api, base_fees_per_gas, gas_used_ratios) - } - - /// Invalid block range - #[tokio::test] - async fn test_fee_history_empty() { - let response = as EthApiServer>::fee_history( - &build_test_eth_api(NoopProvider::default()), - U64::from(1), - BlockNumberOrTag::Latest, - None, - ) - .await; - assert!(response.is_err()); - let error_object = response.unwrap_err(); - assert_eq!(error_object.code(), INVALID_PARAMS_CODE); - } - - #[tokio::test] - /// Invalid block range (request is before genesis) - async fn test_fee_history_invalid_block_range_before_genesis() { - let block_count = 10; - let newest_block = 1337; - let oldest_block = None; - - let (eth_api, _, _) = - prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - - let response = as EthApiServer>::fee_history( - ð_api, - U64::from(newest_block + 1), - newest_block.into(), - Some(vec![10.0]), - ) - .await; - - assert!(response.is_err()); - let error_object = response.unwrap_err(); - assert_eq!(error_object.code(), INVALID_PARAMS_CODE); - } - - #[tokio::test] - /// Invalid block range (request is in the future) - async fn test_fee_history_invalid_block_range_in_future() { - let block_count = 10; - let newest_block = 1337; - let oldest_block = None; - - let (eth_api, _, _) = - prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - - let response = as EthApiServer>::fee_history( - ð_api, - U64::from(1), - (newest_block + 1000).into(), - Some(vec![10.0]), - ) - .await; - - assert!(response.is_err()); - let error_object = response.unwrap_err(); - assert_eq!(error_object.code(), INVALID_PARAMS_CODE); - } - - #[tokio::test] - /// Requesting no block should result in a default response - async fn test_fee_history_no_block_requested() { - let block_count = 10; - let newest_block = 1337; - let oldest_block = None; - - let (eth_api, _, _) = - prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - - let response = as EthApiServer>::fee_history( - ð_api, - U64::from(0), - newest_block.into(), - None, - ) - .await - .unwrap(); - assert_eq!( - response, - FeeHistory::default(), - "none: requesting no block should yield a default response" - ); - } - - #[tokio::test] - /// Requesting a single block should return 1 block (+ base fee for the next block over) - async fn test_fee_history_single_block() { - let block_count = 10; - let newest_block = 1337; - let oldest_block = None; - - let (eth_api, base_fees_per_gas, gas_used_ratios) = - prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - - let fee_history = - eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap(); - assert_eq!( - fee_history.base_fee_per_gas, - &base_fees_per_gas[base_fees_per_gas.len() - 2..], - "one: base fee per gas is incorrect" - ); - assert_eq!( - fee_history.base_fee_per_gas.len(), - 2, - "one: should return base fee of the next block as well" - ); - assert_eq!( - &fee_history.gas_used_ratio, - &gas_used_ratios[gas_used_ratios.len() - 1..], - "one: gas used ratio is incorrect" - ); - assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect"); - assert!( - fee_history.reward.is_none(), - "one: no percentiles were requested, so there should be no rewards result" - ); - } - - /// Requesting all blocks should be ok - #[tokio::test] - async fn test_fee_history_all_blocks() { - let block_count = 10; - let newest_block = 1337; - let oldest_block = None; - - let (eth_api, base_fees_per_gas, gas_used_ratios) = - prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - - let fee_history = - eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap(); - - assert_eq!( - &fee_history.base_fee_per_gas, &base_fees_per_gas, - "all: base fee per gas is incorrect" - ); - assert_eq!( - fee_history.base_fee_per_gas.len() as u64, - block_count + 1, - "all: should return base fee of the next block as well" - ); - assert_eq!( - &fee_history.gas_used_ratio, &gas_used_ratios, - "all: gas used ratio is incorrect" - ); - assert_eq!( - fee_history.oldest_block, - newest_block - block_count + 1, - "all: oldest block is incorrect" - ); - assert!( - fee_history.reward.is_none(), - "all: no percentiles were requested, so there should be no rewards result" - ); - } -} diff --git a/crates/rpc/rpc-eth-api/src/api/bundle.rs b/crates/rpc/rpc-eth-api/src/bundle.rs similarity index 100% rename from crates/rpc/rpc-eth-api/src/api/bundle.rs rename to crates/rpc/rpc-eth-api/src/bundle.rs diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs new file mode 100644 index 000000000000..834682670cae --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -0,0 +1,727 @@ +//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`] trait. Handles RPC requests for +//! the `eth_` namespace. + +use alloy_dyn_abi::TypedData; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; +use reth_rpc_eth_types::EthApiError; +use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; +use reth_rpc_types::{ + serde_helpers::JsonStorageKey, + state::{EvmOverrides, StateOverride}, + AccessListWithGasUsed, AnyTransactionReceipt, BlockOverrides, Bundle, + EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, Index, RichBlock, + StateContext, SyncStatus, Transaction, TransactionRequest, Work, +}; +use tracing::trace; + +use crate::helpers::{ + EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadReceipt, Trace, +}; + +/// Eth rpc interface: +#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] +#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] +pub trait EthApi { + /// Returns the protocol version encoded as a string. + #[method(name = "protocolVersion")] + async fn protocol_version(&self) -> RpcResult; + + /// Returns an object with data about the sync status or false. + #[method(name = "syncing")] + fn syncing(&self) -> RpcResult; + + /// Returns the client coinbase address. + #[method(name = "coinbase")] + async fn author(&self) -> RpcResult
; + + /// Returns a list of addresses owned by client. + #[method(name = "accounts")] + fn accounts(&self) -> RpcResult>; + + /// Returns the number of most recent block. + #[method(name = "blockNumber")] + fn block_number(&self) -> RpcResult; + + /// Returns the chain ID of the current network. + #[method(name = "chainId")] + async fn chain_id(&self) -> RpcResult>; + + /// Returns information about a block by hash. + #[method(name = "getBlockByHash")] + async fn block_by_hash(&self, hash: B256, full: bool) -> RpcResult>; + + /// Returns information about a block by number. + #[method(name = "getBlockByNumber")] + async fn block_by_number( + &self, + number: BlockNumberOrTag, + full: bool, + ) -> RpcResult>; + + /// Returns the number of transactions in a block from a block matching the given block hash. + #[method(name = "getBlockTransactionCountByHash")] + async fn block_transaction_count_by_hash(&self, hash: B256) -> RpcResult>; + + /// Returns the number of transactions in a block matching the given block number. + #[method(name = "getBlockTransactionCountByNumber")] + async fn block_transaction_count_by_number( + &self, + number: BlockNumberOrTag, + ) -> RpcResult>; + + /// Returns the number of uncles in a block from a block matching the given block hash. + #[method(name = "getUncleCountByBlockHash")] + async fn block_uncles_count_by_hash(&self, hash: B256) -> RpcResult>; + + /// Returns the number of uncles in a block with given block number. + #[method(name = "getUncleCountByBlockNumber")] + async fn block_uncles_count_by_number( + &self, + number: BlockNumberOrTag, + ) -> RpcResult>; + + /// Returns all transaction receipts for a given block. + #[method(name = "getBlockReceipts")] + async fn block_receipts( + &self, + block_id: BlockId, + ) -> RpcResult>>; + + /// Returns an uncle block of the given block and index. + #[method(name = "getUncleByBlockHashAndIndex")] + async fn uncle_by_block_hash_and_index( + &self, + hash: B256, + index: Index, + ) -> RpcResult>; + + /// Returns an uncle block of the given block and index. + #[method(name = "getUncleByBlockNumberAndIndex")] + async fn uncle_by_block_number_and_index( + &self, + number: BlockNumberOrTag, + index: Index, + ) -> RpcResult>; + + /// Returns the EIP-2718 encoded transaction if it exists. + /// + /// If this is a EIP-4844 transaction that is in the pool it will include the sidecar. + #[method(name = "getRawTransactionByHash")] + async fn raw_transaction_by_hash(&self, hash: B256) -> RpcResult>; + + /// Returns the information about a transaction requested by transaction hash. + #[method(name = "getTransactionByHash")] + async fn transaction_by_hash(&self, hash: B256) -> RpcResult>; + + /// Returns information about a raw transaction by block hash and transaction index position. + #[method(name = "getRawTransactionByBlockHashAndIndex")] + async fn raw_transaction_by_block_hash_and_index( + &self, + hash: B256, + index: Index, + ) -> RpcResult>; + + /// Returns information about a transaction by block hash and transaction index position. + #[method(name = "getTransactionByBlockHashAndIndex")] + async fn transaction_by_block_hash_and_index( + &self, + hash: B256, + index: Index, + ) -> RpcResult>; + + /// Returns information about a raw transaction by block number and transaction index + /// position. + #[method(name = "getRawTransactionByBlockNumberAndIndex")] + async fn raw_transaction_by_block_number_and_index( + &self, + number: BlockNumberOrTag, + index: Index, + ) -> RpcResult>; + + /// Returns information about a transaction by block number and transaction index position. + #[method(name = "getTransactionByBlockNumberAndIndex")] + async fn transaction_by_block_number_and_index( + &self, + number: BlockNumberOrTag, + index: Index, + ) -> RpcResult>; + + /// Returns the receipt of a transaction by transaction hash. + #[method(name = "getTransactionReceipt")] + async fn transaction_receipt(&self, hash: B256) -> RpcResult>; + + /// Returns the balance of the account of given address. + #[method(name = "getBalance")] + async fn balance(&self, address: Address, block_number: Option) -> RpcResult; + + /// Returns the value from a storage position at a given address + #[method(name = "getStorageAt")] + async fn storage_at( + &self, + address: Address, + index: JsonStorageKey, + block_number: Option, + ) -> RpcResult; + + /// Returns the number of transactions sent from an address at given block number. + #[method(name = "getTransactionCount")] + async fn transaction_count( + &self, + address: Address, + block_number: Option, + ) -> RpcResult; + + /// Returns code at a given address at given block number. + #[method(name = "getCode")] + async fn get_code(&self, address: Address, block_number: Option) -> RpcResult; + + /// Returns the block's header at given number. + #[method(name = "getHeaderByNumber")] + async fn header_by_number(&self, hash: BlockNumberOrTag) -> RpcResult>; + + /// Returns the block's header at given hash. + #[method(name = "getHeaderByHash")] + async fn header_by_hash(&self, hash: B256) -> RpcResult>; + + /// Executes a new message call immediately without creating a transaction on the block chain. + #[method(name = "call")] + async fn call( + &self, + request: TransactionRequest, + block_number: Option, + state_overrides: Option, + block_overrides: Option>, + ) -> RpcResult; + + /// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the + /// optionality of state overrides + #[method(name = "callMany")] + async fn call_many( + &self, + bundle: Bundle, + state_context: Option, + state_override: Option, + ) -> RpcResult>; + + /// Generates an access list for a transaction. + /// + /// This method creates an [EIP2930](https://eips.ethereum.org/EIPS/eip-2930) type accessList based on a given Transaction. + /// + /// An access list contains all storage slots and addresses touched by the transaction, except + /// for the sender account and the chain's precompiles. + /// + /// It returns list of addresses and storage keys used by the transaction, plus the gas + /// consumed when the access list is added. That is, it gives you the list of addresses and + /// storage keys that will be used by that transaction, plus the gas consumed if the access + /// list is included. Like eth_estimateGas, this is an estimation; the list could change + /// when the transaction is actually mined. Adding an accessList to your transaction does + /// not necessary result in lower gas usage compared to a transaction without an access + /// list. + #[method(name = "createAccessList")] + async fn create_access_list( + &self, + request: TransactionRequest, + block_number: Option, + ) -> RpcResult; + + /// Generates and returns an estimate of how much gas is necessary to allow the transaction to + /// complete. + #[method(name = "estimateGas")] + async fn estimate_gas( + &self, + request: TransactionRequest, + block_number: Option, + state_override: Option, + ) -> RpcResult; + + /// Returns the current price per gas in wei. + #[method(name = "gasPrice")] + async fn gas_price(&self) -> RpcResult; + + /// Introduced in EIP-1559, returns suggestion for the priority for dynamic fee transactions. + #[method(name = "maxPriorityFeePerGas")] + async fn max_priority_fee_per_gas(&self) -> RpcResult; + + /// Introduced in EIP-4844, returns the current blob base fee in wei. + #[method(name = "blobBaseFee")] + async fn blob_base_fee(&self) -> RpcResult; + + /// Returns the Transaction fee history + /// + /// Introduced in EIP-1559 for getting information on the appropriate priority fee to use. + /// + /// Returns transaction base fee per gas and effective priority fee per gas for the + /// requested/supported block range. The returned Fee history for the returned block range + /// can be a subsection of the requested range if not all blocks are available. + #[method(name = "feeHistory")] + async fn fee_history( + &self, + block_count: U64, + newest_block: BlockNumberOrTag, + reward_percentiles: Option>, + ) -> RpcResult; + + /// Returns whether the client is actively mining new blocks. + #[method(name = "mining")] + async fn is_mining(&self) -> RpcResult; + + /// Returns the number of hashes per second that the node is mining with. + #[method(name = "hashrate")] + async fn hashrate(&self) -> RpcResult; + + /// Returns the hash of the current block, the seedHash, and the boundary condition to be met + /// (“target”) + #[method(name = "getWork")] + async fn get_work(&self) -> RpcResult; + + /// Used for submitting mining hashrate. + /// + /// Can be used for remote miners to submit their hash rate. + /// It accepts the miner hash rate and an identifier which must be unique between nodes. + /// Returns `true` if the block was successfully submitted, `false` otherwise. + #[method(name = "submitHashrate")] + async fn submit_hashrate(&self, hashrate: U256, id: B256) -> RpcResult; + + /// Used for submitting a proof-of-work solution. + #[method(name = "submitWork")] + async fn submit_work(&self, nonce: B64, pow_hash: B256, mix_digest: B256) -> RpcResult; + + /// Sends transaction; will block waiting for signer to return the + /// transaction hash. + #[method(name = "sendTransaction")] + async fn send_transaction(&self, request: TransactionRequest) -> RpcResult; + + /// Sends signed transaction, returning its hash. + #[method(name = "sendRawTransaction")] + async fn send_raw_transaction(&self, bytes: Bytes) -> RpcResult; + + /// Returns an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + /// + len(message) + message))). + #[method(name = "sign")] + async fn sign(&self, address: Address, message: Bytes) -> RpcResult; + + /// Signs a transaction that can be submitted to the network at a later time using with + /// `sendRawTransaction.` + #[method(name = "signTransaction")] + async fn sign_transaction(&self, transaction: TransactionRequest) -> RpcResult; + + /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). + #[method(name = "signTypedData")] + async fn sign_typed_data(&self, address: Address, data: TypedData) -> RpcResult; + + /// Returns the account and storage values of the specified account including the Merkle-proof. + /// This call can be used to verify that the data you are pulling from is not tampered with. + #[method(name = "getProof")] + async fn get_proof( + &self, + address: Address, + keys: Vec, + block_number: Option, + ) -> RpcResult; +} + +#[async_trait::async_trait] +impl EthApiServer for T +where + Self: EthApiSpec + + EthTransactions + + EthBlocks + + EthState + + EthCall + + EthFees + + Trace + + LoadReceipt, +{ + /// Handler for: `eth_protocolVersion` + async fn protocol_version(&self) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_protocolVersion"); + EthApiSpec::protocol_version(self).await.to_rpc_result() + } + + /// Handler for: `eth_syncing` + fn syncing(&self) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_syncing"); + EthApiSpec::sync_status(self).to_rpc_result() + } + + /// Handler for: `eth_coinbase` + async fn author(&self) -> RpcResult
{ + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for: `eth_accounts` + fn accounts(&self) -> RpcResult> { + trace!(target: "rpc::eth", "Serving eth_accounts"); + Ok(EthApiSpec::accounts(self)) + } + + /// Handler for: `eth_blockNumber` + fn block_number(&self) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_blockNumber"); + Ok(U256::from( + EthApiSpec::chain_info(self).with_message("failed to read chain info")?.best_number, + )) + } + + /// Handler for: `eth_chainId` + async fn chain_id(&self) -> RpcResult> { + trace!(target: "rpc::eth", "Serving eth_chainId"); + Ok(Some(EthApiSpec::chain_id(self))) + } + + /// Handler for: `eth_getBlockByHash` + async fn block_by_hash(&self, hash: B256, full: bool) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, ?full, "Serving eth_getBlockByHash"); + Ok(EthBlocks::rpc_block(self, hash.into(), full).await?) + } + + /// Handler for: `eth_getBlockByNumber` + async fn block_by_number( + &self, + number: BlockNumberOrTag, + full: bool, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?number, ?full, "Serving eth_getBlockByNumber"); + Ok(EthBlocks::rpc_block(self, number.into(), full).await?) + } + + /// Handler for: `eth_getBlockTransactionCountByHash` + async fn block_transaction_count_by_hash(&self, hash: B256) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getBlockTransactionCountByHash"); + Ok(EthBlocks::block_transaction_count(self, hash.into()).await?.map(U256::from)) + } + + /// Handler for: `eth_getBlockTransactionCountByNumber` + async fn block_transaction_count_by_number( + &self, + number: BlockNumberOrTag, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?number, "Serving eth_getBlockTransactionCountByNumber"); + Ok(EthBlocks::block_transaction_count(self, number.into()).await?.map(U256::from)) + } + + /// Handler for: `eth_getUncleCountByBlockHash` + async fn block_uncles_count_by_hash(&self, hash: B256) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getUncleCountByBlockHash"); + Ok(EthBlocks::ommers(self, hash.into())?.map(|ommers| U256::from(ommers.len()))) + } + + /// Handler for: `eth_getUncleCountByBlockNumber` + async fn block_uncles_count_by_number( + &self, + number: BlockNumberOrTag, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?number, "Serving eth_getUncleCountByBlockNumber"); + Ok(EthBlocks::ommers(self, number.into())?.map(|ommers| U256::from(ommers.len()))) + } + + /// Handler for: `eth_getBlockReceipts` + async fn block_receipts( + &self, + block_id: BlockId, + ) -> RpcResult>> { + trace!(target: "rpc::eth", ?block_id, "Serving eth_getBlockReceipts"); + Ok(EthBlocks::block_receipts(self, block_id).await?) + } + + /// Handler for: `eth_getUncleByBlockHashAndIndex` + async fn uncle_by_block_hash_and_index( + &self, + hash: B256, + index: Index, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getUncleByBlockHashAndIndex"); + Ok(EthBlocks::ommer_by_block_and_index(self, hash.into(), index).await?) + } + + /// Handler for: `eth_getUncleByBlockNumberAndIndex` + async fn uncle_by_block_number_and_index( + &self, + number: BlockNumberOrTag, + index: Index, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getUncleByBlockNumberAndIndex"); + Ok(EthBlocks::ommer_by_block_and_index(self, number.into(), index).await?) + } + + /// Handler for: `eth_getRawTransactionByHash` + async fn raw_transaction_by_hash(&self, hash: B256) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getRawTransactionByHash"); + Ok(EthTransactions::raw_transaction_by_hash(self, hash).await?) + } + + /// Handler for: `eth_getTransactionByHash` + async fn transaction_by_hash(&self, hash: B256) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionByHash"); + Ok(EthTransactions::transaction_by_hash(self, hash).await?.map(Into::into)) + } + + /// Handler for: `eth_getRawTransactionByBlockHashAndIndex` + async fn raw_transaction_by_block_hash_and_index( + &self, + hash: B256, + index: Index, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getRawTransactionByBlockHashAndIndex"); + Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, hash.into(), index).await?) + } + + /// Handler for: `eth_getTransactionByBlockHashAndIndex` + async fn transaction_by_block_hash_and_index( + &self, + hash: B256, + index: Index, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getTransactionByBlockHashAndIndex"); + Ok(EthTransactions::transaction_by_block_and_tx_index(self, hash.into(), index).await?) + } + + /// Handler for: `eth_getRawTransactionByBlockNumberAndIndex` + async fn raw_transaction_by_block_number_and_index( + &self, + number: BlockNumberOrTag, + index: Index, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getRawTransactionByBlockNumberAndIndex"); + Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, number.into(), index) + .await?) + } + + /// Handler for: `eth_getTransactionByBlockNumberAndIndex` + async fn transaction_by_block_number_and_index( + &self, + number: BlockNumberOrTag, + index: Index, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getTransactionByBlockNumberAndIndex"); + Ok(EthTransactions::transaction_by_block_and_tx_index(self, number.into(), index).await?) + } + + /// Handler for: `eth_getTransactionReceipt` + async fn transaction_receipt(&self, hash: B256) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionReceipt"); + Ok(EthTransactions::transaction_receipt(self, hash).await?) + } + + /// Handler for: `eth_getBalance` + async fn balance(&self, address: Address, block_number: Option) -> RpcResult { + trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getBalance"); + Ok(EthState::balance(self, address, block_number).await?) + } + + /// Handler for: `eth_getStorageAt` + async fn storage_at( + &self, + address: Address, + index: JsonStorageKey, + block_number: Option, + ) -> RpcResult { + trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getStorageAt"); + let res: B256 = EthState::storage_at(self, address, index, block_number).await?; + Ok(res) + } + + /// Handler for: `eth_getTransactionCount` + async fn transaction_count( + &self, + address: Address, + block_number: Option, + ) -> RpcResult { + trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getTransactionCount"); + Ok(EthState::transaction_count(self, address, block_number).await?) + } + + /// Handler for: `eth_getCode` + async fn get_code(&self, address: Address, block_number: Option) -> RpcResult { + trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getCode"); + Ok(EthState::get_code(self, address, block_number).await?) + } + + /// Handler for: `eth_getHeaderByNumber` + async fn header_by_number(&self, block_number: BlockNumberOrTag) -> RpcResult> { + trace!(target: "rpc::eth", ?block_number, "Serving eth_getHeaderByNumber"); + Ok(EthBlocks::rpc_block_header(self, block_number.into()).await?) + } + + /// Handler for: `eth_getHeaderByHash` + async fn header_by_hash(&self, hash: B256) -> RpcResult> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getHeaderByHash"); + Ok(EthBlocks::rpc_block_header(self, hash.into()).await?) + } + + /// Handler for: `eth_call` + async fn call( + &self, + request: TransactionRequest, + block_number: Option, + state_overrides: Option, + block_overrides: Option>, + ) -> RpcResult { + trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, ?block_overrides, "Serving eth_call"); + Ok(EthCall::call( + self, + request, + block_number, + EvmOverrides::new(state_overrides, block_overrides), + ) + .await?) + } + + /// Handler for: `eth_callMany` + async fn call_many( + &self, + bundle: Bundle, + state_context: Option, + state_override: Option, + ) -> RpcResult> { + trace!(target: "rpc::eth", ?bundle, ?state_context, ?state_override, "Serving eth_callMany"); + Ok(EthCall::call_many(self, bundle, state_context, state_override).await?) + } + + /// Handler for: `eth_createAccessList` + async fn create_access_list( + &self, + request: TransactionRequest, + block_number: Option, + ) -> RpcResult { + trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_createAccessList"); + let access_list_with_gas_used = + EthCall::create_access_list_at(self, request, block_number).await?; + + Ok(access_list_with_gas_used) + } + + /// Handler for: `eth_estimateGas` + async fn estimate_gas( + &self, + request: TransactionRequest, + block_number: Option, + state_override: Option, + ) -> RpcResult { + trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_estimateGas"); + Ok(EthCall::estimate_gas_at( + self, + request, + block_number.unwrap_or_default(), + state_override, + ) + .await?) + } + + /// Handler for: `eth_gasPrice` + async fn gas_price(&self) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_gasPrice"); + return Ok(EthFees::gas_price(self).await?) + } + + /// Handler for: `eth_maxPriorityFeePerGas` + async fn max_priority_fee_per_gas(&self) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_maxPriorityFeePerGas"); + return Ok(EthFees::suggested_priority_fee(self).await?) + } + + /// Handler for: `eth_blobBaseFee` + async fn blob_base_fee(&self) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_blobBaseFee"); + return Ok(EthFees::blob_base_fee(self).await?) + } + + // FeeHistory is calculated based on lazy evaluation of fees for historical blocks, and further + // caching of it in the LRU cache. + // When new RPC call is executed, the cache gets locked, we check it for the historical fees + // according to the requested block range, and fill any cache misses (in both RPC response + // and cache itself) with the actual data queried from the database. + // To minimize the number of database seeks required to query the missing data, we calculate the + // first non-cached block number and last non-cached block number. After that, we query this + // range of consecutive blocks from the database. + /// Handler for: `eth_feeHistory` + async fn fee_history( + &self, + block_count: U64, + newest_block: BlockNumberOrTag, + reward_percentiles: Option>, + ) -> RpcResult { + trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory"); + return Ok( + EthFees::fee_history(self, block_count.to(), newest_block, reward_percentiles).await? + ) + } + + /// Handler for: `eth_mining` + async fn is_mining(&self) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for: `eth_hashrate` + async fn hashrate(&self) -> RpcResult { + Ok(U256::ZERO) + } + + /// Handler for: `eth_getWork` + async fn get_work(&self) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for: `eth_submitHashrate` + async fn submit_hashrate(&self, _hashrate: U256, _id: B256) -> RpcResult { + Ok(false) + } + + /// Handler for: `eth_submitWork` + async fn submit_work( + &self, + _nonce: B64, + _pow_hash: B256, + _mix_digest: B256, + ) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for: `eth_sendTransaction` + async fn send_transaction(&self, request: TransactionRequest) -> RpcResult { + trace!(target: "rpc::eth", ?request, "Serving eth_sendTransaction"); + Ok(EthTransactions::send_transaction(self, request).await?) + } + + /// Handler for: `eth_sendRawTransaction` + async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult { + trace!(target: "rpc::eth", ?tx, "Serving eth_sendRawTransaction"); + Ok(EthTransactions::send_raw_transaction(self, tx).await?) + } + + /// Handler for: `eth_sign` + async fn sign(&self, address: Address, message: Bytes) -> RpcResult { + trace!(target: "rpc::eth", ?address, ?message, "Serving eth_sign"); + Ok(EthTransactions::sign(self, address, message).await?) + } + + /// Handler for: `eth_signTransaction` + async fn sign_transaction(&self, _transaction: TransactionRequest) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for: `eth_signTypedData` + async fn sign_typed_data(&self, address: Address, data: TypedData) -> RpcResult { + trace!(target: "rpc::eth", ?address, ?data, "Serving eth_signTypedData"); + Ok(EthTransactions::sign_typed_data(self, &data, address)?) + } + + /// Handler for: `eth_getProof` + async fn get_proof( + &self, + address: Address, + keys: Vec, + block_number: Option, + ) -> RpcResult { + trace!(target: "rpc::eth", ?address, ?keys, ?block_number, "Serving eth_getProof"); + let res = EthState::get_proof(self, address, keys, block_number)?.await; + + Ok(res.map_err(|e| match e { + EthApiError::InvalidBlockRange => { + internal_rpc_err("eth_getProof is unimplemented for historical blocks") + } + _ => e.into(), + })?) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/filter.rs b/crates/rpc/rpc-eth-api/src/filter.rs similarity index 100% rename from crates/rpc/rpc-eth-api/src/api/filter.rs rename to crates/rpc/rpc-eth-api/src/filter.rs diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs b/crates/rpc/rpc-eth-api/src/helpers/block.rs similarity index 94% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs rename to crates/rpc/rpc-eth-api/src/helpers/block.rs index 804069f6e617..78f1ef9da66b 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/block.rs @@ -5,13 +5,11 @@ use std::sync::Arc; use futures::Future; use reth_primitives::{BlockId, Receipt, SealedBlock, SealedBlockWithSenders, TransactionMeta}; use reth_provider::{BlockIdReader, BlockReader, BlockReaderIdExt, HeaderProvider}; +use reth_rpc_eth_types::{EthApiError, EthResult, EthStateCache, ReceiptBuilder}; use reth_rpc_types::{AnyTransactionReceipt, Header, Index, RichBlock}; use reth_rpc_types_compat::block::{from_block, uncle_block_from_header}; -use crate::{ - servers::{LoadPendingBlock, LoadReceipt, SpawnBlocking}, - EthApiError, EthResult, EthStateCache, ReceiptBuilder, -}; +use super::{LoadPendingBlock, LoadReceipt, SpawnBlocking}; /// Block related functions for the [`EthApiServer`](crate::EthApiServer) trait in the /// `eth_` namespace. @@ -64,10 +62,8 @@ pub trait EthBlocks: LoadBlock { /// Returns `None` if the block does not exist fn block_transaction_count( &self, - block_id: impl Into, + block_id: BlockId, ) -> impl Future>> + Send { - let block_id = block_id.into(); - async move { if block_id.is_pending() { // Pending block can be fetched directly without need for caching @@ -155,11 +151,7 @@ pub trait EthBlocks: LoadBlock { /// Returns uncle headers of given block. /// /// Returns an empty vec if there are none. - fn ommers( - &self, - block_id: impl Into, - ) -> EthResult>> { - let block_id = block_id.into(); + fn ommers(&self, block_id: BlockId) -> EthResult>> { Ok(LoadBlock::provider(self).ommers_by_id(block_id)?) } @@ -168,11 +160,9 @@ pub trait EthBlocks: LoadBlock { /// Returns `None` if index out of range. fn ommer_by_block_and_index( &self, - block_id: impl Into, + block_id: BlockId, index: Index, ) -> impl Future>> + Send { - let block_id = block_id.into(); - async move { let uncles = if block_id.is_pending() { // Pending block can be fetched directly without need for caching diff --git a/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs b/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs new file mode 100644 index 000000000000..c199d4de6178 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs @@ -0,0 +1,54 @@ +//! Spawns a blocking task. CPU heavy tasks are executed with the `rayon` library. IO heavy tasks +//! are executed on the `tokio` runtime. + +use futures::Future; +use reth_rpc_eth_types::{EthApiError, EthResult}; +use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; +use tokio::sync::oneshot; + +/// Executes code on a blocking thread. +pub trait SpawnBlocking: Clone + Send + Sync + 'static { + /// Returns a handle for spawning IO heavy blocking tasks. + /// + /// Runtime access in default trait method implementations. + fn io_task_spawner(&self) -> impl TaskSpawner; + + /// Returns a handle for spawning CPU heavy blocking tasks. + /// + /// Thread pool access in default trait method implementations. + fn tracing_task_pool(&self) -> &BlockingTaskPool; + + /// Executes the future on a new blocking task. + /// + /// Note: This is expected for futures that are dominated by blocking IO operations, for tracing + /// or CPU bound operations in general use [`spawn_tracing`](Self::spawn_tracing). + fn spawn_blocking_io(&self, f: F) -> impl Future> + Send + where + F: FnOnce(Self) -> EthResult + Send + 'static, + R: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + let this = self.clone(); + self.io_task_spawner().spawn_blocking(Box::pin(async move { + let res = async move { f(this) }.await; + let _ = tx.send(res); + })); + + async move { rx.await.map_err(|_| EthApiError::InternalEthError)? } + } + + /// Executes a blocking task on the tracing pool. + /// + /// Note: This is expected for futures that are predominantly CPU bound, as it uses `rayon` + /// under the hood, for blocking IO futures use [`spawn_blocking`](Self::spawn_blocking_io). See + /// . + fn spawn_tracing(&self, f: F) -> impl Future> + Send + where + F: FnOnce(Self) -> EthResult + Send + 'static, + R: Send + 'static, + { + let this = self.clone(); + let fut = self.tracing_task_pool().spawn(move || f(this)); + async move { fut.await.map_err(|_| EthApiError::InternalBlockingTaskError)? } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs rename to crates/rpc/rpc-eth-api/src/helpers/call.rs index ee531b7f9e3f..888b739945ee 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -13,6 +13,16 @@ use reth_primitives::{ }; use reth_provider::StateProvider; use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef}; +use reth_rpc_eth_types::{ + cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, + error::ensure_success, + revm_utils::{ + apply_state_overrides, build_call_evm_env, caller_gas_allowance, + cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env, + }, + EthApiError, EthResult, RevertError, RpcInvalidTransactionError, StateCacheDb, +}; +use reth_rpc_server_types::constants::gas_oracle::{ESTIMATE_GAS_ERROR_RATIO, MIN_TRANSACTION_GAS}; use reth_rpc_types::{ state::{EvmOverrides, StateOverride}, AccessListWithGasUsed, BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo, @@ -22,17 +32,7 @@ use revm::{Database, DatabaseCommit}; use revm_inspectors::access_list::AccessListInspector; use tracing::trace; -use crate::{ - cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, - error::ensure_success, - revm_utils::{ - apply_state_overrides, build_call_evm_env, caller_gas_allowance, - cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env, - }, - servers::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace}, - EthApiError, EthResult, RevertError, RpcInvalidTransactionError, StateCacheDb, - ESTIMATE_GAS_ERROR_RATIO, MIN_TRANSACTION_GAS, -}; +use super::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace}; /// Execution related functions for the [`EthApiServer`](crate::EthApiServer) trait in /// the `eth_` namespace. diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs similarity index 98% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs rename to crates/rpc/rpc-eth-api/src/helpers/fee.rs index 9217b7d5f653..54c577ea2504 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -3,14 +3,14 @@ use futures::Future; use reth_primitives::U256; use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider}; +use reth_rpc_eth_types::{ + fee_history::calculate_reward_percentiles_for_block, EthApiError, EthResult, EthStateCache, + FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, RpcInvalidTransactionError, +}; use reth_rpc_types::{BlockNumberOrTag, FeeHistory}; use tracing::debug; -use crate::{ - fee_history::calculate_reward_percentiles_for_block, servers::LoadBlock, EthApiError, - EthResult, EthStateCache, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, - RpcInvalidTransactionError, -}; +use super::LoadBlock; /// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the /// `eth_` namespace. diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs b/crates/rpc/rpc-eth-api/src/helpers/mod.rs similarity index 55% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs rename to crates/rpc/rpc-eth-api/src/helpers/mod.rs index c714a166ddc0..321e9b03ec5d 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/mod.rs @@ -6,16 +6,13 @@ //! trait. //! //! Traits with `Eth` prefix, compose specific data needed to serve RPC requests in the `eth` -//! namespace. They use `Load` traits as building blocks. -//! [`EthTransactions`](crate::servers::EthTransactions) also writes data (submits transactions). -//! Based on the `eth_` request method semantics, request methods are divided into: -//! [`EthTransactions`](crate::servers::EthTransactions), [`EthBlocks`](crate::servers::EthBlocks), -//! [`EthFees`](crate::servers::EthFees), [`EthState`](crate::servers::EthState) and -//! [`EthCall`](crate::servers::EthCall). Default implementation of the `Eth` traits, is done w.r.t. -//! L1. +//! namespace. They use `Load` traits as building blocks. [`EthTransactions`] also writes data +//! (submits transactions). Based on the `eth_` request method semantics, request methods are +//! divided into: [`EthTransactions`], [`EthBlocks`], [`EthFees`], [`EthState`] and [`EthCall`]. +//! Default implementation of the `Eth` traits, is done w.r.t. L1. //! //! [`EthApiServer`](crate::EthApiServer), is implemented for any type that implements -//! all the `Eth` traits, e.g. [`EthApi`](crate::EthApi). +//! all the `Eth` traits, e.g. `reth_rpc::EthApi`. pub mod block; pub mod blocking_task; @@ -29,12 +26,17 @@ pub mod state; pub mod trace; pub mod transaction; -use block::LoadBlock; -use blocking_task::SpawnBlocking; -use call::Call; -use pending_block::LoadPendingBlock; -use trace::Trace; -use transaction::LoadTransaction; +pub use block::{EthBlocks, LoadBlock}; +pub use blocking_task::SpawnBlocking; +pub use call::{Call, EthCall}; +pub use fee::{EthFees, LoadFee}; +pub use pending_block::LoadPendingBlock; +pub use receipt::LoadReceipt; +pub use signer::EthSigner; +pub use spec::EthApiSpec; +pub use state::{EthState, LoadState}; +pub use trace::Trace; +pub use transaction::{EthTransactions, LoadTransaction}; /// Extension trait that bundles traits needed for tracing transactions. pub trait TraceExt: diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs rename to crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index f167a82fef20..c7036f4bedaa 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -24,16 +24,16 @@ use reth_provider::{ use reth_revm::{ database::StateProviderDatabase, state_change::post_block_withdrawals_balance_increments, }; +use reth_rpc_eth_types::{ + pending_block::{pre_block_beacon_root_contract_call, pre_block_blockhashes_update}, + EthApiError, EthResult, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin, +}; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State}; use tokio::sync::Mutex; use tracing::debug; -use crate::{ - pending_block::{pre_block_beacon_root_contract_call, pre_block_blockhashes_update}, - servers::SpawnBlocking, - EthApiError, EthResult, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin, -}; +use super::SpawnBlocking; /// Loads a pending block from database. /// diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs b/crates/rpc/rpc-eth-api/src/helpers/receipt.rs similarity index 94% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs rename to crates/rpc/rpc-eth-api/src/helpers/receipt.rs index 83c4b9d03d8a..5cd6c03c4d9f 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/receipt.rs @@ -3,10 +3,9 @@ use futures::Future; use reth_primitives::{Receipt, TransactionMeta, TransactionSigned}; +use reth_rpc_eth_types::{EthApiError, EthResult, EthStateCache, ReceiptBuilder}; use reth_rpc_types::AnyTransactionReceipt; -use crate::{EthApiError, EthResult, EthStateCache, ReceiptBuilder}; - /// Assembles transaction receipt data w.r.t to network. /// /// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` receipts RPC methods. diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs b/crates/rpc/rpc-eth-api/src/helpers/signer.rs similarity index 97% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs rename to crates/rpc/rpc-eth-api/src/helpers/signer.rs index 637708eb7c44..2a75d9abb02e 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/signer.rs @@ -5,10 +5,9 @@ use std::result; use alloy_dyn_abi::TypedData; use dyn_clone::DynClone; use reth_primitives::{Address, Signature, TransactionSigned}; +use reth_rpc_eth_types::SignError; use reth_rpc_types::TypedTransactionRequest; -use crate::SignError; - /// Result returned by [`EthSigner`] methods. pub type Result = result::Result; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs b/crates/rpc/rpc-eth-api/src/helpers/spec.rs similarity index 100% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs rename to crates/rpc/rpc-eth-api/src/helpers/spec.rs diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs rename to crates/rpc/rpc-eth-api/src/helpers/state.rs index 220a33e91683..80859b929b7b 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -7,15 +7,15 @@ use reth_primitives::{ B256, U256, }; use reth_provider::{BlockIdReader, StateProvider, StateProviderBox, StateProviderFactory}; +use reth_rpc_eth_types::{ + EthApiError, EthResult, EthStateCache, PendingBlockEnv, RpcInvalidTransactionError, +}; use reth_rpc_types::{serde_helpers::JsonStorageKey, EIP1186AccountProofResponse}; use reth_rpc_types_compat::proof::from_primitive_account_proof; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, SpecId}; -use crate::{ - servers::{EthApiSpec, LoadPendingBlock, SpawnBlocking}, - EthApiError, EthResult, EthStateCache, PendingBlockEnv, RpcInvalidTransactionError, -}; +use super::{EthApiSpec, LoadPendingBlock, SpawnBlocking}; /// Helper methods for `eth_` methods relating to state (accounts). pub trait EthState: LoadState + SpawnBlocking { diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs b/crates/rpc/rpc-eth-api/src/helpers/trace.rs similarity index 98% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs rename to crates/rpc/rpc-eth-api/src/helpers/trace.rs index a1eedf5dc1bf..63b210204065 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/trace.rs @@ -4,16 +4,16 @@ use futures::Future; use reth_evm::ConfigureEvm; use reth_primitives::{revm::env::tx_env_with_recovered, B256}; use reth_revm::database::StateProviderDatabase; +use reth_rpc_eth_types::{ + cache::db::{StateCacheDb, StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, + EthApiError, EthResult, +}; use reth_rpc_types::{BlockId, TransactionInfo}; use revm::{db::CacheDB, Database, DatabaseCommit, GetInspector, Inspector}; use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; use revm_primitives::{EnvWithHandlerCfg, EvmState, ExecutionResult, ResultAndState}; -use crate::{ - cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, - servers::{Call, LoadBlock, LoadPendingBlock, LoadState, LoadTransaction}, - EthApiError, EthResult, StateCacheDb, -}; +use super::{Call, LoadBlock, LoadPendingBlock, LoadState, LoadTransaction}; /// Executes CPU heavy tasks. pub trait Trace: LoadState { diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs rename to crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 5c87de1d5ed6..acf0156c6d9f 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -10,6 +10,10 @@ use reth_primitives::{ SealedBlockWithSenders, TransactionMeta, TransactionSigned, TxHash, TxKind, B256, U256, }; use reth_provider::{BlockReaderIdExt, ReceiptProvider, TransactionsProvider}; +use reth_rpc_eth_types::{ + utils::recover_raw_transaction, EthApiError, EthResult, EthStateCache, SignError, + TransactionSource, +}; use reth_rpc_types::{ transaction::{ EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest, @@ -20,14 +24,9 @@ use reth_rpc_types::{ use reth_rpc_types_compat::transaction::from_recovered_with_block_context; use reth_transaction_pool::{TransactionOrigin, TransactionPool}; -use crate::{ - servers::{ - Call, EthApiSpec, EthSigner, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, - SpawnBlocking, - }, - utils::recover_raw_transaction, - EthApiError, EthResult, EthStateCache, SignError, TransactionSource, -}; +use super::EthSigner; + +use super::{Call, EthApiSpec, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, SpawnBlocking}; /// Transaction related functions for the [`EthApiServer`](crate::EthApiServer) trait in /// the `eth_` namespace. diff --git a/crates/rpc/rpc-eth-api/src/lib.rs b/crates/rpc/rpc-eth-api/src/lib.rs index 51cf5dafc07b..922c6ed77fad 100644 --- a/crates/rpc/rpc-eth-api/src/lib.rs +++ b/crates/rpc/rpc-eth-api/src/lib.rs @@ -12,51 +12,22 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -pub mod api; -pub mod cache; -pub mod error; -pub mod fee_history; -pub mod gas_oracle; -pub mod id_provider; -pub mod logs_utils; -pub mod pending_block; -pub mod receipt; -pub mod result; -pub mod revm_utils; -pub mod transaction; -pub mod utils; +pub mod bundle; +pub mod core; +pub mod filter; +pub mod helpers; +pub mod pubsub; +pub use bundle::{EthBundleApiServer, EthCallBundleApiServer}; +pub use core::EthApiServer; +pub use filter::EthFilterApiServer; +pub use pubsub::EthPubSubApiServer; + +pub use helpers::transaction::RawTransactionForwarder; + +#[cfg(feature = "client")] +pub use bundle::{EthBundleApiClient, EthCallBundleApiClient}; +#[cfg(feature = "client")] +pub use core::EthApiClient; #[cfg(feature = "client")] -pub use api::{ - bundle::{EthBundleApiClient, EthCallBundleApiClient}, - filter::EthFilterApiClient, - EthApiClient, -}; -pub use api::{ - bundle::{EthBundleApiServer, EthCallBundleApiServer}, - filter::EthFilterApiServer, - pubsub::EthPubSubApiServer, - servers::{ - self, - bundle::EthBundle, - filter::{EthFilter, EthFilterConfig}, - pubsub::EthPubSub, - EthApi, - }, - EthApiServer, -}; -pub use cache::{ - config::EthStateCacheConfig, db::StateCacheDb, multi_consumer::MultiConsumerLruCache, - EthStateCache, -}; -pub use error::{EthApiError, EthResult, RevertError, RpcInvalidTransactionError, SignError}; -pub use fee_history::{FeeHistoryCache, FeeHistoryCacheConfig, FeeHistoryEntry}; -pub use gas_oracle::{ - GasCap, GasPriceOracle, GasPriceOracleConfig, GasPriceOracleResult, ESTIMATE_GAS_ERROR_RATIO, - MIN_TRANSACTION_GAS, RPC_DEFAULT_GAS_CAP, -}; -pub use id_provider::EthSubscriptionIdProvider; -pub use pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; -pub use receipt::ReceiptBuilder; -pub use result::ToRpcResult; -pub use transaction::TransactionSource; +pub use filter::EthFilterApiClient; diff --git a/crates/rpc/rpc-eth-api/src/api/pubsub.rs b/crates/rpc/rpc-eth-api/src/pubsub.rs similarity index 100% rename from crates/rpc/rpc-eth-api/src/api/pubsub.rs rename to crates/rpc/rpc-eth-api/src/pubsub.rs diff --git a/crates/rpc/rpc-eth-types/Cargo.toml b/crates/rpc/rpc-eth-types/Cargo.toml new file mode 100644 index 000000000000..b1c307191025 --- /dev/null +++ b/crates/rpc/rpc-eth-types/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "reth-rpc-eth-types" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Types supporting implementation of 'eth' namespace RPC server API" + +[lints] +workspace = true + +[dependencies] +reth-chainspec.workspace = true +reth-errors.workspace = true +reth-evm.workspace = true +reth-execution-types.workspace = true +reth-metrics.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true +reth-revm.workspace = true +reth-rpc-server-types.workspace = true +reth-rpc-types.workspace = true +reth-rpc-types-compat.workspace = true +reth-tasks.workspace = true +reth-transaction-pool.workspace = true +reth-trie.workspace = true + +# ethereum +alloy-sol-types.workspace = true +revm.workspace = true +revm-inspectors = { workspace = true, features = ["js-tracer"] } +revm-primitives = { workspace = true, features = ["dev"] } + +# rpc +jsonrpsee-core.workspace = true +jsonrpsee-types.workspace = true + +# async +futures.workspace = true +tokio.workspace = true +tokio-stream.workspace = true + +# metrics +metrics.workspace = true + +# misc +serde = { workspace = true, features = ["derive"] } +thiserror.workspace = true +derive_more.workspace = true +schnellru.workspace = true +rand.workspace = true +tracing.workspace = true + +[dev-dependencies] +serde_json.workspace = true + +[features] +optimism = [ + "reth-primitives/optimism", + "reth-provider/optimism", + "reth-revm/optimism", + "reth-chainspec/optimism", + "reth-execution-types/optimism", + "reth-revm/optimism", + "revm/optimism" +] \ No newline at end of file diff --git a/crates/rpc/rpc-eth-api/src/cache/config.rs b/crates/rpc/rpc-eth-types/src/cache/config.rs similarity index 80% rename from crates/rpc/rpc-eth-api/src/cache/config.rs rename to crates/rpc/rpc-eth-types/src/cache/config.rs index 93207c930f5c..c2d379652a47 100644 --- a/crates/rpc/rpc-eth-api/src/cache/config.rs +++ b/crates/rpc/rpc-eth-types/src/cache/config.rs @@ -1,9 +1,13 @@ //! Configuration for RPC cache. -use reth_rpc_server_types::constants::cache::*; use serde::{Deserialize, Serialize}; -/// Settings for the [`EthStateCache`](crate::EthStateCache). +use reth_rpc_server_types::constants::cache::{ + DEFAULT_BLOCK_CACHE_MAX_LEN, DEFAULT_CONCURRENT_DB_REQUESTS, DEFAULT_ENV_CACHE_MAX_LEN, + DEFAULT_RECEIPT_CACHE_MAX_LEN, +}; + +/// Settings for the [`EthStateCache`](super::EthStateCache). #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EthStateCacheConfig { diff --git a/crates/rpc/rpc-eth-api/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/cache/db.rs rename to crates/rpc/rpc-eth-types/src/cache/db.rs index 74b20defbc60..47af8e85df31 100644 --- a/crates/rpc/rpc-eth-api/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -1,6 +1,6 @@ //! Helper types to workaround 'higher-ranked lifetime error' //! in default implementation of -//! [`Call`](crate::servers::Call). +//! `reth_rpc_eth_api::helpers::Call`. use reth_primitives::{B256, U256}; use reth_provider::StateProvider; diff --git a/crates/rpc/rpc-eth-api/src/cache/metrics.rs b/crates/rpc/rpc-eth-types/src/cache/metrics.rs similarity index 100% rename from crates/rpc/rpc-eth-api/src/cache/metrics.rs rename to crates/rpc/rpc-eth-types/src/cache/metrics.rs diff --git a/crates/rpc/rpc-eth-api/src/cache/mod.rs b/crates/rpc/rpc-eth-types/src/cache/mod.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/cache/mod.rs rename to crates/rpc/rpc-eth-types/src/cache/mod.rs index 92e96806bc9b..cda3d72584c3 100644 --- a/crates/rpc/rpc-eth-api/src/cache/mod.rs +++ b/crates/rpc/rpc-eth-types/src/cache/mod.rs @@ -26,7 +26,7 @@ use tokio::sync::{ }; use tokio_stream::wrappers::UnboundedReceiverStream; -use crate::{EthStateCacheConfig, MultiConsumerLruCache}; +use super::{EthStateCacheConfig, MultiConsumerLruCache}; pub mod config; pub mod db; @@ -276,7 +276,7 @@ impl EthStateCache { /// handles messages and does LRU lookups and never blocking IO. /// /// Caution: The channel for the data is _unbounded_ it is assumed that this is mainly used by the -/// [`EthApi`](crate::EthApi) which is typically invoked by the RPC server, which already uses +/// `reth_rpc::EthApi` which is typically invoked by the RPC server, which already uses /// permits to limit concurrent requests. #[must_use = "Type does nothing unless spawned"] pub(crate) struct EthStateCacheService< diff --git a/crates/rpc/rpc-eth-api/src/cache/multi_consumer.rs b/crates/rpc/rpc-eth-types/src/cache/multi_consumer.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/cache/multi_consumer.rs rename to crates/rpc/rpc-eth-types/src/cache/multi_consumer.rs index b63795b59b3c..77d861343307 100644 --- a/crates/rpc/rpc-eth-api/src/cache/multi_consumer.rs +++ b/crates/rpc/rpc-eth-types/src/cache/multi_consumer.rs @@ -1,14 +1,16 @@ //! Metered cache, which also provides storage for senders in order to queue queries that result in //! a cache miss. -use super::metrics::CacheMetrics; -use schnellru::{ByLength, Limiter, LruMap}; use std::{ collections::{hash_map::Entry, HashMap}, fmt::{self, Debug, Formatter}, hash::Hash, }; +use schnellru::{ByLength, Limiter, LruMap}; + +use super::metrics::CacheMetrics; + /// A multi-consumer LRU cache. pub struct MultiConsumerLruCache where diff --git a/crates/rpc/rpc-eth-api/src/error.rs b/crates/rpc/rpc-eth-types/src/error.rs similarity index 97% rename from crates/rpc/rpc-eth-api/src/error.rs rename to crates/rpc/rpc-eth-types/src/error.rs index 84c1504015a3..4ddbf9a38dfe 100644 --- a/crates/rpc/rpc-eth-api/src/error.rs +++ b/crates/rpc/rpc-eth-types/src/error.rs @@ -1,10 +1,13 @@ //! Implementation specific Errors for the `eth_` namespace. -use crate::result::{internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code}; +use std::time::Duration; + use alloy_sol_types::decode_revert_reason; -use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObject}; use reth_errors::RethError; use reth_primitives::{revm_primitives::InvalidHeader, Address, Bytes}; +use reth_rpc_server_types::result::{ + internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code, +}; use reth_rpc_types::{ error::EthRpcErrorCode, request::TransactionInputError, BlockError, ToRpcError, }; @@ -14,7 +17,6 @@ use reth_transaction_pool::error::{ }; use revm::primitives::{EVMError, ExecutionResult, HaltReason, OutOfGasError}; use revm_inspectors::tracing::{js::JsInspectorError, MuxError}; -use std::time::Duration; /// Result alias pub type EthResult = Result; @@ -134,7 +136,7 @@ impl EthApiError { } } -impl From for ErrorObject<'static> { +impl From for jsonrpsee_types::error::ErrorObject<'static> { fn from(error: EthApiError) -> Self { match error { EthApiError::FailedToDecodeSignedTransaction | @@ -165,9 +167,10 @@ impl From for ErrorObject<'static> { EthApiError::Unsupported(msg) => internal_rpc_err(msg), EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg), EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg), - err @ EthApiError::ExecutionTimedOut(_) => { - rpc_error_with_code(CALL_EXECUTION_FAILED_CODE, err.to_string()) - } + err @ EthApiError::ExecutionTimedOut(_) => rpc_error_with_code( + jsonrpsee_types::error::CALL_EXECUTION_FAILED_CODE, + err.to_string(), + ), err @ EthApiError::InternalBlockingTaskError | err @ EthApiError::InternalEthError => { internal_rpc_err(err.to_string()) } @@ -386,7 +389,7 @@ impl RpcInvalidTransactionError { /// Converts the halt error /// /// Takes the configured gas limit of the transaction which is attached to the error - pub(crate) const fn halt(reason: HaltReason, gas_limit: u64) -> Self { + pub const fn halt(reason: HaltReason, gas_limit: u64) -> Self { match reason { HaltReason::OutOfGas(err) => Self::out_of_gas(err, gas_limit), HaltReason::NonceOverflow => Self::NonceMaxValue, @@ -395,7 +398,7 @@ impl RpcInvalidTransactionError { } /// Converts the out of gas error - pub(crate) const fn out_of_gas(reason: OutOfGasError, gas_limit: u64) -> Self { + pub const fn out_of_gas(reason: OutOfGasError, gas_limit: u64) -> Self { match reason { OutOfGasError::Basic => Self::BasicOutOfGas(gas_limit), OutOfGasError::Memory | OutOfGasError::MemoryLimit => Self::MemoryOutOfGas(gas_limit), @@ -405,7 +408,7 @@ impl RpcInvalidTransactionError { } } -impl From for ErrorObject<'static> { +impl From for jsonrpsee_types::error::ErrorObject<'static> { fn from(err: RpcInvalidTransactionError) -> Self { match err { RpcInvalidTransactionError::Revert(revert) => { @@ -580,7 +583,7 @@ pub enum RpcPoolError { Other(Box), } -impl From for ErrorObject<'static> { +impl From for jsonrpsee_types::error::ErrorObject<'static> { fn from(error: RpcPoolError) -> Self { match error { RpcPoolError::Invalid(err) => err.into(), @@ -655,7 +658,7 @@ pub enum SignError { /// Converts the evm [`ExecutionResult`] into a result where `Ok` variant is the output bytes if it /// is [`ExecutionResult::Success`]. -pub(crate) fn ensure_success(result: ExecutionResult) -> EthResult { +pub fn ensure_success(result: ExecutionResult) -> EthResult { match result { ExecutionResult::Success { output, .. } => Ok(output.into_data()), ExecutionResult::Revert { output, .. } => { diff --git a/crates/rpc/rpc-eth-api/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/fee_history.rs rename to crates/rpc/rpc-eth-types/src/fee_history.rs index 10c06eab06c6..6f84511aa6eb 100644 --- a/crates/rpc/rpc-eth-api/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -18,12 +18,13 @@ use reth_primitives::{ Receipt, SealedBlock, TransactionSigned, B256, }; use reth_provider::{BlockReaderIdExt, CanonStateNotification, ChainSpecProvider}; -use reth_rpc_server_types::constants::gas_oracle::MAX_HEADER_HISTORY; use reth_rpc_types::TxGasAndReward; use serde::{Deserialize, Serialize}; use tracing::trace; -use crate::{EthApiError, EthStateCache}; +use reth_rpc_server_types::constants::gas_oracle::MAX_HEADER_HISTORY; + +use super::{EthApiError, EthStateCache}; /// Contains cached fee history entries for blocks. /// @@ -265,7 +266,7 @@ pub async fn fee_history_cache_new_blocks_task( /// the corresponding rewards for the transactions at each percentile. /// /// The results are returned as a vector of U256 values. -pub(crate) fn calculate_reward_percentiles_for_block( +pub fn calculate_reward_percentiles_for_block( percentiles: &[f64], gas_used: u64, base_fee_per_gas: u64, diff --git a/crates/rpc/rpc-eth-api/src/gas_oracle.rs b/crates/rpc/rpc-eth-types/src/gas_oracle.rs similarity index 90% rename from crates/rpc/rpc-eth-api/src/gas_oracle.rs rename to crates/rpc/rpc-eth-types/src/gas_oracle.rs index 3cc55eb2b341..4993c5d78a6e 100644 --- a/crates/rpc/rpc-eth-api/src/gas_oracle.rs +++ b/crates/rpc/rpc-eth-types/src/gas_oracle.rs @@ -3,30 +3,25 @@ use std::fmt::{self, Debug, Formatter}; -use derive_more::{Deref, DerefMut}; +use derive_more::{Deref, DerefMut, From, Into}; use reth_primitives::{constants::GWEI_TO_WEI, BlockNumberOrTag, B256, U256}; use reth_provider::BlockReaderIdExt; -use reth_rpc_server_types::constants::gas_oracle::*; +use reth_rpc_server_types::constants; use schnellru::{ByLength, LruMap}; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; use tracing::warn; -use crate::{EthApiError, EthResult, EthStateCache, RpcInvalidTransactionError}; +use reth_rpc_server_types::constants::gas_oracle::{ + DEFAULT_GAS_PRICE_BLOCKS, DEFAULT_GAS_PRICE_PERCENTILE, DEFAULT_IGNORE_GAS_PRICE, + DEFAULT_MAX_GAS_PRICE, MAX_HEADER_HISTORY, SAMPLE_NUMBER, +}; -/// The default gas limit for `eth_call` and adjacent calls. -/// -/// This is different from the default to regular 30M block gas limit -/// [`ETHEREUM_BLOCK_GAS_LIMIT`](reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT) to allow for -/// more complex calls. -pub const RPC_DEFAULT_GAS_CAP: GasCap = GasCap(50_000_000); +use super::{EthApiError, EthResult, EthStateCache, RpcInvalidTransactionError}; -/// Gas per transaction not creating a contract. -pub const MIN_TRANSACTION_GAS: u64 = 21_000u64; -/// Allowed error ratio for gas estimation -/// Taken from Geth's implementation in order to pass the hive tests -/// -pub const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015; +/// The default gas limit for `eth_call` and adjacent calls. See +/// [`RPC_DEFAULT_GAS_CAP`](constants::gas_oracle::RPC_DEFAULT_GAS_CAP). +pub const RPC_DEFAULT_GAS_CAP: GasCap = GasCap(constants::gas_oracle::RPC_DEFAULT_GAS_CAP); /// Settings for the [`GasPriceOracle`] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -300,8 +295,8 @@ impl Default for GasPriceOracleResult { } /// The wrapper type for gas limit -#[derive(Debug, Clone, Copy)] -pub struct GasCap(u64); +#[derive(Debug, Clone, Copy, From, Into)] +pub struct GasCap(pub u64); impl Default for GasCap { fn default() -> Self { @@ -309,18 +304,6 @@ impl Default for GasCap { } } -impl From for GasCap { - fn from(gas_cap: u64) -> Self { - Self(gas_cap) - } -} - -impl From for u64 { - fn from(gas_cap: GasCap) -> Self { - gas_cap.0 - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-eth-api/src/id_provider.rs b/crates/rpc/rpc-eth-types/src/id_provider.rs similarity index 86% rename from crates/rpc/rpc-eth-api/src/id_provider.rs rename to crates/rpc/rpc-eth-types/src/id_provider.rs index 0b63da39f425..642d87578f81 100644 --- a/crates/rpc/rpc-eth-api/src/id_provider.rs +++ b/crates/rpc/rpc-eth-types/src/id_provider.rs @@ -1,19 +1,19 @@ -//! Helper type for [`EthPubSubApiServer`](crate::EthPubSubApiServer) implementation. +//! Helper type for `reth_rpc_eth_api::EthPubSubApiServer` implementation. //! //! Generates IDs for tracking subscriptions. use std::fmt::Write; -use jsonrpsee::types::SubscriptionId; +use jsonrpsee_types::SubscriptionId; -/// An [`IdProvider`](jsonrpsee::core::traits::IdProvider) for ethereum subscription ids. +/// An [`IdProvider`](jsonrpsee_core::traits::IdProvider) for ethereum subscription ids. /// /// Returns new hex-string [QUANTITY](https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding) ids #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] pub struct EthSubscriptionIdProvider; -impl jsonrpsee::core::traits::IdProvider for EthSubscriptionIdProvider { +impl jsonrpsee_core::traits::IdProvider for EthSubscriptionIdProvider { fn next_id(&self) -> SubscriptionId<'static> { to_quantity(rand::random::()) } diff --git a/crates/rpc/rpc-eth-types/src/lib.rs b/crates/rpc/rpc-eth-types/src/lib.rs new file mode 100644 index 000000000000..be4e1619ce42 --- /dev/null +++ b/crates/rpc/rpc-eth-types/src/lib.rs @@ -0,0 +1,34 @@ +//! Reth RPC server types, used in server implementation of `eth` namespace API. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +pub mod cache; +pub mod error; +pub mod fee_history; +pub mod gas_oracle; +pub mod id_provider; +pub mod logs_utils; +pub mod pending_block; +pub mod receipt; +pub mod revm_utils; +pub mod transaction; +pub mod utils; + +pub use cache::{ + config::EthStateCacheConfig, db::StateCacheDb, multi_consumer::MultiConsumerLruCache, + EthStateCache, +}; +pub use error::{EthApiError, EthResult, RevertError, RpcInvalidTransactionError, SignError}; +pub use fee_history::{FeeHistoryCache, FeeHistoryCacheConfig, FeeHistoryEntry}; +pub use gas_oracle::{GasCap, GasPriceOracle, GasPriceOracleConfig, GasPriceOracleResult}; +pub use id_provider::EthSubscriptionIdProvider; +pub use logs_utils::EthFilterError; +pub use pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; +pub use receipt::ReceiptBuilder; +pub use transaction::TransactionSource; diff --git a/crates/rpc/rpc-eth-api/src/logs_utils.rs b/crates/rpc/rpc-eth-types/src/logs_utils.rs similarity index 79% rename from crates/rpc/rpc-eth-api/src/logs_utils.rs rename to crates/rpc/rpc-eth-types/src/logs_utils.rs index f98567c4ed9b..5cd5fa789d04 100644 --- a/crates/rpc/rpc-eth-api/src/logs_utils.rs +++ b/crates/rpc/rpc-eth-types/src/logs_utils.rs @@ -1,16 +1,66 @@ -//! Helper functions for [`EthFilterApiServer`](crate::EthFilterApiServer) implementation. +//! Helper functions for `reth_rpc_eth_api::EthFilterApiServer` implementation. //! //! Log parsing for building filter. use reth_chainspec::ChainInfo; use reth_primitives::{BlockNumHash, Receipt, TxHash}; use reth_provider::{BlockReader, ProviderError}; -use reth_rpc_types::{FilteredParams, Log}; +use reth_rpc_server_types::result::rpc_error_with_code; +use reth_rpc_types::{FilterId, FilteredParams, Log}; -use crate::servers::filter::EthFilterError; +use crate::EthApiError; + +/// Errors that can occur in the handler implementation +#[derive(Debug, thiserror::Error)] +pub enum EthFilterError { + /// Filter not found. + #[error("filter not found")] + FilterNotFound(FilterId), + /// Invalid block range. + #[error("invalid block range params")] + InvalidBlockRangeParams, + /// Query scope is too broad. + #[error("query exceeds max block range {0}")] + QueryExceedsMaxBlocks(u64), + /// Query result is too large. + #[error("query exceeds max results {0}")] + QueryExceedsMaxResults(usize), + /// Error serving request in `eth_` namespace. + #[error(transparent)] + EthAPIError(#[from] EthApiError), + /// Error thrown when a spawned task failed to deliver a response. + #[error("internal filter error")] + InternalError, +} + +// convert the error +impl From for jsonrpsee_types::error::ErrorObject<'static> { + fn from(err: EthFilterError) -> Self { + match err { + EthFilterError::FilterNotFound(_) => { + rpc_error_with_code(jsonrpsee_types::error::INVALID_PARAMS_CODE, "filter not found") + } + err @ EthFilterError::InternalError => { + rpc_error_with_code(jsonrpsee_types::error::INTERNAL_ERROR_CODE, err.to_string()) + } + EthFilterError::EthAPIError(err) => err.into(), + err @ EthFilterError::InvalidBlockRangeParams | + err @ EthFilterError::QueryExceedsMaxBlocks(_) | + err @ EthFilterError::QueryExceedsMaxResults(_) => { + rpc_error_with_code(jsonrpsee_types::error::INVALID_PARAMS_CODE, err.to_string()) + } + } + } +} + +impl From for EthFilterError { + fn from(err: ProviderError) -> Self { + Self::EthAPIError(err.into()) + } +} /// Returns all matching of a block's receipts when the transaction hashes are known. -pub(crate) fn matching_block_logs_with_tx_hashes<'a, I>( +pub fn matching_block_logs_with_tx_hashes<'a, I>( filter: &FilteredParams, block_num_hash: BlockNumHash, tx_hashes_and_receipts: I, @@ -47,7 +97,7 @@ where /// Appends all matching logs of a block's receipts. /// If the log matches, look up the corresponding transaction hash. -pub(crate) fn append_matching_block_logs( +pub fn append_matching_block_logs( all_logs: &mut Vec, provider: impl BlockReader, filter: &FilteredParams, @@ -114,7 +164,7 @@ pub(crate) fn append_matching_block_logs( } /// Returns true if the log matches the filter and should be included -pub(crate) fn log_matches_filter( +pub fn log_matches_filter( block: BlockNumHash, log: &reth_primitives::Log, params: &FilteredParams, @@ -131,7 +181,7 @@ pub(crate) fn log_matches_filter( } /// Computes the block range based on the filter range and current block numbers -pub(crate) fn get_filter_block_range( +pub fn get_filter_block_range( from_block: Option, to_block: Option, start_block: u64, diff --git a/crates/rpc/rpc-eth-api/src/pending_block.rs b/crates/rpc/rpc-eth-types/src/pending_block.rs similarity index 97% rename from crates/rpc/rpc-eth-api/src/pending_block.rs rename to crates/rpc/rpc-eth-types/src/pending_block.rs index a1671b2833ac..7e27a9d6b692 100644 --- a/crates/rpc/rpc-eth-api/src/pending_block.rs +++ b/crates/rpc/rpc-eth-types/src/pending_block.rs @@ -1,4 +1,4 @@ -//! Helper types for [`EthApiServer`](crate::EthApiServer) implementation. +//! Helper types for `reth_rpc_eth_api::EthApiServer` implementation. //! //! Types used in block building. @@ -14,7 +14,7 @@ use revm_primitives::{ BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, }; -use crate::{EthApiError, EthResult}; +use super::{EthApiError, EthResult}; /// Configured [`BlockEnv`] and [`CfgEnvWithHandlerCfg`] for a pending block #[derive(Debug, Clone, Constructor)] diff --git a/crates/rpc/rpc-eth-api/src/receipt.rs b/crates/rpc/rpc-eth-types/src/receipt.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/receipt.rs rename to crates/rpc/rpc-eth-types/src/receipt.rs index ac9944ab8825..cd3fd1ed514b 100644 --- a/crates/rpc/rpc-eth-api/src/receipt.rs +++ b/crates/rpc/rpc-eth-types/src/receipt.rs @@ -7,7 +7,7 @@ use reth_rpc_types::{ }; use revm_primitives::calc_blob_gasprice; -use crate::{EthApiError, EthResult}; +use super::{EthApiError, EthResult}; /// Receipt response builder. #[derive(Debug)] diff --git a/crates/rpc/rpc-eth-api/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs similarity index 99% rename from crates/rpc/rpc-eth-api/src/revm_utils.rs rename to crates/rpc/rpc-eth-types/src/revm_utils.rs index b0d0e6a882d3..6b30de26c4da 100644 --- a/crates/rpc/rpc-eth-api/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -19,7 +19,7 @@ use revm::{ }; use tracing::trace; -use crate::{EthApiError, EthResult, RpcInvalidTransactionError}; +use super::{EthApiError, EthResult, RpcInvalidTransactionError}; /// Returns the addresses of the precompiles corresponding to the `SpecId`. #[inline] diff --git a/crates/rpc/rpc-eth-api/src/transaction.rs b/crates/rpc/rpc-eth-types/src/transaction.rs similarity index 97% rename from crates/rpc/rpc-eth-api/src/transaction.rs rename to crates/rpc/rpc-eth-types/src/transaction.rs index 685f37b93d79..32c81d3966f8 100644 --- a/crates/rpc/rpc-eth-api/src/transaction.rs +++ b/crates/rpc/rpc-eth-types/src/transaction.rs @@ -1,4 +1,4 @@ -//! Helper types for [`EthApiServer`](crate::EthApiServer) implementation. +//! Helper types for `reth_rpc_eth_api::EthApiServer` implementation. //! //! Transaction wrapper that labels transaction with its origin. diff --git a/crates/rpc/rpc-eth-api/src/utils.rs b/crates/rpc/rpc-eth-types/src/utils.rs similarity index 95% rename from crates/rpc/rpc-eth-api/src/utils.rs rename to crates/rpc/rpc-eth-types/src/utils.rs index 6573fb6b77cc..a35708396813 100644 --- a/crates/rpc/rpc-eth-api/src/utils.rs +++ b/crates/rpc/rpc-eth-types/src/utils.rs @@ -2,7 +2,7 @@ use reth_primitives::{Bytes, PooledTransactionsElement, PooledTransactionsElementEcRecovered}; -use crate::{EthApiError, EthResult}; +use super::{EthApiError, EthResult}; /// Recovers a [`PooledTransactionsElementEcRecovered`] from an enveloped encoded byte stream. /// diff --git a/crates/rpc/rpc-server-types/Cargo.toml b/crates/rpc/rpc-server-types/Cargo.toml index ddecc0a490ca..628654ebaf98 100644 --- a/crates/rpc/rpc-server-types/Cargo.toml +++ b/crates/rpc/rpc-server-types/Cargo.toml @@ -12,9 +12,20 @@ description = "RPC server types and constants" workspace = true [dependencies] +reth-errors.workspace = true +reth-network-api.workspace = true +reth-primitives.workspace = true +reth-rpc-types.workspace = true + + # ethereum alloy-primitives.workspace = true +# rpc +jsonrpsee-core.workspace = true +jsonrpsee-types.workspace = true + # misc strum = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] } + diff --git a/crates/rpc/rpc-server-types/src/constants.rs b/crates/rpc/rpc-server-types/src/constants.rs index 3784d7508ff2..807d96a91256 100644 --- a/crates/rpc/rpc-server-types/src/constants.rs +++ b/crates/rpc/rpc-server-types/src/constants.rs @@ -64,6 +64,20 @@ pub mod gas_oracle { /// The default minimum gas price, under which the sample will be ignored pub const DEFAULT_IGNORE_GAS_PRICE: U256 = U256::from_limbs([2u64, 0, 0, 0]); + + /// The default gas limit for `eth_call` and adjacent calls. + /// + /// This is different from the default to regular 30M block gas limit + /// [`ETHEREUM_BLOCK_GAS_LIMIT`](reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT) to allow + /// for more complex calls. + pub const RPC_DEFAULT_GAS_CAP: u64 = 50_000_000; + + /// Gas per transaction not creating a contract. + pub const MIN_TRANSACTION_GAS: u64 = 21_000u64; + /// Allowed error ratio for gas estimation + /// Taken from Geth's implementation in order to pass the hive tests + /// + pub const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015; } /// Cache specific constants diff --git a/crates/rpc/rpc-server-types/src/lib.rs b/crates/rpc/rpc-server-types/src/lib.rs index 4bdee53f83cb..c20b578816b8 100644 --- a/crates/rpc/rpc-server-types/src/lib.rs +++ b/crates/rpc/rpc-server-types/src/lib.rs @@ -10,6 +10,9 @@ /// Common RPC constants. pub mod constants; +pub mod result; mod module; pub use module::{RethRpcModule, RpcModuleSelection}; + +pub use result::ToRpcResult; diff --git a/crates/rpc/rpc-eth-api/src/result.rs b/crates/rpc/rpc-server-types/src/result.rs similarity index 80% rename from crates/rpc/rpc-eth-api/src/result.rs rename to crates/rpc/rpc-server-types/src/result.rs index ac55bb3fefea..252c78f241b2 100644 --- a/crates/rpc/rpc-eth-api/src/result.rs +++ b/crates/rpc/rpc-server-types/src/result.rs @@ -2,7 +2,7 @@ use std::fmt::Display; -use jsonrpsee::core::RpcResult; +use jsonrpsee_core::RpcResult; use reth_rpc_types::engine::PayloadError; /// Helper trait to easily convert various `Result` types into [`RpcResult`] @@ -22,14 +22,14 @@ pub trait ToRpcResult: Sized { M: Into; /// Converts this type into an [`RpcResult`] with the - /// [`jsonrpsee::types::error::INTERNAL_ERROR_CODE` and the given message. + /// [`jsonrpsee_types::error::INTERNAL_ERROR_CODE`] and the given message. fn map_internal_err(self, op: F) -> RpcResult where F: FnOnce(Err) -> M, M: Into; /// Converts this type into an [`RpcResult`] with the - /// [`jsonrpsee::types::error::INTERNAL_ERROR_CODE`] and given message and data. + /// [`jsonrpsee_types::error::INTERNAL_ERROR_CODE`] and given message and data. fn map_internal_err_with_data<'a, F, M>(self, op: F) -> RpcResult where F: FnOnce(Err) -> (M, &'a [u8]), @@ -47,7 +47,7 @@ macro_rules! impl_to_rpc_result { ($err:ty) => { impl ToRpcResult for Result { #[inline] - fn map_rpc_err<'a, F, M>(self, op: F) -> jsonrpsee::core::RpcResult + fn map_rpc_err<'a, F, M>(self, op: F) -> jsonrpsee_core::RpcResult where F: FnOnce($err) -> (i32, M, Option<&'a [u8]>), M: Into, @@ -62,7 +62,7 @@ macro_rules! impl_to_rpc_result { } #[inline] - fn map_internal_err<'a, F, M>(self, op: F) -> jsonrpsee::core::RpcResult + fn map_internal_err<'a, F, M>(self, op: F) -> jsonrpsee_core::RpcResult where F: FnOnce($err) -> M, M: Into, @@ -71,7 +71,7 @@ macro_rules! impl_to_rpc_result { } #[inline] - fn map_internal_err_with_data<'a, F, M>(self, op: F) -> jsonrpsee::core::RpcResult + fn map_internal_err_with_data<'a, F, M>(self, op: F) -> jsonrpsee_core::RpcResult where F: FnOnce($err) -> (M, &'a [u8]), M: Into, @@ -86,7 +86,7 @@ macro_rules! impl_to_rpc_result { } #[inline] - fn with_message(self, msg: &str) -> jsonrpsee::core::RpcResult { + fn with_message(self, msg: &str) -> jsonrpsee_core::RpcResult { match self { Ok(t) => Ok(t), Err(err) => { @@ -107,28 +107,28 @@ impl_to_rpc_result!(reth_network_api::NetworkError); /// Constructs an invalid params JSON-RPC error. pub fn invalid_params_rpc_err( msg: impl Into, -) -> jsonrpsee::types::error::ErrorObject<'static> { - rpc_err(jsonrpsee::types::error::INVALID_PARAMS_CODE, msg, None) +) -> jsonrpsee_types::error::ErrorObject<'static> { + rpc_err(jsonrpsee_types::error::INVALID_PARAMS_CODE, msg, None) } /// Constructs an internal JSON-RPC error. -pub fn internal_rpc_err(msg: impl Into) -> jsonrpsee::types::error::ErrorObject<'static> { - rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, None) +pub fn internal_rpc_err(msg: impl Into) -> jsonrpsee_types::error::ErrorObject<'static> { + rpc_err(jsonrpsee_types::error::INTERNAL_ERROR_CODE, msg, None) } /// Constructs an internal JSON-RPC error with data pub fn internal_rpc_err_with_data( msg: impl Into, data: &[u8], -) -> jsonrpsee::types::error::ErrorObject<'static> { - rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, Some(data)) +) -> jsonrpsee_types::error::ErrorObject<'static> { + rpc_err(jsonrpsee_types::error::INTERNAL_ERROR_CODE, msg, Some(data)) } /// Constructs an internal JSON-RPC error with code and message pub fn rpc_error_with_code( code: i32, msg: impl Into, -) -> jsonrpsee::types::error::ErrorObject<'static> { +) -> jsonrpsee_types::error::ErrorObject<'static> { rpc_err(code, msg, None) } @@ -137,12 +137,12 @@ pub fn rpc_err( code: i32, msg: impl Into, data: Option<&[u8]>, -) -> jsonrpsee::types::error::ErrorObject<'static> { - jsonrpsee::types::error::ErrorObject::owned( +) -> jsonrpsee_types::error::ErrorObject<'static> { + jsonrpsee_types::error::ErrorObject::owned( code, msg.into(), data.map(|data| { - jsonrpsee::core::to_json_raw_value(&reth_primitives::hex::encode_prefixed(data)) + jsonrpsee_core::to_json_raw_value(&reth_primitives::hex::encode_prefixed(data)) .expect("serializing String can't fail") }), ) diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index 648b8b24f2ca..7a6e081a24ff 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -29,11 +29,6 @@ serde = { workspace = true, features = ["derive"] } serde_json.workspace = true jsonrpsee-types = { workspace = true, optional = true } -[features] -default = ["jsonrpsee-types"] -arbitrary = ["alloy-primitives/arbitrary", "alloy-rpc-types/arbitrary"] - - [dev-dependencies] # misc alloy-primitives = { workspace = true, features = ["rand", "rlp", "serde", "arbitrary"] } @@ -44,3 +39,6 @@ rand.workspace = true similar-asserts.workspace = true bytes.workspace = true +[features] +default = ["jsonrpsee-types"] +arbitrary = ["alloy-primitives/arbitrary", "alloy-rpc-types/arbitrary"] \ No newline at end of file diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 7082a75fcc8e..118a9a82190d 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -29,8 +29,13 @@ reth-consensus-common.workspace = true reth-rpc-types-compat.workspace = true revm-inspectors = { workspace = true, features = ["js-tracer"] } reth-network-peers.workspace = true +reth-evm.workspace = true +reth-rpc-eth-types.workspace = true +reth-rpc-server-types.workspace = true +reth-evm-optimism = { workspace = true, optional = true } # eth +alloy-dyn-abi.workspace = true alloy-rlp.workspace = true alloy-primitives.workspace = true alloy-genesis.workspace = true @@ -40,6 +45,7 @@ revm = { workspace = true, features = [ "optional_no_base_fee", ] } revm-primitives = { workspace = true, features = ["serde"] } +secp256k1.workspace = true # rpc jsonrpsee.workspace = true @@ -47,21 +53,29 @@ http.workspace = true http-body.workspace = true hyper.workspace = true jsonwebtoken.workspace = true +serde_json.workspace = true +jsonrpsee-types = { workspace = true, optional = true } # async async-trait.workspace = true tokio = { workspace = true, features = ["sync"] } +tokio-stream.workspace = true tower.workspace = true pin-project.workspace = true +parking_lot.workspace = true # misc tracing.workspace = true tracing-futures = "0.2" futures.workspace = true +rand.workspace = true +serde.workspace = true +thiserror.workspace = true [dev-dependencies] reth-evm-ethereum.workspace = true reth-testing-utils.workspace = true +jsonrpsee-types.workspace = true jsonrpsee = { workspace = true, features = ["client"] } assert_matches.workspace = true @@ -75,4 +89,7 @@ optimism = [ "reth-rpc-api/optimism", "reth-rpc-eth-api/optimism", "reth-revm/optimism", + "jsonrpsee-types", + "reth-evm-optimism", + "reth-rpc-eth-types/optimism", ] diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 0e1e13a0c013..772bc77e3f76 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -8,13 +8,12 @@ use reth_chainspec::ChainSpec; use reth_network_api::{NetworkInfo, PeerKind, Peers}; use reth_network_peers::{AnyNode, NodeRecord}; use reth_rpc_api::AdminApiServer; +use reth_rpc_server_types::ToRpcResult; use reth_rpc_types::{ admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo}, PeerEthProtocolInfo, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, }; -use crate::result::ToRpcResult; - /// `admin` API implementation. /// /// This type provides the functionality for handling `admin` related requests. diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 8fd7c582a115..5a098feb7eec 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -13,12 +13,9 @@ use reth_provider::{ }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; -use reth_rpc_eth_api::{ - result::internal_rpc_err, - revm_utils::prepare_call_env, - servers::{EthApiSpec, EthTransactions, TraceExt}, - EthApiError, EthResult, StateCacheDb, ToRpcResult, -}; +use reth_rpc_eth_api::helpers::{EthApiSpec, EthTransactions, TraceExt}; +use reth_rpc_eth_types::{revm_utils::prepare_call_env, EthApiError, EthResult, StateCacheDb}; +use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use reth_rpc_types::{ state::EvmOverrides, trace::geth::{ diff --git a/crates/rpc/rpc-eth-api/src/api/servers/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs similarity index 97% rename from crates/rpc/rpc-eth-api/src/api/servers/bundle.rs rename to crates/rpc/rpc/src/eth/bundle.rs index b272d72a9548..b72d548b1fbc 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -18,10 +18,12 @@ use revm::{ }; use revm_primitives::{EnvKzgSettings, EnvWithHandlerCfg, MAX_BLOB_GAS_PER_BLOCK}; -use crate::{ - servers::{Call, EthTransactions, LoadPendingBlock}, - utils::recover_raw_transaction, - EthApiError, EthCallBundleApiServer, EthResult, RpcInvalidTransactionError, +use reth_rpc_eth_api::{ + helpers::{Call, EthTransactions, LoadPendingBlock}, + EthCallBundleApiServer, +}; +use reth_rpc_eth_types::{ + utils::recover_raw_transaction, EthApiError, EthResult, RpcInvalidTransactionError, }; /// `Eth` bundle implementation. diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs new file mode 100644 index 000000000000..f2eb81851f9b --- /dev/null +++ b/crates/rpc/rpc/src/eth/core.rs @@ -0,0 +1,601 @@ +//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`](crate::EthApi) trait +//! Handles RPC requests for the `eth_` namespace. + +use std::sync::Arc; + +use reth_primitives::{BlockNumberOrTag, U256}; +use reth_provider::{BlockReaderIdExt, ChainSpecProvider}; +use reth_rpc_eth_api::{ + helpers::{EthSigner, SpawnBlocking}, + RawTransactionForwarder, +}; +use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock}; +use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor}; +use tokio::sync::Mutex; + +use crate::eth::DevSigner; + +/// `Eth` API implementation. +/// +/// This type provides the functionality for handling `eth_` related requests. +/// These are implemented two-fold: Core functionality is implemented as +/// [`EthApiSpec`](reth_rpc_eth_api::helpers::EthApiSpec) trait. Additionally, the required server +/// implementations (e.g. [`EthApiServer`](reth_rpc_eth_api::EthApiServer)) are implemented +/// separately in submodules. The rpc handler implementation can then delegate to the main impls. +/// This way [`EthApi`] is not limited to [`jsonrpsee`] and can be used standalone or in other +/// network handlers (for example ipc). +pub struct EthApi { + /// All nested fields bundled together. + pub(super) inner: Arc>, +} + +impl EthApi { + /// Sets a forwarder for `eth_sendRawTransaction` + /// + /// Note: this might be removed in the future in favor of a more generic approach. + pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { + self.inner.raw_transaction_forwarder.write().replace(forwarder); + } +} + +impl EthApi +where + Provider: BlockReaderIdExt + ChainSpecProvider, +{ + /// Creates a new, shareable instance using the default tokio task spawner. + #[allow(clippy::too_many_arguments)] + pub fn new( + provider: Provider, + pool: Pool, + network: Network, + eth_cache: EthStateCache, + gas_oracle: GasPriceOracle, + gas_cap: impl Into, + blocking_task_pool: BlockingTaskPool, + fee_history_cache: FeeHistoryCache, + evm_config: EvmConfig, + raw_transaction_forwarder: Option>, + ) -> Self { + Self::with_spawner( + provider, + pool, + network, + eth_cache, + gas_oracle, + gas_cap.into().into(), + Box::::default(), + blocking_task_pool, + fee_history_cache, + evm_config, + raw_transaction_forwarder, + ) + } + + /// Creates a new, shareable instance. + #[allow(clippy::too_many_arguments)] + pub fn with_spawner( + provider: Provider, + pool: Pool, + network: Network, + eth_cache: EthStateCache, + gas_oracle: GasPriceOracle, + gas_cap: u64, + task_spawner: Box, + blocking_task_pool: BlockingTaskPool, + fee_history_cache: FeeHistoryCache, + evm_config: EvmConfig, + raw_transaction_forwarder: Option>, + ) -> Self { + // get the block number of the latest block + let latest_block = provider + .header_by_number_or_tag(BlockNumberOrTag::Latest) + .ok() + .flatten() + .map(|header| header.number) + .unwrap_or_default(); + + let inner = EthApiInner { + provider, + pool, + network, + signers: parking_lot::RwLock::new(Default::default()), + eth_cache, + gas_oracle, + gas_cap, + starting_block: U256::from(latest_block), + task_spawner, + pending_block: Default::default(), + blocking_task_pool, + fee_history_cache, + evm_config, + raw_transaction_forwarder: parking_lot::RwLock::new(raw_transaction_forwarder), + }; + + Self { inner: Arc::new(inner) } + } + + /// Returns the state cache frontend + pub fn cache(&self) -> &EthStateCache { + &self.inner.eth_cache + } + + /// Returns the gas oracle frontend + pub fn gas_oracle(&self) -> &GasPriceOracle { + &self.inner.gas_oracle + } + + /// Returns the configured gas limit cap for `eth_call` and tracing related calls + pub fn gas_cap(&self) -> u64 { + self.inner.gas_cap + } + + /// Returns the inner `Provider` + pub fn provider(&self) -> &Provider { + &self.inner.provider + } + + /// Returns the inner `Network` + pub fn network(&self) -> &Network { + &self.inner.network + } + + /// Returns the inner `Pool` + pub fn pool(&self) -> &Pool { + &self.inner.pool + } + + /// Returns fee history cache + pub fn fee_history_cache(&self) -> &FeeHistoryCache { + &self.inner.fee_history_cache + } +} + +impl std::fmt::Debug + for EthApi +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EthApi").finish_non_exhaustive() + } +} + +impl Clone for EthApi { + fn clone(&self) -> Self { + Self { inner: Arc::clone(&self.inner) } + } +} + +impl SpawnBlocking + for EthApi +where + Self: Clone + Send + Sync + 'static, +{ + #[inline] + fn io_task_spawner(&self) -> impl reth_tasks::TaskSpawner { + self.inner.task_spawner() + } + + #[inline] + fn tracing_task_pool(&self) -> &reth_tasks::pool::BlockingTaskPool { + self.inner.blocking_task_pool() + } +} + +impl EthApi { + /// Generates 20 random developer accounts. + /// Used in DEV mode. + pub fn with_dev_accounts(&self) { + let mut signers = self.inner.signers.write(); + *signers = DevSigner::random_signers(20); + } +} + +/// Container type `EthApi` +#[allow(missing_debug_implementations)] +pub struct EthApiInner { + /// The transaction pool. + pool: Pool, + /// The provider that can interact with the chain. + provider: Provider, + /// An interface to interact with the network + network: Network, + /// All configured Signers + signers: parking_lot::RwLock>>, + /// The async cache frontend for eth related data + eth_cache: EthStateCache, + /// The async gas oracle frontend for gas price suggestions + gas_oracle: GasPriceOracle, + /// Maximum gas limit for `eth_call` and call tracing RPC methods. + gas_cap: u64, + /// The block number at which the node started + starting_block: U256, + /// The type that can spawn tasks which would otherwise block. + task_spawner: Box, + /// Cached pending block if any + pending_block: Mutex>, + /// A pool dedicated to CPU heavy blocking tasks. + blocking_task_pool: BlockingTaskPool, + /// Cache for block fees history + fee_history_cache: FeeHistoryCache, + /// The type that defines how to configure the EVM + evm_config: EvmConfig, + /// Allows forwarding received raw transactions + raw_transaction_forwarder: parking_lot::RwLock>>, +} + +impl EthApiInner { + /// Returns a handle to data on disk. + #[inline] + pub const fn provider(&self) -> &Provider { + &self.provider + } + + /// Returns a handle to data in memory. + #[inline] + pub const fn cache(&self) -> &EthStateCache { + &self.eth_cache + } + + /// Returns a handle to the pending block. + #[inline] + pub const fn pending_block(&self) -> &Mutex> { + &self.pending_block + } + + /// Returns a handle to the task spawner. + #[inline] + pub const fn task_spawner(&self) -> &dyn TaskSpawner { + &*self.task_spawner + } + + /// Returns a handle to the blocking thread pool. + #[inline] + pub const fn blocking_task_pool(&self) -> &BlockingTaskPool { + &self.blocking_task_pool + } + + /// Returns a handle to the EVM config. + #[inline] + pub const fn evm_config(&self) -> &EvmConfig { + &self.evm_config + } + + /// Returns a handle to the transaction pool. + #[inline] + pub const fn pool(&self) -> &Pool { + &self.pool + } + + /// Returns a handle to the transaction forwarder. + #[inline] + pub fn raw_tx_forwarder(&self) -> Option> { + self.raw_transaction_forwarder.read().clone() + } + + /// Returns the gas cap. + #[inline] + pub const fn gas_cap(&self) -> u64 { + self.gas_cap + } + + /// Returns a handle to the gas oracle. + #[inline] + pub const fn gas_oracle(&self) -> &GasPriceOracle { + &self.gas_oracle + } + + /// Returns a handle to the fee history cache. + #[inline] + pub const fn fee_history_cache(&self) -> &FeeHistoryCache { + &self.fee_history_cache + } + + /// Returns a handle to the signers. + #[inline] + pub const fn signers(&self) -> &parking_lot::RwLock>> { + &self.signers + } + + /// Returns the starting block. + #[inline] + pub const fn starting_block(&self) -> U256 { + self.starting_block + } +} + +#[cfg(test)] +mod tests { + use jsonrpsee_types::error::INVALID_PARAMS_CODE; + use reth_chainspec::BaseFeeParams; + use reth_evm_ethereum::EthEvmConfig; + use reth_network_api::noop::NoopNetwork; + use reth_primitives::{ + constants::ETHEREUM_BLOCK_GAS_LIMIT, Block, BlockNumberOrTag, Header, TransactionSigned, + B256, U64, + }; + use reth_provider::{ + test_utils::{MockEthProvider, NoopProvider}, + BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory, + }; + use reth_rpc_eth_api::EthApiServer; + use reth_rpc_eth_types::{ + EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, + }; + use reth_rpc_types::FeeHistory; + use reth_tasks::pool::BlockingTaskPool; + use reth_testing_utils::{generators, generators::Rng}; + use reth_transaction_pool::test_utils::{testing_pool, TestPool}; + + use crate::EthApi; + + fn build_test_eth_api< + P: BlockReaderIdExt + + BlockReader + + ChainSpecProvider + + EvmEnvProvider + + StateProviderFactory + + Unpin + + Clone + + 'static, + >( + provider: P, + ) -> EthApi { + let evm_config = EthEvmConfig::default(); + let cache = EthStateCache::spawn(provider.clone(), Default::default(), evm_config); + let fee_history_cache = + FeeHistoryCache::new(cache.clone(), FeeHistoryCacheConfig::default()); + + EthApi::new( + provider.clone(), + testing_pool(), + NoopNetwork::default(), + cache.clone(), + GasPriceOracle::new(provider, Default::default(), cache), + ETHEREUM_BLOCK_GAS_LIMIT, + BlockingTaskPool::build().expect("failed to build tracing pool"), + fee_history_cache, + evm_config, + None, + ) + } + + // Function to prepare the EthApi with mock data + fn prepare_eth_api( + newest_block: u64, + mut oldest_block: Option, + block_count: u64, + mock_provider: MockEthProvider, + ) -> (EthApi, Vec, Vec) { + let mut rng = generators::rng(); + + // Build mock data + let mut gas_used_ratios = Vec::new(); + let mut base_fees_per_gas = Vec::new(); + let mut last_header = None; + let mut parent_hash = B256::default(); + + for i in (0..block_count).rev() { + let hash = rng.gen(); + let gas_limit: u64 = rng.gen(); + let gas_used: u64 = rng.gen(); + // Note: Generates a u32 to avoid overflows later + let base_fee_per_gas: Option = rng.gen::().then(|| rng.gen::() as u64); + + let header = Header { + number: newest_block - i, + gas_limit, + gas_used, + base_fee_per_gas, + parent_hash, + ..Default::default() + }; + last_header = Some(header.clone()); + parent_hash = hash; + + let mut transactions = vec![]; + for _ in 0..100 { + let random_fee: u128 = rng.gen(); + + if let Some(base_fee_per_gas) = header.base_fee_per_gas { + let transaction = TransactionSigned { + transaction: reth_primitives::Transaction::Eip1559( + reth_primitives::TxEip1559 { + max_priority_fee_per_gas: random_fee, + max_fee_per_gas: random_fee + base_fee_per_gas as u128, + ..Default::default() + }, + ), + ..Default::default() + }; + + transactions.push(transaction); + } else { + let transaction = TransactionSigned { + transaction: reth_primitives::Transaction::Legacy(Default::default()), + ..Default::default() + }; + + transactions.push(transaction); + } + } + + mock_provider.add_block( + hash, + Block { header: header.clone(), body: transactions, ..Default::default() }, + ); + mock_provider.add_header(hash, header); + + oldest_block.get_or_insert(hash); + gas_used_ratios.push(gas_used as f64 / gas_limit as f64); + base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default()); + } + + // Add final base fee (for the next block outside of the request) + let last_header = last_header.unwrap(); + base_fees_per_gas.push(BaseFeeParams::ethereum().next_block_base_fee( + last_header.gas_used as u128, + last_header.gas_limit as u128, + last_header.base_fee_per_gas.unwrap_or_default() as u128, + )); + + let eth_api = build_test_eth_api(mock_provider); + + (eth_api, base_fees_per_gas, gas_used_ratios) + } + + /// Invalid block range + #[tokio::test] + async fn test_fee_history_empty() { + let response = as EthApiServer>::fee_history( + &build_test_eth_api(NoopProvider::default()), + U64::from(1), + BlockNumberOrTag::Latest, + None, + ) + .await; + assert!(response.is_err()); + let error_object = response.unwrap_err(); + assert_eq!(error_object.code(), INVALID_PARAMS_CODE); + } + + #[tokio::test] + /// Invalid block range (request is before genesis) + async fn test_fee_history_invalid_block_range_before_genesis() { + let block_count = 10; + let newest_block = 1337; + let oldest_block = None; + + let (eth_api, _, _) = + prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); + + let response = as EthApiServer>::fee_history( + ð_api, + U64::from(newest_block + 1), + newest_block.into(), + Some(vec![10.0]), + ) + .await; + + assert!(response.is_err()); + let error_object = response.unwrap_err(); + assert_eq!(error_object.code(), INVALID_PARAMS_CODE); + } + + #[tokio::test] + /// Invalid block range (request is in the future) + async fn test_fee_history_invalid_block_range_in_future() { + let block_count = 10; + let newest_block = 1337; + let oldest_block = None; + + let (eth_api, _, _) = + prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); + + let response = as EthApiServer>::fee_history( + ð_api, + U64::from(1), + (newest_block + 1000).into(), + Some(vec![10.0]), + ) + .await; + + assert!(response.is_err()); + let error_object = response.unwrap_err(); + assert_eq!(error_object.code(), INVALID_PARAMS_CODE); + } + + #[tokio::test] + /// Requesting no block should result in a default response + async fn test_fee_history_no_block_requested() { + let block_count = 10; + let newest_block = 1337; + let oldest_block = None; + + let (eth_api, _, _) = + prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); + + let response = as EthApiServer>::fee_history( + ð_api, + U64::from(0), + newest_block.into(), + None, + ) + .await + .unwrap(); + assert_eq!( + response, + FeeHistory::default(), + "none: requesting no block should yield a default response" + ); + } + + #[tokio::test] + /// Requesting a single block should return 1 block (+ base fee for the next block over) + async fn test_fee_history_single_block() { + let block_count = 10; + let newest_block = 1337; + let oldest_block = None; + + let (eth_api, base_fees_per_gas, gas_used_ratios) = + prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); + + let fee_history = + eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap(); + assert_eq!( + fee_history.base_fee_per_gas, + &base_fees_per_gas[base_fees_per_gas.len() - 2..], + "one: base fee per gas is incorrect" + ); + assert_eq!( + fee_history.base_fee_per_gas.len(), + 2, + "one: should return base fee of the next block as well" + ); + assert_eq!( + &fee_history.gas_used_ratio, + &gas_used_ratios[gas_used_ratios.len() - 1..], + "one: gas used ratio is incorrect" + ); + assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect"); + assert!( + fee_history.reward.is_none(), + "one: no percentiles were requested, so there should be no rewards result" + ); + } + + /// Requesting all blocks should be ok + #[tokio::test] + async fn test_fee_history_all_blocks() { + let block_count = 10; + let newest_block = 1337; + let oldest_block = None; + + let (eth_api, base_fees_per_gas, gas_used_ratios) = + prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); + + let fee_history = + eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap(); + + assert_eq!( + &fee_history.base_fee_per_gas, &base_fees_per_gas, + "all: base fee per gas is incorrect" + ); + assert_eq!( + fee_history.base_fee_per_gas.len() as u64, + block_count + 1, + "all: should return base fee of the next block as well" + ); + assert_eq!( + &fee_history.gas_used_ratio, &gas_used_ratios, + "all: gas used ratio is incorrect" + ); + assert_eq!( + fee_history.oldest_block, + newest_block - block_count + 1, + "all: oldest block is incorrect" + ); + assert!( + fee_history.reward.is_none(), + "all: no percentiles were requested, so there should be no rewards result" + ); + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/filter.rs b/crates/rpc/rpc/src/eth/filter.rs similarity index 93% rename from crates/rpc/rpc-eth-api/src/api/servers/filter.rs rename to crates/rpc/rpc/src/eth/filter.rs index 8f2ff382c544..1a2e55a5257c 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -14,6 +14,12 @@ use jsonrpsee::{core::RpcResult, server::IdProvider}; use reth_chainspec::ChainInfo; use reth_primitives::{IntoRecoveredTransaction, TxHash}; use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider, ProviderError}; +use reth_rpc_eth_api::EthFilterApiServer; +use reth_rpc_eth_types::{ + logs_utils::{self, append_matching_block_logs}, + EthApiError, EthFilterError, EthStateCache, EthSubscriptionIdProvider, +}; +use reth_rpc_server_types::ToRpcResult; use reth_rpc_types::{ BlockNumHash, Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log, PendingTransactionFilterKind, @@ -26,12 +32,6 @@ use tokio::{ }; use tracing::trace; -use crate::{ - logs_utils::{self, append_matching_block_logs}, - result::rpc_error_with_code, - EthApiError, EthFilterApiServer, EthStateCache, EthSubscriptionIdProvider, ToRpcResult, -}; - /// The maximum number of headers we read at once when handling a range filter. const MAX_HEADERS_RANGE: u64 = 1_000; // with ~530bytes per header this is ~500kb @@ -706,56 +706,6 @@ impl Iterator for BlockRangeInclusiveIter { } } -/// Errors that can occur in the handler implementation -#[derive(Debug, thiserror::Error)] -pub enum EthFilterError { - /// Filter not found. - #[error("filter not found")] - FilterNotFound(FilterId), - /// Invalid block range. - #[error("invalid block range params")] - InvalidBlockRangeParams, - /// Query scope is too broad. - #[error("query exceeds max block range {0}")] - QueryExceedsMaxBlocks(u64), - /// Query result is too large. - #[error("query exceeds max results {0}")] - QueryExceedsMaxResults(usize), - /// Error serving request in `eth_` namespace. - #[error(transparent)] - EthAPIError(#[from] EthApiError), - /// Error thrown when a spawned task failed to deliver a response. - #[error("internal filter error")] - InternalError, -} - -// convert the error -impl From for jsonrpsee::types::error::ErrorObject<'static> { - fn from(err: EthFilterError) -> Self { - match err { - EthFilterError::FilterNotFound(_) => rpc_error_with_code( - jsonrpsee::types::error::INVALID_PARAMS_CODE, - "filter not found", - ), - err @ EthFilterError::InternalError => { - rpc_error_with_code(jsonrpsee::types::error::INTERNAL_ERROR_CODE, err.to_string()) - } - EthFilterError::EthAPIError(err) => err.into(), - err @ EthFilterError::InvalidBlockRangeParams | - err @ EthFilterError::QueryExceedsMaxBlocks(_) | - err @ EthFilterError::QueryExceedsMaxResults(_) => { - rpc_error_with_code(jsonrpsee::types::error::INVALID_PARAMS_CODE, err.to_string()) - } - } - } -} - -impl From for EthFilterError { - fn from(err: ProviderError) -> Self { - Self::EthAPIError(err.into()) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs b/crates/rpc/rpc/src/eth/helpers/block.rs similarity index 83% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs rename to crates/rpc/rpc/src/eth/helpers/block.rs index 2d583161f921..2ce6c7ed2e93 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs +++ b/crates/rpc/rpc/src/eth/helpers/block.rs @@ -1,11 +1,10 @@ //! Contains RPC handler implementations specific to blocks. use reth_provider::{BlockReaderIdExt, HeaderProvider}; +use reth_rpc_eth_api::helpers::{EthBlocks, LoadBlock, LoadPendingBlock, SpawnBlocking}; +use reth_rpc_eth_types::EthStateCache; -use crate::{ - servers::{EthBlocks, LoadBlock, LoadPendingBlock, SpawnBlocking}, - EthApi, EthStateCache, -}; +use crate::EthApi; impl EthBlocks for EthApi where diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs b/crates/rpc/rpc/src/eth/helpers/blocking_task.rs similarity index 100% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs rename to crates/rpc/rpc/src/eth/helpers/blocking_task.rs diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs b/crates/rpc/rpc/src/eth/helpers/call.rs similarity index 84% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs rename to crates/rpc/rpc/src/eth/helpers/call.rs index 151cfc91f44b..c442c46b4b80 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs +++ b/crates/rpc/rpc/src/eth/helpers/call.rs @@ -1,11 +1,9 @@ //! Contains RPC handler implementations specific to endpoints that call/execute within evm. use reth_evm::ConfigureEvm; +use reth_rpc_eth_api::helpers::{Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking}; -use crate::{ - servers::{Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking}, - EthApi, -}; +use crate::EthApi; impl EthCall for EthApi where Self: Call + LoadPendingBlock diff --git a/crates/rpc/rpc/src/eth/helpers/fee.rs b/crates/rpc/rpc/src/eth/helpers/fee.rs new file mode 100644 index 000000000000..7d795e52f38b --- /dev/null +++ b/crates/rpc/rpc/src/eth/helpers/fee.rs @@ -0,0 +1,346 @@ +//! Loads fee history from database. Helper trait for `eth_` fee and transaction RPC methods. + +use futures::Future; +use reth_primitives::U256; +use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider}; +use reth_rpc_types::{BlockNumberOrTag, FeeHistory}; +use tracing::debug; + +use crate::{ + fee_history::calculate_reward_percentiles_for_block, api::LoadBlock, EthApiError, + EthResult, EthStateCache, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, + RpcInvalidTransactionError, +}; + +/// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the +/// `eth_` namespace. +pub trait EthFees: LoadFee { + /// Returns a suggestion for a gas price for legacy transactions. + /// + /// See also: + fn gas_price(&self) -> impl Future> + Send + where + Self: LoadBlock, + { + LoadFee::gas_price(self) + } + + /// Returns a suggestion for a base fee for blob transactions. + fn blob_base_fee(&self) -> impl Future> + Send + where + Self: LoadBlock, + { + LoadFee::blob_base_fee(self) + } + + /// Returns a suggestion for the priority fee (the tip) + fn suggested_priority_fee(&self) -> impl Future> + Send + where + Self: 'static, + { + LoadFee::suggested_priority_fee(self) + } + + /// Reports the fee history, for the given amount of blocks, up until the given newest block. + /// + /// If `reward_percentiles` are provided the [`FeeHistory`] will include the _approximated_ + /// rewards for the requested range. + fn fee_history( + &self, + mut block_count: u64, + newest_block: BlockNumberOrTag, + reward_percentiles: Option>, + ) -> impl Future> + Send { + async move { + if block_count == 0 { + return Ok(FeeHistory::default()) + } + + // See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225 + let max_fee_history = if reward_percentiles.is_none() { + self.gas_oracle().config().max_header_history + } else { + self.gas_oracle().config().max_block_history + }; + + if block_count > max_fee_history { + debug!( + requested = block_count, + truncated = max_fee_history, + "Sanitizing fee history block count" + ); + block_count = max_fee_history + } + + let Some(end_block) = + LoadFee::provider(self).block_number_for_id(newest_block.into())? + else { + return Err(EthApiError::UnknownBlockNumber) + }; + + // need to add 1 to the end block to get the correct (inclusive) range + let end_block_plus = end_block + 1; + // Ensure that we would not be querying outside of genesis + if end_block_plus < block_count { + block_count = end_block_plus; + } + + // If reward percentiles were specified, we + // need to validate that they are monotonically + // increasing and 0 <= p <= 100 + // Note: The types used ensure that the percentiles are never < 0 + if let Some(percentiles) = &reward_percentiles { + if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) { + return Err(EthApiError::InvalidRewardPercentiles) + } + } + + // Fetch the headers and ensure we got all of them + // + // Treat a request for 1 block as a request for `newest_block..=newest_block`, + // otherwise `newest_block - 2 + // NOTE: We ensured that block count is capped + let start_block = end_block_plus - block_count; + + // Collect base fees, gas usage ratios and (optionally) reward percentile data + let mut base_fee_per_gas: Vec = Vec::new(); + let mut gas_used_ratio: Vec = Vec::new(); + + let mut base_fee_per_blob_gas: Vec = Vec::new(); + let mut blob_gas_used_ratio: Vec = Vec::new(); + + let mut rewards: Vec> = Vec::new(); + + // Check if the requested range is within the cache bounds + let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await; + + if let Some(fee_entries) = fee_entries { + if fee_entries.len() != block_count as usize { + return Err(EthApiError::InvalidBlockRange) + } + + for entry in &fee_entries { + base_fee_per_gas.push(entry.base_fee_per_gas as u128); + gas_used_ratio.push(entry.gas_used_ratio); + base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default()); + blob_gas_used_ratio.push(entry.blob_gas_used_ratio); + + if let Some(percentiles) = &reward_percentiles { + let mut block_rewards = Vec::with_capacity(percentiles.len()); + for &percentile in percentiles { + block_rewards.push(self.approximate_percentile(entry, percentile)); + } + rewards.push(block_rewards); + } + } + let last_entry = fee_entries.last().expect("is not empty"); + + // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the + // next block + base_fee_per_gas + .push(last_entry.next_block_base_fee(&LoadFee::provider(self).chain_spec()) + as u128); + + base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); + } else { + // read the requested header range + let headers = LoadFee::provider(self).sealed_headers_range(start_block..=end_block)?; + if headers.len() != block_count as usize { + return Err(EthApiError::InvalidBlockRange) + } + + for header in &headers { + base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default() as u128); + gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64); + base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default()); + blob_gas_used_ratio.push( + header.blob_gas_used.unwrap_or_default() as f64 / + reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64, + ); + + // Percentiles were specified, so we need to collect reward percentile ino + if let Some(percentiles) = &reward_percentiles { + let (transactions, receipts) = LoadFee::cache(self) + .get_transactions_and_receipts(header.hash()) + .await? + .ok_or(EthApiError::InvalidBlockRange)?; + rewards.push( + calculate_reward_percentiles_for_block( + percentiles, + header.gas_used, + header.base_fee_per_gas.unwrap_or_default(), + &transactions, + &receipts, + ) + .unwrap_or_default(), + ); + } + } + + // The spec states that `base_fee_per_gas` "[..] includes the next block after the + // newest of the returned range, because this value can be derived from the + // newest block" + // + // The unwrap is safe since we checked earlier that we got at least 1 header. + let last_header = headers.last().expect("is present"); + base_fee_per_gas.push( + LoadFee::provider(self).chain_spec().base_fee_params_at_timestamp(last_header.timestamp).next_block_base_fee( + last_header.gas_used as u128, + last_header.gas_limit as u128, + last_header.base_fee_per_gas.unwrap_or_default() as u128, + )); + + // Same goes for the `base_fee_per_blob_gas`: + // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. + base_fee_per_blob_gas + .push(last_header.next_block_blob_fee().unwrap_or_default()); + }; + + Ok(FeeHistory { + base_fee_per_gas, + gas_used_ratio, + base_fee_per_blob_gas, + blob_gas_used_ratio, + oldest_block: start_block, + reward: reward_percentiles.map(|_| rewards), + }) + } + } + + /// Approximates reward at a given percentile for a specific block + /// Based on the configured resolution + fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 { + let resolution = self.fee_history_cache().resolution(); + let rounded_percentile = + (requested_percentile * resolution as f64).round() / resolution as f64; + let clamped_percentile = rounded_percentile.clamp(0.0, 100.0); + + // Calculate the index in the precomputed rewards array + let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize; + // Fetch the reward from the FeeHistoryEntry + entry.rewards.get(index).cloned().unwrap_or_default() + } +} + +/// Loads fee from database. +/// +/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` fees RPC methods. +pub trait LoadFee: LoadBlock { + // Returns a handle for reading data from disk. + /// + /// Data access in default (L1) trait method implementations. + fn provider(&self) -> impl BlockIdReader + HeaderProvider + ChainSpecProvider; + + /// Returns a handle for reading data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn cache(&self) -> &EthStateCache; + + /// Returns a handle for reading gas price. + /// + /// Data access in default (L1) trait method implementations. + fn gas_oracle(&self) -> &GasPriceOracle; + + /// Returns a handle for reading fee history data from memory. + /// + /// Data access in default (L1) trait method implementations. + fn fee_history_cache(&self) -> &FeeHistoryCache; + + /// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy + /// transactions. + fn legacy_gas_price( + &self, + gas_price: Option, + ) -> impl Future> + Send { + async move { + match gas_price { + Some(gas_price) => Ok(gas_price), + None => { + // fetch a suggested gas price + self.gas_price().await + } + } + } + } + + /// Returns the EIP-1559 fees if they are set, otherwise fetches a suggested gas price for + /// EIP-1559 transactions. + /// + /// Returns (`max_fee`, `priority_fee`) + fn eip1559_fees( + &self, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + ) -> impl Future> + Send { + async move { + let max_fee_per_gas = match max_fee_per_gas { + Some(max_fee_per_gas) => max_fee_per_gas, + None => { + // fetch pending base fee + let base_fee = self + .block(BlockNumberOrTag::Pending) + .await? + .ok_or(EthApiError::UnknownBlockNumber)? + .base_fee_per_gas + .ok_or_else(|| { + EthApiError::InvalidTransaction( + RpcInvalidTransactionError::TxTypeNotSupported, + ) + })?; + U256::from(base_fee) + } + }; + + let max_priority_fee_per_gas = match max_priority_fee_per_gas { + Some(max_priority_fee_per_gas) => max_priority_fee_per_gas, + None => self.suggested_priority_fee().await?, + }; + Ok((max_fee_per_gas, max_priority_fee_per_gas)) + } + } + + /// Returns the EIP-4844 blob fee if it is set, otherwise fetches a blob fee. + fn eip4844_blob_fee( + &self, + blob_fee: Option, + ) -> impl Future> + Send { + async move { + match blob_fee { + Some(blob_fee) => Ok(blob_fee), + None => self.blob_base_fee().await, + } + } + } + + /// Returns a suggestion for a gas price for legacy transactions. + /// + /// See also: + fn gas_price(&self) -> impl Future> + Send { + let header = self.block(BlockNumberOrTag::Latest); + let suggested_tip = self.suggested_priority_fee(); + async move { + let (header, suggested_tip) = futures::try_join!(header, suggested_tip)?; + let base_fee = header.and_then(|h| h.base_fee_per_gas).unwrap_or_default(); + Ok(suggested_tip + U256::from(base_fee)) + } + } + + /// Returns a suggestion for a base fee for blob transactions. + fn blob_base_fee(&self) -> impl Future> + Send { + async move { + self.block(BlockNumberOrTag::Latest) + .await? + .and_then(|h: reth_primitives::SealedBlock| h.next_block_blob_fee()) + .ok_or(EthApiError::ExcessBlobGasNotSet) + .map(U256::from) + } + } + + /// Returns a suggestion for the priority fee (the tip) + fn suggested_priority_fee(&self) -> impl Future> + Send + where + Self: 'static, + { + self.gas_oracle().suggest_tip_cap() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs b/crates/rpc/rpc/src/eth/helpers/fees.rs similarity index 85% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs rename to crates/rpc/rpc/src/eth/helpers/fees.rs index 351ce9c18a31..7380f4ea2c20 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs +++ b/crates/rpc/rpc/src/eth/helpers/fees.rs @@ -2,10 +2,10 @@ use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider}; -use crate::{ - servers::{EthFees, LoadBlock, LoadFee}, - EthApi, EthStateCache, FeeHistoryCache, GasPriceOracle, -}; +use reth_rpc_eth_api::helpers::{EthFees, LoadBlock, LoadFee}; +use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasPriceOracle}; + +use crate::EthApi; impl EthFees for EthApi where Self: LoadFee diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs b/crates/rpc/rpc/src/eth/helpers/mod.rs similarity index 95% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs rename to crates/rpc/rpc/src/eth/helpers/mod.rs index e360df71394f..4c86e2b5fa18 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs +++ b/crates/rpc/rpc/src/eth/helpers/mod.rs @@ -2,7 +2,6 @@ //! files. pub mod signer; -pub mod traits; mod block; mod call; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs b/crates/rpc/rpc/src/eth/helpers/optimism.rs similarity index 96% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs rename to crates/rpc/rpc/src/eth/helpers/optimism.rs index 4726a00e848e..751c06463a00 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs +++ b/crates/rpc/rpc/src/eth/helpers/optimism.rs @@ -15,11 +15,11 @@ use reth_transaction_pool::TransactionPool; use revm::L1BlockInfo; use revm_primitives::{BlockEnv, ExecutionResult}; -use crate::{ - result::internal_rpc_err, - servers::{LoadPendingBlock, LoadReceipt, SpawnBlocking}, - EthApi, EthApiError, EthResult, EthStateCache, PendingBlock, ReceiptBuilder, -}; +use reth_rpc_eth_api::helpers::{LoadPendingBlock, LoadReceipt, SpawnBlocking}; +use reth_rpc_eth_types::{EthApiError, EthResult, EthStateCache, PendingBlock, ReceiptBuilder}; +use reth_rpc_server_types::result::internal_rpc_err; + +use crate::EthApi; /// L1 fee and data gas for a transaction, along with the L1 block info. #[derive(Debug, Default, Clone)] diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs similarity index 89% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs rename to crates/rpc/rpc/src/eth/helpers/pending_block.rs index 33db098da6d0..d1a47da75853 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -2,12 +2,11 @@ use reth_evm::ConfigureEvm; use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; +use reth_rpc_eth_api::helpers::{LoadPendingBlock, SpawnBlocking}; +use reth_rpc_eth_types::PendingBlock; use reth_transaction_pool::TransactionPool; -use crate::{ - servers::{LoadPendingBlock, SpawnBlocking}, - EthApi, PendingBlock, -}; +use crate::EthApi; impl LoadPendingBlock for EthApi diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs b/crates/rpc/rpc/src/eth/helpers/receipt.rs similarity index 66% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs rename to crates/rpc/rpc/src/eth/helpers/receipt.rs index 404b526e4a9a..db1fee781fd3 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs +++ b/crates/rpc/rpc/src/eth/helpers/receipt.rs @@ -1,6 +1,9 @@ //! Builds an RPC receipt response w.r.t. data layout of network. -use crate::{servers::LoadReceipt, EthApi, EthStateCache}; +use reth_rpc_eth_api::helpers::LoadReceipt; +use reth_rpc_eth_types::EthStateCache; + +use crate::EthApi; impl LoadReceipt for EthApi where @@ -8,6 +11,6 @@ where { #[inline] fn cache(&self) -> &EthStateCache { - &self.inner.eth_cache + self.inner.cache() } } diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs b/crates/rpc/rpc/src/eth/helpers/signer.rs similarity index 98% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs rename to crates/rpc/rpc/src/eth/helpers/signer.rs index bd2cdc244ac6..a4cb726a2915 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs +++ b/crates/rpc/rpc/src/eth/helpers/signer.rs @@ -6,15 +6,12 @@ use alloy_dyn_abi::TypedData; use reth_primitives::{ eip191_hash_message, sign_message, Address, Signature, TransactionSigned, B256, }; +use reth_rpc_eth_api::helpers::{signer::Result, EthSigner}; +use reth_rpc_eth_types::SignError; use reth_rpc_types::TypedTransactionRequest; use reth_rpc_types_compat::transaction::to_primitive_transaction; use secp256k1::SecretKey; -use crate::{ - servers::{helpers::traits::signer::Result, EthSigner}, - SignError, -}; - /// Holds developer keys #[derive(Debug, Clone)] pub struct DevSigner { diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs b/crates/rpc/rpc/src/eth/helpers/spec.rs similarity index 90% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs rename to crates/rpc/rpc/src/eth/helpers/spec.rs index f1ec323743f2..a93d662eaf6e 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs +++ b/crates/rpc/rpc/src/eth/helpers/spec.rs @@ -4,10 +4,11 @@ use reth_evm::ConfigureEvm; use reth_network_api::NetworkInfo; use reth_primitives::{Address, U256, U64}; use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; +use reth_rpc_eth_api::helpers::EthApiSpec; use reth_rpc_types::{SyncInfo, SyncStatus}; use reth_transaction_pool::TransactionPool; -use crate::{servers::EthApiSpec, EthApi}; +use crate::EthApi; impl EthApiSpec for EthApi where @@ -36,7 +37,7 @@ where } fn accounts(&self) -> Vec
{ - self.inner.signers.read().iter().flat_map(|s| s.accounts()).collect() + self.inner.signers().read().iter().flat_map(|s| s.accounts()).collect() } fn is_syncing(&self) -> bool { @@ -50,7 +51,7 @@ where self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(), ); SyncStatus::Info(SyncInfo { - starting_block: self.inner.starting_block, + starting_block: self.inner.starting_block(), current_block, highest_block: current_block, warp_chunks_amount: None, diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs b/crates/rpc/rpc/src/eth/helpers/state.rs similarity index 92% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs rename to crates/rpc/rpc/src/eth/helpers/state.rs index c55c695c346c..dd28c6465665 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs +++ b/crates/rpc/rpc/src/eth/helpers/state.rs @@ -3,10 +3,10 @@ use reth_provider::StateProviderFactory; use reth_transaction_pool::TransactionPool; -use crate::{ - servers::{EthState, LoadState, SpawnBlocking}, - EthApi, EthStateCache, -}; +use reth_rpc_eth_api::helpers::{EthState, LoadState, SpawnBlocking}; +use reth_rpc_eth_types::EthStateCache; + +use crate::EthApi; impl EthState for EthApi where Self: LoadState + SpawnBlocking @@ -43,13 +43,13 @@ mod tests { constants::ETHEREUM_BLOCK_GAS_LIMIT, Address, StorageKey, StorageValue, U256, }; use reth_provider::test_utils::{ExtendedAccount, MockEthProvider, NoopProvider}; + use reth_rpc_eth_api::helpers::EthState; + use reth_rpc_eth_types::{ + EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, + }; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::test_utils::testing_pool; - use crate::{ - servers::EthState, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, - }; - use super::*; #[tokio::test] diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs b/crates/rpc/rpc/src/eth/helpers/trace.rs similarity index 83% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs rename to crates/rpc/rpc/src/eth/helpers/trace.rs index 14b853adbc49..fe1ee9f13cf4 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs +++ b/crates/rpc/rpc/src/eth/helpers/trace.rs @@ -1,11 +1,9 @@ //! Contains RPC handler implementations specific to tracing. use reth_evm::ConfigureEvm; +use reth_rpc_eth_api::helpers::{LoadState, Trace}; -use crate::{ - servers::{LoadState, Trace}, - EthApi, -}; +use crate::EthApi; impl Trace for EthApi where diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs b/crates/rpc/rpc/src/eth/helpers/transaction.rs similarity index 94% rename from crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs rename to crates/rpc/rpc/src/eth/helpers/transaction.rs index 1f4afa7a9b0e..9d86be1b2e24 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs +++ b/crates/rpc/rpc/src/eth/helpers/transaction.rs @@ -1,14 +1,14 @@ //! Contains RPC handler implementations specific to transactions use reth_provider::{BlockReaderIdExt, TransactionsProvider}; +use reth_rpc_eth_api::{ + helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking}, + RawTransactionForwarder, +}; +use reth_rpc_eth_types::EthStateCache; use reth_transaction_pool::TransactionPool; -use crate::{ - servers::{ - EthSigner, EthTransactions, LoadTransaction, RawTransactionForwarder, SpawnBlocking, - }, - EthApi, EthStateCache, -}; +use crate::EthApi; impl EthTransactions for EthApi @@ -64,14 +64,13 @@ mod tests { use reth_network_api::noop::NoopNetwork; use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, hex_literal::hex, Bytes}; use reth_provider::test_utils::NoopProvider; + use reth_rpc_eth_api::helpers::EthTransactions; + use reth_rpc_eth_types::{ + EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, + }; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::{test_utils::testing_pool, TransactionPool}; - use crate::{ - servers::EthTransactions, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, - GasPriceOracle, - }; - use super::*; #[tokio::test] diff --git a/crates/rpc/rpc/src/eth/mod.rs b/crates/rpc/rpc/src/eth/mod.rs new file mode 100644 index 000000000000..4e6a0cbb8c75 --- /dev/null +++ b/crates/rpc/rpc/src/eth/mod.rs @@ -0,0 +1,17 @@ +//! Sever implementation of `eth` namespace API. + +pub mod bundle; +pub mod core; +pub mod filter; +pub mod helpers; +pub mod pubsub; + +/// Implementation of `eth` namespace API. +pub use bundle::EthBundle; +pub use core::EthApi; +pub use filter::{EthFilter, EthFilterConfig}; +pub use pubsub::EthPubSub; + +pub use helpers::signer::DevSigner; + +pub use reth_rpc_eth_api::RawTransactionForwarder; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs similarity index 98% rename from crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs rename to crates/rpc/rpc/src/eth/pubsub.rs index a0e886f6d947..3585be9f8561 100644 --- a/crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -9,6 +9,9 @@ use jsonrpsee::{ use reth_network_api::NetworkInfo; use reth_primitives::{IntoRecoveredTransaction, TxHash}; use reth_provider::{BlockReader, CanonStateSubscriptions, EvmEnvProvider}; +use reth_rpc_eth_api::pubsub::EthPubSubApiServer; +use reth_rpc_eth_types::logs_utils; +use reth_rpc_server_types::result::{internal_rpc_err, invalid_params_rpc_err}; use reth_rpc_types::{ pubsub::{ Params, PubSubSyncStatus, SubscriptionKind, SubscriptionResult as EthSubscriptionResult, @@ -24,12 +27,6 @@ use tokio_stream::{ Stream, }; -use crate::{ - logs_utils, - result::{internal_rpc_err, invalid_params_rpc_err}, - EthPubSubApiServer, -}; - /// `Eth` pubsub RPC implementation. /// /// This handles `eth_subscribe` RPC calls. diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index fbd9244734b2..eec14981bf57 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -11,12 +11,11 @@ //! and can reduce overall performance of all concurrent requests handled via the jsonrpsee server. //! //! To avoid this, all blocking or CPU intensive handlers must be spawned to a separate task. See -//! the [`EthApi`](reth_rpc_eth_api::EthApi) handler implementations for examples. The rpc-api -//! traits make no use of the available jsonrpsee `blocking` attribute to give implementers more -//! freedom because the `blocking` attribute and async handlers are mutually exclusive. However, as -//! mentioned above, a lot of handlers make use of async functions, caching for example, but are -//! also using blocking disk-io, hence these calls are spawned as futures to a blocking task -//! manually. +//! the [`EthApi`] handler implementations for examples. The rpc-api traits make no use of the +//! available jsonrpsee `blocking` attribute to give implementers more freedom because the +//! `blocking` attribute and async handlers are mutually exclusive. However, as mentioned above, a +//! lot of handlers make use of async functions, caching for example, but are also using blocking +//! disk-io, hence these calls are spawned as futures to a blocking task manually. #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", @@ -36,6 +35,7 @@ use tower as _; mod admin; mod debug; mod engine; +pub mod eth; mod net; mod otterscan; mod reth; @@ -46,6 +46,7 @@ mod web3; pub use admin::AdminApi; pub use debug::DebugApi; pub use engine::{EngineApi, EngineEthApi}; +pub use eth::{EthApi, EthBundle, EthFilter, EthPubSub}; pub use net::NetApi; pub use otterscan::OtterscanApi; pub use reth::RethApi; @@ -53,6 +54,3 @@ pub use rpc::RPCApi; pub use trace::TraceApi; pub use txpool::TxPoolApi; pub use web3::Web3Api; - -pub use reth_rpc_eth_api as eth; -pub use reth_rpc_eth_api::result; diff --git a/crates/rpc/rpc/src/net.rs b/crates/rpc/rpc/src/net.rs index c3a1229a5c39..79e85ac48e02 100644 --- a/crates/rpc/rpc/src/net.rs +++ b/crates/rpc/rpc/src/net.rs @@ -2,7 +2,7 @@ use jsonrpsee::core::RpcResult as Result; use reth_network_api::PeersInfo; use reth_primitives::U64; use reth_rpc_api::NetApiServer; -use reth_rpc_eth_api::servers::EthApiSpec; +use reth_rpc_eth_api::helpers::EthApiSpec; use reth_rpc_types::PeerCount; /// `Net` API implementation. diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index 9405ffd3e1c1..4088d71ca0fa 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256}; use reth_rpc_api::{EthApiServer, OtterscanServer}; -use reth_rpc_eth_api::{result::internal_rpc_err, servers::TraceExt}; +use reth_rpc_eth_api::helpers::TraceExt; +use reth_rpc_server_types::result::internal_rpc_err; use reth_rpc_types::{ trace::otterscan::{ BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions, diff --git a/crates/rpc/rpc/src/reth.rs b/crates/rpc/rpc/src/reth.rs index 11c437e696a6..33dc74920406 100644 --- a/crates/rpc/rpc/src/reth.rs +++ b/crates/rpc/rpc/src/reth.rs @@ -6,7 +6,7 @@ use reth_errors::RethResult; use reth_primitives::{Address, BlockId, U256}; use reth_provider::{BlockReaderIdExt, ChangeSetReader, StateProviderFactory}; use reth_rpc_api::RethApiServer; -use reth_rpc_eth_api::{EthApiError, EthResult}; +use reth_rpc_eth_types::{EthApiError, EthResult}; use reth_tasks::TaskSpawner; use tokio::sync::oneshot; diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 2c3e9334a43f..bf1e7020c865 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -9,10 +9,10 @@ use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, Header, use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::TraceApiServer; -use reth_rpc_eth_api::{ +use reth_rpc_eth_api::helpers::TraceExt; +use reth_rpc_eth_types::{ error::{EthApiError, EthResult}, revm_utils::prepare_call_env, - servers::TraceExt, utils::recover_raw_transaction, }; use reth_rpc_types::{ diff --git a/crates/rpc/rpc/src/web3.rs b/crates/rpc/rpc/src/web3.rs index 78464327aa2c..787604e25e23 100644 --- a/crates/rpc/rpc/src/web3.rs +++ b/crates/rpc/rpc/src/web3.rs @@ -3,8 +3,7 @@ use jsonrpsee::core::RpcResult; use reth_network_api::NetworkInfo; use reth_primitives::{keccak256, Bytes, B256}; use reth_rpc_api::Web3ApiServer; - -use crate::result::ToRpcResult; +use reth_rpc_server_types::ToRpcResult; /// `web3` API implementation. /// diff --git a/examples/custom-dev-node/src/main.rs b/examples/custom-dev-node/src/main.rs index 5f346e7a2932..24e7b229f54b 100644 --- a/examples/custom-dev-node/src/main.rs +++ b/examples/custom-dev-node/src/main.rs @@ -3,18 +3,19 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] +use std::sync::Arc; + use futures_util::StreamExt; use reth::{ builder::{NodeBuilder, NodeHandle}, providers::CanonStateSubscriptions, - rpc::eth::servers::EthTransactions, + rpc::api::eth::helpers::EthTransactions, tasks::TaskManager, }; use reth_chainspec::ChainSpec; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::EthereumNode; use reth_primitives::{b256, hex, Genesis}; -use std::sync::Arc; #[tokio::main] async fn main() -> eyre::Result<()> { diff --git a/examples/custom-inspector/src/main.rs b/examples/custom-inspector/src/main.rs index b3f33af07437..fd1d82b59338 100644 --- a/examples/custom-inspector/src/main.rs +++ b/examples/custom-inspector/src/main.rs @@ -21,7 +21,7 @@ use reth::{ interpreter::{Interpreter, OpCode}, Database, Evm, EvmContext, Inspector, }, - rpc::{compat::transaction::transaction_to_call_request, eth::servers::Call}, + rpc::{api::eth::helpers::Call, compat::transaction::transaction_to_call_request}, transaction_pool::TransactionPool, }; use reth_node_ethereum::node::EthereumNode; diff --git a/examples/rpc-db/src/myrpc_ext.rs b/examples/rpc-db/src/myrpc_ext.rs index d1898b81cb15..e38b6fc24d37 100644 --- a/examples/rpc-db/src/myrpc_ext.rs +++ b/examples/rpc-db/src/myrpc_ext.rs @@ -3,7 +3,7 @@ use reth::{primitives::Block, providers::BlockReaderIdExt}; // Rpc related imports use jsonrpsee::proc_macros::rpc; -use reth::rpc::eth::error::EthResult; +use reth::rpc::server_types::eth::EthResult; /// trait interface for a custom rpc namespace: `MyRpc` /// From 6d8cbae3e6d4b441b04af06bbbdba0511993d036 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 27 Jun 2024 16:26:17 +0200 Subject: [PATCH 238/405] chore(rpc): add me to RPC codeowners (#9144) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 1fd984ec1291..11c19aa43454 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,7 +26,7 @@ crates/primitives/ @DaniPopes @Rjected crates/primitives-traits/ @DaniPopes @Rjected @joshieDo crates/prune/ @shekhirin @joshieDo crates/revm/ @mattsse @rakita -crates/rpc/ @mattsse @Rjected +crates/rpc/ @mattsse @Rjected @emhane crates/stages/ @onbjerg @rkrasiuk @shekhirin crates/static-file/ @joshieDo @shekhirin crates/storage/codecs/ @joshieDo From 5aaf91dbcb7cbc1ed2f72b710d50bc7046e1b566 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 27 Jun 2024 19:25:24 +0400 Subject: [PATCH 239/405] refactor: clean-up discv5 configuration (#9143) --- Cargo.lock | 1 - bin/reth/src/commands/debug_cmd/execution.rs | 7 +- .../commands/debug_cmd/in_memory_merkle.rs | 7 +- bin/reth/src/commands/debug_cmd/merkle.rs | 7 +- .../src/commands/debug_cmd/replay_engine.rs | 7 +- bin/reth/src/commands/p2p/mod.rs | 51 +-------- crates/net/network/src/config.rs | 41 ++----- crates/node-core/src/args/network.rs | 101 ++++++++++++++---- crates/node-core/src/node_config.rs | 1 + crates/node/builder/Cargo.toml | 3 - crates/node/builder/src/builder/mod.rs | 69 +++--------- crates/node/builder/src/launch/common.rs | 4 +- crates/optimism/node/src/node.rs | 20 +--- 13 files changed, 120 insertions(+), 199 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 326850d6eb8b..2a9fc0a7fab7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7520,7 +7520,6 @@ dependencies = [ "aquamarine", "backon", "confy", - "discv5", "eyre", "fdlimit", "futures", diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index 9e39c90b39fc..2df188c73404 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -34,7 +34,7 @@ use reth_stages::{ }; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; -use std::{net::SocketAddr, path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use tokio::sync::watch; use tracing::*; @@ -130,11 +130,6 @@ impl Command { .network .network_config(config, provider_factory.chain_spec(), secret_key, default_peers_path) .with_task_executor(Box::new(task_executor)) - .listener_addr(SocketAddr::new(self.network.addr, self.network.port)) - .discovery_addr(SocketAddr::new( - self.network.discovery.addr, - self.network.discovery.port, - )) .build(provider_factory) .start_network() .await?; diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index 8f78d6711073..d7a5b42bc201 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -26,7 +26,7 @@ use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; use reth_tasks::TaskExecutor; use reth_trie::{updates::TrieKey, StateRoot}; -use std::{net::SocketAddr, path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use tracing::*; /// `reth debug in-memory-merkle` command @@ -64,11 +64,6 @@ impl Command { .network .network_config(config, provider_factory.chain_spec(), secret_key, default_peers_path) .with_task_executor(Box::new(task_executor)) - .listener_addr(SocketAddr::new(self.network.addr, self.network.port)) - .discovery_addr(SocketAddr::new( - self.network.discovery.addr, - self.network.discovery.port, - )) .build(provider_factory) .start_network() .await?; diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 5244cbad316f..bd8f690b9a59 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -30,7 +30,7 @@ use reth_stages::{ ExecInput, Stage, StageCheckpoint, }; use reth_tasks::TaskExecutor; -use std::{net::SocketAddr, path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use tracing::*; /// `reth debug merkle` command @@ -69,11 +69,6 @@ impl Command { .network .network_config(config, provider_factory.chain_spec(), secret_key, default_peers_path) .with_task_executor(Box::new(task_executor)) - .listener_addr(SocketAddr::new(self.network.addr, self.network.port)) - .discovery_addr(SocketAddr::new( - self.network.discovery.addr, - self.network.discovery.port, - )) .build(provider_factory) .start_network() .await?; diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 171b828bbc2b..224a0c993401 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -27,7 +27,7 @@ use reth_stages::Pipeline; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; use reth_transaction_pool::noop::NoopTransactionPool; -use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; +use std::{path::PathBuf, sync::Arc, time::Duration}; use tokio::sync::oneshot; use tracing::*; @@ -65,11 +65,6 @@ impl Command { .network .network_config(config, provider_factory.chain_spec(), secret_key, default_peers_path) .with_task_executor(Box::new(task_executor)) - .listener_addr(SocketAddr::new(self.network.addr, self.network.port)) - .discovery_addr(SocketAddr::new( - self.network.discovery.addr, - self.network.discovery.port, - )) .build(provider_factory) .start_network() .await?; diff --git a/bin/reth/src/commands/p2p/mod.rs b/bin/reth/src/commands/p2p/mod.rs index 290a0a0b08bb..161eb6cc9133 100644 --- a/bin/reth/src/commands/p2p/mod.rs +++ b/bin/reth/src/commands/p2p/mod.rs @@ -4,13 +4,12 @@ use crate::{ args::{ get_secret_key, utils::{chain_help, chain_value_parser, hash_or_num_value_parser, SUPPORTED_CHAINS}, - DatabaseArgs, DiscoveryArgs, NetworkArgs, + DatabaseArgs, NetworkArgs, }, utils::get_single_header, }; use backon::{ConstantBuilder, Retryable}; use clap::{Parser, Subcommand}; -use discv5::ListenConfig; use reth_chainspec::ChainSpec; use reth_config::Config; use reth_db::create_db; @@ -19,11 +18,7 @@ use reth_network_p2p::bodies::client::BodiesClient; use reth_node_core::args::DatadirArgs; use reth_primitives::BlockHashOrNumber; use reth_provider::{providers::StaticFileProvider, ProviderFactory}; -use std::{ - net::{IpAddr, SocketAddrV4, SocketAddrV6}, - path::PathBuf, - sync::Arc, -}; +use std::{path::PathBuf, sync::Arc}; /// `reth p2p` command #[derive(Debug, Parser)] @@ -113,47 +108,7 @@ impl Command { .disable_discv4_discovery_if(self.chain.chain.is_optimism()) .boot_nodes(boot_nodes.clone()) .apply(|builder| { - self.network - .discovery - .apply_to_builder(builder, rlpx_socket) - .map_discv5_config_builder(|builder| { - let DiscoveryArgs { - discv5_addr, - discv5_addr_ipv6, - discv5_port, - discv5_port_ipv6, - discv5_lookup_interval, - discv5_bootstrap_lookup_interval, - discv5_bootstrap_lookup_countdown, - .. - } = self.network.discovery; - - // Use rlpx address if none given - let discv5_addr_ipv4 = discv5_addr.or(match self.network.addr { - IpAddr::V4(ip) => Some(ip), - IpAddr::V6(_) => None, - }); - let discv5_addr_ipv6 = discv5_addr_ipv6.or(match self.network.addr { - IpAddr::V4(_) => None, - IpAddr::V6(ip) => Some(ip), - }); - - builder - .discv5_config( - discv5::ConfigBuilder::new(ListenConfig::from_two_sockets( - discv5_addr_ipv4 - .map(|addr| SocketAddrV4::new(addr, discv5_port)), - discv5_addr_ipv6.map(|addr| { - SocketAddrV6::new(addr, discv5_port_ipv6, 0, 0) - }), - )) - .build(), - ) - .add_unsigned_boot_nodes(boot_nodes.into_iter()) - .lookup_interval(discv5_lookup_interval) - .bootstrap_lookup_interval(discv5_bootstrap_lookup_interval) - .bootstrap_lookup_countdown(discv5_bootstrap_lookup_countdown) - }) + self.network.discovery.apply_to_builder(builder, rlpx_socket, boot_nodes) }) .build(Arc::new(ProviderFactory::new( noop_db, diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index c42d204f5e29..2b5d36cdb267 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -410,36 +410,6 @@ impl NetworkConfigBuilder { } } - /// Calls a closure on [`reth_discv5::ConfigBuilder`], if discv5 discovery is enabled and the - /// builder has been set. - /// ``` - /// use reth_chainspec::MAINNET; - /// use reth_network::NetworkConfigBuilder; - /// use reth_provider::test_utils::NoopProvider; - /// use secp256k1::{rand::thread_rng, SecretKey}; - /// - /// let sk = SecretKey::new(&mut thread_rng()); - /// let fork_id = MAINNET.latest_fork_id(); - /// let network_config = NetworkConfigBuilder::new(sk) - /// .map_discv5_config_builder(|builder| builder.fork(b"eth", fork_id)) - /// .build(NoopProvider::default()); - /// ``` - pub fn map_discv5_config_builder( - mut self, - f: impl FnOnce(reth_discv5::ConfigBuilder) -> reth_discv5::ConfigBuilder, - ) -> Self { - if let Some(mut builder) = self.discovery_v5_builder { - if let Some(network_stack_id) = NetworkStackId::id(&self.chain_spec) { - let fork_id = self.chain_spec.latest_fork_id(); - builder = builder.fork(network_stack_id, fork_id); - } - - self.discovery_v5_builder = Some(f(builder)); - } - - self - } - /// Adds a new additional protocol to the `RLPx` sub-protocol list. pub fn add_rlpx_sub_protocol(mut self, protocol: impl IntoRlpxSubProtocol) -> Self { self.extra_protocols.push(protocol); @@ -479,7 +449,7 @@ impl NetworkConfigBuilder { secret_key, mut dns_discovery_config, discovery_v4_builder, - discovery_v5_builder, + mut discovery_v5_builder, boot_nodes, discovery_addr, listener_addr, @@ -496,6 +466,15 @@ impl NetworkConfigBuilder { transactions_manager_config, } = self; + discovery_v5_builder = discovery_v5_builder.map(|mut builder| { + if let Some(network_stack_id) = NetworkStackId::id(&chain_spec) { + let fork_id = chain_spec.latest_fork_id(); + builder = builder.fork(network_stack_id, fork_id) + } + + builder + }); + let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS); let mut hello_message = diff --git a/crates/node-core/src/args/network.rs b/crates/node-core/src/args/network.rs index a5763495cd8d..725838d08bca 100644 --- a/crates/node-core/src/args/network.rs +++ b/crates/node-core/src/args/network.rs @@ -4,9 +4,9 @@ use crate::version::P2P_CLIENT_VERSION; use clap::Args; use reth_chainspec::{net::mainnet_nodes, ChainSpec}; use reth_config::Config; -use reth_discv4::{DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT}; +use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT}; use reth_discv5::{ - DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT, + discv5::ListenConfig, DEFAULT_COUNT_BOOTSTRAP_LOOKUPS, DEFAULT_DISCOVERY_V5_PORT, DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL, DEFAULT_SECONDS_LOOKUP_INTERVAL, }; use reth_net_nat::NatResolver; @@ -21,7 +21,7 @@ use reth_network::{ use reth_network_peers::TrustedPeer; use secp256k1::SecretKey; use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, ops::Not, path::PathBuf, sync::Arc, @@ -173,23 +173,17 @@ impl NetworkArgs { // apply discovery settings .apply(|builder| { let rlpx_socket = (self.addr, self.port).into(); - self.discovery.apply_to_builder(builder, rlpx_socket) - }) - // modify discv5 settings if enabled in previous step - .map_discv5_config_builder(|builder| { - let DiscoveryArgs { - discv5_lookup_interval, - discv5_bootstrap_lookup_interval, - discv5_bootstrap_lookup_countdown, - .. - } = self.discovery; - - builder - .add_unsigned_boot_nodes(chain_bootnodes) - .lookup_interval(discv5_lookup_interval) - .bootstrap_lookup_interval(discv5_bootstrap_lookup_interval) - .bootstrap_lookup_countdown(discv5_bootstrap_lookup_countdown) + self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes) }) + .listener_addr(SocketAddr::new( + self.addr, // set discovery port based on instance number + self.port, + )) + .discovery_addr(SocketAddr::new( + self.discovery.addr, + // set discovery port based on instance number + self.discovery.port, + )) } /// If `no_persist_peers` is false then this returns the path to the persistent peers file path. @@ -211,6 +205,17 @@ impl NetworkArgs { self.discovery = self.discovery.with_unused_discovery_port(); self } + + /// Change networking port numbers based on the instance number. + /// Ports are updated to `previous_value + instance - 1` + /// + /// # Panics + /// Warning: if `instance` is zero in debug mode, this will panic. + pub fn adjust_instance_ports(&mut self, instance: u16) { + debug_assert_ne!(instance, 0, "instance must be non-zero"); + self.port += instance - 1; + self.discovery.adjust_instance_ports(instance); + } } impl Default for NetworkArgs { @@ -309,6 +314,7 @@ impl DiscoveryArgs { &self, mut network_config_builder: NetworkConfigBuilder, rlpx_tcp_socket: SocketAddr, + boot_nodes: impl IntoIterator, ) -> NetworkConfigBuilder { if self.disable_discovery || self.disable_dns_discovery { network_config_builder = network_config_builder.disable_dns_discovery(); @@ -319,19 +325,72 @@ impl DiscoveryArgs { } if !self.disable_discovery && self.enable_discv5_discovery { - network_config_builder = - network_config_builder.discovery_v5(reth_discv5::Config::builder(rlpx_tcp_socket)); + network_config_builder = network_config_builder + .discovery_v5(self.discovery_v5_builder(rlpx_tcp_socket, boot_nodes)); } network_config_builder } + /// Creates a [`reth_discv5::ConfigBuilder`] filling it with the values from this struct. + pub fn discovery_v5_builder( + &self, + rlpx_tcp_socket: SocketAddr, + boot_nodes: impl IntoIterator, + ) -> reth_discv5::ConfigBuilder { + let Self { + discv5_addr, + discv5_addr_ipv6, + discv5_port, + discv5_port_ipv6, + discv5_lookup_interval, + discv5_bootstrap_lookup_interval, + discv5_bootstrap_lookup_countdown, + .. + } = self; + + // Use rlpx address if none given + let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket { + SocketAddr::V4(addr) => Some(*addr.ip()), + SocketAddr::V6(_) => None, + }); + let discv5_addr_ipv6 = discv5_addr_ipv6.or(match rlpx_tcp_socket { + SocketAddr::V4(_) => None, + SocketAddr::V6(addr) => Some(*addr.ip()), + }); + + reth_discv5::Config::builder(rlpx_tcp_socket) + .discv5_config( + reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets( + discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)), + discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)), + )) + .build(), + ) + .add_unsigned_boot_nodes(boot_nodes) + .lookup_interval(*discv5_lookup_interval) + .bootstrap_lookup_interval(*discv5_bootstrap_lookup_interval) + .bootstrap_lookup_countdown(*discv5_bootstrap_lookup_countdown) + } + /// Set the discovery port to zero, to allow the OS to assign a random unused port when /// discovery binds to the socket. pub const fn with_unused_discovery_port(mut self) -> Self { self.port = 0; self } + + /// Change networking port numbers based on the instance number. + /// Ports are updated to `previous_value + instance - 1` + /// + /// # Panics + /// Warning: if `instance` is zero in debug mode, this will panic. + pub fn adjust_instance_ports(&mut self, instance: u16) { + debug_assert_ne!(instance, 0, "instance must be non-zero"); + self.port += instance - 1; + self.discv5_port += instance - 1; + self.discv5_port_ipv6 += instance - 1; + } } impl Default for DiscoveryArgs { diff --git a/crates/node-core/src/node_config.rs b/crates/node-core/src/node_config.rs index 932c12e70436..8f9ff42f9eae 100644 --- a/crates/node-core/src/node_config.rs +++ b/crates/node-core/src/node_config.rs @@ -390,6 +390,7 @@ impl NodeConfig { /// [`RpcServerArgs::adjust_instance_ports`] method. pub fn adjust_instance_ports(&mut self) { self.rpc.adjust_instance_ports(self.instance); + self.network.adjust_instance_ports(self.instance); } /// Sets networking and RPC ports to zero, causing the OS to choose random unused ports when diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index fc13a8621c06..f83145b052e9 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -57,9 +57,6 @@ tokio = { workspace = true, features = [ ] } tokio-stream.workspace = true -## ethereum -discv5.workspace = true - ## crypto secp256k1 = { workspace = true, features = [ "global-context", diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 85fab2c27f00..72e56b71a3ea 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -9,7 +9,6 @@ use crate::{ rpc::{RethRpcServerHandles, RpcContext}, DefaultNodeLauncher, Node, NodeHandle, }; -use discv5::ListenConfig; use futures::Future; use reth_chainspec::ChainSpec; use reth_db::{ @@ -39,10 +38,7 @@ use reth_tasks::TaskExecutor; use reth_transaction_pool::{PoolConfig, TransactionPool}; use secp256k1::SecretKey; pub use states::*; -use std::{ - net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6}, - sync::Arc, -}; +use std::sync::Arc; mod states; @@ -531,12 +527,19 @@ impl BuilderContext { pub fn network_config_builder(&self) -> eyre::Result { let secret_key = self.network_secret(&self.config().datadir())?; let default_peers_path = self.config().datadir().known_peers(); - Ok(self.config().network.network_config( - self.reth_config(), - self.config().chain.clone(), - secret_key, - default_peers_path, - )) + let builder = self + .config() + .network + .network_config( + self.reth_config(), + self.config().chain.clone(), + secret_key, + default_peers_path, + ) + .with_task_executor(Box::new(self.executor.clone())) + .set_head(self.head); + + Ok(builder) } /// Get the network secret from the given data dir @@ -552,49 +555,7 @@ impl BuilderContext { &self, network_builder: NetworkConfigBuilder, ) -> NetworkConfig { - network_builder - .with_task_executor(Box::new(self.executor.clone())) - .set_head(self.head) - .listener_addr(SocketAddr::new( - self.config().network.addr, - // set discovery port based on instance number - self.config().network.port + self.config().instance - 1, - )) - .discovery_addr(SocketAddr::new( - self.config().network.discovery.addr, - // set discovery port based on instance number - self.config().network.discovery.port + self.config().instance - 1, - )) - .map_discv5_config_builder(|builder| { - // Use rlpx address if none given - let discv5_addr_ipv4 = self.config().network.discovery.discv5_addr.or( - match self.config().network.addr { - IpAddr::V4(ip) => Some(ip), - IpAddr::V6(_) => None, - }, - ); - let discv5_addr_ipv6 = self.config().network.discovery.discv5_addr_ipv6.or( - match self.config().network.addr { - IpAddr::V4(_) => None, - IpAddr::V6(ip) => Some(ip), - }, - ); - - let discv5_port_ipv4 = - self.config().network.discovery.discv5_port + self.config().instance - 1; - let discv5_port_ipv6 = - self.config().network.discovery.discv5_port_ipv6 + self.config().instance - 1; - - builder.discv5_config( - discv5::ConfigBuilder::new(ListenConfig::from_two_sockets( - discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, discv5_port_ipv4)), - discv5_addr_ipv6 - .map(|addr| SocketAddrV6::new(addr, discv5_port_ipv6, 0, 0)), - )) - .build(), - ) - }) - .build(self.provider.clone()) + network_builder.build(self.provider.clone()) } } diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 1c48fd763041..a1512e25798f 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -251,7 +251,7 @@ impl LaunchContextWith> { /// - Making sure the ETL dir is set to the datadir /// - RPC settings are adjusted to the correct port pub fn with_adjusted_configs(self) -> Self { - self.ensure_etl_datadir().with_adjusted_rpc_instance_ports() + self.ensure_etl_datadir().with_adjusted_instance_ports() } /// Make sure ETL doesn't default to /tmp/, but to whatever datadir is set to @@ -265,7 +265,7 @@ impl LaunchContextWith> { } /// Change rpc port numbers based on the instance number. - pub fn with_adjusted_rpc_instance_ports(mut self) -> Self { + pub fn with_adjusted_instance_ports(mut self) -> Self { self.node_config_mut().adjust_instance_ports(); self } diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index d8628dc6bf85..2ea24da6754a 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -283,27 +283,17 @@ where // purposefully disable discv4 .disable_discv4_discovery() // apply discovery settings - .apply(|builder| { + .apply(|mut builder| { let rlpx_socket = (args.addr, args.port).into(); - let mut builder = args.discovery.apply_to_builder(builder, rlpx_socket); if !args.discovery.disable_discovery { - builder = builder.discovery_v5(reth_discv5::Config::builder(rlpx_socket)); + builder = builder.discovery_v5(args.discovery.discovery_v5_builder( + rlpx_socket, + ctx.chain_spec().bootnodes().unwrap_or_default(), + )); } builder - }) - // ensure we configure discv5 - .map_discv5_config_builder(|builder| { - builder - .add_unsigned_boot_nodes(ctx.chain_spec().bootnodes().unwrap_or_default()) - .lookup_interval(ctx.config().network.discovery.discv5_lookup_interval) - .bootstrap_lookup_interval( - ctx.config().network.discovery.discv5_bootstrap_lookup_interval, - ) - .bootstrap_lookup_countdown( - ctx.config().network.discovery.discv5_bootstrap_lookup_countdown, - ) }); let mut network_config = ctx.build_network_config(network_builder); From 5b2cd27a1a50bffd2b765e800b8842ff99129309 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:42:29 +0200 Subject: [PATCH 240/405] chore: remove unused methods from `EvmEnvProviders` (#9148) --- crates/evm/src/provider.rs | 14 ------ .../provider/src/providers/database/mod.rs | 16 ------- .../src/providers/database/provider.rs | 43 ++----------------- crates/storage/provider/src/providers/mod.rs | 16 ------- .../storage/provider/src/test_utils/mock.rs | 16 ------- .../storage/provider/src/test_utils/noop.rs | 16 ------- 6 files changed, 3 insertions(+), 118 deletions(-) diff --git a/crates/evm/src/provider.rs b/crates/evm/src/provider.rs index b976351c66b0..2e73ff2fa985 100644 --- a/crates/evm/src/provider.rs +++ b/crates/evm/src/provider.rs @@ -52,20 +52,6 @@ pub trait EvmEnvProvider: Send + Sync { where EvmConfig: ConfigureEvmEnv; - /// Fills the [BlockEnv] fields with values specific to the given [BlockHashOrNumber]. - fn fill_block_env_at( - &self, - block_env: &mut BlockEnv, - at: BlockHashOrNumber, - ) -> ProviderResult<()>; - - /// Fills the [BlockEnv] fields with values specific to the given [Header]. - fn fill_block_env_with_header( - &self, - block_env: &mut BlockEnv, - header: &Header, - ) -> ProviderResult<()>; - /// Fills the [`CfgEnvWithHandlerCfg`] fields with values specific to the given /// [BlockHashOrNumber]. fn fill_cfg_env_at( diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 57e7e9615217..31332377d64a 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -519,22 +519,6 @@ impl EvmEnvProvider for ProviderFactory { self.provider()?.fill_env_with_header(cfg, block_env, header, evm_config) } - fn fill_block_env_at( - &self, - block_env: &mut BlockEnv, - at: BlockHashOrNumber, - ) -> ProviderResult<()> { - self.provider()?.fill_block_env_at(block_env, at) - } - - fn fill_block_env_with_header( - &self, - block_env: &mut BlockEnv, - header: &Header, - ) -> ProviderResult<()> { - self.provider()?.fill_block_env_with_header(block_env, header) - } - fn fill_cfg_env_at( &self, cfg: &mut CfgEnvWithHandlerCfg, diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 02287b96cad0..22260cc9cb39 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -32,10 +32,8 @@ use reth_evm::ConfigureEvmEnv; use reth_execution_types::{Chain, ExecutionOutcome}; use reth_network_p2p::headers::downloader::SyncTarget; use reth_primitives::{ - keccak256, - revm::{config::revm_spec, env::fill_block_env}, - Account, Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, - GotExpected, Head, Header, Receipt, Requests, SealedBlock, SealedBlockWithSenders, + keccak256, Account, Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, + BlockWithSenders, GotExpected, Header, Receipt, Requests, SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, StorageEntry, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, @@ -48,7 +46,7 @@ use reth_trie::{ updates::TrieUpdates, HashedPostState, Nibbles, StateRoot, }; -use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg, SpecId}; +use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg}; use std::{ cmp::Ordering, collections::{hash_map, BTreeMap, BTreeSet, HashMap, HashSet}, @@ -2005,41 +2003,6 @@ impl EvmEnvProvider for DatabaseProvider { Ok(()) } - fn fill_block_env_at( - &self, - block_env: &mut BlockEnv, - at: BlockHashOrNumber, - ) -> ProviderResult<()> { - let hash = self.convert_number(at)?.ok_or(ProviderError::HeaderNotFound(at))?; - let header = self.header(&hash)?.ok_or(ProviderError::HeaderNotFound(at))?; - - self.fill_block_env_with_header(block_env, &header) - } - - fn fill_block_env_with_header( - &self, - block_env: &mut BlockEnv, - header: &Header, - ) -> ProviderResult<()> { - let total_difficulty = self - .header_td_by_number(header.number)? - .ok_or_else(|| ProviderError::HeaderNotFound(header.number.into()))?; - let spec_id = revm_spec( - &self.chain_spec, - Head { - number: header.number, - timestamp: header.timestamp, - difficulty: header.difficulty, - total_difficulty, - // Not required - hash: Default::default(), - }, - ); - let after_merge = spec_id >= SpecId::MERGE; - fill_block_env(block_env, &self.chain_spec, header, after_merge); - Ok(()) - } - fn fill_cfg_env_at( &self, cfg: &mut CfgEnvWithHandlerCfg, diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 9da41269c137..69116517f7cd 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -538,22 +538,6 @@ where self.database.provider()?.fill_env_with_header(cfg, block_env, header, evm_config) } - fn fill_block_env_at( - &self, - block_env: &mut BlockEnv, - at: BlockHashOrNumber, - ) -> ProviderResult<()> { - self.database.provider()?.fill_block_env_at(block_env, at) - } - - fn fill_block_env_with_header( - &self, - block_env: &mut BlockEnv, - header: &Header, - ) -> ProviderResult<()> { - self.database.provider()?.fill_block_env_with_header(block_env, header) - } - fn fill_cfg_env_at( &self, cfg: &mut CfgEnvWithHandlerCfg, diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 974982121a7e..73ee53eeca6b 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -599,22 +599,6 @@ impl EvmEnvProvider for MockEthProvider { Ok(()) } - fn fill_block_env_at( - &self, - _block_env: &mut BlockEnv, - _at: BlockHashOrNumber, - ) -> ProviderResult<()> { - Ok(()) - } - - fn fill_block_env_with_header( - &self, - _block_env: &mut BlockEnv, - _header: &Header, - ) -> ProviderResult<()> { - Ok(()) - } - fn fill_cfg_env_at( &self, _cfg: &mut CfgEnvWithHandlerCfg, diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 74577732d2a7..4f7bdabce70d 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -358,22 +358,6 @@ impl EvmEnvProvider for NoopProvider { Ok(()) } - fn fill_block_env_at( - &self, - _block_env: &mut BlockEnv, - _at: BlockHashOrNumber, - ) -> ProviderResult<()> { - Ok(()) - } - - fn fill_block_env_with_header( - &self, - _block_env: &mut BlockEnv, - _header: &Header, - ) -> ProviderResult<()> { - Ok(()) - } - fn fill_cfg_env_at( &self, _cfg: &mut CfgEnvWithHandlerCfg, From 26b79f84f32faa273a4e9368997e1e227652e5a6 Mon Sep 17 00:00:00 2001 From: Kien Trinh <51135161+kien6034@users.noreply.github.com> Date: Thu, 27 Jun 2024 23:01:12 +0700 Subject: [PATCH 241/405] chore(static_files): fix hacky type inference (#9150) --- crates/storage/provider/src/providers/static_file/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/storage/provider/src/providers/static_file/mod.rs b/crates/storage/provider/src/providers/static_file/mod.rs index 5c2057b3b57d..e7073defeed0 100644 --- a/crates/storage/provider/src/providers/static_file/mod.rs +++ b/crates/storage/provider/src/providers/static_file/mod.rs @@ -69,6 +69,7 @@ mod tests { }; use reth_primitives::{static_file::find_fixed_range, BlockNumber, B256, U256}; use reth_testing_utils::generators::{self, random_header_range}; + use std::vec::IntoIter; #[test] fn test_snap() { @@ -128,9 +129,7 @@ mod tests { let provider = factory.provider().unwrap(); let tx = provider.tx_ref(); - // Hacky type inference. TODO fix - let mut none_vec = Some(vec![vec![vec![0u8]].into_iter()]); - let _ = none_vec.take(); + let none_vec: Option>>> = None; // Generate list of hashes for filters & PHF let mut cursor = tx.cursor_read::>().unwrap(); From c23fe39dd3488294afe206e53b6d8ff7702dd2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:11:06 +0200 Subject: [PATCH 242/405] feat: integrate CLI runner in CLI trait (#9146) --- Cargo.lock | 1 + crates/cli/cli/Cargo.toml | 3 +++ crates/cli/cli/src/lib.rs | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2a9fc0a7fab7..b580a68e6871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6579,6 +6579,7 @@ name = "reth-cli" version = "1.0.0" dependencies = [ "clap", + "reth-cli-runner", ] [[package]] diff --git a/crates/cli/cli/Cargo.toml b/crates/cli/cli/Cargo.toml index effd1ed29962..c2aa22e70e71 100644 --- a/crates/cli/cli/Cargo.toml +++ b/crates/cli/cli/Cargo.toml @@ -11,5 +11,8 @@ repository.workspace = true [dependencies] +# reth +reth-cli-runner.workspace = true + # misc clap.workspace = true diff --git a/crates/cli/cli/src/lib.rs b/crates/cli/cli/src/lib.rs index 5e273d88717f..ccaa900edbd7 100644 --- a/crates/cli/cli/src/lib.rs +++ b/crates/cli/cli/src/lib.rs @@ -10,6 +10,8 @@ use std::{borrow::Cow, ffi::OsString}; +use reth_cli_runner::CliRunner; + use clap::{Error, Parser}; /// Reth based node cli. @@ -42,4 +44,25 @@ pub trait RethCli: Sized { { ::try_parse_from(itr) } + + /// Executes a command. + fn with_runner(self, f: F) -> R + where + F: FnOnce(Self, CliRunner) -> R, + { + let runner = CliRunner::default(); + + f(self, runner) + } + + /// Parses and executes a command. + fn execute(f: F) -> Result + where + Self: Parser + Sized, + F: FnOnce(Self, CliRunner) -> R, + { + let cli = Self::parse_args()?; + + Ok(cli.with_runner(f)) + } } From 50ee497c753af68573328e121a477275152307ed Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:39:35 +0200 Subject: [PATCH 243/405] feat: use new `ChainHardforks` type on `ChainSpec` (#9065) --- Cargo.lock | 4 + crates/blockchain-tree/src/blockchain_tree.rs | 8 +- crates/chainspec/src/spec.rs | 665 ++++++++--------- crates/consensus/auto-seal/src/lib.rs | 2 +- crates/consensus/beacon/src/engine/mod.rs | 8 +- crates/consensus/common/src/calc.rs | 8 +- crates/consensus/common/src/validation.rs | 10 +- crates/ethereum-forks/Cargo.toml | 4 + crates/ethereum-forks/src/chains/dev.rs | 23 - crates/ethereum-forks/src/chains/ethereum.rs | 94 --- crates/ethereum-forks/src/chains/mod.rs | 9 - crates/ethereum-forks/src/chains/optimism.rs | 105 --- crates/ethereum-forks/src/display.rs | 19 +- crates/ethereum-forks/src/hardfork.rs | 702 ------------------ crates/ethereum-forks/src/hardfork/dev.rs | 32 + .../ethereum-forks/src/hardfork/ethereum.rs | 441 +++++++++++ crates/ethereum-forks/src/hardfork/macros.rs | 52 ++ crates/ethereum-forks/src/hardfork/mod.rs | 126 ++++ .../ethereum-forks/src/hardfork/optimism.rs | 337 +++++++++ .../ethereum-forks/src/hardforks/ethereum.rs | 56 ++ crates/ethereum-forks/src/hardforks/mod.rs | 131 ++++ .../ethereum-forks/src/hardforks/optimism.rs | 12 + crates/ethereum-forks/src/lib.rs | 7 +- crates/ethereum/consensus/src/lib.rs | 6 +- crates/ethereum/consensus/src/validation.rs | 2 +- .../ethereum/engine-primitives/src/payload.rs | 4 +- crates/ethereum/evm/src/execute.rs | 34 +- crates/ethereum/payload/src/lib.rs | 3 +- crates/net/discv5/src/enr.rs | 4 +- crates/net/dns/src/lib.rs | 4 +- crates/net/eth-wire-types/src/status.rs | 24 +- crates/net/network/src/config.rs | 3 +- crates/net/network/src/session/active.rs | 4 +- crates/optimism/consensus/src/lib.rs | 2 +- crates/optimism/consensus/src/validation.rs | 2 +- crates/optimism/evm/src/execute.rs | 6 +- crates/optimism/evm/src/l1.rs | 23 +- crates/optimism/node/src/engine.rs | 4 +- crates/optimism/payload/src/builder.rs | 13 +- crates/optimism/payload/src/payload.rs | 2 +- crates/payload/basic/src/lib.rs | 2 +- crates/payload/primitives/src/lib.rs | 3 +- crates/payload/validator/src/lib.rs | 2 +- crates/primitives/src/proofs.rs | 13 +- crates/primitives/src/revm/config.rs | 50 +- crates/revm/src/state_change.rs | 2 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 24 +- .../rpc-eth-api/src/helpers/pending_block.rs | 1 + crates/rpc/rpc/src/debug.rs | 1 + crates/rpc/rpc/src/trace.rs | 1 + crates/storage/db-common/src/init.rs | 2 +- .../src/providers/database/provider.rs | 2 +- crates/transaction-pool/src/validate/eth.rs | 2 +- examples/bsc-p2p/src/chainspec.rs | 12 +- examples/exex/rollup/src/execution.rs | 27 +- examples/manual-p2p/src/main.rs | 6 +- examples/polygon-p2p/src/chain_cfg.rs | 20 +- 57 files changed, 1704 insertions(+), 1461 deletions(-) delete mode 100644 crates/ethereum-forks/src/chains/dev.rs delete mode 100644 crates/ethereum-forks/src/chains/ethereum.rs delete mode 100644 crates/ethereum-forks/src/chains/mod.rs delete mode 100644 crates/ethereum-forks/src/chains/optimism.rs delete mode 100644 crates/ethereum-forks/src/hardfork.rs create mode 100644 crates/ethereum-forks/src/hardfork/dev.rs create mode 100644 crates/ethereum-forks/src/hardfork/ethereum.rs create mode 100644 crates/ethereum-forks/src/hardfork/macros.rs create mode 100644 crates/ethereum-forks/src/hardfork/mod.rs create mode 100644 crates/ethereum-forks/src/hardfork/optimism.rs create mode 100644 crates/ethereum-forks/src/hardforks/ethereum.rs create mode 100644 crates/ethereum-forks/src/hardforks/mod.rs create mode 100644 crates/ethereum-forks/src/hardforks/optimism.rs diff --git a/Cargo.lock b/Cargo.lock index b580a68e6871..f4c97a3dad6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7083,9 +7083,13 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "arbitrary", + "auto_impl", "crc", + "dyn-clone", + "once_cell", "proptest", "proptest-derive 0.5.0", + "rustc-hash 2.0.0", "serde", "thiserror-no-std", ] diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 5d73a1a78e06..66ed04478540 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -15,8 +15,8 @@ use reth_evm::execute::BlockExecutorProvider; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; use reth_execution_types::{Chain, ExecutionOutcome}; use reth_primitives::{ - BlockHash, BlockNumHash, BlockNumber, ForkBlock, GotExpected, Hardfork, Receipt, SealedBlock, - SealedBlockWithSenders, SealedHeader, StaticFileSegment, B256, U256, + BlockHash, BlockNumHash, BlockNumber, EthereumHardfork, ForkBlock, GotExpected, Receipt, + SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, B256, U256, }; use reth_provider::{ BlockExecutionWriter, BlockNumReader, BlockWriter, CanonStateNotification, @@ -402,7 +402,7 @@ where .externals .provider_factory .chain_spec() - .fork(Hardfork::Paris) + .fork(EthereumHardfork::Paris) .active_at_ttd(parent_td, U256::ZERO) { return Err(BlockExecutionError::Validation(BlockValidationError::BlockPreMerge { @@ -1043,7 +1043,7 @@ where .externals .provider_factory .chain_spec() - .fork(Hardfork::Paris) + .fork(EthereumHardfork::Paris) .active_at_ttd(td, U256::ZERO) { return Err(CanonicalError::from(BlockValidationError::BlockPreMerge { diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 4d53352dc498..81e7597726fd 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -14,8 +14,8 @@ use alloy_trie::EMPTY_ROOT_HASH; use derive_more::From; use once_cell::sync::Lazy; use reth_ethereum_forks::{ - chains::ethereum::{GOERLI_HARDFORKS, HOLESKY_HARDFORKS, MAINNET_HARDFORKS, SEPOLIA_HARDFORKS}, - DisplayHardforks, ForkCondition, ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head, + ChainHardforks, DisplayHardforks, EthereumHardfork, EthereumHardforks, ForkCondition, + ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head, DEV_HARDFORKS, }; use reth_network_peers::NodeRecord; use reth_primitives_traits::{ @@ -27,7 +27,7 @@ use reth_primitives_traits::{ }; use reth_trie_common::root::state_root_ref_unhashed; #[cfg(feature = "std")] -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; #[cfg(feature = "optimism")] use crate::constants::optimism::{ @@ -36,7 +36,7 @@ use crate::constants::optimism::{ }; pub use alloy_eips::eip1559::BaseFeeParams; #[cfg(feature = "optimism")] -use reth_ethereum_forks::chains::optimism::*; +use reth_ethereum_forks::OptimismHardfork; #[cfg(feature = "optimism")] use crate::net::{base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes}; @@ -56,7 +56,7 @@ pub static MAINNET: Lazy> = Lazy::new(|| { 15537394, U256::from(58_750_003_716_598_352_816_469u128), )), - hardforks: MAINNET_HARDFORKS.into(), + hardforks: EthereumHardfork::mainnet().into(), // https://etherscan.io/tx/0xe75fb554e433e03763a1560646ee22dcb74e5274b34c5ad644e7c0f619a7e1d0 deposit_contract: Some(DepositContract::new( address!("00000000219ab540356cbb839cbe05303d7705fa"), @@ -80,7 +80,7 @@ pub static GOERLI: Lazy> = Lazy::new(|| { )), // paris_block_and_final_difficulty: Some((7382818, U256::from(10_790_000))), - hardforks: GOERLI_HARDFORKS.into(), + hardforks: EthereumHardfork::goerli().into(), // https://goerli.etherscan.io/tx/0xa3c07dc59bfdb1bfc2d50920fed2ef2c1c4e0a09fe2325dbc14e07702f965a78 deposit_contract: Some(DepositContract::new( address!("ff50ed3d0ec03ac01d4c79aad74928bff48a7b2b"), @@ -104,7 +104,7 @@ pub static SEPOLIA: Lazy> = Lazy::new(|| { )), // paris_block_and_final_difficulty: Some((1450409, U256::from(17_000_018_015_853_232u128))), - hardforks: SEPOLIA_HARDFORKS.into(), + hardforks: EthereumHardfork::sepolia().into(), // https://sepolia.etherscan.io/tx/0x025ecbf81a2f1220da6285d1701dc89fb5a956b62562ee922e1a9efd73eb4b14 deposit_contract: Some(DepositContract::new( address!("7f02c3e3c98b133055b8b348b2ac625669ed295d"), @@ -127,7 +127,7 @@ pub static HOLESKY: Lazy> = Lazy::new(|| { "b5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4" )), paris_block_and_final_difficulty: Some((0, U256::from(1))), - hardforks: HOLESKY_HARDFORKS.into(), + hardforks: EthereumHardfork::holesky().into(), deposit_contract: Some(DepositContract::new( address!("4242424242424242424242424242424242424242"), 0, @@ -152,32 +152,7 @@ pub static DEV: Lazy> = Lazy::new(|| { "2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: BTreeMap::from([ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(0)), - (Hardfork::Cancun, ForkCondition::Timestamp(0)), - #[cfg(feature = "optimism")] - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - #[cfg(feature = "optimism")] - (Hardfork::Bedrock, ForkCondition::Block(0)), - #[cfg(feature = "optimism")] - (Hardfork::Ecotone, ForkCondition::Timestamp(0)), - ]), + hardforks: DEV_HARDFORKS.clone(), base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), deposit_contract: None, // TODO: do we even have? ..Default::default() @@ -198,11 +173,11 @@ pub static OP_MAINNET: Lazy> = Lazy::new(|| { "7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: OP_MAINNET_HARDFORKS.into(), + hardforks: OptimismHardfork::op_mainnet(), base_fee_params: BaseFeeParamsKind::Variable( vec![ - (Hardfork::London, OP_BASE_FEE_PARAMS), - (Hardfork::Canyon, OP_CANYON_BASE_FEE_PARAMS), + (EthereumHardfork::London.boxed(), OP_BASE_FEE_PARAMS), + (OptimismHardfork::Canyon.boxed(), OP_CANYON_BASE_FEE_PARAMS), ] .into(), ), @@ -223,11 +198,11 @@ pub static OP_SEPOLIA: Lazy> = Lazy::new(|| { "102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: OP_SEPOLIA_HARDFORKS.into(), + hardforks: OptimismHardfork::op_sepolia(), base_fee_params: BaseFeeParamsKind::Variable( vec![ - (Hardfork::London, OP_SEPOLIA_BASE_FEE_PARAMS), - (Hardfork::Canyon, OP_SEPOLIA_CANYON_BASE_FEE_PARAMS), + (EthereumHardfork::London.boxed(), OP_SEPOLIA_BASE_FEE_PARAMS), + (OptimismHardfork::Canyon.boxed(), OP_SEPOLIA_CANYON_BASE_FEE_PARAMS), ] .into(), ), @@ -248,11 +223,11 @@ pub static BASE_SEPOLIA: Lazy> = Lazy::new(|| { "0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: BASE_SEPOLIA_HARDFORKS.into(), + hardforks: OptimismHardfork::base_sepolia(), base_fee_params: BaseFeeParamsKind::Variable( vec![ - (Hardfork::London, BASE_SEPOLIA_BASE_FEE_PARAMS), - (Hardfork::Canyon, BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS), + (EthereumHardfork::London.boxed(), BASE_SEPOLIA_BASE_FEE_PARAMS), + (OptimismHardfork::Canyon.boxed(), BASE_SEPOLIA_CANYON_BASE_FEE_PARAMS), ] .into(), ), @@ -273,11 +248,11 @@ pub static BASE_MAINNET: Lazy> = Lazy::new(|| { "f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd" )), paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: BASE_MAINNET_HARDFORKS.into(), + hardforks: OptimismHardfork::base_mainnet(), base_fee_params: BaseFeeParamsKind::Variable( vec![ - (Hardfork::London, OP_BASE_FEE_PARAMS), - (Hardfork::Canyon, OP_CANYON_BASE_FEE_PARAMS), + (EthereumHardfork::London.boxed(), OP_BASE_FEE_PARAMS), + (OptimismHardfork::Canyon.boxed(), OP_CANYON_BASE_FEE_PARAMS), ] .into(), ), @@ -319,7 +294,15 @@ impl From for BaseFeeParamsKind { /// A type alias to a vector of tuples of [Hardfork] and [`BaseFeeParams`], sorted by [Hardfork] /// activation order. This is used to specify dynamic EIP-1559 parameters for chains like Optimism. #[derive(Clone, Debug, PartialEq, Eq, From)] -pub struct ForkBaseFeeParams(Vec<(Hardfork, BaseFeeParams)>); +pub struct ForkBaseFeeParams(Vec<(Box, BaseFeeParams)>); + +impl std::ops::Deref for ChainSpec { + type Target = ChainHardforks; + + fn deref(&self) -> &Self::Target { + &self.hardforks + } +} /// An Ethereum chain specification. /// @@ -342,12 +325,12 @@ pub struct ChainSpec { /// The genesis block pub genesis: Genesis, - /// The block at which [`Hardfork::Paris`] was activated and the final difficulty at this - /// block. + /// The block at which [`EthereumHardfork::Paris`] was activated and the final difficulty at + /// this block. pub paris_block_and_final_difficulty: Option<(u64, U256)>, /// The active hard forks and their activation conditions - pub hardforks: BTreeMap, + pub hardforks: ChainHardforks, /// The deposit contract deployed for `PoS` pub deposit_contract: Option, @@ -404,7 +387,7 @@ impl ChainSpec { #[inline] #[cfg(feature = "optimism")] pub fn is_optimism(&self) -> bool { - self.chain.is_optimism() || self.hardforks.contains_key(&Hardfork::Bedrock) + self.chain.is_optimism() || self.hardforks.get(OptimismHardfork::Bedrock).is_some() } /// Returns `true` if this chain contains Optimism configuration. @@ -435,7 +418,7 @@ impl ChainSpec { // If shanghai is activated, initialize the header with an empty withdrawals hash, and // empty withdrawals list. let withdrawals_root = self - .fork(Hardfork::Shanghai) + .fork(EthereumHardfork::Shanghai) .active_at_timestamp(self.genesis.timestamp) .then_some(EMPTY_WITHDRAWALS); @@ -496,7 +479,7 @@ impl ChainSpec { self.genesis.base_fee_per_gas.map(|fee| fee as u64).unwrap_or(EIP1559_INITIAL_BASE_FEE); // If London is activated at genesis, we set the initial base fee as per EIP-1559. - self.fork(Hardfork::London).active_at_block(0).then_some(genesis_base_fee) + self.hardforks.fork(EthereumHardfork::London).active_at_block(0).then_some(genesis_base_fee) } /// Get the [`BaseFeeParams`] for the chain at the given timestamp. @@ -508,7 +491,7 @@ impl ChainSpec { // first one that corresponds to a hardfork that is active at the // given timestamp. for (fork, params) in bf_params.iter().rev() { - if self.is_fork_active_at_timestamp(*fork, timestamp) { + if self.hardforks.is_fork_active_at_timestamp(fork.clone(), timestamp) { return *params } } @@ -527,7 +510,7 @@ impl ChainSpec { // first one that corresponds to a hardfork that is active at the // given timestamp. for (fork, params) in bf_params.iter().rev() { - if self.is_fork_active_at_block(*fork, block_number) { + if self.hardforks.is_fork_active_at_block(fork.clone(), block_number) { return *params } } @@ -564,130 +547,55 @@ impl ChainSpec { } /// Get the fork filter for the given hardfork - pub fn hardfork_fork_filter(&self, fork: Hardfork) -> Option { - match self.fork(fork) { + pub fn hardfork_fork_filter(&self, fork: H) -> Option { + match self.hardforks.fork(fork.clone()) { ForkCondition::Never => None, - _ => Some(self.fork_filter(self.satisfy(self.fork(fork)))), + _ => Some(self.fork_filter(self.satisfy(self.hardforks.fork(fork)))), } } - /// Returns the forks in this specification and their activation conditions. - pub const fn hardforks(&self) -> &BTreeMap { - &self.hardforks - } - /// Returns the hardfork display helper. pub fn display_hardforks(&self) -> DisplayHardforks { DisplayHardforks::new( - self.hardforks(), + &self.hardforks, self.paris_block_and_final_difficulty.map(|(block, _)| block), ) } /// Get the fork id for the given hardfork. #[inline] - pub fn hardfork_fork_id(&self, fork: Hardfork) -> Option { - match self.fork(fork) { + pub fn hardfork_fork_id(&self, fork: H) -> Option { + let condition = self.hardforks.fork(fork); + match condition { ForkCondition::Never => None, - _ => Some(self.fork_id(&self.satisfy(self.fork(fork)))), + _ => Some(self.fork_id(&self.satisfy(condition))), } } - /// Convenience method to get the fork id for [`Hardfork::Shanghai`] from a given chainspec. + /// Convenience method to get the fork id for [`EthereumHardfork::Shanghai`] from a given + /// chainspec. #[inline] pub fn shanghai_fork_id(&self) -> Option { - self.hardfork_fork_id(Hardfork::Shanghai) + self.hardfork_fork_id(EthereumHardfork::Shanghai) } - /// Convenience method to get the fork id for [`Hardfork::Cancun`] from a given chainspec. + /// Convenience method to get the fork id for [`EthereumHardfork::Cancun`] from a given + /// chainspec. #[inline] pub fn cancun_fork_id(&self) -> Option { - self.hardfork_fork_id(Hardfork::Cancun) + self.hardfork_fork_id(EthereumHardfork::Cancun) } /// Convenience method to get the latest fork id from the chainspec. Panics if chainspec has no /// hardforks. #[inline] pub fn latest_fork_id(&self) -> ForkId { - self.hardfork_fork_id(*self.hardforks().last_key_value().unwrap().0).unwrap() - } - - /// Get the fork condition for the given fork. - pub fn fork(&self, fork: Hardfork) -> ForkCondition { - self.hardforks.get(&fork).copied().unwrap_or(ForkCondition::Never) - } - - /// Get an iterator of all hardforks with their respective activation conditions. - pub fn forks_iter(&self) -> impl Iterator + '_ { - self.hardforks.iter().map(|(f, b)| (*f, *b)) - } - - /// Convenience method to check if a fork is active at a given timestamp. - #[inline] - pub fn is_fork_active_at_timestamp(&self, fork: Hardfork, timestamp: u64) -> bool { - self.fork(fork).active_at_timestamp(timestamp) - } - - /// Convenience method to check if a fork is active at a given block number - #[inline] - pub fn is_fork_active_at_block(&self, fork: Hardfork, block_number: u64) -> bool { - self.fork(fork).active_at_block(block_number) - } - - /// Convenience method to check if [`Hardfork::Shanghai`] is active at a given timestamp. - #[inline] - pub fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool { - self.is_fork_active_at_timestamp(Hardfork::Shanghai, timestamp) - } - - /// Convenience method to check if [`Hardfork::Cancun`] is active at a given timestamp. - #[inline] - pub fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool { - self.is_fork_active_at_timestamp(Hardfork::Cancun, timestamp) - } - - /// Convenience method to check if [`Hardfork::Prague`] is active at a given timestamp. - #[inline] - pub fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool { - self.is_fork_active_at_timestamp(Hardfork::Prague, timestamp) - } - - /// Convenience method to check if [`Hardfork::Byzantium`] is active at a given block number. - #[inline] - pub fn is_byzantium_active_at_block(&self, block_number: u64) -> bool { - self.fork(Hardfork::Byzantium).active_at_block(block_number) - } - - /// Convenience method to check if [`Hardfork::SpuriousDragon`] is active at a given block - /// number. - #[inline] - pub fn is_spurious_dragon_active_at_block(&self, block_number: u64) -> bool { - self.fork(Hardfork::SpuriousDragon).active_at_block(block_number) - } - - /// Convenience method to check if [`Hardfork::Homestead`] is active at a given block number. - #[inline] - pub fn is_homestead_active_at_block(&self, block_number: u64) -> bool { - self.fork(Hardfork::Homestead).active_at_block(block_number) - } - - /// The Paris hardfork (merge) is activated via block number. If we have knowledge of the block, - /// this function will return true if the block number is greater than or equal to the Paris - /// (merge) block. - pub fn is_paris_active_at_block(&self, block_number: u64) -> Option { - self.paris_block_and_final_difficulty.map(|(paris_block, _)| block_number >= paris_block) - } - - /// Convenience method to check if [`Hardfork::Bedrock`] is active at a given block number. - #[cfg(feature = "optimism")] - #[inline] - pub fn is_bedrock_active_at_block(&self, block_number: u64) -> bool { - self.fork(Hardfork::Bedrock).active_at_block(block_number) + self.hardfork_fork_id(self.hardforks.last().unwrap().0).unwrap() } /// Creates a [`ForkFilter`] for the block described by [Head]. pub fn fork_filter(&self, head: Head) -> ForkFilter { - let forks = self.forks_iter().filter_map(|(_, condition)| { + let forks = self.hardforks.forks_iter().filter_map(|(_, condition)| { // We filter out TTD-based forks w/o a pre-known block since those do not show up in the // fork filter. Some(match condition { @@ -707,7 +615,7 @@ impl ChainSpec { let mut current_applied = 0; // handle all block forks before handling timestamp based forks. see: https://eips.ethereum.org/EIPS/eip-6122 - for (_, cond) in self.forks_iter() { + for (_, cond) in self.hardforks.forks_iter() { // handle block based forks and the sepolia merge netsplit block edge case (TTD // ForkCondition with Some(block)) if let ForkCondition::Block(block) | @@ -729,7 +637,7 @@ impl ChainSpec { // timestamp are ALWAYS applied after the merge. // // this filter ensures that no block-based forks are returned - for timestamp in self.forks_iter().filter_map(|(_, cond)| { + for timestamp in self.hardforks.forks_iter().filter_map(|(_, cond)| { cond.as_timestamp().filter(|time| time > &self.genesis.timestamp) }) { let cond = ForkCondition::Timestamp(timestamp); @@ -773,7 +681,7 @@ impl ChainSpec { /// /// Note: this returns None if the `ChainSpec` is not configured with a TTD/Timestamp fork. pub(crate) fn last_block_fork_before_merge_or_timestamp(&self) -> Option { - let mut hardforks_iter = self.forks_iter().peekable(); + let mut hardforks_iter = self.hardforks.forks_iter().peekable(); while let Some((_, curr_cond)) = hardforks_iter.next() { if let Some((_, next_cond)) = hardforks_iter.peek() { // peek and find the first occurrence of ForkCondition::TTD (merge) , or in @@ -839,37 +747,37 @@ impl From for ChainSpec { // Block-based hardforks let hardfork_opts = [ - (Hardfork::Homestead, genesis.config.homestead_block), - (Hardfork::Dao, genesis.config.dao_fork_block), - (Hardfork::Tangerine, genesis.config.eip150_block), - (Hardfork::SpuriousDragon, genesis.config.eip155_block), - (Hardfork::Byzantium, genesis.config.byzantium_block), - (Hardfork::Constantinople, genesis.config.constantinople_block), - (Hardfork::Petersburg, genesis.config.petersburg_block), - (Hardfork::Istanbul, genesis.config.istanbul_block), - (Hardfork::MuirGlacier, genesis.config.muir_glacier_block), - (Hardfork::Berlin, genesis.config.berlin_block), - (Hardfork::London, genesis.config.london_block), - (Hardfork::ArrowGlacier, genesis.config.arrow_glacier_block), - (Hardfork::GrayGlacier, genesis.config.gray_glacier_block), + (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block), + (EthereumHardfork::Dao.boxed(), genesis.config.dao_fork_block), + (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block), + (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block), + (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block), + (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block), + (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block), + (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block), + (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block), + (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block), + (EthereumHardfork::London.boxed(), genesis.config.london_block), + (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block), + (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block), #[cfg(feature = "optimism")] - (Hardfork::Bedrock, optimism_genesis_info.bedrock_block), + (OptimismHardfork::Bedrock.boxed(), optimism_genesis_info.bedrock_block), ]; let mut hardforks = hardfork_opts - .iter() - .filter_map(|(hardfork, opt)| opt.map(|block| (*hardfork, ForkCondition::Block(block)))) - .collect::>(); + .into_iter() + .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block)))) + .collect::>(); // Paris let paris_block_and_final_difficulty = if let Some(ttd) = genesis.config.terminal_total_difficulty { - hardforks.insert( - Hardfork::Paris, + hardforks.push(( + EthereumHardfork::Paris.boxed(), ForkCondition::TTD { total_difficulty: ttd, fork_block: genesis.config.merge_netsplit_block, }, - ); + )); genesis.config.merge_netsplit_block.map(|block| (block, ttd)) } else { @@ -878,28 +786,45 @@ impl From for ChainSpec { // Time-based hardforks let time_hardfork_opts = [ - (Hardfork::Shanghai, genesis.config.shanghai_time), - (Hardfork::Cancun, genesis.config.cancun_time), - (Hardfork::Prague, genesis.config.prague_time), + (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time), + (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time), + (EthereumHardfork::Prague.boxed(), genesis.config.prague_time), #[cfg(feature = "optimism")] - (Hardfork::Regolith, optimism_genesis_info.regolith_time), + (OptimismHardfork::Regolith.boxed(), optimism_genesis_info.regolith_time), #[cfg(feature = "optimism")] - (Hardfork::Canyon, optimism_genesis_info.canyon_time), + (OptimismHardfork::Canyon.boxed(), optimism_genesis_info.canyon_time), #[cfg(feature = "optimism")] - (Hardfork::Ecotone, optimism_genesis_info.ecotone_time), + (OptimismHardfork::Ecotone.boxed(), optimism_genesis_info.ecotone_time), #[cfg(feature = "optimism")] - (Hardfork::Fjord, optimism_genesis_info.fjord_time), + (OptimismHardfork::Fjord.boxed(), optimism_genesis_info.fjord_time), ]; let time_hardforks = time_hardfork_opts - .iter() + .into_iter() .filter_map(|(hardfork, opt)| { - opt.map(|time| (*hardfork, ForkCondition::Timestamp(time))) + opt.map(|time| (hardfork, ForkCondition::Timestamp(time))) }) - .collect::>(); + .collect::>(); hardforks.extend(time_hardforks); + // Uses ethereum or optimism main chains to find proper order + #[cfg(not(feature = "optimism"))] + let mainnet_hardforks: ChainHardforks = EthereumHardfork::mainnet().into(); + #[cfg(not(feature = "optimism"))] + let mainnet_order = mainnet_hardforks.forks_iter(); + #[cfg(feature = "optimism")] + let mainnet_hardforks = OptimismHardfork::op_mainnet(); + #[cfg(feature = "optimism")] + let mainnet_order = mainnet_hardforks.forks_iter(); + + let mut ordered_hardforks = Vec::with_capacity(hardforks.len()); + for (hardfork, _) in mainnet_order { + if let Some(pos) = hardforks.iter().position(|(e, _)| **e == *hardfork) { + ordered_hardforks.push(hardforks[pos].clone()); + } + } + // NOTE: in full node, we prune all receipts except the deposit contract's. We do not // have the deployment block in the genesis file, so we use block zero. We use the same // deposit topic as the mainnet contract if we have the deposit contract address in the @@ -912,7 +837,7 @@ impl From for ChainSpec { chain: genesis.config.chain_id.into(), genesis, genesis_hash: None, - hardforks, + hardforks: ChainHardforks::new(hardforks), paris_block_and_final_difficulty, deposit_contract, #[cfg(feature = "optimism")] @@ -927,7 +852,7 @@ impl From for ChainSpec { pub struct ChainSpecBuilder { chain: Option, genesis: Option, - hardforks: BTreeMap, + hardforks: ChainHardforks, } impl ChainSpecBuilder { @@ -939,7 +864,9 @@ impl ChainSpecBuilder { hardforks: MAINNET.hardforks.clone(), } } +} +impl ChainSpecBuilder { /// Set the chain ID pub const fn chain(mut self, chain: Chain) -> Self { self.chain = Some(chain); @@ -953,14 +880,14 @@ impl ChainSpecBuilder { } /// Add the given fork with the given activation condition to the spec. - pub fn with_fork(mut self, fork: Hardfork, condition: ForkCondition) -> Self { + pub fn with_fork(mut self, fork: EthereumHardfork, condition: ForkCondition) -> Self { self.hardforks.insert(fork, condition); self } /// Remove the given fork from the spec. - pub fn without_fork(mut self, fork: Hardfork) -> Self { - self.hardforks.remove(&fork); + pub fn without_fork(mut self, fork: EthereumHardfork) -> Self { + self.hardforks.remove(fork); self } @@ -969,77 +896,77 @@ impl ChainSpecBuilder { /// Does not set the merge netsplit block. pub fn paris_at_ttd(self, ttd: U256) -> Self { self.with_fork( - Hardfork::Paris, + EthereumHardfork::Paris, ForkCondition::TTD { total_difficulty: ttd, fork_block: None }, ) } /// Enable Frontier at genesis. pub fn frontier_activated(mut self) -> Self { - self.hardforks.insert(Hardfork::Frontier, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Frontier, ForkCondition::Block(0)); self } /// Enable Homestead at genesis. pub fn homestead_activated(mut self) -> Self { self = self.frontier_activated(); - self.hardforks.insert(Hardfork::Homestead, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Homestead, ForkCondition::Block(0)); self } /// Enable Tangerine at genesis. pub fn tangerine_whistle_activated(mut self) -> Self { self = self.homestead_activated(); - self.hardforks.insert(Hardfork::Tangerine, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Tangerine, ForkCondition::Block(0)); self } /// Enable Spurious Dragon at genesis. pub fn spurious_dragon_activated(mut self) -> Self { self = self.tangerine_whistle_activated(); - self.hardforks.insert(Hardfork::SpuriousDragon, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0)); self } /// Enable Byzantium at genesis. pub fn byzantium_activated(mut self) -> Self { self = self.spurious_dragon_activated(); - self.hardforks.insert(Hardfork::Byzantium, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Byzantium, ForkCondition::Block(0)); self } /// Enable Constantinople at genesis. pub fn constantinople_activated(mut self) -> Self { self = self.byzantium_activated(); - self.hardforks.insert(Hardfork::Constantinople, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Constantinople, ForkCondition::Block(0)); self } /// Enable Petersburg at genesis. pub fn petersburg_activated(mut self) -> Self { self = self.constantinople_activated(); - self.hardforks.insert(Hardfork::Petersburg, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Petersburg, ForkCondition::Block(0)); self } /// Enable Istanbul at genesis. pub fn istanbul_activated(mut self) -> Self { self = self.petersburg_activated(); - self.hardforks.insert(Hardfork::Istanbul, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Istanbul, ForkCondition::Block(0)); self } /// Enable Berlin at genesis. pub fn berlin_activated(mut self) -> Self { self = self.istanbul_activated(); - self.hardforks.insert(Hardfork::Berlin, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::Berlin, ForkCondition::Block(0)); self } /// Enable London at genesis. pub fn london_activated(mut self) -> Self { self = self.berlin_activated(); - self.hardforks.insert(Hardfork::London, ForkCondition::Block(0)); + self.hardforks.insert(EthereumHardfork::London, ForkCondition::Block(0)); self } @@ -1047,7 +974,7 @@ impl ChainSpecBuilder { pub fn paris_activated(mut self) -> Self { self = self.london_activated(); self.hardforks.insert( - Hardfork::Paris, + EthereumHardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }, ); self @@ -1056,14 +983,14 @@ impl ChainSpecBuilder { /// Enable Shanghai at genesis. pub fn shanghai_activated(mut self) -> Self { self = self.paris_activated(); - self.hardforks.insert(Hardfork::Shanghai, ForkCondition::Timestamp(0)); + self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0)); self } /// Enable Cancun at genesis. pub fn cancun_activated(mut self) -> Self { self = self.shanghai_activated(); - self.hardforks.insert(Hardfork::Cancun, ForkCondition::Timestamp(0)); + self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0)); self } @@ -1071,7 +998,7 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn bedrock_activated(mut self) -> Self { self = self.paris_activated(); - self.hardforks.insert(Hardfork::Bedrock, ForkCondition::Block(0)); + self.hardforks.insert(OptimismHardfork::Bedrock, ForkCondition::Block(0)); self } @@ -1079,7 +1006,7 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn regolith_activated(mut self) -> Self { self = self.bedrock_activated(); - self.hardforks.insert(Hardfork::Regolith, ForkCondition::Timestamp(0)); + self.hardforks.insert(OptimismHardfork::Regolith, ForkCondition::Timestamp(0)); self } @@ -1088,8 +1015,8 @@ impl ChainSpecBuilder { pub fn canyon_activated(mut self) -> Self { self = self.regolith_activated(); // Canyon also activates changes from L1's Shanghai hardfork - self.hardforks.insert(Hardfork::Shanghai, ForkCondition::Timestamp(0)); - self.hardforks.insert(Hardfork::Canyon, ForkCondition::Timestamp(0)); + self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0)); + self.hardforks.insert(OptimismHardfork::Canyon, ForkCondition::Timestamp(0)); self } @@ -1097,8 +1024,8 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn ecotone_activated(mut self) -> Self { self = self.canyon_activated(); - self.hardforks.insert(Hardfork::Cancun, ForkCondition::Timestamp(0)); - self.hardforks.insert(Hardfork::Ecotone, ForkCondition::Timestamp(0)); + self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0)); + self.hardforks.insert(OptimismHardfork::Ecotone, ForkCondition::Timestamp(0)); self } @@ -1106,7 +1033,7 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn fjord_activated(mut self) -> Self { self = self.ecotone_activated(); - self.hardforks.insert(Hardfork::Fjord, ForkCondition::Timestamp(0)); + self.hardforks.insert(OptimismHardfork::Fjord, ForkCondition::Timestamp(0)); self } @@ -1118,9 +1045,9 @@ impl ChainSpecBuilder { /// [`Self::genesis`]) pub fn build(self) -> ChainSpec { let paris_block_and_final_difficulty = { - self.hardforks.get(&Hardfork::Paris).and_then(|cond| { + self.hardforks.get(EthereumHardfork::Paris).and_then(|cond| { if let ForkCondition::TTD { fork_block, total_difficulty } = cond { - fork_block.map(|fork_block| (fork_block, *total_difficulty)) + fork_block.map(|fork_block| (fork_block, total_difficulty)) } else { None } @@ -1208,11 +1135,11 @@ impl OptimismGenesisInfo { BaseFeeParamsKind::Variable( vec![ ( - Hardfork::London, + EthereumHardfork::London.boxed(), BaseFeeParams::new(denominator as u128, elasticity as u128), ), ( - Hardfork::Canyon, + OptimismHardfork::Canyon.boxed(), BaseFeeParams::new(canyon_denominator as u128, elasticity as u128), ), ] @@ -1236,10 +1163,14 @@ mod tests { use alloy_chains::Chain; use alloy_genesis::{ChainConfig, GenesisAccount}; use alloy_primitives::{b256, hex}; + use core::ops::Deref; use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head}; use reth_trie_common::TrieAccount; use std::{collections::HashMap, str::FromStr}; + #[cfg(feature = "optimism")] + use reth_ethereum_forks::OptimismHardforks; + fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) { for (block, expected_id) in cases { let computed_id = spec.fork_id(block); @@ -1251,14 +1182,14 @@ mod tests { } } - fn test_hardfork_fork_ids(spec: &ChainSpec, cases: &[(Hardfork, ForkId)]) { + fn test_hardfork_fork_ids(spec: &ChainSpec, cases: &[(EthereumHardfork, ForkId)]) { for (hardfork, expected_id) in cases { if let Some(computed_id) = spec.hardfork_fork_id(*hardfork) { assert_eq!( expected_id, &computed_id, "Expected fork ID {expected_id:?}, computed fork ID {computed_id:?} for hardfork {hardfork}" ); - if matches!(hardfork, Hardfork::Shanghai) { + if matches!(hardfork, EthereumHardfork::Shanghai) { if let Some(shangai_id) = spec.shanghai_fork_id() { assert_eq!( expected_id, &shangai_id, @@ -1304,8 +1235,8 @@ Post-merge hard forks (timestamp based): let spec = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(Genesis::default()) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Shanghai, ForkCondition::Never) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Shanghai, ForkCondition::Never) .build(); assert_eq!( spec.display_hardforks().to_string(), @@ -1320,21 +1251,21 @@ Post-merge hard forks (timestamp based): let spec = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(Genesis::default()) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Homestead, ForkCondition::Block(0)) - .with_fork(Hardfork::Tangerine, ForkCondition::Block(0)) - .with_fork(Hardfork::SpuriousDragon, ForkCondition::Block(0)) - .with_fork(Hardfork::Byzantium, ForkCondition::Block(0)) - .with_fork(Hardfork::Constantinople, ForkCondition::Block(0)) - .with_fork(Hardfork::Istanbul, ForkCondition::Block(0)) - .with_fork(Hardfork::MuirGlacier, ForkCondition::Block(0)) - .with_fork(Hardfork::Berlin, ForkCondition::Block(0)) - .with_fork(Hardfork::London, ForkCondition::Block(0)) - .with_fork(Hardfork::ArrowGlacier, ForkCondition::Block(0)) - .with_fork(Hardfork::GrayGlacier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::SpuriousDragon, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Istanbul, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::MuirGlacier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Berlin, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::London, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::GrayGlacier, ForkCondition::Block(0)) .build(); - assert_eq!(spec.hardforks().len(), 12, "12 forks should be active."); + assert_eq!(spec.deref().len(), 12, "12 forks should be active."); assert_eq!( spec.fork_id(&Head { number: 1, ..Default::default() }), ForkId { hash: ForkHash::from(spec.genesis_hash()), next: 0 }, @@ -1348,16 +1279,16 @@ Post-merge hard forks (timestamp based): let unique_spec = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(empty_genesis.clone()) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Homestead, ForkCondition::Block(1)) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1)) .build(); let duplicate_spec = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(empty_genesis) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Homestead, ForkCondition::Block(1)) - .with_fork(Hardfork::Tangerine, ForkCondition::Block(1)) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(1)) + .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(1)) .build(); assert_eq!( @@ -1374,9 +1305,9 @@ Post-merge hard forks (timestamp based): let happy_path_case = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(empty_genesis.clone()) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Homestead, ForkCondition::Block(73)) - .with_fork(Hardfork::Shanghai, ForkCondition::Timestamp(11313123)) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73)) + .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123)) .build(); let happy_path_head = happy_path_case.satisfy(ForkCondition::Timestamp(11313123)); let happy_path_expected = Head { number: 73, timestamp: 11313123, ..Default::default() }; @@ -1388,10 +1319,10 @@ Post-merge hard forks (timestamp based): let multiple_timestamp_fork_case = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(empty_genesis.clone()) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Homestead, ForkCondition::Block(73)) - .with_fork(Hardfork::Shanghai, ForkCondition::Timestamp(11313123)) - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(11313398)) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73)) + .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123)) + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(11313398)) .build(); let multi_timestamp_head = multiple_timestamp_fork_case.satisfy(ForkCondition::Timestamp(11313398)); @@ -1405,7 +1336,7 @@ Post-merge hard forks (timestamp based): let no_block_fork_case = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(empty_genesis.clone()) - .with_fork(Hardfork::Shanghai, ForkCondition::Timestamp(11313123)) + .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123)) .build(); let no_block_fork_head = no_block_fork_case.satisfy(ForkCondition::Timestamp(11313123)); let no_block_fork_expected = Head { number: 0, timestamp: 11313123, ..Default::default() }; @@ -1417,16 +1348,16 @@ Post-merge hard forks (timestamp based): let fork_cond_ttd_blocknum_case = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(empty_genesis.clone()) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Homestead, ForkCondition::Block(73)) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73)) .with_fork( - Hardfork::Paris, + EthereumHardfork::Paris, ForkCondition::TTD { fork_block: Some(101), total_difficulty: U256::from(10_790_000), }, ) - .with_fork(Hardfork::Shanghai, ForkCondition::Timestamp(11313123)) + .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(11313123)) .build(); let fork_cond_ttd_blocknum_head = fork_cond_ttd_blocknum_case.satisfy(ForkCondition::Timestamp(11313123)); @@ -1443,8 +1374,8 @@ Post-merge hard forks (timestamp based): let fork_cond_block_only_case = ChainSpec::builder() .chain(Chain::mainnet()) .genesis(empty_genesis) - .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) - .with_fork(Hardfork::Homestead, ForkCondition::Block(73)) + .with_fork(EthereumHardfork::Frontier, ForkCondition::Block(0)) + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(73)) .build(); let fork_cond_block_only_head = fork_cond_block_only_case.satisfy(ForkCondition::Block(73)); let fork_cond_block_only_expected = Head { number: 73, ..Default::default() }; @@ -1472,63 +1403,69 @@ Post-merge hard forks (timestamp based): &MAINNET, &[ ( - Hardfork::Frontier, + EthereumHardfork::Frontier, ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 }, ), ( - Hardfork::Homestead, + EthereumHardfork::Homestead, ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 }, ), - (Hardfork::Dao, ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 }), ( - Hardfork::Tangerine, + EthereumHardfork::Dao, + ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 }, + ), + ( + EthereumHardfork::Tangerine, ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 }, ), ( - Hardfork::SpuriousDragon, + EthereumHardfork::SpuriousDragon, ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 }, ), ( - Hardfork::Byzantium, + EthereumHardfork::Byzantium, ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 }, ), ( - Hardfork::Constantinople, + EthereumHardfork::Constantinople, ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 }, ), ( - Hardfork::Petersburg, + EthereumHardfork::Petersburg, ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 }, ), ( - Hardfork::Istanbul, + EthereumHardfork::Istanbul, ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 }, ), ( - Hardfork::MuirGlacier, + EthereumHardfork::MuirGlacier, ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 }, ), ( - Hardfork::Berlin, + EthereumHardfork::Berlin, ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 }, ), ( - Hardfork::London, + EthereumHardfork::London, ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 }, ), ( - Hardfork::ArrowGlacier, + EthereumHardfork::ArrowGlacier, ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 }, ), ( - Hardfork::GrayGlacier, + EthereumHardfork::GrayGlacier, ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 }, ), ( - Hardfork::Shanghai, + EthereumHardfork::Shanghai, ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 }, ), - (Hardfork::Cancun, ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 }), + ( + EthereumHardfork::Cancun, + ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 0 }, + ), ], ); } @@ -1539,50 +1476,53 @@ Post-merge hard forks (timestamp based): &GOERLI, &[ ( - Hardfork::Frontier, + EthereumHardfork::Frontier, ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, ), ( - Hardfork::Homestead, + EthereumHardfork::Homestead, ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, ), ( - Hardfork::Tangerine, + EthereumHardfork::Tangerine, ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, ), ( - Hardfork::SpuriousDragon, + EthereumHardfork::SpuriousDragon, ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, ), ( - Hardfork::Byzantium, + EthereumHardfork::Byzantium, ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, ), ( - Hardfork::Constantinople, + EthereumHardfork::Constantinople, ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, ), ( - Hardfork::Petersburg, + EthereumHardfork::Petersburg, ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, ), ( - Hardfork::Istanbul, + EthereumHardfork::Istanbul, ForkId { hash: ForkHash([0xc2, 0x5e, 0xfa, 0x5c]), next: 4460644 }, ), ( - Hardfork::Berlin, + EthereumHardfork::Berlin, ForkId { hash: ForkHash([0x75, 0x7a, 0x1c, 0x47]), next: 5062605 }, ), ( - Hardfork::London, + EthereumHardfork::London, ForkId { hash: ForkHash([0xb8, 0xc6, 0x29, 0x9d]), next: 1678832736 }, ), ( - Hardfork::Shanghai, + EthereumHardfork::Shanghai, ForkId { hash: ForkHash([0xf9, 0x84, 0x3a, 0xbf]), next: 1705473120 }, ), - (Hardfork::Cancun, ForkId { hash: ForkHash([0x70, 0xcc, 0x14, 0xe2]), next: 0 }), + ( + EthereumHardfork::Cancun, + ForkId { hash: ForkHash([0x70, 0xcc, 0x14, 0xe2]), next: 0 }, + ), ], ); } @@ -1593,54 +1533,57 @@ Post-merge hard forks (timestamp based): &SEPOLIA, &[ ( - Hardfork::Frontier, + EthereumHardfork::Frontier, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Homestead, + EthereumHardfork::Homestead, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Tangerine, + EthereumHardfork::Tangerine, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::SpuriousDragon, + EthereumHardfork::SpuriousDragon, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Byzantium, + EthereumHardfork::Byzantium, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Constantinople, + EthereumHardfork::Constantinople, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Petersburg, + EthereumHardfork::Petersburg, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Istanbul, + EthereumHardfork::Istanbul, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Berlin, + EthereumHardfork::Berlin, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::London, + EthereumHardfork::London, ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 }, ), ( - Hardfork::Paris, + EthereumHardfork::Paris, ForkId { hash: ForkHash([0xb9, 0x6c, 0xbd, 0x13]), next: 1677557088 }, ), ( - Hardfork::Shanghai, + EthereumHardfork::Shanghai, ForkId { hash: ForkHash([0xf7, 0xf9, 0xbc, 0x08]), next: 1706655072 }, ), - (Hardfork::Cancun, ForkId { hash: ForkHash([0x88, 0xcf, 0x81, 0xd9]), next: 0 }), + ( + EthereumHardfork::Cancun, + ForkId { hash: ForkHash([0x88, 0xcf, 0x81, 0xd9]), next: 0 }, + ), ], ); } @@ -2141,8 +2084,8 @@ Post-merge hard forks (timestamp based): cancun_time: u64, ) -> ChainSpec { builder - .with_fork(Hardfork::Shanghai, ForkCondition::Timestamp(shanghai_time)) - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(cancun_time)) + .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(shanghai_time)) + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(cancun_time)) .build() } @@ -2197,14 +2140,14 @@ Post-merge hard forks (timestamp based): let terminal_block_ttd = U256::from(58750003716598352816469_u128); let terminal_block_difficulty = U256::from(11055787484078698_u128); assert!(!chainspec - .fork(Hardfork::Paris) + .fork(EthereumHardfork::Paris) .active_at_ttd(terminal_block_ttd, terminal_block_difficulty)); // Check that Paris is active on first PoS block #15537394. let first_pos_block_ttd = U256::from(58750003716598352816469_u128); let first_pos_difficulty = U256::ZERO; assert!(chainspec - .fork(Hardfork::Paris) + .fork(EthereumHardfork::Paris) .active_at_ttd(first_pos_block_ttd, first_pos_difficulty)); } @@ -2280,55 +2223,64 @@ Post-merge hard forks (timestamp based): // assert a bunch of hardforks that should be set assert_eq!( - chainspec.hardforks.get(&Hardfork::Homestead).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::Homestead).unwrap(), + ForkCondition::Block(0) + ); + assert_eq!( + chainspec.hardforks.get(EthereumHardfork::Tangerine).unwrap(), + ForkCondition::Block(0) + ); + assert_eq!( + chainspec.hardforks.get(EthereumHardfork::SpuriousDragon).unwrap(), + ForkCondition::Block(0) ); assert_eq!( - chainspec.hardforks.get(&Hardfork::Tangerine).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::Byzantium).unwrap(), + ForkCondition::Block(0) ); assert_eq!( - chainspec.hardforks.get(&Hardfork::SpuriousDragon).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::Constantinople).unwrap(), + ForkCondition::Block(0) ); assert_eq!( - chainspec.hardforks.get(&Hardfork::Byzantium).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::Petersburg).unwrap(), + ForkCondition::Block(0) ); assert_eq!( - chainspec.hardforks.get(&Hardfork::Constantinople).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::Istanbul).unwrap(), + ForkCondition::Block(0) ); assert_eq!( - chainspec.hardforks.get(&Hardfork::Petersburg).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::MuirGlacier).unwrap(), + ForkCondition::Block(0) ); - assert_eq!(chainspec.hardforks.get(&Hardfork::Istanbul).unwrap(), &ForkCondition::Block(0)); assert_eq!( - chainspec.hardforks.get(&Hardfork::MuirGlacier).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::Berlin).unwrap(), + ForkCondition::Block(0) ); - assert_eq!(chainspec.hardforks.get(&Hardfork::Berlin).unwrap(), &ForkCondition::Block(0)); - assert_eq!(chainspec.hardforks.get(&Hardfork::London).unwrap(), &ForkCondition::Block(0)); assert_eq!( - chainspec.hardforks.get(&Hardfork::ArrowGlacier).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::London).unwrap(), + ForkCondition::Block(0) ); assert_eq!( - chainspec.hardforks.get(&Hardfork::GrayGlacier).unwrap(), - &ForkCondition::Block(0) + chainspec.hardforks.get(EthereumHardfork::ArrowGlacier).unwrap(), + ForkCondition::Block(0) + ); + assert_eq!( + chainspec.hardforks.get(EthereumHardfork::GrayGlacier).unwrap(), + ForkCondition::Block(0) ); // including time based hardforks assert_eq!( - chainspec.hardforks.get(&Hardfork::Shanghai).unwrap(), - &ForkCondition::Timestamp(0) + chainspec.hardforks.get(EthereumHardfork::Shanghai).unwrap(), + ForkCondition::Timestamp(0) ); // including time based hardforks assert_eq!( - chainspec.hardforks.get(&Hardfork::Cancun).unwrap(), - &ForkCondition::Timestamp(1) + chainspec.hardforks.get(EthereumHardfork::Cancun).unwrap(), + ForkCondition::Timestamp(1) ); // alloc key -> expected rlp mapping @@ -2424,14 +2376,14 @@ Post-merge hard forks (timestamp based): hex!("9a6049ac535e3dc7436c189eaa81c73f35abd7f282ab67c32944ff0301d63360").into(); assert_eq!(chainspec.genesis_header().state_root, expected_state_root); let hard_forks = vec![ - Hardfork::Byzantium, - Hardfork::Homestead, - Hardfork::Istanbul, - Hardfork::Petersburg, - Hardfork::Constantinople, + EthereumHardfork::Byzantium, + EthereumHardfork::Homestead, + EthereumHardfork::Istanbul, + EthereumHardfork::Petersburg, + EthereumHardfork::Constantinople, ]; - for ref fork in hard_forks { - assert_eq!(chainspec.hardforks.get(fork).unwrap(), &ForkCondition::Block(0)); + for fork in hard_forks { + assert_eq!(chainspec.hardforks.get(fork).unwrap(), ForkCondition::Block(0)); } let expected_hash: B256 = @@ -2680,7 +2632,7 @@ Post-merge hard forks (timestamp based): #[test] fn holesky_paris_activated_at_genesis() { assert!(HOLESKY - .fork(Hardfork::Paris) + .fork(EthereumHardfork::Paris) .active_at_ttd(HOLESKY.genesis.difficulty, HOLESKY.genesis.difficulty)); } @@ -2734,13 +2686,16 @@ Post-merge hard forks (timestamp based): chain: Chain::mainnet(), genesis: Genesis::default(), genesis_hash: None, - hardforks: BTreeMap::from([(Hardfork::Frontier, ForkCondition::Never)]), + hardforks: ChainHardforks::new(vec![( + EthereumHardfork::Frontier.boxed(), + ForkCondition::Never, + )]), paris_block_and_final_difficulty: None, deposit_contract: None, ..Default::default() }; - assert_eq!(spec.hardfork_fork_id(Hardfork::Frontier), None); + assert_eq!(spec.hardfork_fork_id(EthereumHardfork::Frontier), None); } #[test] @@ -2749,13 +2704,16 @@ Post-merge hard forks (timestamp based): chain: Chain::mainnet(), genesis: Genesis::default(), genesis_hash: None, - hardforks: BTreeMap::from([(Hardfork::Shanghai, ForkCondition::Never)]), + hardforks: ChainHardforks::new(vec![( + EthereumHardfork::Shanghai.boxed(), + ForkCondition::Never, + )]), paris_block_and_final_difficulty: None, deposit_contract: None, ..Default::default() }; - assert_eq!(spec.hardfork_fork_filter(Hardfork::Shanghai), None); + assert_eq!(spec.hardfork_fork_filter(EthereumHardfork::Shanghai), None); } #[test] @@ -2873,17 +2831,17 @@ Post-merge hard forks (timestamp based): BaseFeeParamsKind::Constant(BaseFeeParams::new(70, 60)) ); - assert!(!chain_spec.is_fork_active_at_block(Hardfork::Bedrock, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Canyon, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Ecotone, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Fjord, 0)); + assert!(!chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 0)); - assert!(chain_spec.is_fork_active_at_block(Hardfork::Bedrock, 10)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, 20)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Canyon, 30)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Ecotone, 40)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Fjord, 50)); + assert!(chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 10)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 20)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 30)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 40)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 50)); } #[cfg(feature = "optimism")] @@ -2934,24 +2892,24 @@ Post-merge hard forks (timestamp based): chain_spec.base_fee_params, BaseFeeParamsKind::Variable( vec![ - (Hardfork::London, BaseFeeParams::new(70, 60)), - (Hardfork::Canyon, BaseFeeParams::new(80, 60)), + (EthereumHardfork::London.boxed(), BaseFeeParams::new(70, 60)), + (OptimismHardfork::Canyon.boxed(), BaseFeeParams::new(80, 60)), ] .into() ) ); - assert!(!chain_spec.is_fork_active_at_block(Hardfork::Bedrock, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Canyon, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Ecotone, 0)); - assert!(!chain_spec.is_fork_active_at_timestamp(Hardfork::Fjord, 0)); + assert!(!chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 0)); - assert!(chain_spec.is_fork_active_at_block(Hardfork::Bedrock, 10)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, 20)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Canyon, 30)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Ecotone, 40)); - assert!(chain_spec.is_fork_active_at_timestamp(Hardfork::Fjord, 50)); + assert!(chain_spec.is_fork_active_at_block(OptimismHardfork::Bedrock, 10)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 20)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, 30)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, 40)); + assert!(chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, 50)); } #[cfg(feature = "optimism")] @@ -2992,7 +2950,10 @@ Post-merge hard forks (timestamp based): let actual_chain_id = genesis.config.chain_id; assert_eq!(actual_chain_id, 8453); - assert_eq!(chainspec.hardforks.get(&Hardfork::Istanbul), Some(&ForkCondition::Block(0))); + assert_eq!( + chainspec.hardforks.get(EthereumHardfork::Istanbul), + Some(ForkCondition::Block(0)) + ); let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock"); assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(0)).as_ref()); @@ -3021,8 +2982,8 @@ Post-merge hard forks (timestamp based): }) ); - assert!(chainspec.is_fork_active_at_block(Hardfork::Bedrock, 0)); + assert!(chainspec.is_fork_active_at_block(OptimismHardfork::Bedrock, 0)); - assert!(chainspec.is_fork_active_at_timestamp(Hardfork::Regolith, 20)); + assert!(chainspec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, 20)); } } diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index ba6c67487e20..2fd93e7caab6 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -16,7 +16,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use reth_beacon_consensus::BeaconEngineMessage; -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_engine_primitives::EngineTypes; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 3391a1de95e5..d08dcc259231 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -2492,7 +2492,7 @@ mod tests { use super::*; use alloy_genesis::Genesis; use reth_db::test_utils::create_test_static_files_dir; - use reth_primitives::{Hardfork, U256}; + use reth_primitives::{EthereumHardfork, U256}; use reth_provider::{ providers::StaticFileProvider, test_utils::blocks::BlockchainTestData, }; @@ -2721,9 +2721,9 @@ mod tests { async fn payload_pre_merge() { let data = BlockchainTestData::default(); let mut block1 = data.blocks[0].0.block.clone(); - block1 - .header - .set_difficulty(MAINNET.fork(Hardfork::Paris).ttd().unwrap() - U256::from(1)); + block1.header.set_difficulty( + MAINNET.fork(EthereumHardfork::Paris).ttd().unwrap() - U256::from(1), + ); block1 = block1.unseal().seal_slow(); let (block2, exec_result2) = data.blocks[1].clone(); let mut block2 = block2.unseal().block; diff --git a/crates/consensus/common/src/calc.rs b/crates/consensus/common/src/calc.rs index 27320700b33e..feb7bff0d908 100644 --- a/crates/consensus/common/src/calc.rs +++ b/crates/consensus/common/src/calc.rs @@ -1,4 +1,4 @@ -use reth_chainspec::{Chain, ChainSpec, Hardfork}; +use reth_chainspec::{Chain, ChainSpec, EthereumHardfork}; use reth_primitives::{constants::ETH_TO_WEI, BlockNumber, U256}; /// Calculates the base block reward. @@ -26,7 +26,7 @@ pub fn base_block_reward( block_difficulty: U256, total_difficulty: U256, ) -> Option { - if chain_spec.fork(Hardfork::Paris).active_at_ttd(total_difficulty, block_difficulty) || + if chain_spec.fork(EthereumHardfork::Paris).active_at_ttd(total_difficulty, block_difficulty) || chain_spec.chain == Chain::goerli() { None @@ -39,9 +39,9 @@ pub fn base_block_reward( /// /// Caution: The caller must ensure that the block number is before the merge. pub fn base_block_reward_pre_merge(chain_spec: &ChainSpec, block_number: BlockNumber) -> u128 { - if chain_spec.fork(Hardfork::Constantinople).active_at_block(block_number) { + if chain_spec.fork(EthereumHardfork::Constantinople).active_at_block(block_number) { ETH_TO_WEI * 2 - } else if chain_spec.fork(Hardfork::Byzantium).active_at_block(block_number) { + } else if chain_spec.fork(EthereumHardfork::Byzantium).active_at_block(block_number) { ETH_TO_WEI * 3 } else { ETH_TO_WEI * 5 diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 31bde166ece0..dc04d74dc9ac 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -1,6 +1,6 @@ //! Collection of methods for block validation. -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus::ConsensusError; use reth_primitives::{ constants::{ @@ -8,7 +8,7 @@ use reth_primitives::{ MAXIMUM_EXTRA_DATA_SIZE, }, eip4844::calculate_excess_blob_gas, - GotExpected, Hardfork, Header, SealedBlock, SealedHeader, + EthereumHardfork, GotExpected, Header, SealedBlock, SealedHeader, }; /// Gas used needs to be less than gas limit. Gas used is going to be checked after execution. @@ -29,7 +29,7 @@ pub fn validate_header_base_fee( header: &SealedHeader, chain_spec: &ChainSpec, ) -> Result<(), ConsensusError> { - if chain_spec.fork(Hardfork::London).active_at_block(header.number) && + if chain_spec.fork(EthereumHardfork::London).active_at_block(header.number) && header.base_fee_per_gas.is_none() { return Err(ConsensusError::BaseFeeMissing) @@ -192,11 +192,11 @@ pub fn validate_against_parent_eip1559_base_fee( parent: &SealedHeader, chain_spec: &ChainSpec, ) -> Result<(), ConsensusError> { - if chain_spec.fork(Hardfork::London).active_at_block(header.number) { + if chain_spec.fork(EthereumHardfork::London).active_at_block(header.number) { let base_fee = header.base_fee_per_gas.ok_or(ConsensusError::BaseFeeMissing)?; let expected_base_fee = - if chain_spec.fork(Hardfork::London).transitions_at_block(header.number) { + if chain_spec.fork(EthereumHardfork::London).transitions_at_block(header.number) { reth_primitives::constants::EIP1559_INITIAL_BASE_FEE } else { // This BaseFeeMissing will not happen as previous blocks are checked to have diff --git a/crates/ethereum-forks/Cargo.toml b/crates/ethereum-forks/Cargo.toml index bcdbc6551a41..9bdc0c98c91e 100644 --- a/crates/ethereum-forks/Cargo.toml +++ b/crates/ethereum-forks/Cargo.toml @@ -23,11 +23,15 @@ crc = "3" # misc serde = { workspace = true, features = ["derive"], optional = true } thiserror-no-std = { workspace = true, default-features = false } +once_cell.workspace = true +dyn-clone.workspace = true +rustc-hash.workspace = true # arbitrary utils arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } proptest-derive = { workspace = true, optional = true } +auto_impl.workspace = true [dev-dependencies] arbitrary = { workspace = true, features = ["derive"] } diff --git a/crates/ethereum-forks/src/chains/dev.rs b/crates/ethereum-forks/src/chains/dev.rs deleted file mode 100644 index 866be0dd4260..000000000000 --- a/crates/ethereum-forks/src/chains/dev.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::{ForkCondition, Hardfork}; -use alloy_primitives::uint; - -/// Dev hardforks -pub const DEV_HARDFORKS: [(Hardfork, ForkCondition); 14] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(1561651)), - (Hardfork::Berlin, ForkCondition::Block(4460644)), - (Hardfork::London, ForkCondition::Block(5062605)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: None, total_difficulty: uint!(10_790_000_U256) }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1678832736)), - (Hardfork::Cancun, ForkCondition::Timestamp(1705473120)), -]; diff --git a/crates/ethereum-forks/src/chains/ethereum.rs b/crates/ethereum-forks/src/chains/ethereum.rs deleted file mode 100644 index 6db4d95fcade..000000000000 --- a/crates/ethereum-forks/src/chains/ethereum.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::{ForkCondition, Hardfork}; -use alloy_primitives::{uint, U256}; - -/// Ethereum mainnet hardforks -pub const MAINNET_HARDFORKS: [(Hardfork, ForkCondition); 17] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(1150000)), - (Hardfork::Dao, ForkCondition::Block(1920000)), - (Hardfork::Tangerine, ForkCondition::Block(2463000)), - (Hardfork::SpuriousDragon, ForkCondition::Block(2675000)), - (Hardfork::Byzantium, ForkCondition::Block(4370000)), - (Hardfork::Constantinople, ForkCondition::Block(7280000)), - (Hardfork::Petersburg, ForkCondition::Block(7280000)), - (Hardfork::Istanbul, ForkCondition::Block(9069000)), - (Hardfork::MuirGlacier, ForkCondition::Block(9200000)), - (Hardfork::Berlin, ForkCondition::Block(12244000)), - (Hardfork::London, ForkCondition::Block(12965000)), - (Hardfork::ArrowGlacier, ForkCondition::Block(13773000)), - (Hardfork::GrayGlacier, ForkCondition::Block(15050000)), - ( - Hardfork::Paris, - ForkCondition::TTD { - fork_block: None, - total_difficulty: uint!(58_750_000_000_000_000_000_000_U256), - }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1681338455)), - (Hardfork::Cancun, ForkCondition::Timestamp(1710338135)), -]; - -/// Ethereum Goerli hardforks -pub const GOERLI_HARDFORKS: [(Hardfork, ForkCondition); 14] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(1561651)), - (Hardfork::Berlin, ForkCondition::Block(4460644)), - (Hardfork::London, ForkCondition::Block(5062605)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: None, total_difficulty: uint!(10_790_000_U256) }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1678832736)), - (Hardfork::Cancun, ForkCondition::Timestamp(1705473120)), -]; - -/// Ethereum Sepolia hardforks -pub const SEPOLIA_HARDFORKS: [(Hardfork, ForkCondition); 15] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - ( - Hardfork::Paris, - ForkCondition::TTD { - fork_block: Some(1735371), - total_difficulty: uint!(17_000_000_000_000_000_U256), - }, - ), - (Hardfork::Shanghai, ForkCondition::Timestamp(1677557088)), - (Hardfork::Cancun, ForkCondition::Timestamp(1706655072)), -]; - -/// Ethereum Holesky hardforks -pub const HOLESKY_HARDFORKS: [(Hardfork, ForkCondition); 15] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Dao, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), - (Hardfork::Shanghai, ForkCondition::Timestamp(1696000704)), - (Hardfork::Cancun, ForkCondition::Timestamp(1707305664)), -]; diff --git a/crates/ethereum-forks/src/chains/mod.rs b/crates/ethereum-forks/src/chains/mod.rs deleted file mode 100644 index ef775777f399..000000000000 --- a/crates/ethereum-forks/src/chains/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -/// Ethereum chains -pub mod ethereum; - -/// Optimism chains -#[cfg(feature = "optimism")] -pub mod optimism; - -/// Dev chain -pub mod dev; diff --git a/crates/ethereum-forks/src/chains/optimism.rs b/crates/ethereum-forks/src/chains/optimism.rs deleted file mode 100644 index 37af4a19ffe6..000000000000 --- a/crates/ethereum-forks/src/chains/optimism.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::{ForkCondition, Hardfork}; -use alloy_primitives::U256; - -/// Optimism mainnet hardforks -pub const OP_MAINNET_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(3950000)), - (Hardfork::London, ForkCondition::Block(105235063)), - (Hardfork::ArrowGlacier, ForkCondition::Block(105235063)), - (Hardfork::GrayGlacier, ForkCondition::Block(105235063)), - ( - Hardfork::Paris, - ForkCondition::TTD { fork_block: Some(105235063), total_difficulty: U256::ZERO }, - ), - (Hardfork::Bedrock, ForkCondition::Block(105235063)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1704992401)), - (Hardfork::Canyon, ForkCondition::Timestamp(1704992401)), - (Hardfork::Cancun, ForkCondition::Timestamp(1710374401)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1710374401)), - (Hardfork::Fjord, ForkCondition::Timestamp(1720627201)), -]; - -/// Optimism Sepolia hardforks -pub const OP_SEPOLIA_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - (Hardfork::ArrowGlacier, ForkCondition::Block(0)), - (Hardfork::GrayGlacier, ForkCondition::Block(0)), - (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), - (Hardfork::Bedrock, ForkCondition::Block(0)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), - (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), - (Hardfork::Cancun, ForkCondition::Timestamp(1708534800)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1708534800)), - (Hardfork::Fjord, ForkCondition::Timestamp(1716998400)), -]; - -/// Base Sepolia hardforks -pub const BASE_SEPOLIA_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - (Hardfork::ArrowGlacier, ForkCondition::Block(0)), - (Hardfork::GrayGlacier, ForkCondition::Block(0)), - (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), - (Hardfork::Bedrock, ForkCondition::Block(0)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), - (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), - (Hardfork::Cancun, ForkCondition::Timestamp(1708534800)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1708534800)), - (Hardfork::Fjord, ForkCondition::Timestamp(1716998400)), -]; - -/// Base Mainnet hardforks -pub const BASE_MAINNET_HARDFORKS: [(Hardfork, ForkCondition); 21] = [ - (Hardfork::Frontier, ForkCondition::Block(0)), - (Hardfork::Homestead, ForkCondition::Block(0)), - (Hardfork::Tangerine, ForkCondition::Block(0)), - (Hardfork::SpuriousDragon, ForkCondition::Block(0)), - (Hardfork::Byzantium, ForkCondition::Block(0)), - (Hardfork::Constantinople, ForkCondition::Block(0)), - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(0)), - (Hardfork::MuirGlacier, ForkCondition::Block(0)), - (Hardfork::Berlin, ForkCondition::Block(0)), - (Hardfork::London, ForkCondition::Block(0)), - (Hardfork::ArrowGlacier, ForkCondition::Block(0)), - (Hardfork::GrayGlacier, ForkCondition::Block(0)), - (Hardfork::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), - (Hardfork::Bedrock, ForkCondition::Block(0)), - (Hardfork::Regolith, ForkCondition::Timestamp(0)), - (Hardfork::Shanghai, ForkCondition::Timestamp(1704992401)), - (Hardfork::Canyon, ForkCondition::Timestamp(1704992401)), - (Hardfork::Cancun, ForkCondition::Timestamp(1710374401)), - (Hardfork::Ecotone, ForkCondition::Timestamp(1710374401)), - (Hardfork::Fjord, ForkCondition::Timestamp(1720627201)), -]; diff --git a/crates/ethereum-forks/src/display.rs b/crates/ethereum-forks/src/display.rs index 3c1424083638..d8a2007e443e 100644 --- a/crates/ethereum-forks/src/display.rs +++ b/crates/ethereum-forks/src/display.rs @@ -6,9 +6,7 @@ use alloc::{ vec::Vec, }; -use crate::{ForkCondition, Hardfork}; -#[cfg(feature = "std")] -use std::collections::BTreeMap; +use crate::{hardforks::Hardforks, ForkCondition}; /// A container to pretty-print a hardfork. /// @@ -146,27 +144,22 @@ impl core::fmt::Display for DisplayHardforks { impl DisplayHardforks { /// Creates a new [`DisplayHardforks`] from an iterator of hardforks. - pub fn new( - hardforks: &BTreeMap, - known_paris_block: Option, - ) -> Self { + pub fn new(hardforks: &H, known_paris_block: Option) -> Self { let mut pre_merge = Vec::new(); let mut with_merge = Vec::new(); let mut post_merge = Vec::new(); - for (fork, condition) in hardforks { + for (fork, condition) in hardforks.forks_iter() { let mut display_fork = - DisplayFork { name: fork.to_string(), activated_at: *condition, eip: None }; + DisplayFork { name: fork.name().to_string(), activated_at: condition, eip: None }; match condition { ForkCondition::Block(_) => { pre_merge.push(display_fork); } ForkCondition::TTD { total_difficulty, .. } => { - display_fork.activated_at = ForkCondition::TTD { - fork_block: known_paris_block, - total_difficulty: *total_difficulty, - }; + display_fork.activated_at = + ForkCondition::TTD { fork_block: known_paris_block, total_difficulty }; with_merge.push(display_fork); } ForkCondition::Timestamp(_) => { diff --git a/crates/ethereum-forks/src/hardfork.rs b/crates/ethereum-forks/src/hardfork.rs deleted file mode 100644 index fa095dea5a18..000000000000 --- a/crates/ethereum-forks/src/hardfork.rs +++ /dev/null @@ -1,702 +0,0 @@ -use alloy_chains::Chain; -use core::{ - fmt, - fmt::{Display, Formatter}, - str::FromStr, -}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String}; - -/// Represents the consensus type of a blockchain fork. -/// -/// This enum defines two variants: `ProofOfWork` for hardforks that use a proof-of-work consensus -/// mechanism, and `ProofOfStake` for hardforks that use a proof-of-stake consensus mechanism. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum ConsensusType { - /// Indicates a proof-of-work consensus mechanism. - ProofOfWork, - /// Indicates a proof-of-stake consensus mechanism. - ProofOfStake, -} - -/// The name of an Ethereum hardfork. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -#[non_exhaustive] -pub enum Hardfork { - /// Frontier: . - Frontier, - /// Homestead: . - Homestead, - /// The DAO fork: . - Dao, - /// Tangerine: . - Tangerine, - /// Spurious Dragon: . - SpuriousDragon, - /// Byzantium: . - Byzantium, - /// Constantinople: . - Constantinople, - /// Petersburg: . - Petersburg, - /// Istanbul: . - Istanbul, - /// Muir Glacier: . - MuirGlacier, - /// Berlin: . - Berlin, - /// London: . - London, - /// Arrow Glacier: . - ArrowGlacier, - /// Gray Glacier: . - GrayGlacier, - /// Paris: . - Paris, - /// Bedrock: . - #[cfg(feature = "optimism")] - Bedrock, - /// Regolith: . - #[cfg(feature = "optimism")] - Regolith, - /// Shanghai: . - Shanghai, - /// Canyon: - /// . - #[cfg(feature = "optimism")] - Canyon, - // ArbOS11, - /// Cancun. - Cancun, - /// Ecotone: . - #[cfg(feature = "optimism")] - Ecotone, - // ArbOS20Atlas, - - // Upcoming - /// Prague: - Prague, - /// Fjord: - #[cfg(feature = "optimism")] - Fjord, -} - -impl Hardfork { - /// Retrieves the consensus type for the specified hardfork. - pub fn consensus_type(&self) -> ConsensusType { - if *self >= Self::Paris { - ConsensusType::ProofOfStake - } else { - ConsensusType::ProofOfWork - } - } - - /// Checks if the hardfork uses Proof of Stake consensus. - pub fn is_proof_of_stake(&self) -> bool { - matches!(self.consensus_type(), ConsensusType::ProofOfStake) - } - - /// Checks if the hardfork uses Proof of Work consensus. - pub fn is_proof_of_work(&self) -> bool { - matches!(self.consensus_type(), ConsensusType::ProofOfWork) - } - - /// Retrieves the activation block for the specified hardfork on the given chain. - pub fn activation_block(&self, chain: Chain) -> Option { - if chain == Chain::mainnet() { - return self.mainnet_activation_block() - } - if chain == Chain::sepolia() { - return self.sepolia_activation_block() - } - if chain == Chain::holesky() { - return self.holesky_activation_block() - } - - #[cfg(feature = "optimism")] - { - if chain == Chain::base_sepolia() { - return self.base_sepolia_activation_block() - } - if chain == Chain::base_mainnet() { - return self.base_mainnet_activation_block() - } - } - - None - } - - /// Retrieves the activation block for the specified hardfork on the Ethereum mainnet. - pub const fn mainnet_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier => Some(0), - Self::Homestead => Some(1150000), - Self::Dao => Some(1920000), - Self::Tangerine => Some(2463000), - Self::SpuriousDragon => Some(2675000), - Self::Byzantium => Some(4370000), - Self::Constantinople | Self::Petersburg => Some(7280000), - Self::Istanbul => Some(9069000), - Self::MuirGlacier => Some(9200000), - Self::Berlin => Some(12244000), - Self::London => Some(12965000), - Self::ArrowGlacier => Some(13773000), - Self::GrayGlacier => Some(15050000), - Self::Paris => Some(15537394), - Self::Shanghai => Some(17034870), - Self::Cancun => Some(19426587), - - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Sepolia testnet. - pub const fn sepolia_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Paris => Some(1735371), - Self::Shanghai => Some(2990908), - Self::Cancun => Some(5187023), - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier => Some(0), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Arbitrum Sepolia testnet. - pub const fn arbitrum_sepolia_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris => Some(0), - Self::Shanghai => Some(10653737), - // Hardfork::ArbOS11 => Some(10653737), - Self::Cancun => Some(18683405), - // Hardfork::ArbOS20Atlas => Some(18683405), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Arbitrum One mainnet. - pub const fn arbitrum_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris => Some(0), - Self::Shanghai => Some(184097479), - // Hardfork::ArbOS11 => Some(184097479), - Self::Cancun => Some(190301729), - // Hardfork::ArbOS20Atlas => Some(190301729), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Base Sepolia testnet. - #[cfg(feature = "optimism")] - pub const fn base_sepolia_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris | - Self::Bedrock | - Self::Regolith => Some(0), - Self::Shanghai | Self::Canyon => Some(2106456), - Self::Cancun | Self::Ecotone => Some(6383256), - Self::Fjord => Some(10615056), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Base mainnet. - #[cfg(feature = "optimism")] - pub const fn base_mainnet_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris | - Self::Bedrock | - Self::Regolith => Some(0), - Self::Shanghai | Self::Canyon => Some(9101527), - Self::Cancun | Self::Ecotone => Some(11188936), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the holesky testnet. - const fn holesky_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris => Some(0), - Self::Shanghai => Some(6698), - Self::Cancun => Some(894733), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the given chain. - pub fn activation_timestamp(&self, chain: Chain) -> Option { - if chain == Chain::mainnet() { - return self.mainnet_activation_timestamp() - } - if chain == Chain::sepolia() { - return self.sepolia_activation_timestamp() - } - if chain == Chain::holesky() { - return self.holesky_activation_timestamp() - } - #[cfg(feature = "optimism")] - { - if chain == Chain::base_sepolia() { - return self.base_sepolia_activation_timestamp() - } - if chain == Chain::base_mainnet() { - return self.base_mainnet_activation_timestamp() - } - } - - None - } - - /// Retrieves the activation timestamp for the specified hardfork on the Ethereum mainnet. - pub const fn mainnet_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier => Some(1438226773), - Self::Homestead => Some(1457938193), - Self::Dao => Some(1468977640), - Self::Tangerine => Some(1476753571), - Self::SpuriousDragon => Some(1479788144), - Self::Byzantium => Some(1508131331), - Self::Constantinople | Self::Petersburg => Some(1551340324), - Self::Istanbul => Some(1575807909), - Self::MuirGlacier => Some(1577953849), - Self::Berlin => Some(1618481223), - Self::London => Some(1628166822), - Self::ArrowGlacier => Some(1639036523), - Self::GrayGlacier => Some(1656586444), - Self::Paris => Some(1663224162), - Self::Shanghai => Some(1681338455), - Self::Cancun => Some(1710338135), - - // upcoming hardforks - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Sepolia testnet. - pub const fn sepolia_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris => Some(1633267481), - Self::Shanghai => Some(1677557088), - Self::Cancun => Some(1706655072), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Holesky testnet. - pub const fn holesky_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Shanghai => Some(1696000704), - Self::Cancun => Some(1707305664), - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris => Some(1695902100), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum Sepolia - /// testnet. - pub const fn arbitrum_sepolia_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris => Some(1692726996), - Self::Shanghai => Some(1706634000), - // Hardfork::ArbOS11 => Some(1706634000), - Self::Cancun => Some(1709229600), - // Hardfork::ArbOS20Atlas => Some(1709229600), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum One mainnet. - pub const fn arbitrum_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris => Some(1622240000), - Self::Shanghai => Some(1708804873), - // Hardfork::ArbOS11 => Some(1708804873), - Self::Cancun => Some(1710424089), - // Hardfork::ArbOS20Atlas => Some(1710424089), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Base Sepolia testnet. - #[cfg(feature = "optimism")] - pub const fn base_sepolia_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris | - Self::Bedrock | - Self::Regolith => Some(1695768288), - Self::Shanghai | Self::Canyon => Some(1699981200), - Self::Cancun | Self::Ecotone => Some(1708534800), - Self::Fjord => Some(1716998400), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Base mainnet. - #[cfg(feature = "optimism")] - pub const fn base_mainnet_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier | - Self::Homestead | - Self::Dao | - Self::Tangerine | - Self::SpuriousDragon | - Self::Byzantium | - Self::Constantinople | - Self::Petersburg | - Self::Istanbul | - Self::MuirGlacier | - Self::Berlin | - Self::London | - Self::ArrowGlacier | - Self::GrayGlacier | - Self::Paris | - Self::Bedrock | - Self::Regolith => Some(1686789347), - Self::Shanghai | Self::Canyon => Some(1704992401), - Self::Cancun | Self::Ecotone => Some(1710374401), - Self::Fjord => Some(1720627201), - _ => None, - } - } -} - -impl FromStr for Hardfork { - type Err = String; - - fn from_str(s: &str) -> Result { - Ok(match s.to_lowercase().as_str() { - "frontier" => Self::Frontier, - "homestead" => Self::Homestead, - "dao" => Self::Dao, - "tangerine" => Self::Tangerine, - "spuriousdragon" => Self::SpuriousDragon, - "byzantium" => Self::Byzantium, - "constantinople" => Self::Constantinople, - "petersburg" => Self::Petersburg, - "istanbul" => Self::Istanbul, - "muirglacier" => Self::MuirGlacier, - "berlin" => Self::Berlin, - "london" => Self::London, - "arrowglacier" => Self::ArrowGlacier, - "grayglacier" => Self::GrayGlacier, - "paris" => Self::Paris, - "shanghai" => Self::Shanghai, - "cancun" => Self::Cancun, - #[cfg(feature = "optimism")] - "bedrock" => Self::Bedrock, - #[cfg(feature = "optimism")] - "regolith" => Self::Regolith, - #[cfg(feature = "optimism")] - "canyon" => Self::Canyon, - #[cfg(feature = "optimism")] - "ecotone" => Self::Ecotone, - #[cfg(feature = "optimism")] - "fjord" => Self::Fjord, - "prague" => Self::Prague, - // "arbos11" => Hardfork::ArbOS11, - // "arbos20atlas" => Hardfork::ArbOS20Atlas, - _ => return Err(format!("Unknown hardfork: {s}")), - }) - } -} - -impl Display for Hardfork { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_hardfork_from_str() { - let hardfork_str = [ - "frOntier", - "homEstead", - "dao", - "tAngerIne", - "spurIousdrAgon", - "byzAntium", - "constantinople", - "petersburg", - "istanbul", - "muirglacier", - "bErlin", - "lonDon", - "arrowglacier", - "grayglacier", - "PARIS", - "ShAnGhAI", - "CaNcUn", - "PrAguE", - ]; - let expected_hardforks = [ - Hardfork::Frontier, - Hardfork::Homestead, - Hardfork::Dao, - Hardfork::Tangerine, - Hardfork::SpuriousDragon, - Hardfork::Byzantium, - Hardfork::Constantinople, - Hardfork::Petersburg, - Hardfork::Istanbul, - Hardfork::MuirGlacier, - Hardfork::Berlin, - Hardfork::London, - Hardfork::ArrowGlacier, - Hardfork::GrayGlacier, - Hardfork::Paris, - Hardfork::Shanghai, - Hardfork::Cancun, - Hardfork::Prague, - ]; - - let hardforks: Vec = - hardfork_str.iter().map(|h| Hardfork::from_str(h).unwrap()).collect(); - - assert_eq!(hardforks, expected_hardforks); - } - - #[test] - #[cfg(feature = "optimism")] - fn check_op_hardfork_from_str() { - let hardfork_str = ["beDrOck", "rEgOlITH", "cAnYoN", "eCoToNe", "FJorD"]; - let expected_hardforks = [ - Hardfork::Bedrock, - Hardfork::Regolith, - Hardfork::Canyon, - Hardfork::Ecotone, - Hardfork::Fjord, - ]; - - let hardforks: Vec = - hardfork_str.iter().map(|h| Hardfork::from_str(h).unwrap()).collect(); - - assert_eq!(hardforks, expected_hardforks); - } - - #[test] - fn check_nonexistent_hardfork_from_str() { - assert!(Hardfork::from_str("not a hardfork").is_err()); - } - - #[test] - fn check_consensus_type() { - let pow_hardforks = [ - Hardfork::Frontier, - Hardfork::Homestead, - Hardfork::Dao, - Hardfork::Tangerine, - Hardfork::SpuriousDragon, - Hardfork::Byzantium, - Hardfork::Constantinople, - Hardfork::Petersburg, - Hardfork::Istanbul, - Hardfork::MuirGlacier, - Hardfork::Berlin, - Hardfork::London, - Hardfork::ArrowGlacier, - Hardfork::GrayGlacier, - ]; - - let pos_hardforks = [Hardfork::Paris, Hardfork::Shanghai, Hardfork::Cancun]; - - #[cfg(feature = "optimism")] - let op_hardforks = [ - Hardfork::Bedrock, - Hardfork::Regolith, - Hardfork::Canyon, - Hardfork::Ecotone, - Hardfork::Fjord, - ]; - - for hardfork in &pow_hardforks { - assert_eq!(hardfork.consensus_type(), ConsensusType::ProofOfWork); - assert!(!hardfork.is_proof_of_stake()); - assert!(hardfork.is_proof_of_work()); - } - - for hardfork in &pos_hardforks { - assert_eq!(hardfork.consensus_type(), ConsensusType::ProofOfStake); - assert!(hardfork.is_proof_of_stake()); - assert!(!hardfork.is_proof_of_work()); - } - - #[cfg(feature = "optimism")] - for hardfork in &op_hardforks { - assert_eq!(hardfork.consensus_type(), ConsensusType::ProofOfStake); - assert!(hardfork.is_proof_of_stake()); - assert!(!hardfork.is_proof_of_work()); - } - } -} diff --git a/crates/ethereum-forks/src/hardfork/dev.rs b/crates/ethereum-forks/src/hardfork/dev.rs new file mode 100644 index 000000000000..4b422141b425 --- /dev/null +++ b/crates/ethereum-forks/src/hardfork/dev.rs @@ -0,0 +1,32 @@ +use crate::{ChainHardforks, EthereumHardfork, ForkCondition}; +use alloy_primitives::U256; +use once_cell::sync::Lazy; + +/// Dev hardforks +pub static DEV_HARDFORKS: Lazy = Lazy::new(|| { + ChainHardforks::new(vec![ + (EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Dao.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(0)), + ( + EthereumHardfork::Paris.boxed(), + ForkCondition::TTD { fork_block: None, total_difficulty: U256::ZERO }, + ), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Timestamp(0)), + (EthereumHardfork::Cancun.boxed(), ForkCondition::Timestamp(0)), + #[cfg(feature = "optimism")] + (crate::OptimismHardfork::Regolith.boxed(), ForkCondition::Timestamp(0)), + #[cfg(feature = "optimism")] + (crate::OptimismHardfork::Bedrock.boxed(), ForkCondition::Block(0)), + #[cfg(feature = "optimism")] + (crate::OptimismHardfork::Ecotone.boxed(), ForkCondition::Timestamp(0)), + ]) +}); diff --git a/crates/ethereum-forks/src/hardfork/ethereum.rs b/crates/ethereum-forks/src/hardfork/ethereum.rs new file mode 100644 index 000000000000..9e2a8a111216 --- /dev/null +++ b/crates/ethereum-forks/src/hardfork/ethereum.rs @@ -0,0 +1,441 @@ +use crate::{hardfork, ChainHardforks, ForkCondition, Hardfork}; +use alloy_chains::Chain; +use alloy_primitives::{uint, U256}; +use core::{ + fmt, + fmt::{Display, Formatter}, + str::FromStr, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +hardfork!( + /// The name of an Ethereum hardfork. + EthereumHardfork { + /// Frontier: . + Frontier, + /// Homestead: . + Homestead, + /// The DAO fork: . + Dao, + /// Tangerine: . + Tangerine, + /// Spurious Dragon: . + SpuriousDragon, + /// Byzantium: . + Byzantium, + /// Constantinople: . + Constantinople, + /// Petersburg: . + Petersburg, + /// Istanbul: . + Istanbul, + /// Muir Glacier: . + MuirGlacier, + /// Berlin: . + Berlin, + /// London: . + London, + /// Arrow Glacier: . + ArrowGlacier, + /// Gray Glacier: . + GrayGlacier, + /// Paris: . + Paris, + /// Shanghai: . + Shanghai, + /// Cancun. + Cancun, + /// Prague: + Prague, + } +); + +impl EthereumHardfork { + /// Retrieves the activation block for the specified hardfork on the given chain. + pub fn activation_block(&self, chain: Chain) -> Option { + if chain == Chain::mainnet() { + return self.mainnet_activation_block() + } + if chain == Chain::sepolia() { + return self.sepolia_activation_block() + } + if chain == Chain::holesky() { + return self.holesky_activation_block() + } + + None + } + + /// Retrieves the activation block for the specified hardfork on the Ethereum mainnet. + pub const fn mainnet_activation_block(&self) -> Option { + match self { + Self::Frontier => Some(0), + Self::Homestead => Some(1150000), + Self::Dao => Some(1920000), + Self::Tangerine => Some(2463000), + Self::SpuriousDragon => Some(2675000), + Self::Byzantium => Some(4370000), + Self::Constantinople | Self::Petersburg => Some(7280000), + Self::Istanbul => Some(9069000), + Self::MuirGlacier => Some(9200000), + Self::Berlin => Some(12244000), + Self::London => Some(12965000), + Self::ArrowGlacier => Some(13773000), + Self::GrayGlacier => Some(15050000), + Self::Paris => Some(15537394), + Self::Shanghai => Some(17034870), + Self::Cancun => Some(19426587), + _ => None, + } + } + + /// Retrieves the activation block for the specified hardfork on the Sepolia testnet. + pub const fn sepolia_activation_block(&self) -> Option { + match self { + Self::Paris => Some(1735371), + Self::Shanghai => Some(2990908), + Self::Cancun => Some(5187023), + Self::Frontier | + Self::Homestead | + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier => Some(0), + _ => None, + } + } + + /// Retrieves the activation block for the specified hardfork on the holesky testnet. + const fn holesky_activation_block(&self) -> Option { + match self { + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier | + Self::Paris => Some(0), + Self::Shanghai => Some(6698), + Self::Cancun => Some(894733), + _ => None, + } + } + + /// Retrieves the activation block for the specified hardfork on the Arbitrum Sepolia testnet. + pub const fn arbitrum_sepolia_activation_block(&self) -> Option { + match self { + Self::Frontier | + Self::Homestead | + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier | + Self::Paris => Some(0), + Self::Shanghai => Some(10653737), + // Hardfork::ArbOS11 => Some(10653737), + Self::Cancun => Some(18683405), + // Hardfork::ArbOS20Atlas => Some(18683405), + _ => None, + } + } + + /// Retrieves the activation block for the specified hardfork on the Arbitrum One mainnet. + pub const fn arbitrum_activation_block(&self) -> Option { + match self { + Self::Frontier | + Self::Homestead | + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier | + Self::Paris => Some(0), + Self::Shanghai => Some(184097479), + // Hardfork::ArbOS11 => Some(184097479), + Self::Cancun => Some(190301729), + // Hardfork::ArbOS20Atlas => Some(190301729), + _ => None, + } + } + + /// Retrieves the activation timestamp for the specified hardfork on the given chain. + pub fn activation_timestamp(&self, chain: Chain) -> Option { + if chain == Chain::mainnet() { + return self.mainnet_activation_timestamp() + } + if chain == Chain::sepolia() { + return self.sepolia_activation_timestamp() + } + if chain == Chain::holesky() { + return self.holesky_activation_timestamp() + } + + None + } + + /// Retrieves the activation timestamp for the specified hardfork on the Ethereum mainnet. + pub const fn mainnet_activation_timestamp(&self) -> Option { + match self { + Self::Frontier => Some(1438226773), + Self::Homestead => Some(1457938193), + Self::Dao => Some(1468977640), + Self::Tangerine => Some(1476753571), + Self::SpuriousDragon => Some(1479788144), + Self::Byzantium => Some(1508131331), + Self::Constantinople | Self::Petersburg => Some(1551340324), + Self::Istanbul => Some(1575807909), + Self::MuirGlacier => Some(1577953849), + Self::Berlin => Some(1618481223), + Self::London => Some(1628166822), + Self::ArrowGlacier => Some(1639036523), + Self::GrayGlacier => Some(1656586444), + Self::Paris => Some(1663224162), + Self::Shanghai => Some(1681338455), + Self::Cancun => Some(1710338135), + + // upcoming hardforks + _ => None, + } + } + + /// Retrieves the activation timestamp for the specified hardfork on the Sepolia testnet. + pub const fn sepolia_activation_timestamp(&self) -> Option { + match self { + Self::Frontier | + Self::Homestead | + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier | + Self::Paris => Some(1633267481), + Self::Shanghai => Some(1677557088), + Self::Cancun => Some(1706655072), + _ => None, + } + } + + /// Retrieves the activation timestamp for the specified hardfork on the Holesky testnet. + pub const fn holesky_activation_timestamp(&self) -> Option { + match self { + Self::Shanghai => Some(1696000704), + Self::Cancun => Some(1707305664), + Self::Frontier | + Self::Homestead | + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier | + Self::Paris => Some(1695902100), + _ => None, + } + } + + /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum Sepolia + /// testnet. + pub const fn arbitrum_sepolia_activation_timestamp(&self) -> Option { + match self { + Self::Frontier | + Self::Homestead | + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier | + Self::Paris => Some(1692726996), + Self::Shanghai => Some(1706634000), + // Hardfork::ArbOS11 => Some(1706634000), + Self::Cancun => Some(1709229600), + // Hardfork::ArbOS20Atlas => Some(1709229600), + _ => None, + } + } + + /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum One mainnet. + pub const fn arbitrum_activation_timestamp(&self) -> Option { + match self { + Self::Frontier | + Self::Homestead | + Self::Dao | + Self::Tangerine | + Self::SpuriousDragon | + Self::Byzantium | + Self::Constantinople | + Self::Petersburg | + Self::Istanbul | + Self::MuirGlacier | + Self::Berlin | + Self::London | + Self::ArrowGlacier | + Self::GrayGlacier | + Self::Paris => Some(1622240000), + Self::Shanghai => Some(1708804873), + // Hardfork::ArbOS11 => Some(1708804873), + Self::Cancun => Some(1710424089), + // Hardfork::ArbOS20Atlas => Some(1710424089), + _ => None, + } + } + + /// Ethereum mainnet list of hardforks. + pub const fn mainnet() -> [(Self, ForkCondition); 17] { + [ + (Self::Frontier, ForkCondition::Block(0)), + (Self::Homestead, ForkCondition::Block(1150000)), + (Self::Dao, ForkCondition::Block(1920000)), + (Self::Tangerine, ForkCondition::Block(2463000)), + (Self::SpuriousDragon, ForkCondition::Block(2675000)), + (Self::Byzantium, ForkCondition::Block(4370000)), + (Self::Constantinople, ForkCondition::Block(7280000)), + (Self::Petersburg, ForkCondition::Block(7280000)), + (Self::Istanbul, ForkCondition::Block(9069000)), + (Self::MuirGlacier, ForkCondition::Block(9200000)), + (Self::Berlin, ForkCondition::Block(12244000)), + (Self::London, ForkCondition::Block(12965000)), + (Self::ArrowGlacier, ForkCondition::Block(13773000)), + (Self::GrayGlacier, ForkCondition::Block(15050000)), + ( + Self::Paris, + ForkCondition::TTD { + fork_block: None, + total_difficulty: uint!(58_750_000_000_000_000_000_000_U256), + }, + ), + (Self::Shanghai, ForkCondition::Timestamp(1681338455)), + (Self::Cancun, ForkCondition::Timestamp(1710338135)), + ] + } + + /// Ethereum goerli list of hardforks. + pub const fn goerli() -> [(Self, ForkCondition); 14] { + [ + (Self::Frontier, ForkCondition::Block(0)), + (Self::Homestead, ForkCondition::Block(0)), + (Self::Dao, ForkCondition::Block(0)), + (Self::Tangerine, ForkCondition::Block(0)), + (Self::SpuriousDragon, ForkCondition::Block(0)), + (Self::Byzantium, ForkCondition::Block(0)), + (Self::Constantinople, ForkCondition::Block(0)), + (Self::Petersburg, ForkCondition::Block(0)), + (Self::Istanbul, ForkCondition::Block(1561651)), + (Self::Berlin, ForkCondition::Block(4460644)), + (Self::London, ForkCondition::Block(5062605)), + ( + Self::Paris, + ForkCondition::TTD { fork_block: None, total_difficulty: uint!(10_790_000_U256) }, + ), + (Self::Shanghai, ForkCondition::Timestamp(1678832736)), + (Self::Cancun, ForkCondition::Timestamp(1705473120)), + ] + } + + /// Ethereum sepolia list of hardforks. + pub const fn sepolia() -> [(Self, ForkCondition); 15] { + [ + (Self::Frontier, ForkCondition::Block(0)), + (Self::Homestead, ForkCondition::Block(0)), + (Self::Dao, ForkCondition::Block(0)), + (Self::Tangerine, ForkCondition::Block(0)), + (Self::SpuriousDragon, ForkCondition::Block(0)), + (Self::Byzantium, ForkCondition::Block(0)), + (Self::Constantinople, ForkCondition::Block(0)), + (Self::Petersburg, ForkCondition::Block(0)), + (Self::Istanbul, ForkCondition::Block(0)), + (Self::MuirGlacier, ForkCondition::Block(0)), + (Self::Berlin, ForkCondition::Block(0)), + (Self::London, ForkCondition::Block(0)), + ( + Self::Paris, + ForkCondition::TTD { + fork_block: Some(1735371), + total_difficulty: uint!(17_000_000_000_000_000_U256), + }, + ), + (Self::Shanghai, ForkCondition::Timestamp(1677557088)), + (Self::Cancun, ForkCondition::Timestamp(1706655072)), + ] + } + + /// Ethereum holesky list of hardforks. + pub const fn holesky() -> [(Self, ForkCondition); 15] { + [ + (Self::Frontier, ForkCondition::Block(0)), + (Self::Homestead, ForkCondition::Block(0)), + (Self::Dao, ForkCondition::Block(0)), + (Self::Tangerine, ForkCondition::Block(0)), + (Self::SpuriousDragon, ForkCondition::Block(0)), + (Self::Byzantium, ForkCondition::Block(0)), + (Self::Constantinople, ForkCondition::Block(0)), + (Self::Petersburg, ForkCondition::Block(0)), + (Self::Istanbul, ForkCondition::Block(0)), + (Self::MuirGlacier, ForkCondition::Block(0)), + (Self::Berlin, ForkCondition::Block(0)), + (Self::London, ForkCondition::Block(0)), + (Self::Paris, ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }), + (Self::Shanghai, ForkCondition::Timestamp(1696000704)), + (Self::Cancun, ForkCondition::Timestamp(1707305664)), + ] + } +} + +impl From<[(EthereumHardfork, ForkCondition); N]> for ChainHardforks { + fn from(list: [(EthereumHardfork, ForkCondition); N]) -> Self { + Self::new( + list.into_iter() + .map(|(fork, cond)| (Box::new(fork) as Box, cond)) + .collect(), + ) + } +} diff --git a/crates/ethereum-forks/src/hardfork/macros.rs b/crates/ethereum-forks/src/hardfork/macros.rs new file mode 100644 index 000000000000..780c15f6e6b9 --- /dev/null +++ b/crates/ethereum-forks/src/hardfork/macros.rs @@ -0,0 +1,52 @@ +/// Macro that defines different variants of a chain specific enum. See [`crate::Hardfork`] as an +/// example. +#[macro_export] +macro_rules! hardfork { + ($(#[$enum_meta:meta])* $enum:ident { $( $(#[$meta:meta])* $variant:ident ),* $(,)? }) => { + $(#[$enum_meta])* + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] + pub enum $enum { + $( $(#[$meta])* $variant ),* + } + + impl $enum { + /// Returns variant as `str`. + pub const fn name(&self) -> &'static str { + match self { + $( $enum::$variant => stringify!($variant), )* + } + } + + /// Boxes `self` and returns it as `Box`. + pub fn boxed(self) -> Box { + Box::new(self) + } + } + + impl FromStr for $enum { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + $( + s if s == stringify!($variant).to_lowercase() => Ok($enum::$variant), + )* + _ => return Err(format!("Unknown hardfork: {s}")), + } + } + } + + impl Hardfork for $enum { + fn name(&self) -> &'static str { + self.name() + } + } + + impl Display for $enum { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } + } + } +} diff --git a/crates/ethereum-forks/src/hardfork/mod.rs b/crates/ethereum-forks/src/hardfork/mod.rs new file mode 100644 index 000000000000..b6faef6ec2f0 --- /dev/null +++ b/crates/ethereum-forks/src/hardfork/mod.rs @@ -0,0 +1,126 @@ +mod macros; + +mod ethereum; +pub use ethereum::EthereumHardfork; + +mod optimism; +pub use optimism::OptimismHardfork; + +mod dev; +pub use dev::DEV_HARDFORKS; + +use core::{ + any::Any, + hash::{Hash, Hasher}, +}; +use dyn_clone::DynClone; + +#[cfg(not(feature = "std"))] +use alloc::{format, string::String}; + +/// Generic hardfork trait. +#[auto_impl::auto_impl(&, Box)] +pub trait Hardfork: Any + DynClone + Send + Sync + 'static { + /// Fork name. + fn name(&self) -> &'static str; +} + +dyn_clone::clone_trait_object!(Hardfork); + +impl core::fmt::Debug for dyn Hardfork + 'static { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct(self.name()).finish() + } +} + +impl PartialEq for dyn Hardfork + 'static { + fn eq(&self, other: &Self) -> bool { + self.name() == other.name() + } +} + +impl Eq for dyn Hardfork + 'static {} + +impl Hash for dyn Hardfork + 'static { + fn hash(&self, state: &mut H) { + self.name().hash(state) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hardfork::optimism::OptimismHardfork; + use std::str::FromStr; + + #[test] + fn check_hardfork_from_str() { + let hardfork_str = [ + "frOntier", + "homEstead", + "dao", + "tAngerIne", + "spurIousdrAgon", + "byzAntium", + "constantinople", + "petersburg", + "istanbul", + "muirglacier", + "bErlin", + "lonDon", + "arrowglacier", + "grayglacier", + "PARIS", + "ShAnGhAI", + "CaNcUn", + "PrAguE", + ]; + let expected_hardforks = [ + EthereumHardfork::Frontier, + EthereumHardfork::Homestead, + EthereumHardfork::Dao, + EthereumHardfork::Tangerine, + EthereumHardfork::SpuriousDragon, + EthereumHardfork::Byzantium, + EthereumHardfork::Constantinople, + EthereumHardfork::Petersburg, + EthereumHardfork::Istanbul, + EthereumHardfork::MuirGlacier, + EthereumHardfork::Berlin, + EthereumHardfork::London, + EthereumHardfork::ArrowGlacier, + EthereumHardfork::GrayGlacier, + EthereumHardfork::Paris, + EthereumHardfork::Shanghai, + EthereumHardfork::Cancun, + EthereumHardfork::Prague, + ]; + + let hardforks: Vec = + hardfork_str.iter().map(|h| EthereumHardfork::from_str(h).unwrap()).collect(); + + assert_eq!(hardforks, expected_hardforks); + } + + #[test] + fn check_op_hardfork_from_str() { + let hardfork_str = ["beDrOck", "rEgOlITH", "cAnYoN", "eCoToNe", "FJorD"]; + let expected_hardforks = [ + OptimismHardfork::Bedrock, + OptimismHardfork::Regolith, + OptimismHardfork::Canyon, + OptimismHardfork::Ecotone, + OptimismHardfork::Fjord, + ]; + + let hardforks: Vec = + hardfork_str.iter().map(|h| OptimismHardfork::from_str(h).unwrap()).collect(); + + assert_eq!(hardforks, expected_hardforks); + } + + #[test] + fn check_nonexistent_hardfork_from_str() { + assert!(EthereumHardfork::from_str("not a hardfork").is_err()); + } +} diff --git a/crates/ethereum-forks/src/hardfork/optimism.rs b/crates/ethereum-forks/src/hardfork/optimism.rs new file mode 100644 index 000000000000..6933b7feddae --- /dev/null +++ b/crates/ethereum-forks/src/hardfork/optimism.rs @@ -0,0 +1,337 @@ +use crate::{hardfork, ChainHardforks, EthereumHardfork, ForkCondition, Hardfork}; +use alloy_chains::Chain; +use alloy_primitives::U256; +use core::{ + any::Any, + fmt::{self, Display, Formatter}, + str::FromStr, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +hardfork!( + /// The name of an optimism hardfork. + /// + /// When building a list of hardforks for a chain, it's still expected to mix with [`EthereumHardfork`]. + OptimismHardfork { + /// Bedrock: . + Bedrock, + /// Regolith: . + Regolith, + /// . + Canyon, + /// Ecotone: . + Ecotone, + /// Fjord: + Fjord, + } +); + +impl OptimismHardfork { + /// Retrieves the activation block for the specified hardfork on the given chain. + pub fn activation_block(self, fork: H, chain: Chain) -> Option { + if chain == Chain::base_sepolia() { + return Self::base_sepolia_activation_block(fork) + } + if chain == Chain::base_mainnet() { + return Self::base_mainnet_activation_block(fork) + } + + None + } + + /// Retrieves the activation timestamp for the specified hardfork on the given chain. + pub fn activation_timestamp(self, fork: H, chain: Chain) -> Option { + if chain == Chain::base_sepolia() { + return Self::base_sepolia_activation_timestamp(fork) + } + if chain == Chain::base_mainnet() { + return Self::base_mainnet_activation_timestamp(fork) + } + + None + } + + /// Retrieves the activation block for the specified hardfork on the Base Sepolia testnet. + pub fn base_sepolia_activation_block(fork: H) -> Option { + match_hardfork( + fork, + |fork| match fork { + EthereumHardfork::Frontier | + EthereumHardfork::Homestead | + EthereumHardfork::Dao | + EthereumHardfork::Tangerine | + EthereumHardfork::SpuriousDragon | + EthereumHardfork::Byzantium | + EthereumHardfork::Constantinople | + EthereumHardfork::Petersburg | + EthereumHardfork::Istanbul | + EthereumHardfork::MuirGlacier | + EthereumHardfork::Berlin | + EthereumHardfork::London | + EthereumHardfork::ArrowGlacier | + EthereumHardfork::GrayGlacier | + EthereumHardfork::Paris | + EthereumHardfork::Shanghai => Some(2106456), + EthereumHardfork::Cancun => Some(6383256), + _ => None, + }, + |fork| match fork { + Self::Bedrock | Self::Regolith => Some(0), + Self::Canyon => Some(2106456), + Self::Ecotone => Some(6383256), + Self::Fjord => Some(10615056), + }, + ) + } + + /// Retrieves the activation block for the specified hardfork on the Base mainnet. + pub fn base_mainnet_activation_block(fork: H) -> Option { + match_hardfork( + fork, + |fork| match fork { + EthereumHardfork::Frontier | + EthereumHardfork::Homestead | + EthereumHardfork::Dao | + EthereumHardfork::Tangerine | + EthereumHardfork::SpuriousDragon | + EthereumHardfork::Byzantium | + EthereumHardfork::Constantinople | + EthereumHardfork::Petersburg | + EthereumHardfork::Istanbul | + EthereumHardfork::MuirGlacier | + EthereumHardfork::Berlin | + EthereumHardfork::London | + EthereumHardfork::ArrowGlacier | + EthereumHardfork::GrayGlacier | + EthereumHardfork::Paris | + EthereumHardfork::Shanghai => Some(9101527), + EthereumHardfork::Cancun => Some(11188936), + _ => None, + }, + |fork| match fork { + Self::Bedrock | Self::Regolith => Some(0), + Self::Canyon => Some(9101527), + Self::Ecotone => Some(11188936), + _ => None, + }, + ) + } + + /// Retrieves the activation timestamp for the specified hardfork on the Base Sepolia testnet. + pub fn base_sepolia_activation_timestamp(fork: H) -> Option { + match_hardfork( + fork, + |fork| match fork { + EthereumHardfork::Frontier | + EthereumHardfork::Homestead | + EthereumHardfork::Dao | + EthereumHardfork::Tangerine | + EthereumHardfork::SpuriousDragon | + EthereumHardfork::Byzantium | + EthereumHardfork::Constantinople | + EthereumHardfork::Petersburg | + EthereumHardfork::Istanbul | + EthereumHardfork::MuirGlacier | + EthereumHardfork::Berlin | + EthereumHardfork::London | + EthereumHardfork::ArrowGlacier | + EthereumHardfork::GrayGlacier | + EthereumHardfork::Paris | + EthereumHardfork::Shanghai => Some(1699981200), + EthereumHardfork::Cancun => Some(1708534800), + _ => None, + }, + |fork| match fork { + Self::Bedrock | Self::Regolith => Some(1695768288), + Self::Canyon => Some(1699981200), + Self::Ecotone => Some(1708534800), + Self::Fjord => Some(1716998400), + }, + ) + } + + /// Retrieves the activation timestamp for the specified hardfork on the Base mainnet. + pub fn base_mainnet_activation_timestamp(fork: H) -> Option { + match_hardfork( + fork, + |fork| match fork { + EthereumHardfork::Frontier | + EthereumHardfork::Homestead | + EthereumHardfork::Dao | + EthereumHardfork::Tangerine | + EthereumHardfork::SpuriousDragon | + EthereumHardfork::Byzantium | + EthereumHardfork::Constantinople | + EthereumHardfork::Petersburg | + EthereumHardfork::Istanbul | + EthereumHardfork::MuirGlacier | + EthereumHardfork::Berlin | + EthereumHardfork::London | + EthereumHardfork::ArrowGlacier | + EthereumHardfork::GrayGlacier | + EthereumHardfork::Paris | + EthereumHardfork::Shanghai => Some(1704992401), + EthereumHardfork::Cancun => Some(1710374401), + _ => None, + }, + |fork| match fork { + Self::Bedrock | Self::Regolith => Some(1686789347), + Self::Canyon => Some(1704992401), + Self::Ecotone => Some(1710374401), + Self::Fjord => Some(1720627201), + }, + ) + } + + /// Optimism mainnet list of hardforks. + pub fn op_mainnet() -> ChainHardforks { + ChainHardforks::new(vec![ + (EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::MuirGlacier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(3950000)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(105235063)), + (EthereumHardfork::ArrowGlacier.boxed(), ForkCondition::Block(105235063)), + (EthereumHardfork::GrayGlacier.boxed(), ForkCondition::Block(105235063)), + ( + EthereumHardfork::Paris.boxed(), + ForkCondition::TTD { fork_block: Some(105235063), total_difficulty: U256::ZERO }, + ), + (Self::Bedrock.boxed(), ForkCondition::Block(105235063)), + (Self::Regolith.boxed(), ForkCondition::Timestamp(0)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Timestamp(1704992401)), + (Self::Canyon.boxed(), ForkCondition::Timestamp(1704992401)), + (EthereumHardfork::Cancun.boxed(), ForkCondition::Timestamp(1710374401)), + (Self::Ecotone.boxed(), ForkCondition::Timestamp(1710374401)), + (Self::Fjord.boxed(), ForkCondition::Timestamp(1720627201)), + ]) + } + + /// Optimism sepolia list of hardforks. + pub fn op_sepolia() -> ChainHardforks { + ChainHardforks::new(vec![ + (EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::MuirGlacier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::ArrowGlacier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::GrayGlacier.boxed(), ForkCondition::Block(0)), + ( + EthereumHardfork::Paris.boxed(), + ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }, + ), + (Self::Bedrock.boxed(), ForkCondition::Block(0)), + (Self::Regolith.boxed(), ForkCondition::Timestamp(0)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Timestamp(1699981200)), + (Self::Canyon.boxed(), ForkCondition::Timestamp(1699981200)), + (EthereumHardfork::Cancun.boxed(), ForkCondition::Timestamp(1708534800)), + (Self::Ecotone.boxed(), ForkCondition::Timestamp(1708534800)), + (Self::Fjord.boxed(), ForkCondition::Timestamp(1716998400)), + ]) + } + + /// Base sepolia list of hardforks. + pub fn base_sepolia() -> ChainHardforks { + ChainHardforks::new(vec![ + (EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::MuirGlacier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::ArrowGlacier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::GrayGlacier.boxed(), ForkCondition::Block(0)), + ( + EthereumHardfork::Paris.boxed(), + ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }, + ), + (Self::Bedrock.boxed(), ForkCondition::Block(0)), + (Self::Regolith.boxed(), ForkCondition::Timestamp(0)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Timestamp(1699981200)), + (Self::Canyon.boxed(), ForkCondition::Timestamp(1699981200)), + (EthereumHardfork::Cancun.boxed(), ForkCondition::Timestamp(1708534800)), + (Self::Ecotone.boxed(), ForkCondition::Timestamp(1708534800)), + (Self::Fjord.boxed(), ForkCondition::Timestamp(1716998400)), + ]) + } + + /// Base mainnet list of hardforks. + pub fn base_mainnet() -> ChainHardforks { + ChainHardforks::new(vec![ + (EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::MuirGlacier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::ArrowGlacier.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::GrayGlacier.boxed(), ForkCondition::Block(0)), + ( + EthereumHardfork::Paris.boxed(), + ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::ZERO }, + ), + (Self::Bedrock.boxed(), ForkCondition::Block(0)), + (Self::Regolith.boxed(), ForkCondition::Timestamp(0)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Timestamp(1704992401)), + (Self::Canyon.boxed(), ForkCondition::Timestamp(1704992401)), + (EthereumHardfork::Cancun.boxed(), ForkCondition::Timestamp(1710374401)), + (Self::Ecotone.boxed(), ForkCondition::Timestamp(1710374401)), + (Self::Fjord.boxed(), ForkCondition::Timestamp(1720627201)), + ]) + } +} + +/// Match helper method since it's not possible to match on `dyn Hardfork` +fn match_hardfork(fork: H, hardfork_fn: HF, optimism_hardfork_fn: OHF) -> Option +where + H: Hardfork, + HF: Fn(&EthereumHardfork) -> Option, + OHF: Fn(&OptimismHardfork) -> Option, +{ + let fork: &dyn Any = ⋔ + if let Some(fork) = fork.downcast_ref::() { + return hardfork_fn(fork) + } + fork.downcast_ref::().and_then(optimism_hardfork_fn) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_match_hardfork() { + assert_eq!( + OptimismHardfork::base_mainnet_activation_block(EthereumHardfork::Cancun), + Some(11188936) + ); + assert_eq!( + OptimismHardfork::base_mainnet_activation_block(OptimismHardfork::Canyon), + Some(9101527) + ); + } +} diff --git a/crates/ethereum-forks/src/hardforks/ethereum.rs b/crates/ethereum-forks/src/hardforks/ethereum.rs new file mode 100644 index 000000000000..3b4c860ad394 --- /dev/null +++ b/crates/ethereum-forks/src/hardforks/ethereum.rs @@ -0,0 +1,56 @@ +use crate::{ + hardforks::{ChainHardforks, Hardforks}, + EthereumHardfork, ForkCondition, +}; + +/// Helper methods for Ethereum forks. +pub trait EthereumHardforks: Hardforks { + /// Convenience method to check if [`EthereumHardfork::Shanghai`] is active at a given + /// timestamp. + fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool { + self.is_fork_active_at_timestamp(EthereumHardfork::Shanghai, timestamp) + } + + /// Convenience method to check if [`EthereumHardfork::Cancun`] is active at a given timestamp. + fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool { + self.is_fork_active_at_timestamp(EthereumHardfork::Cancun, timestamp) + } + + /// Convenience method to check if [`EthereumHardfork::Prague`] is active at a given timestamp. + fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool { + self.is_fork_active_at_timestamp(EthereumHardfork::Prague, timestamp) + } + + /// Convenience method to check if [`EthereumHardfork::Byzantium`] is active at a given block + /// number. + fn is_byzantium_active_at_block(&self, block_number: u64) -> bool { + self.fork(EthereumHardfork::Byzantium).active_at_block(block_number) + } + + /// Convenience method to check if [`EthereumHardfork::SpuriousDragon`] is active at a given + /// block number. + fn is_spurious_dragon_active_at_block(&self, block_number: u64) -> bool { + self.fork(EthereumHardfork::SpuriousDragon).active_at_block(block_number) + } + + /// Convenience method to check if [`EthereumHardfork::Homestead`] is active at a given block + /// number. + fn is_homestead_active_at_block(&self, block_number: u64) -> bool { + self.fork(EthereumHardfork::Homestead).active_at_block(block_number) + } + + /// The Paris hardfork (merge) is activated via block number. If we have knowledge of the block, + /// this function will return true if the block number is greater than or equal to the Paris + /// (merge) block. + fn is_paris_active_at_block(&self, block_number: u64) -> Option { + match self.fork(EthereumHardfork::Paris) { + ForkCondition::Block(paris_block) => Some(block_number >= paris_block), + ForkCondition::TTD { fork_block, .. } => { + fork_block.map(|paris_block| block_number >= paris_block) + } + _ => None, + } + } +} + +impl EthereumHardforks for ChainHardforks {} diff --git a/crates/ethereum-forks/src/hardforks/mod.rs b/crates/ethereum-forks/src/hardforks/mod.rs new file mode 100644 index 000000000000..121a189e0152 --- /dev/null +++ b/crates/ethereum-forks/src/hardforks/mod.rs @@ -0,0 +1,131 @@ +/// Ethereum helper methods +mod ethereum; +pub use ethereum::EthereumHardforks; + +/// Optimism helper methods +mod optimism; +pub use optimism::OptimismHardforks; + +use crate::{ForkCondition, Hardfork}; +use rustc_hash::FxHashMap; + +/// Generic trait over a set of ordered hardforks +pub trait Hardforks: Default + Clone { + /// Retrieves [`ForkCondition`] from `fork`. If `fork` is not present, returns + /// [`ForkCondition::Never`]. + fn fork(&self, fork: H) -> ForkCondition; + + /// Get an iterator of all hardforks with their respective activation conditions. + fn forks_iter(&self) -> impl Iterator; + + /// Convenience method to check if a fork is active at a given timestamp. + fn is_fork_active_at_timestamp(&self, fork: H, timestamp: u64) -> bool { + self.fork(fork).active_at_timestamp(timestamp) + } + + /// Convenience method to check if a fork is active at a given block number. + fn is_fork_active_at_block(&self, fork: H, block_number: u64) -> bool { + self.fork(fork).active_at_block(block_number) + } +} + +/// Ordered list of a chain hardforks that implement [`Hardfork`]. +#[derive(Default, Clone, PartialEq, Eq)] +pub struct ChainHardforks { + forks: Vec<(Box, ForkCondition)>, + map: FxHashMap<&'static str, ForkCondition>, +} + +impl ChainHardforks { + /// Creates a new [`ChainHardforks`] from a list which **must be ordered** by activation. + /// + /// Equivalent Ethereum hardforks **must be included** as well. + pub fn new(forks: Vec<(Box, ForkCondition)>) -> Self { + let map = forks.iter().map(|(fork, condition)| (fork.name(), *condition)).collect(); + + Self { forks, map } + } + + /// Total number of hardforks. + pub fn len(&self) -> usize { + self.forks.len() + } + + /// Checks if the fork list is empty. + pub fn is_empty(&self) -> bool { + self.forks.is_empty() + } + + /// Retrieves [`ForkCondition`] from `fork`. If `fork` is not present, returns + /// [`ForkCondition::Never`]. + pub fn fork(&self, fork: H) -> ForkCondition { + self.get(fork).unwrap_or(ForkCondition::Never) + } + + /// Retrieves [`ForkCondition`] from `fork` if it exists, otherwise `None`. + pub fn get(&self, fork: H) -> Option { + self.map.get(fork.name()).copied() + } + + /// Get an iterator of all hardforks with their respective activation conditions. + pub fn forks_iter(&self) -> impl Iterator { + self.forks.iter().map(|(f, b)| (&**f, *b)) + } + + /// Get last hardfork from the list. + pub fn last(&self) -> Option<(Box, ForkCondition)> { + self.forks.last().map(|(f, b)| (f.clone(), *b)) + } + + /// Convenience method to check if a fork is active at a given timestamp. + pub fn is_fork_active_at_timestamp(&self, fork: H, timestamp: u64) -> bool { + self.fork(fork).active_at_timestamp(timestamp) + } + + /// Convenience method to check if a fork is active at a given block number. + pub fn is_fork_active_at_block(&self, fork: H, block_number: u64) -> bool { + self.fork(fork).active_at_block(block_number) + } + + /// Inserts `fork` into list, updating with a new [`ForkCondition`] if it already exists. + pub fn insert(&mut self, fork: H, condition: ForkCondition) { + match self.map.entry(fork.name()) { + std::collections::hash_map::Entry::Occupied(mut entry) => { + *entry.get_mut() = condition; + if let Some((_, inner)) = + self.forks.iter_mut().find(|(inner, _)| inner.name() == fork.name()) + { + *inner = condition; + } + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(condition); + self.forks.push((Box::new(fork), condition)); + } + } + } + + /// Removes `fork` from list. + pub fn remove(&mut self, fork: H) { + self.forks.retain(|(inner_fork, _)| inner_fork.name() != fork.name()); + self.map.remove(fork.name()); + } +} + +impl Hardforks for ChainHardforks { + fn fork(&self, fork: H) -> ForkCondition { + self.fork(fork) + } + + fn forks_iter(&self) -> impl Iterator { + self.forks_iter() + } +} + +impl core::fmt::Debug for ChainHardforks { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ChainHardforks") + .field("0", &self.forks_iter().map(|(hf, cond)| (hf.name(), cond)).collect::>()) + .finish() + } +} diff --git a/crates/ethereum-forks/src/hardforks/optimism.rs b/crates/ethereum-forks/src/hardforks/optimism.rs new file mode 100644 index 000000000000..39b2bf4ab454 --- /dev/null +++ b/crates/ethereum-forks/src/hardforks/optimism.rs @@ -0,0 +1,12 @@ +use crate::{ChainHardforks, EthereumHardforks, OptimismHardfork}; + +/// Extends [`crate::EthereumHardforks`] with optimism helper methods. +pub trait OptimismHardforks: EthereumHardforks { + /// Convenience method to check if [`OptimismHardfork::Bedrock`] is active at a given block + /// number. + fn is_bedrock_active_at_block(&self, block_number: u64) -> bool { + self.fork(OptimismHardfork::Bedrock).active_at_block(block_number) + } +} + +impl OptimismHardforks for ChainHardforks {} diff --git a/crates/ethereum-forks/src/lib.rs b/crates/ethereum-forks/src/lib.rs index 37e9b8710823..51e619f4db0d 100644 --- a/crates/ethereum-forks/src/lib.rs +++ b/crates/ethereum-forks/src/lib.rs @@ -22,19 +22,18 @@ mod display; mod forkcondition; mod forkid; mod hardfork; +mod hardforks; mod head; pub use forkid::{ EnrForkIdEntry, ForkFilter, ForkFilterKey, ForkHash, ForkId, ForkTransition, ValidationError, }; -pub use hardfork::Hardfork; +pub use hardfork::{EthereumHardfork, Hardfork, OptimismHardfork, DEV_HARDFORKS}; pub use head::Head; pub use display::DisplayHardforks; pub use forkcondition::ForkCondition; - -/// Chains hardforks -pub mod chains; +pub use hardforks::*; #[cfg(any(test, feature = "arbitrary"))] pub use arbitrary; diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 210f54461394..2027fd539c1f 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -8,7 +8,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use reth_chainspec::{Chain, ChainSpec, Hardfork}; +use reth_chainspec::{Chain, ChainSpec, EthereumHardfork, EthereumHardforks}; use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ validate_4844_header_standalone, validate_against_parent_4844, @@ -51,7 +51,7 @@ impl EthBeaconConsensus { ) -> Result<(), ConsensusError> { // Determine the parent gas limit, considering elasticity multiplier on the London fork. let parent_gas_limit = - if self.chain_spec.fork(Hardfork::London).transitions_at_block(header.number) { + if self.chain_spec.fork(EthereumHardfork::London).transitions_at_block(header.number) { parent.gas_limit * self.chain_spec .base_fee_params_at_timestamp(header.timestamp) @@ -153,7 +153,7 @@ impl Consensus for EthBeaconConsensus { ) -> Result<(), ConsensusError> { let is_post_merge = self .chain_spec - .fork(Hardfork::Paris) + .fork(EthereumHardfork::Paris) .active_at_ttd(total_difficulty, header.difficulty); if is_post_merge { diff --git a/crates/ethereum/consensus/src/validation.rs b/crates/ethereum/consensus/src/validation.rs index 1566ec176215..fafd0ee4217b 100644 --- a/crates/ethereum/consensus/src/validation.rs +++ b/crates/ethereum/consensus/src/validation.rs @@ -1,4 +1,4 @@ -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus::ConsensusError; use reth_primitives::{ gas_spent_by_transactions, BlockWithSenders, Bloom, GotExpected, Receipt, Request, B256, diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index 8976f0caa5af..7a4129963cb4 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -5,7 +5,7 @@ use reth_chainspec::ChainSpec; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ constants::EIP1559_INITIAL_BASE_FEE, revm::config::revm_spec_by_timestamp_after_merge, Address, - BlobTransactionSidecar, Hardfork, Header, SealedBlock, Withdrawals, B256, U256, + BlobTransactionSidecar, EthereumHardfork, Header, SealedBlock, Withdrawals, B256, U256, }; use reth_rpc_types::engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, @@ -266,7 +266,7 @@ impl PayloadBuilderAttributes for EthPayloadBuilderAttributes { // If we are on the London fork boundary, we need to multiply the parent's gas limit by the // elasticity multiplier to get the new gas limit. - if chain_spec.fork(Hardfork::London).transitions_at_block(parent.number + 1) { + if chain_spec.fork(EthereumHardfork::London).transitions_at_block(parent.number + 1) { let elasticity_multiplier = chain_spec.base_fee_params_at_timestamp(self.timestamp()).elasticity_multiplier; diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index d4cc8be77d20..503034e1aa6d 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -4,9 +4,7 @@ use crate::{ dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS}, EthEvmConfig, }; -#[cfg(not(feature = "std"))] -use alloc::{sync::Arc, vec, vec::Vec}; -use reth_chainspec::{ChainSpec, MAINNET}; +use reth_chainspec::{ChainSpec, EthereumHardforks, MAINNET}; use reth_ethereum_consensus::validate_block_post_execution; use reth_evm::{ execute::{ @@ -17,7 +15,7 @@ use reth_evm::{ }; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ - BlockNumber, BlockWithSenders, Hardfork, Header, Receipt, Request, Withdrawals, U256, + BlockNumber, BlockWithSenders, EthereumHardfork, Header, Receipt, Request, Withdrawals, U256, }; use reth_prune_types::PruneModes; use reth_revm::{ @@ -35,7 +33,7 @@ use revm_primitives::{ }; #[cfg(feature = "std")] -use std::{fmt::Display, sync::Arc}; +use std::{fmt::Display, sync::Arc, vec, vec::Vec}; /// Provides executors to execute regular ethereum blocks #[derive(Debug, Clone)] pub struct EthExecutorProvider { @@ -342,7 +340,7 @@ where ); // Irregular state change at Ethereum DAO hardfork - if self.chain_spec().fork(Hardfork::Dao).transitions_at_block(block.number) { + if self.chain_spec().fork(EthereumHardfork::Dao).transitions_at_block(block.number) { // drain balances from hardcoded addresses. let drained_balance: u128 = self .state @@ -539,7 +537,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) .build(), ); @@ -636,7 +634,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) .build(), ); @@ -679,7 +677,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) .build(), ); @@ -732,7 +730,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(0)) + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0)) .build(), ); @@ -819,7 +817,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Cancun, ForkCondition::Timestamp(1)) + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1)) .build(), ); @@ -889,7 +887,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Prague, ForkCondition::Never) + .with_fork(EthereumHardfork::Prague, ForkCondition::Never) .build(), ); @@ -942,7 +940,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Prague, ForkCondition::Timestamp(0)) + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0)) .build(), ); @@ -994,7 +992,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Prague, ForkCondition::Timestamp(1)) + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1)) .build(), ); @@ -1057,7 +1055,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Prague, ForkCondition::Timestamp(1)) + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1)) .build(), ); @@ -1116,7 +1114,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Prague, ForkCondition::Timestamp(0)) + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0)) .build(), ); @@ -1253,7 +1251,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Prague, ForkCondition::Timestamp(0)) + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0)) .build(), ); @@ -1334,7 +1332,7 @@ mod tests { let chain_spec = Arc::new( ChainSpecBuilder::from(&*MAINNET) .shanghai_activated() - .with_fork(Hardfork::Prague, ForkCondition::Timestamp(0)) + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0)) .build(), ); diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 1e53180d99bb..43a00fb98cff 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -28,7 +28,8 @@ use reth_primitives::{ eip4844::calculate_excess_blob_gas, proofs::{self, calculate_requests_root}, revm::env::tx_env_with_recovered, - Block, Header, IntoRecoveredTransaction, Receipt, EMPTY_OMMER_ROOT_HASH, U256, + Block, EthereumHardforks, Header, IntoRecoveredTransaction, Receipt, EMPTY_OMMER_ROOT_HASH, + U256, }; use reth_provider::StateProviderFactory; use reth_revm::{database::StateProviderDatabase, state_change::apply_blockhashes_update}; diff --git a/crates/net/discv5/src/enr.rs b/crates/net/discv5/src/enr.rs index eb8b6be006b2..bb49f72e8f6d 100644 --- a/crates/net/discv5/src/enr.rs +++ b/crates/net/discv5/src/enr.rs @@ -58,7 +58,7 @@ mod tests { use super::*; use alloy_rlp::Encodable; use discv5::enr::{CombinedKey, EnrKey}; - use reth_chainspec::{Hardfork, MAINNET}; + use reth_chainspec::{EthereumHardfork, MAINNET}; use reth_network_peers::NodeRecord; #[test] @@ -84,7 +84,7 @@ mod tests { let key = CombinedKey::generate_secp256k1(); let mut buf = Vec::new(); - let fork_id = MAINNET.hardfork_fork_id(Hardfork::Frontier); + let fork_id = MAINNET.hardfork_fork_id(EthereumHardfork::Frontier); fork_id.unwrap().encode(&mut buf); let enr = Enr::builder() diff --git a/crates/net/dns/src/lib.rs b/crates/net/dns/src/lib.rs index f07fde2e4a67..55d93c459cb8 100644 --- a/crates/net/dns/src/lib.rs +++ b/crates/net/dns/src/lib.rs @@ -415,7 +415,7 @@ mod tests { use alloy_rlp::{Decodable, Encodable}; use enr::EnrKey; use reth_chainspec::MAINNET; - use reth_ethereum_forks::{ForkHash, Hardfork}; + use reth_ethereum_forks::{EthereumHardfork, ForkHash}; use secp256k1::rand::thread_rng; use std::{future::poll_fn, net::Ipv4Addr}; @@ -513,7 +513,7 @@ mod tests { resolver.insert(link.domain.clone(), root.to_string()); let mut builder = Enr::builder(); - let fork_id = MAINNET.hardfork_fork_id(Hardfork::Frontier).unwrap(); + let fork_id = MAINNET.hardfork_fork_id(EthereumHardfork::Frontier).unwrap(); builder .ip4(Ipv4Addr::LOCALHOST) .udp4(30303) diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 95e5aa84adf5..873af22274bd 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -3,7 +3,7 @@ use alloy_genesis::Genesis; use alloy_rlp::{RlpDecodable, RlpEncodable}; use reth_chainspec::{Chain, ChainSpec, NamedChain, MAINNET}; use reth_codecs_derive::derive_arbitrary; -use reth_primitives::{hex, ForkId, Hardfork, Head, B256, U256}; +use reth_primitives::{hex, EthereumHardfork, ForkId, Head, B256, U256}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; @@ -142,7 +142,7 @@ impl Default for Status { blockhash: mainnet_genesis, genesis: mainnet_genesis, forkid: MAINNET - .hardfork_fork_id(Hardfork::Frontier) + .hardfork_fork_id(EthereumHardfork::Frontier) .expect("The Frontier hardfork should always exist"), } } @@ -152,7 +152,7 @@ impl Default for Status { /// /// # Example /// ``` -/// use reth_chainspec::{Chain, Hardfork, MAINNET}; +/// use reth_chainspec::{Chain, EthereumHardfork, MAINNET}; /// use reth_eth_wire_types::{EthVersion, Status}; /// use reth_primitives::{B256, MAINNET_GENESIS_HASH, U256}; /// @@ -163,7 +163,7 @@ impl Default for Status { /// .total_difficulty(U256::from(100)) /// .blockhash(B256::from(MAINNET_GENESIS_HASH)) /// .genesis(B256::from(MAINNET_GENESIS_HASH)) -/// .forkid(MAINNET.hardfork_fork_id(Hardfork::Paris).unwrap()) +/// .forkid(MAINNET.hardfork_fork_id(EthereumHardfork::Paris).unwrap()) /// .build(); /// /// assert_eq!( @@ -174,7 +174,7 @@ impl Default for Status { /// total_difficulty: U256::from(100), /// blockhash: B256::from(MAINNET_GENESIS_HASH), /// genesis: B256::from(MAINNET_GENESIS_HASH), -/// forkid: MAINNET.hardfork_fork_id(Hardfork::Paris).unwrap(), +/// forkid: MAINNET.hardfork_fork_id(EthereumHardfork::Paris).unwrap(), /// } /// ); /// ``` @@ -233,7 +233,7 @@ mod tests { use alloy_rlp::{Decodable, Encodable}; use rand::Rng; use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain}; - use reth_primitives::{hex, ForkHash, ForkId, Hardfork, Head, B256, U256}; + use reth_primitives::{hex, EthereumHardfork, ForkHash, ForkId, Head, B256, U256}; use std::str::FromStr; #[test] @@ -368,12 +368,12 @@ mod tests { // add a few hardforks let hardforks = vec![ - (Hardfork::Tangerine, ForkCondition::Block(1)), - (Hardfork::SpuriousDragon, ForkCondition::Block(2)), - (Hardfork::Byzantium, ForkCondition::Block(3)), - (Hardfork::MuirGlacier, ForkCondition::Block(5)), - (Hardfork::London, ForkCondition::Block(8)), - (Hardfork::Shanghai, ForkCondition::Timestamp(13)), + (EthereumHardfork::Tangerine, ForkCondition::Block(1)), + (EthereumHardfork::SpuriousDragon, ForkCondition::Block(2)), + (EthereumHardfork::Byzantium, ForkCondition::Block(3)), + (EthereumHardfork::MuirGlacier, ForkCondition::Block(5)), + (EthereumHardfork::London, ForkCondition::Block(8)), + (EthereumHardfork::Shanghai, ForkCondition::Timestamp(13)), ]; let mut chainspec = ChainSpec::builder().genesis(genesis).chain(Chain::from_id(1337)); diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 2b5d36cdb267..5edded5d6c3f 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -563,7 +563,6 @@ mod tests { use reth_dns_discovery::tree::LinkEntry; use reth_primitives::ForkHash; use reth_provider::test_utils::NoopProvider; - use std::collections::BTreeMap; fn builder() -> NetworkConfigBuilder { let secret_key = SecretKey::new(&mut thread_rng()); @@ -587,7 +586,7 @@ mod tests { let mut chain_spec = Arc::clone(&MAINNET); // remove any `next` fields we would have by removing all hardforks - Arc::make_mut(&mut chain_spec).hardforks = BTreeMap::new(); + Arc::make_mut(&mut chain_spec).hardforks = Default::default(); // check that the forkid is initialized with the genesis and no other forks let genesis_fork_hash = ForkHash::from(chain_spec.genesis_hash()); diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 97ccaf2c6a78..1cd090105993 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -768,7 +768,7 @@ mod tests { }; use reth_network_peers::pk2id; use reth_network_types::session::config::PROTOCOL_BREACH_REQUEST_TIMEOUT; - use reth_primitives::{ForkFilter, Hardfork}; + use reth_primitives::{EthereumHardfork, ForkFilter}; use secp256k1::{SecretKey, SECP256K1}; use tokio::{ net::{TcpListener, TcpStream}, @@ -918,7 +918,7 @@ mod tests { local_peer_id, status: StatusBuilder::default().build(), fork_filter: MAINNET - .hardfork_fork_filter(Hardfork::Frontier) + .hardfork_fork_filter(EthereumHardfork::Frontier) .expect("The Frontier fork filter should exist on mainnet"), } } diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index 0c5439bf7448..61aa23bde15f 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -9,7 +9,7 @@ // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks, OptimismHardforks}; use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ validate_against_parent_4844, validate_against_parent_eip1559_base_fee, diff --git a/crates/optimism/consensus/src/validation.rs b/crates/optimism/consensus/src/validation.rs index 8aa00c53cec4..d7bb7681c517 100644 --- a/crates/optimism/consensus/src/validation.rs +++ b/crates/optimism/consensus/src/validation.rs @@ -1,4 +1,4 @@ -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus::ConsensusError; use reth_primitives::{ gas_spent_by_transactions, proofs::calculate_receipt_root_optimism, BlockWithSenders, Bloom, diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 3580c9108dd4..bf9fbc57f525 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -1,7 +1,7 @@ //! Optimism block executor. use crate::{l1::ensure_create2_deployer, OptimismBlockExecutionError, OptimismEvmConfig}; -use reth_chainspec::{ChainSpec, Hardfork}; +use reth_chainspec::{ChainSpec, EthereumHardforks, OptimismHardfork}; use reth_evm::{ execute::{ BatchExecutor, BlockExecutionError, BlockExecutionInput, BlockExecutionOutput, @@ -133,7 +133,7 @@ where // execute transactions let is_regolith = - self.chain_spec.fork(Hardfork::Regolith).active_at_timestamp(block.timestamp); + self.chain_spec.fork(OptimismHardfork::Regolith).active_at_timestamp(block.timestamp); // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism // blocks will always have at least a single transaction in them (the L1 info transaction), @@ -220,7 +220,7 @@ where // this is only set for post-Canyon deposit transactions. deposit_receipt_version: (transaction.is_deposit() && self.chain_spec - .is_fork_active_at_timestamp(Hardfork::Canyon, block.timestamp)) + .is_fork_active_at_timestamp(OptimismHardfork::Canyon, block.timestamp)) .then_some(1), }); } diff --git a/crates/optimism/evm/src/l1.rs b/crates/optimism/evm/src/l1.rs index a750c8f4f0a9..62b412891f64 100644 --- a/crates/optimism/evm/src/l1.rs +++ b/crates/optimism/evm/src/l1.rs @@ -1,7 +1,7 @@ //! Optimism-specific implementation and utilities for the executor use crate::OptimismBlockExecutionError; -use reth_chainspec::{ChainSpec, Hardfork}; +use reth_chainspec::{ChainSpec, OptimismHardfork}; use reth_execution_errors::BlockExecutionError; use reth_primitives::{address, b256, hex, Address, Block, Bytes, B256, U256}; use revm::{ @@ -191,13 +191,14 @@ impl RethL1BlockInfo for L1BlockInfo { return Ok(U256::ZERO) } - let spec_id = if chain_spec.is_fork_active_at_timestamp(Hardfork::Fjord, timestamp) { + let spec_id = if chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, timestamp) + { SpecId::FJORD - } else if chain_spec.is_fork_active_at_timestamp(Hardfork::Ecotone, timestamp) { + } else if chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Ecotone, timestamp) { SpecId::ECOTONE - } else if chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, timestamp) { + } else if chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, timestamp) { SpecId::REGOLITH - } else if chain_spec.is_fork_active_at_timestamp(Hardfork::Bedrock, timestamp) { + } else if chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Bedrock, timestamp) { SpecId::BEDROCK } else { return Err(OptimismBlockExecutionError::L1BlockInfoError { @@ -214,11 +215,12 @@ impl RethL1BlockInfo for L1BlockInfo { timestamp: u64, input: &[u8], ) -> Result { - let spec_id = if chain_spec.is_fork_active_at_timestamp(Hardfork::Fjord, timestamp) { + let spec_id = if chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Fjord, timestamp) + { SpecId::FJORD - } else if chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, timestamp) { + } else if chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Regolith, timestamp) { SpecId::REGOLITH - } else if chain_spec.is_fork_active_at_timestamp(Hardfork::Bedrock, timestamp) { + } else if chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Bedrock, timestamp) { SpecId::BEDROCK } else { return Err(OptimismBlockExecutionError::L1BlockInfoError { @@ -245,8 +247,9 @@ where // previous block timestamp (heuristically, block time is not perfectly constant at 2s), and the // chain is an optimism chain, then we need to force-deploy the create2 deployer contract. if chain_spec.is_optimism() && - chain_spec.is_fork_active_at_timestamp(Hardfork::Canyon, timestamp) && - !chain_spec.is_fork_active_at_timestamp(Hardfork::Canyon, timestamp.saturating_sub(2)) + chain_spec.is_fork_active_at_timestamp(OptimismHardfork::Canyon, timestamp) && + !chain_spec + .is_fork_active_at_timestamp(OptimismHardfork::Canyon, timestamp.saturating_sub(2)) { trace!(target: "evm", "Forcing create2 deployer contract deployment on Canyon transition"); diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index 6507d5c9290f..d0fc9b53ef1e 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -1,4 +1,4 @@ -use reth_chainspec::{ChainSpec, Hardfork}; +use reth_chainspec::{ChainSpec, OptimismHardfork}; use reth_node_api::{ payload::{ validate_parent_beacon_block_root_presence, EngineApiMessageVersion, @@ -69,7 +69,7 @@ pub fn validate_withdrawals_presence( timestamp: u64, has_withdrawals: bool, ) -> Result<(), EngineObjectValidationError> { - let is_shanghai = chain_spec.fork(Hardfork::Canyon).active_at_timestamp(timestamp); + let is_shanghai = chain_spec.fork(OptimismHardfork::Canyon).active_at_timestamp(timestamp); match version { EngineApiMessageVersion::V1 => { diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index fa17982cb8c6..3b08b8df0a18 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -5,7 +5,7 @@ use crate::{ payload::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}, }; use reth_basic_payload_builder::*; -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks, OptimismHardfork}; use reth_evm::ConfigureEvm; use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; @@ -14,8 +14,7 @@ use reth_primitives::{ eip4844::calculate_excess_blob_gas, proofs, revm::env::tx_env_with_recovered, - Block, Hardfork, Header, IntoRecoveredTransaction, Receipt, TxType, EMPTY_OMMER_ROOT_HASH, - U256, + Block, Header, IntoRecoveredTransaction, Receipt, TxType, EMPTY_OMMER_ROOT_HASH, U256, }; use reth_provider::StateProviderFactory; use reth_revm::database::StateProviderDatabase; @@ -281,8 +280,10 @@ where let block_number = initialized_block_env.number.to::(); - let is_regolith = chain_spec - .is_fork_active_at_timestamp(Hardfork::Regolith, attributes.payload_attributes.timestamp); + let is_regolith = chain_spec.is_fork_active_at_timestamp( + OptimismHardfork::Regolith, + attributes.payload_attributes.timestamp, + ); // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( @@ -393,7 +394,7 @@ where // ensures this is only set for post-Canyon deposit transactions. deposit_receipt_version: chain_spec .is_fork_active_at_timestamp( - Hardfork::Canyon, + OptimismHardfork::Canyon, attributes.payload_attributes.timestamp, ) .then_some(1), diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index ae63246558dd..1cdc1f6c4d6c 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -3,7 +3,7 @@ //! Optimism builder support use alloy_rlp::Encodable; -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_payload_builder::EthPayloadBuilderAttributes; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index 26dc06293404..d677ce842ffd 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -11,7 +11,7 @@ use crate::metrics::PayloadBuilderMetrics; use futures_core::ready; use futures_util::FutureExt; -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_payload_builder::{ database::CachedReads, error::PayloadBuilderError, KeepPayloadJobAlive, PayloadId, PayloadJob, PayloadJobGenerator, diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index 52029a3c4a37..99601301710c 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -20,8 +20,7 @@ pub use traits::{BuiltPayload, PayloadAttributes, PayloadBuilderAttributes}; mod payload; pub use payload::PayloadOrAttributes; -use reth_chainspec::ChainSpec; - +use reth_chainspec::{ChainSpec, EthereumHardforks}; /// The types that are used by the engine API. pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone { /// The built payload type. diff --git a/crates/payload/validator/src/lib.rs b/crates/payload/validator/src/lib.rs index afc19037732d..e71d2b58df4a 100644 --- a/crates/payload/validator/src/lib.rs +++ b/crates/payload/validator/src/lib.rs @@ -8,7 +8,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_primitives::SealedBlock; use reth_rpc_types::{engine::MaybeCancunPayloadFields, ExecutionPayload, PayloadError}; use reth_rpc_types_compat::engine::payload::try_into_block; diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index 5521fedecf97..9e0a0357905f 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -4,9 +4,8 @@ use crate::{ constants::EMPTY_OMMER_ROOT_HASH, keccak256, Header, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Request, TransactionSigned, Withdrawal, B256, }; -use reth_trie_common::root::{ordered_trie_root, ordered_trie_root_with_encoder}; - use alloy_eips::eip7685::Encodable7685; +use reth_trie_common::root::{ordered_trie_root, ordered_trie_root_with_encoder}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -50,8 +49,9 @@ pub fn calculate_receipt_root_optimism( // encoding. In the Regolith Hardfork, we must strip the deposit nonce from the // receipts before calculating the receipt root. This was corrected in the Canyon // hardfork. - if chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Regolith, timestamp) && - !chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Canyon, timestamp) + if chain_spec.is_fork_active_at_timestamp(reth_chainspec::OptimismHardfork::Regolith, timestamp) && + !chain_spec + .is_fork_active_at_timestamp(reth_chainspec::OptimismHardfork::Canyon, timestamp) { let receipts = receipts .iter() @@ -98,8 +98,9 @@ pub fn calculate_receipt_root_no_memo_optimism( // encoding. In the Regolith Hardfork, we must strip the deposit nonce from the // receipts before calculating the receipt root. This was corrected in the Canyon // hardfork. - if chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Regolith, timestamp) && - !chain_spec.is_fork_active_at_timestamp(reth_chainspec::Hardfork::Canyon, timestamp) + if chain_spec.is_fork_active_at_timestamp(reth_chainspec::OptimismHardfork::Regolith, timestamp) && + !chain_spec + .is_fork_active_at_timestamp(reth_chainspec::OptimismHardfork::Canyon, timestamp) { let receipts = receipts .iter() diff --git a/crates/primitives/src/revm/config.rs b/crates/primitives/src/revm/config.rs index 5914dbf59348..9ed2650926a8 100644 --- a/crates/primitives/src/revm/config.rs +++ b/crates/primitives/src/revm/config.rs @@ -1,5 +1,7 @@ -use reth_chainspec::ChainSpec; -use reth_ethereum_forks::{Hardfork, Head}; +#[cfg(feature = "optimism")] +use reth_chainspec::OptimismHardfork; +use reth_chainspec::{ChainSpec, EthereumHardforks}; +use reth_ethereum_forks::{EthereumHardfork, Head}; /// Returns the spec id at the given timestamp. /// @@ -11,13 +13,13 @@ pub fn revm_spec_by_timestamp_after_merge( ) -> revm_primitives::SpecId { #[cfg(feature = "optimism")] if chain_spec.is_optimism() { - return if chain_spec.fork(Hardfork::Fjord).active_at_timestamp(timestamp) { + return if chain_spec.fork(OptimismHardfork::Fjord).active_at_timestamp(timestamp) { revm_primitives::FJORD - } else if chain_spec.fork(Hardfork::Ecotone).active_at_timestamp(timestamp) { + } else if chain_spec.fork(OptimismHardfork::Ecotone).active_at_timestamp(timestamp) { revm_primitives::ECOTONE - } else if chain_spec.fork(Hardfork::Canyon).active_at_timestamp(timestamp) { + } else if chain_spec.fork(OptimismHardfork::Canyon).active_at_timestamp(timestamp) { revm_primitives::CANYON - } else if chain_spec.fork(Hardfork::Regolith).active_at_timestamp(timestamp) { + } else if chain_spec.fork(OptimismHardfork::Regolith).active_at_timestamp(timestamp) { revm_primitives::REGOLITH } else { revm_primitives::BEDROCK @@ -39,44 +41,44 @@ pub fn revm_spec_by_timestamp_after_merge( pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm_primitives::SpecId { #[cfg(feature = "optimism")] if chain_spec.is_optimism() { - if chain_spec.fork(Hardfork::Fjord).active_at_head(&block) { + if chain_spec.fork(OptimismHardfork::Fjord).active_at_head(&block) { return revm_primitives::FJORD - } else if chain_spec.fork(Hardfork::Ecotone).active_at_head(&block) { + } else if chain_spec.fork(OptimismHardfork::Ecotone).active_at_head(&block) { return revm_primitives::ECOTONE - } else if chain_spec.fork(Hardfork::Canyon).active_at_head(&block) { + } else if chain_spec.fork(OptimismHardfork::Canyon).active_at_head(&block) { return revm_primitives::CANYON - } else if chain_spec.fork(Hardfork::Regolith).active_at_head(&block) { + } else if chain_spec.fork(OptimismHardfork::Regolith).active_at_head(&block) { return revm_primitives::REGOLITH - } else if chain_spec.fork(Hardfork::Bedrock).active_at_head(&block) { + } else if chain_spec.fork(OptimismHardfork::Bedrock).active_at_head(&block) { return revm_primitives::BEDROCK } } - if chain_spec.fork(Hardfork::Prague).active_at_head(&block) { + if chain_spec.fork(EthereumHardfork::Prague).active_at_head(&block) { revm_primitives::PRAGUE - } else if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Cancun).active_at_head(&block) { revm_primitives::CANCUN - } else if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Shanghai).active_at_head(&block) { revm_primitives::SHANGHAI - } else if chain_spec.fork(Hardfork::Paris).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Paris).active_at_head(&block) { revm_primitives::MERGE - } else if chain_spec.fork(Hardfork::London).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::London).active_at_head(&block) { revm_primitives::LONDON - } else if chain_spec.fork(Hardfork::Berlin).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Berlin).active_at_head(&block) { revm_primitives::BERLIN - } else if chain_spec.fork(Hardfork::Istanbul).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Istanbul).active_at_head(&block) { revm_primitives::ISTANBUL - } else if chain_spec.fork(Hardfork::Petersburg).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Petersburg).active_at_head(&block) { revm_primitives::PETERSBURG - } else if chain_spec.fork(Hardfork::Byzantium).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Byzantium).active_at_head(&block) { revm_primitives::BYZANTIUM - } else if chain_spec.fork(Hardfork::SpuriousDragon).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::SpuriousDragon).active_at_head(&block) { revm_primitives::SPURIOUS_DRAGON - } else if chain_spec.fork(Hardfork::Tangerine).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Tangerine).active_at_head(&block) { revm_primitives::TANGERINE - } else if chain_spec.fork(Hardfork::Homestead).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Homestead).active_at_head(&block) { revm_primitives::HOMESTEAD - } else if chain_spec.fork(Hardfork::Frontier).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Frontier).active_at_head(&block) { revm_primitives::FRONTIER } else { panic!( diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index 4505d2ee9761..fdbb4c461398 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -3,7 +3,7 @@ use alloy_eips::{ eip7002::WithdrawalRequest, }; use alloy_rlp::Buf; -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus_common::calc; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 8185bbe8cc04..11bde1c744f8 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -10,7 +10,7 @@ use reth_payload_primitives::{ validate_payload_timestamp, EngineApiMessageVersion, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes, }; -use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, Hardfork, B256, U64}; +use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, EthereumHardfork, B256, U64}; use reth_rpc_api::EngineApiServer; use reth_rpc_types::engine::{ CancunPayloadFields, ClientVersionV1, ExecutionPayload, ExecutionPayloadBodiesV1, @@ -457,7 +457,7 @@ where let merge_terminal_td = self .inner .chain_spec - .fork(Hardfork::Paris) + .fork(EthereumHardfork::Paris) .ttd() .expect("the engine API should not be running for chains w/o paris"); @@ -1036,7 +1036,11 @@ mod tests { let (handle, api) = setup_engine_api(); let transition_config = TransitionConfiguration { - terminal_total_difficulty: handle.chain_spec.fork(Hardfork::Paris).ttd().unwrap() + + terminal_total_difficulty: handle + .chain_spec + .fork(EthereumHardfork::Paris) + .ttd() + .unwrap() + U256::from(1), ..Default::default() }; @@ -1046,7 +1050,7 @@ mod tests { assert_matches!( res, Err(EngineApiError::TerminalTD { execution, consensus }) - if execution == handle.chain_spec.fork(Hardfork::Paris).ttd().unwrap() && consensus == U256::from(transition_config.terminal_total_difficulty) + if execution == handle.chain_spec.fork(EthereumHardfork::Paris).ttd().unwrap() && consensus == U256::from(transition_config.terminal_total_difficulty) ); } @@ -1063,7 +1067,11 @@ mod tests { random_block(&mut rng, terminal_block_number, None, None, None); let transition_config = TransitionConfiguration { - terminal_total_difficulty: handle.chain_spec.fork(Hardfork::Paris).ttd().unwrap(), + terminal_total_difficulty: handle + .chain_spec + .fork(EthereumHardfork::Paris) + .ttd() + .unwrap(), terminal_block_hash: consensus_terminal_block.hash(), terminal_block_number: U64::from(terminal_block_number), }; @@ -1101,7 +1109,11 @@ mod tests { random_block(&mut generators::rng(), terminal_block_number, None, None, None); let transition_config = TransitionConfiguration { - terminal_total_difficulty: handle.chain_spec.fork(Hardfork::Paris).ttd().unwrap(), + terminal_total_difficulty: handle + .chain_spec + .fork(EthereumHardfork::Paris) + .ttd() + .unwrap(), terminal_block_hash: terminal_block.hash(), terminal_block_number: U64::from(terminal_block_number), }; diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index c7036f4bedaa..f30e397e030f 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -4,6 +4,7 @@ use std::time::{Duration, Instant}; use futures::Future; +use reth_chainspec::EthereumHardforks; use reth_evm::ConfigureEvm; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 5a098feb7eec..2658547b1963 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use alloy_rlp::{Decodable, Encodable}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; +use reth_chainspec::EthereumHardforks; use reth_primitives::{ revm::env::tx_env_with_recovered, Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, Withdrawals, B256, U256, diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index bf1e7020c865..110f35dbd1eb 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -2,6 +2,7 @@ use std::{collections::HashSet, sync::Arc}; use async_trait::async_trait; use jsonrpsee::core::RpcResult as Result; +use reth_chainspec::EthereumHardforks; use reth_consensus_common::calc::{ base_block_reward, base_block_reward_pre_merge, block_reward, ommer_reward, }; diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index c6b4802b7829..76314f0c8b71 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -620,7 +620,7 @@ mod tests { ]), ..Default::default() }, - hardforks: BTreeMap::default(), + hardforks: Default::default(), genesis_hash: None, paris_block_and_final_difficulty: None, deposit_contract: None, diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 22260cc9cb39..e5fa31642142 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -14,7 +14,7 @@ use crate::{ WithdrawalsProvider, }; use itertools::{izip, Itertools}; -use reth_chainspec::{ChainInfo, ChainSpec}; +use reth_chainspec::{ChainInfo, ChainSpec, EthereumHardforks}; use reth_db::{tables, BlockNumberList}; use reth_db_api::{ common::KeyValue, diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 79a93cf2e2a7..36413709f838 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -9,7 +9,7 @@ use crate::{ EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig, PoolTransaction, TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator, }; -use reth_chainspec::ChainSpec; +use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_primitives::{ constants::{eip4844::MAX_BLOBS_PER_BLOCK, ETHEREUM_BLOCK_GAS_LIMIT}, Address, GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID, diff --git a/examples/bsc-p2p/src/chainspec.rs b/examples/bsc-p2p/src/chainspec.rs index d9c3a868297a..8983a2ef3805 100644 --- a/examples/bsc-p2p/src/chainspec.rs +++ b/examples/bsc-p2p/src/chainspec.rs @@ -1,7 +1,10 @@ -use reth_chainspec::{net::NodeRecord, BaseFeeParams, Chain, ChainSpec, ForkCondition, Hardfork}; +use reth_chainspec::{ + net::NodeRecord, BaseFeeParams, Chain, ChainHardforks, ChainSpec, EthereumHardfork, + ForkCondition, +}; use reth_primitives::{b256, B256}; -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; pub const SHANGHAI_TIME: u64 = 1705996800; @@ -13,7 +16,10 @@ pub(crate) fn bsc_chain_spec() -> Arc { genesis: serde_json::from_str(include_str!("./genesis.json")).expect("deserialize genesis"), genesis_hash: Some(GENESIS), paris_block_and_final_difficulty: None, - hardforks: BTreeMap::from([(Hardfork::Shanghai, ForkCondition::Timestamp(SHANGHAI_TIME))]), + hardforks: ChainHardforks::new(vec![( + EthereumHardfork::Shanghai.boxed(), + ForkCondition::Timestamp(SHANGHAI_TIME), + )]), deposit_contract: None, base_fee_params: reth_chainspec::BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 0, diff --git a/examples/exex/rollup/src/execution.rs b/examples/exex/rollup/src/execution.rs index cc5b716bdc09..2f755183fab4 100644 --- a/examples/exex/rollup/src/execution.rs +++ b/examples/exex/rollup/src/execution.rs @@ -12,8 +12,8 @@ use reth_primitives::{ keccak256, revm::env::fill_tx_env, revm_primitives::{CfgEnvWithHandlerCfg, EVMError, ExecutionResult, ResultAndState}, - Address, Block, BlockWithSenders, Bytes, Hardfork, Header, Receipt, TransactionSigned, TxType, - B256, U256, + Address, Block, BlockWithSenders, Bytes, EthereumHardfork, Header, Receipt, TransactionSigned, + TxType, B256, U256, }; use reth_revm::{ db::{states::bundle_state::BundleRetention, BundleState}, @@ -69,16 +69,17 @@ fn construct_header(db: &Database, header: &RollupContract::BlockHeader) -> eyre let block_number = u64::try_from(header.sequence)?; // Calculate base fee per gas for EIP-1559 transactions - let base_fee_per_gas = if CHAIN_SPEC.fork(Hardfork::London).transitions_at_block(block_number) { - constants::EIP1559_INITIAL_BASE_FEE - } else { - parent_block - .as_ref() - .ok_or(eyre::eyre!("parent block not found"))? - .header - .next_block_base_fee(CHAIN_SPEC.base_fee_params_at_block(block_number)) - .ok_or(eyre::eyre!("failed to calculate base fee"))? - }; + let base_fee_per_gas = + if CHAIN_SPEC.fork(EthereumHardfork::London).transitions_at_block(block_number) { + constants::EIP1559_INITIAL_BASE_FEE + } else { + parent_block + .as_ref() + .ok_or(eyre::eyre!("parent block not found"))? + .header + .next_block_base_fee(CHAIN_SPEC.base_fee_params_at_block(block_number)) + .ok_or(eyre::eyre!("failed to calculate base fee"))? + }; // Construct header Ok(Header { @@ -103,7 +104,7 @@ fn configure_evm<'a>( .build(), ); evm.db_mut().set_state_clear_flag( - CHAIN_SPEC.fork(Hardfork::SpuriousDragon).active_at_block(header.number), + CHAIN_SPEC.fork(EthereumHardfork::SpuriousDragon).active_at_block(header.number), ); let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(evm.cfg().clone(), evm.spec_id()); diff --git a/examples/manual-p2p/src/main.rs b/examples/manual-p2p/src/main.rs index 2b89b5539d4f..7379aa4e77b2 100644 --- a/examples/manual-p2p/src/main.rs +++ b/examples/manual-p2p/src/main.rs @@ -18,7 +18,7 @@ use reth_eth_wire::{ }; use reth_network::config::rng_secret_key; use reth_network_peers::{pk2id, NodeRecord}; -use reth_primitives::{Hardfork, Head, MAINNET_GENESIS_HASH}; +use reth_primitives::{EthereumHardfork, Head, MAINNET_GENESIS_HASH}; use secp256k1::{SecretKey, SECP256K1}; use tokio::net::TcpStream; @@ -95,14 +95,14 @@ async fn handshake_p2p( // Perform a ETH Wire handshake with a peer async fn handshake_eth(p2p_stream: AuthedP2PStream) -> eyre::Result<(AuthedEthStream, Status)> { let fork_filter = MAINNET.fork_filter(Head { - timestamp: MAINNET.fork(Hardfork::Shanghai).as_timestamp().unwrap(), + timestamp: MAINNET.fork(EthereumHardfork::Shanghai).as_timestamp().unwrap(), ..Default::default() }); let status = Status::builder() .chain(Chain::mainnet()) .genesis(MAINNET_GENESIS_HASH) - .forkid(MAINNET.hardfork_fork_id(Hardfork::Shanghai).unwrap()) + .forkid(MAINNET.hardfork_fork_id(EthereumHardfork::Shanghai).unwrap()) .build(); let status = Status { version: p2p_stream.shared_capabilities().eth()?.version(), ..status }; diff --git a/examples/polygon-p2p/src/chain_cfg.rs b/examples/polygon-p2p/src/chain_cfg.rs index b178d13499d7..92256a1be1c3 100644 --- a/examples/polygon-p2p/src/chain_cfg.rs +++ b/examples/polygon-p2p/src/chain_cfg.rs @@ -1,8 +1,10 @@ -use reth_chainspec::{BaseFeeParams, Chain, ChainSpec, ForkCondition, Hardfork}; +use reth_chainspec::{ + BaseFeeParams, Chain, ChainHardforks, ChainSpec, EthereumHardfork, ForkCondition, +}; use reth_discv4::NodeRecord; use reth_primitives::{b256, Head, B256}; -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; const SHANGAI_BLOCK: u64 = 50523000; @@ -15,13 +17,13 @@ pub(crate) fn polygon_chain_spec() -> Arc { genesis: serde_json::from_str(include_str!("./genesis.json")).expect("deserialize genesis"), genesis_hash: Some(GENESIS), paris_block_and_final_difficulty: None, - hardforks: BTreeMap::from([ - (Hardfork::Petersburg, ForkCondition::Block(0)), - (Hardfork::Istanbul, ForkCondition::Block(3395000)), - (Hardfork::MuirGlacier, ForkCondition::Block(3395000)), - (Hardfork::Berlin, ForkCondition::Block(14750000)), - (Hardfork::London, ForkCondition::Block(23850000)), - (Hardfork::Shanghai, ForkCondition::Block(SHANGAI_BLOCK)), + hardforks: ChainHardforks::new(vec![ + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(3395000)), + (EthereumHardfork::MuirGlacier.boxed(), ForkCondition::Block(3395000)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(14750000)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(23850000)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Block(SHANGAI_BLOCK)), ]), deposit_contract: None, base_fee_params: reth_chainspec::BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), From 867be500c7655c047915ffa8e83c300151b9fb72 Mon Sep 17 00:00:00 2001 From: Kien Trinh <51135161+kien6034@users.noreply.github.com> Date: Fri, 28 Jun 2024 00:42:02 +0700 Subject: [PATCH 244/405] refactor(node-core/matrics): refactor the register version metrics function (#9149) --- .../src/metrics/prometheus_exporter.rs | 4 +- .../node-core/src/metrics/version_metrics.rs | 54 +++++++++++++++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/crates/node-core/src/metrics/prometheus_exporter.rs b/crates/node-core/src/metrics/prometheus_exporter.rs index b7a3ba7015c3..5e049cc977fe 100644 --- a/crates/node-core/src/metrics/prometheus_exporter.rs +++ b/crates/node-core/src/metrics/prometheus_exporter.rs @@ -1,6 +1,6 @@ //! Prometheus exporter -use crate::metrics::version_metrics::register_version_metrics; +use crate::metrics::version_metrics::VersionInfo; use eyre::WrapErr; use futures::{future::FusedFuture, FutureExt}; use http::Response; @@ -151,7 +151,7 @@ where process.describe(); describe_memory_stats(); describe_io_stats(); - register_version_metrics(); + VersionInfo::default().register_version_metrics(); Ok(()) } diff --git a/crates/node-core/src/metrics/version_metrics.rs b/crates/node-core/src/metrics/version_metrics.rs index 253e759dbb33..cea907fc3ca0 100644 --- a/crates/node-core/src/metrics/version_metrics.rs +++ b/crates/node-core/src/metrics/version_metrics.rs @@ -3,16 +3,48 @@ use crate::version::{build_profile_name, VERGEN_GIT_SHA}; use metrics::gauge; -const LABELS: [(&str, &str); 6] = [ - ("version", env!("CARGO_PKG_VERSION")), - ("build_timestamp", env!("VERGEN_BUILD_TIMESTAMP")), - ("cargo_features", env!("VERGEN_CARGO_FEATURES")), - ("git_sha", VERGEN_GIT_SHA), - ("target_triple", env!("VERGEN_CARGO_TARGET_TRIPLE")), - ("build_profile", build_profile_name()), -]; +/// Contains version information for the application. +#[derive(Debug, Clone)] +pub struct VersionInfo { + /// The version of the application. + pub version: &'static str, + /// The build timestamp of the application. + pub build_timestamp: &'static str, + /// The cargo features enabled for the build. + pub cargo_features: &'static str, + /// The Git SHA of the build. + pub git_sha: &'static str, + /// The target triple for the build. + pub target_triple: &'static str, + /// The build profile (e.g., debug or release). + pub build_profile: &'static str, +} + +impl Default for VersionInfo { + fn default() -> Self { + Self { + version: env!("CARGO_PKG_VERSION"), + build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"), + cargo_features: env!("VERGEN_CARGO_FEATURES"), + git_sha: VERGEN_GIT_SHA, + target_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"), + build_profile: build_profile_name(), + } + } +} + +impl VersionInfo { + /// This exposes reth's version information over prometheus. + pub fn register_version_metrics(&self) { + let labels: [(&str, &str); 6] = [ + ("version", self.version), + ("build_timestamp", self.build_timestamp), + ("cargo_features", self.cargo_features), + ("git_sha", self.git_sha), + ("target_triple", self.target_triple), + ("build_profile", self.build_profile), + ]; -/// This exposes reth's version information over prometheus. -pub fn register_version_metrics() { - let _gauge = gauge!("info", &LABELS); + let _gauge = gauge!("info", &labels); + } } From 9fd2cf027f130e14d75ca6eecb24b13ab6dec1ba Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 27 Jun 2024 11:41:31 -0700 Subject: [PATCH 245/405] chore: rename `TrieCursorFactory::storage_tries_cursor` to `TrieCursorFactory::storage_trie_cursor` (#9145) --- crates/trie/trie/src/trie.rs | 2 +- crates/trie/trie/src/trie_cursor/database_cursors.rs | 2 +- crates/trie/trie/src/trie_cursor/mod.rs | 2 +- crates/trie/trie/src/trie_cursor/noop.rs | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 25ae616584d7..e56946747169 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -491,7 +491,7 @@ where } let mut tracker = TrieTracker::default(); - let trie_cursor = self.trie_cursor_factory.storage_tries_cursor(self.hashed_address)?; + let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(self.hashed_address)?; let walker = TrieWalker::new(trie_cursor, self.prefix_set).with_updates(retain_updates); let mut hash_builder = HashBuilder::default().with_updates(retain_updates); diff --git a/crates/trie/trie/src/trie_cursor/database_cursors.rs b/crates/trie/trie/src/trie_cursor/database_cursors.rs index 910ae61b4648..84cc69ce1925 100644 --- a/crates/trie/trie/src/trie_cursor/database_cursors.rs +++ b/crates/trie/trie/src/trie_cursor/database_cursors.rs @@ -13,7 +13,7 @@ impl<'a, TX: DbTx> TrieCursorFactory for &'a TX { Ok(Box::new(DatabaseAccountTrieCursor::new(self.cursor_read::()?))) } - fn storage_tries_cursor( + fn storage_trie_cursor( &self, hashed_address: B256, ) -> Result, DatabaseError> { diff --git a/crates/trie/trie/src/trie_cursor/mod.rs b/crates/trie/trie/src/trie_cursor/mod.rs index aae7e773c690..e083be76411a 100644 --- a/crates/trie/trie/src/trie_cursor/mod.rs +++ b/crates/trie/trie/src/trie_cursor/mod.rs @@ -18,7 +18,7 @@ pub trait TrieCursorFactory { fn account_trie_cursor(&self) -> Result, DatabaseError>; /// Create a storage tries cursor. - fn storage_tries_cursor( + fn storage_trie_cursor( &self, hashed_address: B256, ) -> Result, DatabaseError>; diff --git a/crates/trie/trie/src/trie_cursor/noop.rs b/crates/trie/trie/src/trie_cursor/noop.rs index 46163180b8b9..98c19216e655 100644 --- a/crates/trie/trie/src/trie_cursor/noop.rs +++ b/crates/trie/trie/src/trie_cursor/noop.rs @@ -1,6 +1,7 @@ use super::{TrieCursor, TrieCursorFactory}; use crate::{updates::TrieKey, BranchNodeCompact, Nibbles}; use reth_db::DatabaseError; +use reth_primitives::B256; /// Noop trie cursor factory. #[derive(Default, Debug)] @@ -14,9 +15,9 @@ impl TrieCursorFactory for NoopTrieCursorFactory { } /// Generates a Noop storage trie cursor. - fn storage_tries_cursor( + fn storage_trie_cursor( &self, - _hashed_address: reth_primitives::B256, + _hashed_address: B256, ) -> Result, DatabaseError> { Ok(Box::::default()) } From d8e6d01308b6234fdbf3e4d447c9a10e6702df02 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 27 Jun 2024 21:11:29 +0200 Subject: [PATCH 246/405] chore: move `revm_spec` methods from `reth-primitives` to chain specific crates (#9152) --- Cargo.lock | 4 + crates/ethereum/engine-primitives/Cargo.toml | 1 + .../ethereum/engine-primitives/src/payload.rs | 5 +- crates/ethereum/evm/Cargo.toml | 1 + .../src/revm => ethereum/evm/src}/config.rs | 169 +++++------------- crates/ethereum/evm/src/lib.rs | 31 +++- crates/evm/src/lib.rs | 29 +-- crates/optimism/evm/Cargo.toml | 1 + crates/optimism/evm/src/config.rs | 133 ++++++++++++++ crates/optimism/evm/src/lib.rs | 6 +- crates/optimism/payload/src/payload.rs | 4 +- crates/primitives/src/revm/mod.rs | 3 - examples/custom-evm/Cargo.toml | 1 + examples/custom-evm/src/main.rs | 33 +++- 14 files changed, 256 insertions(+), 165 deletions(-) rename crates/{primitives/src/revm => ethereum/evm/src}/config.rs (54%) create mode 100644 crates/optimism/evm/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index f4c97a3dad6d..063d3a26c620 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2778,6 +2778,7 @@ dependencies = [ "eyre", "reth", "reth-chainspec", + "reth-evm-ethereum", "reth-node-api", "reth-node-core", "reth-node-ethereum", @@ -7065,6 +7066,7 @@ dependencies = [ "alloy-rlp", "reth-chainspec", "reth-engine-primitives", + "reth-evm-ethereum", "reth-payload-primitives", "reth-primitives", "reth-rpc-types", @@ -7147,6 +7149,7 @@ dependencies = [ "alloy-sol-types", "reth-chainspec", "reth-ethereum-consensus", + "reth-ethereum-forks", "reth-evm", "reth-execution-types", "reth-primitives", @@ -7164,6 +7167,7 @@ version = "1.0.0" dependencies = [ "reth-chainspec", "reth-consensus-common", + "reth-ethereum-forks", "reth-evm", "reth-execution-errors", "reth-execution-types", diff --git a/crates/ethereum/engine-primitives/Cargo.toml b/crates/ethereum/engine-primitives/Cargo.toml index 231c7f640b39..8a1f25808937 100644 --- a/crates/ethereum/engine-primitives/Cargo.toml +++ b/crates/ethereum/engine-primitives/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth reth-chainspec.workspace = true +reth-evm-ethereum.workspace = true reth-primitives.workspace = true reth-engine-primitives.workspace = true reth-payload-primitives.workspace = true diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index 7a4129963cb4..f9fde7028e32 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -2,10 +2,11 @@ use alloy_rlp::Encodable; use reth_chainspec::ChainSpec; +use reth_evm_ethereum::revm_spec_by_timestamp_after_merge; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ - constants::EIP1559_INITIAL_BASE_FEE, revm::config::revm_spec_by_timestamp_after_merge, Address, - BlobTransactionSidecar, EthereumHardfork, Header, SealedBlock, Withdrawals, B256, U256, + constants::EIP1559_INITIAL_BASE_FEE, Address, BlobTransactionSidecar, EthereumHardfork, Header, + SealedBlock, Withdrawals, B256, U256, }; use reth_rpc_types::engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 1d996e5d3995..7ea2e4b587c9 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # Reth reth-chainspec.workspace = true +reth-ethereum-forks.workspace = true reth-evm.workspace = true reth-primitives.workspace = true reth-revm.workspace = true diff --git a/crates/primitives/src/revm/config.rs b/crates/ethereum/evm/src/config.rs similarity index 54% rename from crates/primitives/src/revm/config.rs rename to crates/ethereum/evm/src/config.rs index 9ed2650926a8..77082b1f7d60 100644 --- a/crates/primitives/src/revm/config.rs +++ b/crates/ethereum/evm/src/config.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "optimism")] -use reth_chainspec::OptimismHardfork; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_ethereum_forks::{EthereumHardfork, Head}; @@ -11,21 +9,6 @@ pub fn revm_spec_by_timestamp_after_merge( chain_spec: &ChainSpec, timestamp: u64, ) -> revm_primitives::SpecId { - #[cfg(feature = "optimism")] - if chain_spec.is_optimism() { - return if chain_spec.fork(OptimismHardfork::Fjord).active_at_timestamp(timestamp) { - revm_primitives::FJORD - } else if chain_spec.fork(OptimismHardfork::Ecotone).active_at_timestamp(timestamp) { - revm_primitives::ECOTONE - } else if chain_spec.fork(OptimismHardfork::Canyon).active_at_timestamp(timestamp) { - revm_primitives::CANYON - } else if chain_spec.fork(OptimismHardfork::Regolith).active_at_timestamp(timestamp) { - revm_primitives::REGOLITH - } else { - revm_primitives::BEDROCK - } - } - if chain_spec.is_prague_active_at_timestamp(timestamp) { revm_primitives::PRAGUE } else if chain_spec.is_cancun_active_at_timestamp(timestamp) { @@ -38,47 +21,32 @@ pub fn revm_spec_by_timestamp_after_merge( } /// return `revm_spec` from spec configuration. -pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm_primitives::SpecId { - #[cfg(feature = "optimism")] - if chain_spec.is_optimism() { - if chain_spec.fork(OptimismHardfork::Fjord).active_at_head(&block) { - return revm_primitives::FJORD - } else if chain_spec.fork(OptimismHardfork::Ecotone).active_at_head(&block) { - return revm_primitives::ECOTONE - } else if chain_spec.fork(OptimismHardfork::Canyon).active_at_head(&block) { - return revm_primitives::CANYON - } else if chain_spec.fork(OptimismHardfork::Regolith).active_at_head(&block) { - return revm_primitives::REGOLITH - } else if chain_spec.fork(OptimismHardfork::Bedrock).active_at_head(&block) { - return revm_primitives::BEDROCK - } - } - - if chain_spec.fork(EthereumHardfork::Prague).active_at_head(&block) { +pub fn revm_spec(chain_spec: &ChainSpec, block: &Head) -> revm_primitives::SpecId { + if chain_spec.fork(EthereumHardfork::Prague).active_at_head(block) { revm_primitives::PRAGUE - } else if chain_spec.fork(EthereumHardfork::Cancun).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Cancun).active_at_head(block) { revm_primitives::CANCUN - } else if chain_spec.fork(EthereumHardfork::Shanghai).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Shanghai).active_at_head(block) { revm_primitives::SHANGHAI - } else if chain_spec.fork(EthereumHardfork::Paris).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Paris).active_at_head(block) { revm_primitives::MERGE - } else if chain_spec.fork(EthereumHardfork::London).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::London).active_at_head(block) { revm_primitives::LONDON - } else if chain_spec.fork(EthereumHardfork::Berlin).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Berlin).active_at_head(block) { revm_primitives::BERLIN - } else if chain_spec.fork(EthereumHardfork::Istanbul).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Istanbul).active_at_head(block) { revm_primitives::ISTANBUL - } else if chain_spec.fork(EthereumHardfork::Petersburg).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Petersburg).active_at_head(block) { revm_primitives::PETERSBURG - } else if chain_spec.fork(EthereumHardfork::Byzantium).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Byzantium).active_at_head(block) { revm_primitives::BYZANTIUM - } else if chain_spec.fork(EthereumHardfork::SpuriousDragon).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::SpuriousDragon).active_at_head(block) { revm_primitives::SPURIOUS_DRAGON - } else if chain_spec.fork(EthereumHardfork::Tangerine).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Tangerine).active_at_head(block) { revm_primitives::TANGERINE - } else if chain_spec.fork(EthereumHardfork::Homestead).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Homestead).active_at_head(block) { revm_primitives::HOMESTEAD - } else if chain_spec.fork(EthereumHardfork::Frontier).active_at_head(&block) { + } else if chain_spec.fork(EthereumHardfork::Frontier).active_at_head(block) { revm_primitives::FRONTIER } else { panic!( @@ -114,137 +82,84 @@ mod tests { revm_spec_by_timestamp_after_merge(&ChainSpecBuilder::mainnet().build(), 0), revm_primitives::MERGE ); - #[cfg(feature = "optimism")] - { - #[inline(always)] - fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { - let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); - f(cs).build() - } - assert_eq!( - revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.fjord_activated()), 0), - revm_primitives::FJORD - ); - assert_eq!( - revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.ecotone_activated()), 0), - revm_primitives::ECOTONE - ); - assert_eq!( - revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.canyon_activated()), 0), - revm_primitives::CANYON - ); - assert_eq!( - revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.bedrock_activated()), 0), - revm_primitives::BEDROCK - ); - assert_eq!( - revm_spec_by_timestamp_after_merge(&op_cs(|cs| cs.regolith_activated()), 0), - revm_primitives::REGOLITH - ); - } } #[test] fn test_to_revm_spec() { assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().cancun_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().cancun_activated().build(), &Head::default()), revm_primitives::CANCUN ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().shanghai_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().shanghai_activated().build(), &Head::default()), revm_primitives::SHANGHAI ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().paris_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().paris_activated().build(), &Head::default()), revm_primitives::MERGE ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().london_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().london_activated().build(), &Head::default()), revm_primitives::LONDON ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().berlin_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().berlin_activated().build(), &Head::default()), revm_primitives::BERLIN ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().istanbul_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().istanbul_activated().build(), &Head::default()), revm_primitives::ISTANBUL ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().petersburg_activated().build(), Head::default()), + revm_spec( + &ChainSpecBuilder::mainnet().petersburg_activated().build(), + &Head::default() + ), revm_primitives::PETERSBURG ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().byzantium_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().byzantium_activated().build(), &Head::default()), revm_primitives::BYZANTIUM ); assert_eq!( revm_spec( &ChainSpecBuilder::mainnet().spurious_dragon_activated().build(), - Head::default() + &Head::default() ), revm_primitives::SPURIOUS_DRAGON ); assert_eq!( revm_spec( &ChainSpecBuilder::mainnet().tangerine_whistle_activated().build(), - Head::default() + &Head::default() ), revm_primitives::TANGERINE ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().homestead_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().homestead_activated().build(), &Head::default()), revm_primitives::HOMESTEAD ); assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), Head::default()), + revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), &Head::default()), revm_primitives::FRONTIER ); - #[cfg(feature = "optimism")] - { - #[inline(always)] - fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { - let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); - f(cs).build() - } - assert_eq!( - revm_spec(&op_cs(|cs| cs.fjord_activated()), Head::default()), - revm_primitives::FJORD - ); - assert_eq!( - revm_spec(&op_cs(|cs| cs.ecotone_activated()), Head::default()), - revm_primitives::ECOTONE - ); - assert_eq!( - revm_spec(&op_cs(|cs| cs.canyon_activated()), Head::default()), - revm_primitives::CANYON - ); - assert_eq!( - revm_spec(&op_cs(|cs| cs.bedrock_activated()), Head::default()), - revm_primitives::BEDROCK - ); - assert_eq!( - revm_spec(&op_cs(|cs| cs.regolith_activated()), Head::default()), - revm_primitives::REGOLITH - ); - } } #[test] fn test_eth_spec() { assert_eq!( - revm_spec(&MAINNET, Head { timestamp: 1710338135, ..Default::default() }), + revm_spec(&MAINNET, &Head { timestamp: 1710338135, ..Default::default() }), revm_primitives::CANCUN ); assert_eq!( - revm_spec(&MAINNET, Head { timestamp: 1681338455, ..Default::default() }), + revm_spec(&MAINNET, &Head { timestamp: 1681338455, ..Default::default() }), revm_primitives::SHANGHAI ); assert_eq!( revm_spec( &MAINNET, - Head { + &Head { total_difficulty: U256::from(58_750_000_000_000_000_000_010_u128), difficulty: U256::from(10_u128), ..Default::default() @@ -256,7 +171,7 @@ mod tests { assert_eq!( revm_spec( &MAINNET, - Head { + &Head { number: 15537394 - 10, total_difficulty: U256::from(58_750_000_000_000_000_000_010_u128), difficulty: U256::from(10_u128), @@ -266,39 +181,39 @@ mod tests { revm_primitives::MERGE ); assert_eq!( - revm_spec(&MAINNET, Head { number: 15537394 - 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 15537394 - 10, ..Default::default() }), revm_primitives::LONDON ); assert_eq!( - revm_spec(&MAINNET, Head { number: 12244000 + 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 12244000 + 10, ..Default::default() }), revm_primitives::BERLIN ); assert_eq!( - revm_spec(&MAINNET, Head { number: 12244000 - 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 12244000 - 10, ..Default::default() }), revm_primitives::ISTANBUL ); assert_eq!( - revm_spec(&MAINNET, Head { number: 7280000 + 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 7280000 + 10, ..Default::default() }), revm_primitives::PETERSBURG ); assert_eq!( - revm_spec(&MAINNET, Head { number: 7280000 - 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 7280000 - 10, ..Default::default() }), revm_primitives::BYZANTIUM ); assert_eq!( - revm_spec(&MAINNET, Head { number: 2675000 + 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 2675000 + 10, ..Default::default() }), revm_primitives::SPURIOUS_DRAGON ); assert_eq!( - revm_spec(&MAINNET, Head { number: 2675000 - 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 2675000 - 10, ..Default::default() }), revm_primitives::TANGERINE ); assert_eq!( - revm_spec(&MAINNET, Head { number: 1150000 + 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 1150000 + 10, ..Default::default() }), revm_primitives::HOMESTEAD ); assert_eq!( - revm_spec(&MAINNET, Head { number: 1150000 - 10, ..Default::default() }), + revm_spec(&MAINNET, &Head { number: 1150000 - 10, ..Default::default() }), revm_primitives::FRONTIER ); } diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 73d8a8111b81..17c0c7370b35 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -12,8 +12,14 @@ #[cfg(not(feature = "std"))] extern crate alloc; +use reth_chainspec::{ChainSpec, Head}; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; +use reth_primitives::{Header, U256}; use reth_revm::{Database, EvmBuilder}; +use revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg}; + +mod config; +pub use config::{revm_spec, revm_spec_by_timestamp_after_merge}; pub mod execute; @@ -28,7 +34,30 @@ pub mod eip6110; #[non_exhaustive] pub struct EthEvmConfig; -impl ConfigureEvmEnv for EthEvmConfig {} +impl ConfigureEvmEnv for EthEvmConfig { + fn fill_cfg_env( + cfg_env: &mut CfgEnvWithHandlerCfg, + chain_spec: &ChainSpec, + header: &Header, + total_difficulty: U256, + ) { + let spec_id = config::revm_spec( + chain_spec, + &Head { + number: header.number, + timestamp: header.timestamp, + difficulty: header.difficulty, + total_difficulty, + hash: Default::default(), + }, + ); + + cfg_env.chain_id = chain_spec.chain().id(); + cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; + + cfg_env.handler_cfg.spec_id = spec_id; + } +} impl ConfigureEvm for EthEvmConfig { type DefaultExternalContext<'a> = (); diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index dd16aa0d046d..f86219eec7fa 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -14,16 +14,11 @@ extern crate alloc; use reth_chainspec::ChainSpec; use reth_primitives::{ - revm::{ - config::revm_spec, - env::{fill_block_env, fill_tx_env}, - }, - Address, Head, Header, TransactionSigned, U256, + revm::env::{fill_block_env, fill_tx_env}, + Address, Header, TransactionSigned, U256, }; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; -use revm_primitives::{ - AnalysisKind, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv, -}; +use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; pub mod either; pub mod execute; @@ -122,23 +117,7 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { chain_spec: &ChainSpec, header: &Header, total_difficulty: U256, - ) { - let spec_id = revm_spec( - chain_spec, - Head { - number: header.number, - timestamp: header.timestamp, - difficulty: header.difficulty, - total_difficulty, - hash: Default::default(), - }, - ); - - cfg_env.chain_id = chain_spec.chain().id(); - cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; - - cfg_env.handler_cfg.spec_id = spec_id; - } + ); /// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and /// [`fill_block_env`]. diff --git a/crates/optimism/evm/Cargo.toml b/crates/optimism/evm/Cargo.toml index 47c8cedee294..f53293edeebf 100644 --- a/crates/optimism/evm/Cargo.toml +++ b/crates/optimism/evm/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # Reth reth-chainspec.workspace = true +reth-ethereum-forks.workspace = true reth-evm.workspace = true reth-primitives.workspace = true reth-revm.workspace = true diff --git a/crates/optimism/evm/src/config.rs b/crates/optimism/evm/src/config.rs new file mode 100644 index 000000000000..1cdc917eac85 --- /dev/null +++ b/crates/optimism/evm/src/config.rs @@ -0,0 +1,133 @@ +use reth_chainspec::{ChainSpec, OptimismHardfork}; +use reth_ethereum_forks::{EthereumHardfork, Head}; + +/// Returns the spec id at the given timestamp. +/// +/// Note: This is only intended to be used after the merge, when hardforks are activated by +/// timestamp. +pub fn revm_spec_by_timestamp_after_bedrock( + chain_spec: &ChainSpec, + timestamp: u64, +) -> revm_primitives::SpecId { + if chain_spec.fork(OptimismHardfork::Fjord).active_at_timestamp(timestamp) { + revm_primitives::FJORD + } else if chain_spec.fork(OptimismHardfork::Ecotone).active_at_timestamp(timestamp) { + revm_primitives::ECOTONE + } else if chain_spec.fork(OptimismHardfork::Canyon).active_at_timestamp(timestamp) { + revm_primitives::CANYON + } else if chain_spec.fork(OptimismHardfork::Regolith).active_at_timestamp(timestamp) { + revm_primitives::REGOLITH + } else { + revm_primitives::BEDROCK + } +} + +/// return `revm_spec` from spec configuration. +pub fn revm_spec(chain_spec: &ChainSpec, block: &Head) -> revm_primitives::SpecId { + if chain_spec.fork(OptimismHardfork::Fjord).active_at_head(block) { + revm_primitives::FJORD + } else if chain_spec.fork(OptimismHardfork::Ecotone).active_at_head(block) { + revm_primitives::ECOTONE + } else if chain_spec.fork(OptimismHardfork::Canyon).active_at_head(block) { + revm_primitives::CANYON + } else if chain_spec.fork(OptimismHardfork::Regolith).active_at_head(block) { + revm_primitives::REGOLITH + } else if chain_spec.fork(OptimismHardfork::Bedrock).active_at_head(block) { + revm_primitives::BEDROCK + } else if chain_spec.fork(EthereumHardfork::Prague).active_at_head(block) { + revm_primitives::PRAGUE + } else if chain_spec.fork(EthereumHardfork::Cancun).active_at_head(block) { + revm_primitives::CANCUN + } else if chain_spec.fork(EthereumHardfork::Shanghai).active_at_head(block) { + revm_primitives::SHANGHAI + } else if chain_spec.fork(EthereumHardfork::Paris).active_at_head(block) { + revm_primitives::MERGE + } else if chain_spec.fork(EthereumHardfork::London).active_at_head(block) { + revm_primitives::LONDON + } else if chain_spec.fork(EthereumHardfork::Berlin).active_at_head(block) { + revm_primitives::BERLIN + } else if chain_spec.fork(EthereumHardfork::Istanbul).active_at_head(block) { + revm_primitives::ISTANBUL + } else if chain_spec.fork(EthereumHardfork::Petersburg).active_at_head(block) { + revm_primitives::PETERSBURG + } else if chain_spec.fork(EthereumHardfork::Byzantium).active_at_head(block) { + revm_primitives::BYZANTIUM + } else if chain_spec.fork(EthereumHardfork::SpuriousDragon).active_at_head(block) { + revm_primitives::SPURIOUS_DRAGON + } else if chain_spec.fork(EthereumHardfork::Tangerine).active_at_head(block) { + revm_primitives::TANGERINE + } else if chain_spec.fork(EthereumHardfork::Homestead).active_at_head(block) { + revm_primitives::HOMESTEAD + } else if chain_spec.fork(EthereumHardfork::Frontier).active_at_head(block) { + revm_primitives::FRONTIER + } else { + panic!( + "invalid hardfork chainspec: expected at least one hardfork, got {:?}", + chain_spec.hardforks + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_chainspec::ChainSpecBuilder; + + #[test] + fn test_revm_spec_by_timestamp_after_merge() { + #[inline(always)] + fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { + let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); + f(cs).build() + } + assert_eq!( + revm_spec_by_timestamp_after_bedrock(&op_cs(|cs| cs.fjord_activated()), 0), + revm_primitives::FJORD + ); + assert_eq!( + revm_spec_by_timestamp_after_bedrock(&op_cs(|cs| cs.ecotone_activated()), 0), + revm_primitives::ECOTONE + ); + assert_eq!( + revm_spec_by_timestamp_after_bedrock(&op_cs(|cs| cs.canyon_activated()), 0), + revm_primitives::CANYON + ); + assert_eq!( + revm_spec_by_timestamp_after_bedrock(&op_cs(|cs| cs.bedrock_activated()), 0), + revm_primitives::BEDROCK + ); + assert_eq!( + revm_spec_by_timestamp_after_bedrock(&op_cs(|cs| cs.regolith_activated()), 0), + revm_primitives::REGOLITH + ); + } + + #[test] + fn test_to_revm_spec() { + #[inline(always)] + fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec { + let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)); + f(cs).build() + } + assert_eq!( + revm_spec(&op_cs(|cs| cs.fjord_activated()), &Head::default()), + revm_primitives::FJORD + ); + assert_eq!( + revm_spec(&op_cs(|cs| cs.ecotone_activated()), &Head::default()), + revm_primitives::ECOTONE + ); + assert_eq!( + revm_spec(&op_cs(|cs| cs.canyon_activated()), &Head::default()), + revm_primitives::CANYON + ); + assert_eq!( + revm_spec(&op_cs(|cs| cs.bedrock_activated()), &Head::default()), + revm_primitives::BEDROCK + ); + assert_eq!( + revm_spec(&op_cs(|cs| cs.regolith_activated()), &Head::default()), + revm_primitives::REGOLITH + ); + } +} diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 68aabf452ca5..5dff8d5de312 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -12,12 +12,14 @@ use reth_chainspec::ChainSpec; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_primitives::{ - revm::{config::revm_spec, env::fill_op_tx_env}, + revm::env::fill_op_tx_env, revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, Address, Head, Header, TransactionSigned, U256, }; use reth_revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; +mod config; +pub use config::{revm_spec, revm_spec_by_timestamp_after_bedrock}; mod execute; pub use execute::*; pub mod l1; @@ -46,7 +48,7 @@ impl ConfigureEvmEnv for OptimismEvmConfig { ) { let spec_id = revm_spec( chain_spec, - Head { + &Head { number: header.number, timestamp: header.timestamp, difficulty: header.difficulty, diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index 1cdc1f6c4d6c..47db0d571ed8 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -4,10 +4,10 @@ use alloy_rlp::Encodable; use reth_chainspec::{ChainSpec, EthereumHardforks}; +use reth_evm_optimism::revm_spec_by_timestamp_after_bedrock; use reth_payload_builder::EthPayloadBuilderAttributes; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ - revm::config::revm_spec_by_timestamp_after_merge, revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}, Address, BlobTransactionSidecar, Header, SealedBlock, TransactionSigned, Withdrawals, B256, U256, @@ -113,7 +113,7 @@ impl PayloadBuilderAttributes for OptimismPayloadBuilderAttributes { let cfg = CfgEnv::default().with_chain_id(chain_spec.chain().id()); // ensure we're not missing any timestamp based hardforks - let spec_id = revm_spec_by_timestamp_after_merge(chain_spec, self.timestamp()); + let spec_id = revm_spec_by_timestamp_after_bedrock(chain_spec, self.timestamp()); // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is // cancun now, we need to set the excess blob gas to the default value diff --git a/crates/primitives/src/revm/mod.rs b/crates/primitives/src/revm/mod.rs index 9937a209b93e..40fc719c950b 100644 --- a/crates/primitives/src/revm/mod.rs +++ b/crates/primitives/src/revm/mod.rs @@ -1,8 +1,5 @@ //! Helpers for working with revm. -/// Reth block execution/validation configuration and constants -pub mod config; - /// The `env` module provides utility methods for filling revm transaction and block environments. /// /// It includes functions to fill transaction and block environments with relevant data, prepare diff --git a/examples/custom-evm/Cargo.toml b/examples/custom-evm/Cargo.toml index 5822bcb22790..a85b6ce8aadb 100644 --- a/examples/custom-evm/Cargo.toml +++ b/examples/custom-evm/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] reth.workspace = true reth-chainspec.workspace = true +reth-evm-ethereum.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true reth-primitives.workspace = true diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 638a2dc172cb..968f0beff1a7 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] +use alloy_genesis::Genesis; use reth::{ builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, primitives::{ @@ -17,11 +18,14 @@ use reth::{ }, tasks::TaskManager, }; -use reth_chainspec::{Chain, ChainSpec}; +use reth_chainspec::{Chain, ChainSpec, Head}; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::{EthExecutorProvider, EthereumNode}; -use reth_primitives::Genesis; +use reth_primitives::{ + revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg}, + Header, U256, +}; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; @@ -61,7 +65,30 @@ impl MyEvmConfig { } } -impl ConfigureEvmEnv for MyEvmConfig {} +impl ConfigureEvmEnv for MyEvmConfig { + fn fill_cfg_env( + cfg_env: &mut CfgEnvWithHandlerCfg, + chain_spec: &ChainSpec, + header: &Header, + total_difficulty: U256, + ) { + let spec_id = reth_evm_ethereum::revm_spec( + chain_spec, + &Head { + number: header.number, + timestamp: header.timestamp, + difficulty: header.difficulty, + total_difficulty, + hash: Default::default(), + }, + ); + + cfg_env.chain_id = chain_spec.chain().id(); + cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; + + cfg_env.handler_cfg.spec_id = spec_id; + } +} impl ConfigureEvm for MyEvmConfig { type DefaultExternalContext<'a> = (); From 530e7e8961b8f82ae2c675d16c368dd266ceba7d Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:45:23 +0200 Subject: [PATCH 247/405] refactor(net): move node record constants to network-peers crate (#9161) --- Cargo.lock | 3 + crates/chainspec/src/lib.rs | 3 - crates/chainspec/src/net.rs | 250 --------------------- crates/chainspec/src/spec.rs | 12 +- crates/e2e-test-utils/Cargo.toml | 1 + crates/e2e-test-utils/src/network.rs | 2 +- crates/net/discv4/src/lib.rs | 5 +- crates/net/network/src/config.rs | 7 +- crates/net/network/src/lib.rs | 4 +- crates/net/network/src/manager.rs | 2 +- crates/net/network/tests/it/connect.rs | 3 +- crates/net/peers/src/bootnodes/ethereum.rs | 40 ++++ crates/net/peers/src/bootnodes/mod.rs | 54 +++++ crates/net/peers/src/bootnodes/optimism.rs | 26 +++ crates/net/peers/src/lib.rs | 3 + crates/node-core/src/args/network.rs | 4 +- crates/primitives/src/lib.rs | 8 +- crates/rpc/rpc-builder/Cargo.toml | 1 + crates/rpc/rpc-builder/tests/it/http.rs | 2 +- examples/bsc-p2p/Cargo.toml | 1 + examples/bsc-p2p/src/chainspec.rs | 4 +- examples/exex/discv5/src/network/mod.rs | 2 +- examples/manual-p2p/src/main.rs | 4 +- 23 files changed, 151 insertions(+), 290 deletions(-) delete mode 100644 crates/chainspec/src/net.rs create mode 100644 crates/net/peers/src/bootnodes/ethereum.rs create mode 100644 crates/net/peers/src/bootnodes/mod.rs create mode 100644 crates/net/peers/src/bootnodes/optimism.rs diff --git a/Cargo.lock b/Cargo.lock index 063d3a26c620..1131239a8d73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2725,6 +2725,7 @@ dependencies = [ "reth-discv4", "reth-network", "reth-network-api", + "reth-network-peers", "reth-primitives", "reth-tracing", "secp256k1", @@ -6909,6 +6910,7 @@ dependencies = [ "reth", "reth-chainspec", "reth-db", + "reth-network-peers", "reth-node-builder", "reth-payload-builder", "reth-primitives", @@ -8109,6 +8111,7 @@ dependencies = [ "reth-ipc", "reth-metrics", "reth-network-api", + "reth-network-peers", "reth-node-core", "reth-payload-builder", "reth-primitives", diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 8dd598abdfb4..162f281b6a4a 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -26,9 +26,6 @@ extern crate alloc; /// The chain info module. mod info; -/// Network related constants -pub mod net; - /// The chain spec module. mod spec; diff --git a/crates/chainspec/src/net.rs b/crates/chainspec/src/net.rs deleted file mode 100644 index 922a7df574ca..000000000000 --- a/crates/chainspec/src/net.rs +++ /dev/null @@ -1,250 +0,0 @@ -pub use reth_network_peers::{NodeRecord, NodeRecordParseError, TrustedPeer}; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -// Ethereum bootnodes come from -// OP bootnodes come from - -/// Ethereum Foundation Go Bootnodes -pub static MAINNET_BOOTNODES : [&str; 4] = [ - "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303", // bootnode-aws-ap-southeast-1-001 - "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303", // bootnode-aws-us-east-1-001 - "enode://2b252ab6a1d0f971d9722cb839a42cb81db019ba44c08754628ab4a823487071b5695317c8ccd085219c3a03af063495b2f1da8d18218da2d6a82981b45e6ffc@65.108.70.101:30303", // bootnode-hetzner-hel - "enode://4aeb4ab6c14b23e2c4cfdce879c04b0748a20d8e9b59e25ded2a08143e265c6c25936e74cbc8e641e3312ca288673d91f2f93f8e277de3cfa444ecdaaf982052@157.90.35.166:30303", // bootnode-hetzner-fsn -]; - -/// Ethereum Foundation Sepolia Bootnodes -pub static SEPOLIA_BOOTNODES : [&str; 5] = [ - "enode://4e5e92199ee224a01932a377160aa432f31d0b351f84ab413a8e0a42f4f36476f8fb1cbe914af0d9aef0d51665c214cf653c651c4bbd9d5550a934f241f1682b@138.197.51.181:30303", // sepolia-bootnode-1-nyc3 - "enode://143e11fb766781d22d92a2e33f8f104cddae4411a122295ed1fdb6638de96a6ce65f5b7c964ba3763bba27961738fef7d3ecc739268f3e5e771fb4c87b6234ba@146.190.1.103:30303", // sepolia-bootnode-1-sfo3 - "enode://8b61dc2d06c3f96fddcbebb0efb29d60d3598650275dc469c22229d3e5620369b0d3dedafd929835fe7f489618f19f456fe7c0df572bf2d914a9f4e006f783a9@170.64.250.88:30303", // sepolia-bootnode-1-syd1 - "enode://10d62eff032205fcef19497f35ca8477bea0eadfff6d769a147e895d8b2b8f8ae6341630c645c30f5df6e67547c03494ced3d9c5764e8622a26587b083b028e8@139.59.49.206:30303", // sepolia-bootnode-1-blr1 - "enode://9e9492e2e8836114cc75f5b929784f4f46c324ad01daf87d956f98b3b6c5fcba95524d6e5cf9861dc96a2c8a171ea7105bb554a197455058de185fa870970c7c@138.68.123.152:30303", // sepolia-bootnode-1-ams3 -]; - -/// Görli Bootnodes -pub static GOERLI_BOOTNODES : [&str; 7] = [ - // Upstream bootnodes - "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", - "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", - "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", - "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", - - // Ethereum Foundation bootnode - "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", - - // Goerli Initiative bootnodes - "enode://d4f764a48ec2a8ecf883735776fdefe0a3949eb0ca476bd7bc8d0954a9defe8fea15ae5da7d40b5d2d59ce9524a99daedadf6da6283fca492cc80b53689fb3b3@46.4.99.122:32109", - "enode://d2b720352e8216c9efc470091aa91ddafc53e222b32780f505c817ceef69e01d5b0b0797b69db254c586f493872352f5a022b4d8479a00fc92ec55f9ad46a27e@88.99.70.182:30303", -]; - -/// Ethereum Foundation Holesky Bootnodes -pub static HOLESKY_BOOTNODES : [&str; 2] = [ - "enode://ac906289e4b7f12df423d654c5a962b6ebe5b3a74cc9e06292a85221f9a64a6f1cfdd6b714ed6dacef51578f92b34c60ee91e9ede9c7f8fadc4d347326d95e2b@146.190.13.128:30303", - "enode://a3435a0155a3e837c02f5e7f5662a2f1fbc25b48e4dc232016e1c51b544cb5b4510ef633ea3278c0e970fa8ad8141e2d4d0f9f95456c537ff05fdf9b31c15072@178.128.136.233:30303", -]; - -#[cfg(feature = "optimism")] -/// OP stack mainnet boot nodes. -pub static OP_BOOTNODES: &[&str] = &[ - // OP Labs - "enode://ca2774c3c401325850b2477fd7d0f27911efbf79b1e8b335066516e2bd8c4c9e0ba9696a94b1cb030a88eac582305ff55e905e64fb77fe0edcd70a4e5296d3ec@34.65.175.185:30305", - "enode://dd751a9ef8912be1bfa7a5e34e2c3785cc5253110bd929f385e07ba7ac19929fb0e0c5d93f77827291f4da02b2232240fbc47ea7ce04c46e333e452f8656b667@34.65.107.0:30305", - "enode://c5d289b56a77b6a2342ca29956dfd07aadf45364dde8ab20d1dc4efd4d1bc6b4655d902501daea308f4d8950737a4e93a4dfedd17b49cd5760ffd127837ca965@34.65.202.239:30305", - // Base - "enode://87a32fd13bd596b2ffca97020e31aef4ddcc1bbd4b95bb633d16c1329f654f34049ed240a36b449fda5e5225d70fe40bc667f53c304b71f8e68fc9d448690b51@3.231.138.188:30301", - "enode://ca21ea8f176adb2e229ce2d700830c844af0ea941a1d8152a9513b966fe525e809c3a6c73a2c18a12b74ed6ec4380edf91662778fe0b79f6a591236e49e176f9@184.72.129.189:30301", - "enode://acf4507a211ba7c1e52cdf4eef62cdc3c32e7c9c47998954f7ba024026f9a6b2150cd3f0b734d9c78e507ab70d59ba61dfe5c45e1078c7ad0775fb251d7735a2@3.220.145.177:30301", - "enode://8a5a5006159bf079d06a04e5eceab2a1ce6e0f721875b2a9c96905336219dbe14203d38f70f3754686a6324f786c2f9852d8c0dd3adac2d080f4db35efc678c5@3.231.11.52:30301", - "enode://cdadbe835308ad3557f9a1de8db411da1a260a98f8421d62da90e71da66e55e98aaa8e90aa7ce01b408a54e4bd2253d701218081ded3dbe5efbbc7b41d7cef79@54.198.153.150:30301" -]; - -#[cfg(feature = "optimism")] -/// OP stack testnet boot nodes. -pub static OP_TESTNET_BOOTNODES: &[&str] = &[ - // OP Labs - "enode://2bd2e657bb3c8efffb8ff6db9071d9eb7be70d7c6d7d980ff80fc93b2629675c5f750bc0a5ef27cd788c2e491b8795a7e9a4a6e72178c14acc6753c0e5d77ae4@34.65.205.244:30305", - "enode://db8e1cab24624cc62fc35dbb9e481b88a9ef0116114cd6e41034c55b5b4f18755983819252333509bd8e25f6b12aadd6465710cd2e956558faf17672cce7551f@34.65.173.88:30305", - "enode://bfda2e0110cfd0f4c9f7aa5bf5ec66e6bd18f71a2db028d36b8bf8b0d6fdb03125c1606a6017b31311d96a36f5ef7e1ad11604d7a166745e6075a715dfa67f8a@34.65.229.245:30305", - // Base - "enode://548f715f3fc388a7c917ba644a2f16270f1ede48a5d88a4d14ea287cc916068363f3092e39936f1a3e7885198bef0e5af951f1d7b1041ce8ba4010917777e71f@18.210.176.114:30301", - "enode://6f10052847a966a725c9f4adf6716f9141155b99a0fb487fea3f51498f4c2a2cb8d534e680ee678f9447db85b93ff7c74562762c3714783a7233ac448603b25f@107.21.251.55:30301", -]; - -/// Returns parsed mainnet nodes -pub fn mainnet_nodes() -> Vec { - parse_nodes(&MAINNET_BOOTNODES[..]) -} - -/// Returns parsed goerli nodes -pub fn goerli_nodes() -> Vec { - parse_nodes(&GOERLI_BOOTNODES[..]) -} - -/// Returns parsed sepolia nodes -pub fn sepolia_nodes() -> Vec { - parse_nodes(&SEPOLIA_BOOTNODES[..]) -} - -/// Returns parsed holesky nodes -pub fn holesky_nodes() -> Vec { - parse_nodes(&HOLESKY_BOOTNODES[..]) -} - -#[cfg(feature = "optimism")] -/// Returns parsed op-stack mainnet nodes -pub fn op_nodes() -> Vec { - parse_nodes(OP_BOOTNODES) -} - -#[cfg(feature = "optimism")] -/// Returns parsed op-stack testnet nodes -pub fn op_testnet_nodes() -> Vec { - parse_nodes(OP_TESTNET_BOOTNODES) -} - -#[cfg(feature = "optimism")] -/// Returns parsed op-stack base mainnet nodes -pub fn base_nodes() -> Vec { - parse_nodes(OP_BOOTNODES) -} - -#[cfg(feature = "optimism")] -/// Returns parsed op-stack base testnet nodes -pub fn base_testnet_nodes() -> Vec { - parse_nodes(OP_TESTNET_BOOTNODES) -} - -/// Parses all the nodes -pub fn parse_nodes(nodes: impl IntoIterator>) -> Vec { - nodes.into_iter().map(|s| s.as_ref().parse().unwrap()).collect() -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_rlp::Decodable; - use rand::{thread_rng, Rng, RngCore}; - use std::net::{IpAddr, Ipv4Addr}; - - #[test] - fn test_mapped_ipv6() { - let mut rng = thread_rng(); - - let v4: Ipv4Addr = "0.0.0.0".parse().unwrap(); - let v6 = v4.to_ipv6_mapped(); - - let record = NodeRecord { - address: v6.into(), - tcp_port: rng.gen(), - udp_port: rng.gen(), - id: rng.gen(), - }; - - assert!(record.clone().convert_ipv4_mapped()); - assert_eq!(record.into_ipv4_mapped().address, IpAddr::from(v4)); - } - - #[test] - fn test_mapped_ipv4() { - let mut rng = thread_rng(); - let v4: Ipv4Addr = "0.0.0.0".parse().unwrap(); - - let record = NodeRecord { - address: v4.into(), - tcp_port: rng.gen(), - udp_port: rng.gen(), - id: rng.gen(), - }; - - assert!(!record.clone().convert_ipv4_mapped()); - assert_eq!(record.into_ipv4_mapped().address, IpAddr::from(v4)); - } - - #[test] - fn test_noderecord_codec_ipv4() { - let mut rng = thread_rng(); - for _ in 0..100 { - let mut ip = [0u8; 4]; - rng.fill_bytes(&mut ip); - let record = NodeRecord { - address: IpAddr::V4(ip.into()), - tcp_port: rng.gen(), - udp_port: rng.gen(), - id: rng.gen(), - }; - - let decoded = NodeRecord::decode(&mut alloy_rlp::encode(record).as_slice()).unwrap(); - assert_eq!(record, decoded); - } - } - - #[test] - fn test_noderecord_codec_ipv6() { - let mut rng = thread_rng(); - for _ in 0..100 { - let mut ip = [0u8; 16]; - rng.fill_bytes(&mut ip); - let record = NodeRecord { - address: IpAddr::V6(ip.into()), - tcp_port: rng.gen(), - udp_port: rng.gen(), - id: rng.gen(), - }; - - let decoded = NodeRecord::decode(&mut alloy_rlp::encode(record).as_slice()).unwrap(); - assert_eq!(record, decoded); - } - } - - #[test] - fn test_url_parse() { - let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301"; - let node: NodeRecord = url.parse().unwrap(); - assert_eq!(node, NodeRecord { - address: IpAddr::V4([10,3,58,6].into()), - tcp_port: 30303, - udp_port: 30301, - id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(), - }) - } - - #[test] - fn test_node_display() { - let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303"; - let node: NodeRecord = url.parse().unwrap(); - assert_eq!(url, &format!("{node}")); - } - - #[test] - fn test_node_display_discport() { - let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301"; - let node: NodeRecord = url.parse().unwrap(); - assert_eq!(url, &format!("{node}")); - } - - #[test] - fn test_node_serialize() { - let node = NodeRecord{ - address: IpAddr::V4([10, 3, 58, 6].into()), - tcp_port: 30303u16, - udp_port: 30301u16, - id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(), - }; - let ser = serde_json::to_string::(&node).expect("couldn't serialize"); - assert_eq!(ser, "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\"") - } - - #[test] - fn test_node_deserialize() { - let url = "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\""; - let node: NodeRecord = serde_json::from_str(url).expect("couldn't deserialize"); - assert_eq!(node, NodeRecord{ - address: IpAddr::V4([10, 3, 58, 6].into()), - tcp_port: 30303u16, - udp_port: 30301u16, - id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(), - }) - } -} diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 81e7597726fd..44c363c6ce77 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -37,10 +37,10 @@ use crate::constants::optimism::{ pub use alloy_eips::eip1559::BaseFeeParams; #[cfg(feature = "optimism")] use reth_ethereum_forks::OptimismHardfork; - -#[cfg(feature = "optimism")] -use crate::net::{base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes}; -use crate::net::{goerli_nodes, holesky_nodes, mainnet_nodes, sepolia_nodes}; +use reth_network_peers::{ + base_nodes, base_testnet_nodes, goerli_nodes, holesky_nodes, mainnet_nodes, op_nodes, + op_testnet_nodes, sepolia_nodes, +}; /// The Ethereum mainnet spec pub static MAINNET: Lazy> = Lazy::new(|| { @@ -727,13 +727,9 @@ impl ChainSpec { C::Goerli => Some(goerli_nodes()), C::Sepolia => Some(sepolia_nodes()), C::Holesky => Some(holesky_nodes()), - #[cfg(feature = "optimism")] C::Base => Some(base_nodes()), - #[cfg(feature = "optimism")] C::Optimism => Some(op_nodes()), - #[cfg(feature = "optimism")] C::BaseGoerli | C::BaseSepolia => Some(base_testnet_nodes()), - #[cfg(feature = "optimism")] C::OptimismSepolia | C::OptimismGoerli | C::OptimismKovan => Some(op_testnet_nodes()), _ => None, } diff --git a/crates/e2e-test-utils/Cargo.toml b/crates/e2e-test-utils/Cargo.toml index 2855ecc51c40..a610d5569684 100644 --- a/crates/e2e-test-utils/Cargo.toml +++ b/crates/e2e-test-utils/Cargo.toml @@ -21,6 +21,7 @@ reth-provider.workspace = true reth-node-builder.workspace = true reth-tokio-util.workspace = true reth-stages-types.workspace = true +reth-network-peers.workspace = true jsonrpsee.workspace = true diff --git a/crates/e2e-test-utils/src/network.rs b/crates/e2e-test-utils/src/network.rs index 8c1a60ba7bd9..e5791afd76f8 100644 --- a/crates/e2e-test-utils/src/network.rs +++ b/crates/e2e-test-utils/src/network.rs @@ -3,7 +3,7 @@ use reth::{ network::{NetworkEvent, NetworkEvents, NetworkHandle, PeersInfo}, rpc::types::PeerId, }; -use reth_chainspec::net::NodeRecord; +use reth_network_peers::NodeRecord; use reth_tokio_util::EventStream; use reth_tracing::tracing::info; diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 6e18892fdd0c..7c14eac9b653 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -214,9 +214,8 @@ impl Discv4 { /// ``` /// # use std::io; /// use rand::thread_rng; - /// use reth_chainspec::net::NodeRecord; /// use reth_discv4::{Discv4, Discv4Config}; - /// use reth_network_peers::{pk2id, PeerId}; + /// use reth_network_peers::{pk2id, NodeRecord, PeerId}; /// use secp256k1::SECP256K1; /// use std::{net::SocketAddr, str::FromStr}; /// # async fn t() -> io::Result<()> { @@ -2288,8 +2287,8 @@ mod tests { use alloy_primitives::hex; use alloy_rlp::{Decodable, Encodable}; use rand::{thread_rng, Rng}; - use reth_chainspec::net::mainnet_nodes; use reth_ethereum_forks::{EnrForkIdEntry, ForkHash}; + use reth_network_peers::mainnet_nodes; use std::future::poll_fn; #[tokio::test] diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 5edded5d6c3f..b6c95521245c 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -6,15 +6,12 @@ use crate::{ transactions::TransactionsManagerConfig, NetworkHandle, NetworkManager, }; -use reth_chainspec::{ - net::{mainnet_nodes, sepolia_nodes, TrustedPeer}, - ChainSpec, MAINNET, -}; +use reth_chainspec::{ChainSpec, MAINNET}; use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS}; use reth_discv5::NetworkStackId; use reth_dns_discovery::DnsDiscoveryConfig; use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status}; -use reth_network_peers::{pk2id, PeerId}; +use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer}; use reth_network_types::{PeersConfig, SessionsConfig}; use reth_primitives::{ForkFilter, Head}; use reth_provider::{BlockReader, HeaderProvider}; diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index f03889f98414..f14f6e850416 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -46,8 +46,8 @@ //! //! ``` //! # async fn launch() { -//! use reth_chainspec::net::mainnet_nodes; //! use reth_network::{config::rng_secret_key, NetworkConfig, NetworkManager}; +//! use reth_network_peers::mainnet_nodes; //! use reth_provider::test_utils::NoopProvider; //! //! // This block provider implementation is used for testing purposes. @@ -71,8 +71,8 @@ //! ### Configure all components of the Network with the [`NetworkBuilder`] //! //! ``` -//! use reth_chainspec::net::mainnet_nodes; //! use reth_network::{config::rng_secret_key, NetworkConfig, NetworkManager}; +//! use reth_network_peers::mainnet_nodes; //! use reth_provider::test_utils::NoopProvider; //! use reth_transaction_pool::TransactionPool; //! async fn launch(pool: Pool) { diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 17827444bc20..c957738e0ce3 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -280,8 +280,8 @@ where /// components of the network /// /// ``` - /// use reth_chainspec::net::mainnet_nodes; /// use reth_network::{config::rng_secret_key, NetworkConfig, NetworkManager}; + /// use reth_network_peers::mainnet_nodes; /// use reth_provider::test_utils::NoopProvider; /// use reth_transaction_pool::TransactionPool; /// async fn launch(pool: Pool) { diff --git a/crates/net/network/tests/it/connect.rs b/crates/net/network/tests/it/connect.rs index a191443ed8cb..2dbd311cb9f1 100644 --- a/crates/net/network/tests/it/connect.rs +++ b/crates/net/network/tests/it/connect.rs @@ -3,7 +3,6 @@ use alloy_node_bindings::Geth; use alloy_provider::{ext::AdminApi, ProviderBuilder}; use futures::StreamExt; -use reth_chainspec::net::mainnet_nodes; use reth_discv4::Discv4Config; use reth_eth_wire::DisconnectReason; use reth_net_banlist::BanList; @@ -16,7 +15,7 @@ use reth_network_p2p::{ headers::client::{HeadersClient, HeadersRequest}, sync::{NetworkSyncUpdater, SyncState}, }; -use reth_network_peers::NodeRecord; +use reth_network_peers::{mainnet_nodes, NodeRecord}; use reth_primitives::HeadersDirection; use reth_provider::test_utils::NoopProvider; use reth_transaction_pool::test_utils::testing_pool; diff --git a/crates/net/peers/src/bootnodes/ethereum.rs b/crates/net/peers/src/bootnodes/ethereum.rs new file mode 100644 index 000000000000..ba77bb701fcd --- /dev/null +++ b/crates/net/peers/src/bootnodes/ethereum.rs @@ -0,0 +1,40 @@ +//! Ethereum bootnodes come from + +/// Ethereum Foundation Go Bootnodes +pub static MAINNET_BOOTNODES : [&str; 4] = [ + "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303", // bootnode-aws-ap-southeast-1-001 + "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303", // bootnode-aws-us-east-1-001 + "enode://2b252ab6a1d0f971d9722cb839a42cb81db019ba44c08754628ab4a823487071b5695317c8ccd085219c3a03af063495b2f1da8d18218da2d6a82981b45e6ffc@65.108.70.101:30303", // bootnode-hetzner-hel + "enode://4aeb4ab6c14b23e2c4cfdce879c04b0748a20d8e9b59e25ded2a08143e265c6c25936e74cbc8e641e3312ca288673d91f2f93f8e277de3cfa444ecdaaf982052@157.90.35.166:30303", // bootnode-hetzner-fsn +]; + +/// Ethereum Foundation Sepolia Bootnodes +pub static SEPOLIA_BOOTNODES : [&str; 5] = [ + "enode://4e5e92199ee224a01932a377160aa432f31d0b351f84ab413a8e0a42f4f36476f8fb1cbe914af0d9aef0d51665c214cf653c651c4bbd9d5550a934f241f1682b@138.197.51.181:30303", // sepolia-bootnode-1-nyc3 + "enode://143e11fb766781d22d92a2e33f8f104cddae4411a122295ed1fdb6638de96a6ce65f5b7c964ba3763bba27961738fef7d3ecc739268f3e5e771fb4c87b6234ba@146.190.1.103:30303", // sepolia-bootnode-1-sfo3 + "enode://8b61dc2d06c3f96fddcbebb0efb29d60d3598650275dc469c22229d3e5620369b0d3dedafd929835fe7f489618f19f456fe7c0df572bf2d914a9f4e006f783a9@170.64.250.88:30303", // sepolia-bootnode-1-syd1 + "enode://10d62eff032205fcef19497f35ca8477bea0eadfff6d769a147e895d8b2b8f8ae6341630c645c30f5df6e67547c03494ced3d9c5764e8622a26587b083b028e8@139.59.49.206:30303", // sepolia-bootnode-1-blr1 + "enode://9e9492e2e8836114cc75f5b929784f4f46c324ad01daf87d956f98b3b6c5fcba95524d6e5cf9861dc96a2c8a171ea7105bb554a197455058de185fa870970c7c@138.68.123.152:30303", // sepolia-bootnode-1-ams3 +]; + +/// Görli Bootnodes +pub static GOERLI_BOOTNODES : [&str; 7] = [ + // Upstream bootnodes + "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", + "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", + "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", + "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", + + // Ethereum Foundation bootnode + "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", + + // Goerli Initiative bootnodes + "enode://d4f764a48ec2a8ecf883735776fdefe0a3949eb0ca476bd7bc8d0954a9defe8fea15ae5da7d40b5d2d59ce9524a99daedadf6da6283fca492cc80b53689fb3b3@46.4.99.122:32109", + "enode://d2b720352e8216c9efc470091aa91ddafc53e222b32780f505c817ceef69e01d5b0b0797b69db254c586f493872352f5a022b4d8479a00fc92ec55f9ad46a27e@88.99.70.182:30303", +]; + +/// Ethereum Foundation Holesky Bootnodes +pub static HOLESKY_BOOTNODES : [&str; 2] = [ + "enode://ac906289e4b7f12df423d654c5a962b6ebe5b3a74cc9e06292a85221f9a64a6f1cfdd6b714ed6dacef51578f92b34c60ee91e9ede9c7f8fadc4d347326d95e2b@146.190.13.128:30303", + "enode://a3435a0155a3e837c02f5e7f5662a2f1fbc25b48e4dc232016e1c51b544cb5b4510ef633ea3278c0e970fa8ad8141e2d4d0f9f95456c537ff05fdf9b31c15072@178.128.136.233:30303", +]; diff --git a/crates/net/peers/src/bootnodes/mod.rs b/crates/net/peers/src/bootnodes/mod.rs new file mode 100644 index 000000000000..ecd6de3103a6 --- /dev/null +++ b/crates/net/peers/src/bootnodes/mod.rs @@ -0,0 +1,54 @@ +//! Bootnodes for the network + +use crate::NodeRecord; + +mod ethereum; +pub use ethereum::*; + +mod optimism; +pub use optimism::*; + +/// Returns parsed mainnet nodes +pub fn mainnet_nodes() -> Vec { + parse_nodes(&MAINNET_BOOTNODES[..]) +} + +/// Returns parsed goerli nodes +pub fn goerli_nodes() -> Vec { + parse_nodes(&GOERLI_BOOTNODES[..]) +} + +/// Returns parsed sepolia nodes +pub fn sepolia_nodes() -> Vec { + parse_nodes(&SEPOLIA_BOOTNODES[..]) +} + +/// Returns parsed holesky nodes +pub fn holesky_nodes() -> Vec { + parse_nodes(&HOLESKY_BOOTNODES[..]) +} + +/// Returns parsed op-stack mainnet nodes +pub fn op_nodes() -> Vec { + parse_nodes(OP_BOOTNODES) +} + +/// Returns parsed op-stack testnet nodes +pub fn op_testnet_nodes() -> Vec { + parse_nodes(OP_TESTNET_BOOTNODES) +} + +/// Returns parsed op-stack base mainnet nodes +pub fn base_nodes() -> Vec { + parse_nodes(OP_BOOTNODES) +} + +/// Returns parsed op-stack base testnet nodes +pub fn base_testnet_nodes() -> Vec { + parse_nodes(OP_TESTNET_BOOTNODES) +} + +/// Parses all the nodes +pub fn parse_nodes(nodes: impl IntoIterator>) -> Vec { + nodes.into_iter().map(|s| s.as_ref().parse().unwrap()).collect() +} diff --git a/crates/net/peers/src/bootnodes/optimism.rs b/crates/net/peers/src/bootnodes/optimism.rs new file mode 100644 index 000000000000..e3465721b1ca --- /dev/null +++ b/crates/net/peers/src/bootnodes/optimism.rs @@ -0,0 +1,26 @@ +//! OP bootnodes come from + +/// OP stack mainnet boot nodes. +pub static OP_BOOTNODES: &[&str] = &[ + // OP Labs + "enode://ca2774c3c401325850b2477fd7d0f27911efbf79b1e8b335066516e2bd8c4c9e0ba9696a94b1cb030a88eac582305ff55e905e64fb77fe0edcd70a4e5296d3ec@34.65.175.185:30305", + "enode://dd751a9ef8912be1bfa7a5e34e2c3785cc5253110bd929f385e07ba7ac19929fb0e0c5d93f77827291f4da02b2232240fbc47ea7ce04c46e333e452f8656b667@34.65.107.0:30305", + "enode://c5d289b56a77b6a2342ca29956dfd07aadf45364dde8ab20d1dc4efd4d1bc6b4655d902501daea308f4d8950737a4e93a4dfedd17b49cd5760ffd127837ca965@34.65.202.239:30305", + // Base + "enode://87a32fd13bd596b2ffca97020e31aef4ddcc1bbd4b95bb633d16c1329f654f34049ed240a36b449fda5e5225d70fe40bc667f53c304b71f8e68fc9d448690b51@3.231.138.188:30301", + "enode://ca21ea8f176adb2e229ce2d700830c844af0ea941a1d8152a9513b966fe525e809c3a6c73a2c18a12b74ed6ec4380edf91662778fe0b79f6a591236e49e176f9@184.72.129.189:30301", + "enode://acf4507a211ba7c1e52cdf4eef62cdc3c32e7c9c47998954f7ba024026f9a6b2150cd3f0b734d9c78e507ab70d59ba61dfe5c45e1078c7ad0775fb251d7735a2@3.220.145.177:30301", + "enode://8a5a5006159bf079d06a04e5eceab2a1ce6e0f721875b2a9c96905336219dbe14203d38f70f3754686a6324f786c2f9852d8c0dd3adac2d080f4db35efc678c5@3.231.11.52:30301", + "enode://cdadbe835308ad3557f9a1de8db411da1a260a98f8421d62da90e71da66e55e98aaa8e90aa7ce01b408a54e4bd2253d701218081ded3dbe5efbbc7b41d7cef79@54.198.153.150:30301" +]; + +/// OP stack testnet boot nodes. +pub static OP_TESTNET_BOOTNODES: &[&str] = &[ + // OP Labs + "enode://2bd2e657bb3c8efffb8ff6db9071d9eb7be70d7c6d7d980ff80fc93b2629675c5f750bc0a5ef27cd788c2e491b8795a7e9a4a6e72178c14acc6753c0e5d77ae4@34.65.205.244:30305", + "enode://db8e1cab24624cc62fc35dbb9e481b88a9ef0116114cd6e41034c55b5b4f18755983819252333509bd8e25f6b12aadd6465710cd2e956558faf17672cce7551f@34.65.173.88:30305", + "enode://bfda2e0110cfd0f4c9f7aa5bf5ec66e6bd18f71a2db028d36b8bf8b0d6fdb03125c1606a6017b31311d96a36f5ef7e1ad11604d7a166745e6075a715dfa67f8a@34.65.229.245:30305", + // Base + "enode://548f715f3fc388a7c917ba644a2f16270f1ede48a5d88a4d14ea287cc916068363f3092e39936f1a3e7885198bef0e5af951f1d7b1041ce8ba4010917777e71f@18.210.176.114:30301", + "enode://6f10052847a966a725c9f4adf6716f9141155b99a0fb487fea3f51498f4c2a2cb8d534e680ee678f9447db85b93ff7c74562762c3714783a7233ac448603b25f@107.21.251.55:30301", +]; diff --git a/crates/net/peers/src/lib.rs b/crates/net/peers/src/lib.rs index f531f1eb8f09..b58f499abf3a 100644 --- a/crates/net/peers/src/lib.rs +++ b/crates/net/peers/src/lib.rs @@ -63,6 +63,9 @@ pub use node_record::{NodeRecord, NodeRecordParseError}; pub mod trusted_peer; pub use trusted_peer::TrustedPeer; +mod bootnodes; +pub use bootnodes::*; + /// This tag should be set to indicate to libsecp256k1 that the following bytes denote an /// uncompressed pubkey. /// diff --git a/crates/node-core/src/args/network.rs b/crates/node-core/src/args/network.rs index 725838d08bca..03bd6a307830 100644 --- a/crates/node-core/src/args/network.rs +++ b/crates/node-core/src/args/network.rs @@ -2,7 +2,7 @@ use crate::version::P2P_CLIENT_VERSION; use clap::Args; -use reth_chainspec::{net::mainnet_nodes, ChainSpec}; +use reth_chainspec::ChainSpec; use reth_config::Config; use reth_discv4::{NodeRecord, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT}; use reth_discv5::{ @@ -18,7 +18,7 @@ use reth_network::{ }, HelloMessageWithProtocols, NetworkConfigBuilder, SessionsConfig, }; -use reth_network_peers::TrustedPeer; +use reth_network_peers::{mainnet_nodes, TrustedPeer}; use secp256k1::SecretKey; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 35a3ee189308..4d6b7b3ed6ad 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -115,13 +115,7 @@ pub use c_kzg as kzg; #[cfg(feature = "optimism")] mod optimism { pub use crate::transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID}; - pub use reth_chainspec::{ - net::{ - base_nodes, base_testnet_nodes, op_nodes, op_testnet_nodes, OP_BOOTNODES, - OP_TESTNET_BOOTNODES, - }, - BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA, - }; + pub use reth_chainspec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; } #[cfg(feature = "optimism")] diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index 9108525d038a..7b7c89c0fcff 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -47,6 +47,7 @@ tracing.workspace = true reth-chainspec.workspace = true reth-beacon-consensus.workspace = true reth-network-api.workspace = true +reth-network-peers.workspace = true reth-evm-ethereum.workspace = true reth-ethereum-engine-primitives.workspace = true reth-payload-builder = { workspace = true, features = ["test-utils"] } diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index caf16ebf6fce..f778dc22a9ab 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -11,7 +11,7 @@ use jsonrpsee::{ rpc_params, types::error::ErrorCode, }; -use reth_chainspec::net::NodeRecord; +use reth_network_peers::NodeRecord; use reth_primitives::{ hex_literal::hex, Address, BlockId, BlockNumberOrTag, Bytes, TxHash, B256, B64, U256, U64, }; diff --git a/examples/bsc-p2p/Cargo.toml b/examples/bsc-p2p/Cargo.toml index c4d1dbf77032..dde02080d135 100644 --- a/examples/bsc-p2p/Cargo.toml +++ b/examples/bsc-p2p/Cargo.toml @@ -12,6 +12,7 @@ reth-chainspec.workspace = true reth-discv4 = { workspace = true, features = ["test-utils"] } reth-network = { workspace = true, features = ["test-utils"] } reth-network-api.workspace = true +reth-network-peers.workspace = true reth-primitives.workspace = true reth-tracing.workspace = true diff --git a/examples/bsc-p2p/src/chainspec.rs b/examples/bsc-p2p/src/chainspec.rs index 8983a2ef3805..0c4cbe1ed961 100644 --- a/examples/bsc-p2p/src/chainspec.rs +++ b/examples/bsc-p2p/src/chainspec.rs @@ -1,7 +1,7 @@ use reth_chainspec::{ - net::NodeRecord, BaseFeeParams, Chain, ChainHardforks, ChainSpec, EthereumHardfork, - ForkCondition, + BaseFeeParams, Chain, ChainHardforks, ChainSpec, EthereumHardfork, ForkCondition, }; +use reth_network_peers::NodeRecord; use reth_primitives::{b256, B256}; use std::sync::Arc; diff --git a/examples/exex/discv5/src/network/mod.rs b/examples/exex/discv5/src/network/mod.rs index 41f57bffc478..ebab28342d88 100644 --- a/examples/exex/discv5/src/network/mod.rs +++ b/examples/exex/discv5/src/network/mod.rs @@ -2,8 +2,8 @@ use discv5::{enr::secp256k1::rand, Enr, Event, ListenConfig}; use reth::network::config::SecretKey; -use reth_chainspec::net::NodeRecord; use reth_discv5::{enr::EnrCombinedKeyWrapper, Config, Discv5}; +use reth_network_peers::NodeRecord; use reth_tracing::tracing::info; use std::{ future::Future, diff --git a/examples/manual-p2p/src/main.rs b/examples/manual-p2p/src/main.rs index 7379aa4e77b2..c23802d26e51 100644 --- a/examples/manual-p2p/src/main.rs +++ b/examples/manual-p2p/src/main.rs @@ -10,14 +10,14 @@ use std::time::Duration; use futures::StreamExt; use once_cell::sync::Lazy; -use reth_chainspec::{net::mainnet_nodes, Chain, MAINNET}; +use reth_chainspec::{Chain, MAINNET}; use reth_discv4::{DiscoveryUpdate, Discv4, Discv4ConfigBuilder, DEFAULT_DISCOVERY_ADDRESS}; use reth_ecies::stream::ECIESStream; use reth_eth_wire::{ EthMessage, EthStream, HelloMessage, P2PStream, Status, UnauthedEthStream, UnauthedP2PStream, }; use reth_network::config::rng_secret_key; -use reth_network_peers::{pk2id, NodeRecord}; +use reth_network_peers::{mainnet_nodes, pk2id, NodeRecord}; use reth_primitives::{EthereumHardfork, Head, MAINNET_GENESIS_HASH}; use secp256k1::{SecretKey, SECP256K1}; use tokio::net::TcpStream; From 472093a3e5e12387f94809288ba48a517bf89c6b Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 28 Jun 2024 00:54:10 -0700 Subject: [PATCH 248/405] chore: fix clippy (#9163) --- crates/net/eth-wire/src/p2pstream.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/net/eth-wire/src/p2pstream.rs b/crates/net/eth-wire/src/p2pstream.rs index 23f106da9a42..aa8770d058c6 100644 --- a/crates/net/eth-wire/src/p2pstream.rs +++ b/crates/net/eth-wire/src/p2pstream.rs @@ -478,11 +478,10 @@ where // // It's possible we already tried to RLP decode this, but it was snappy // compressed, so we need to RLP decode it again. - let reason = DisconnectReason::decode(&mut &decompress_buf[1..]).map_err(|err| { + let reason = DisconnectReason::decode(&mut &decompress_buf[1..]).inspect_err(|err| { debug!( %err, msg=%hex::encode(&decompress_buf[1..]), "Failed to decode disconnect message from peer" ); - err })?; return Poll::Ready(Some(Err(P2PStreamError::Disconnected(reason)))) } From 3fd5df3d00b027d5c9c93bb9cac35196a07b1137 Mon Sep 17 00:00:00 2001 From: Darshan Kathiriya <8559992+lakshya-sky@users.noreply.github.com> Date: Fri, 28 Jun 2024 03:54:36 -0400 Subject: [PATCH 249/405] feat(trie): in-memory trie node overlay (#8199) Co-authored-by: Roman Krasiuk --- crates/trie/trie/src/trie.rs | 127 +++++++++++- .../trie/src/trie_cursor/database_cursors.rs | 14 +- crates/trie/trie/src/trie_cursor/mod.rs | 11 +- crates/trie/trie/src/trie_cursor/noop.rs | 11 +- crates/trie/trie/src/trie_cursor/update.rs | 191 ++++++++++++++++++ crates/trie/trie/src/updates.rs | 42 ++++ 6 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 crates/trie/trie/src/trie_cursor/update.rs diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index e56946747169..6671840f1b41 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -546,9 +546,11 @@ where mod tests { use super::*; use crate::{ + hashed_cursor::HashedPostStateCursorFactory, prefix_set::PrefixSetMut, test_utils::{state_root, state_root_prehashed, storage_root, storage_root_prehashed}, - BranchNodeCompact, TrieMask, + trie_cursor::TrieUpdatesCursorFactory, + BranchNodeCompact, HashedPostState, HashedStorage, TrieMask, }; use proptest::{prelude::ProptestConfig, proptest}; use proptest_arbitrary_interop::arb; @@ -562,6 +564,7 @@ mod tests { use reth_trie_common::triehash::KeccakHasher; use std::{ collections::{BTreeMap, HashMap}, + iter, ops::Mul, str::FromStr, sync::Arc, @@ -1369,4 +1372,126 @@ mod tests { assert_eq!(node.root_hash, None); assert_eq!(node.hashes.len(), 1); } + + #[test] + fn trie_updates_across_multiple_iterations() { + let address = Address::ZERO; + let hashed_address = keccak256(address); + + let factory = create_test_provider_factory(); + + let mut hashed_storage = BTreeMap::default(); + let mut post_state = HashedPostState::default(); + + // Block #1 + // Update specific storage slots + let mut modified_storage = BTreeMap::default(); + + // 0x0f.. + let modified_key_prefix = Nibbles::from_nibbles( + [0x0, 0xf].into_iter().chain(iter::repeat(0).take(62)).collect::>(), + ); + + // 0x0faa0.. + let mut modified_entry1 = modified_key_prefix.clone(); + modified_entry1.set_at(2, 0xa); + modified_entry1.set_at(3, 0xa); + + // 0x0faaa.. + let mut modified_entry2 = modified_key_prefix.clone(); + modified_entry2.set_at(2, 0xa); + modified_entry2.set_at(3, 0xa); + modified_entry2.set_at(4, 0xa); + + // 0x0fab0.. + let mut modified_entry3 = modified_key_prefix.clone(); + modified_entry3.set_at(2, 0xa); + modified_entry3.set_at(3, 0xb); + + // 0x0fba0.. + let mut modified_entry4 = modified_key_prefix; + modified_entry4.set_at(2, 0xb); + modified_entry4.set_at(3, 0xa); + + [modified_entry1, modified_entry2, modified_entry3.clone(), modified_entry4] + .into_iter() + .for_each(|key| { + modified_storage.insert(B256::from_slice(&key.pack()), U256::from(1)); + }); + + // Update main hashed storage. + hashed_storage.extend(modified_storage.clone()); + post_state.extend(HashedPostState::default().with_storages([( + hashed_address, + HashedStorage::from_iter(false, modified_storage.clone()), + )])); + + let (storage_root, block1_updates) = compute_storage_root( + address, + factory.provider().unwrap().tx_ref(), + &post_state, + &TrieUpdates::default(), + ); + assert_eq!(storage_root, storage_root_prehashed(hashed_storage.clone())); + + // Block #2 + // Set 0x0fab0.. hashed slot to 0 + modified_storage.insert(B256::from_slice(&modified_entry3.pack()), U256::ZERO); + + // Update main hashed storage. + hashed_storage.remove(&B256::from_slice(&modified_entry3.pack())); + post_state.extend(HashedPostState::default().with_storages([( + hashed_address, + HashedStorage::from_iter(false, modified_storage.clone()), + )])); + + let (storage_root, block2_updates) = compute_storage_root( + address, + factory.provider().unwrap().tx_ref(), + &post_state, + &block1_updates, + ); + assert_eq!(storage_root, storage_root_prehashed(hashed_storage.clone())); + + // Commit trie updates + { + let mut updates = block1_updates; + updates.extend(block2_updates); + + let provider_rw = factory.provider_rw().unwrap(); + let mut hashed_storage_cursor = + provider_rw.tx_ref().cursor_dup_write::().unwrap(); + for (hashed_slot, value) in &hashed_storage { + hashed_storage_cursor + .upsert(hashed_address, StorageEntry { key: *hashed_slot, value: *value }) + .unwrap(); + } + updates.flush(provider_rw.tx_ref()).unwrap(); + provider_rw.commit().unwrap(); + } + + // Recompute storage root for block #3 + let storage_root = + StorageRoot::from_tx(factory.provider().unwrap().tx_ref(), address).root().unwrap(); + assert_eq!(storage_root, storage_root_prehashed(hashed_storage.clone())); + } + + fn compute_storage_root( + address: Address, + tx: &TX, + post_state: &HashedPostState, + update: &TrieUpdates, + ) -> (B256, TrieUpdates) { + let mut prefix_sets = post_state.construct_prefix_sets(); + let (root, _, updates) = StorageRoot::from_tx(tx, address) + .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( + tx, + &post_state.clone().into_sorted(), + )) + .with_trie_cursor_factory(TrieUpdatesCursorFactory::new(tx, &update.sorted())) + .with_prefix_set(prefix_sets.storage_prefix_sets.remove(&keccak256(address)).unwrap()) + .root_with_updates() + .unwrap(); + (root, updates) + } } diff --git a/crates/trie/trie/src/trie_cursor/database_cursors.rs b/crates/trie/trie/src/trie_cursor/database_cursors.rs index 84cc69ce1925..a425c70f8c34 100644 --- a/crates/trie/trie/src/trie_cursor/database_cursors.rs +++ b/crates/trie/trie/src/trie_cursor/database_cursors.rs @@ -9,18 +9,22 @@ use reth_primitives::B256; /// Implementation of the trie cursor factory for a database transaction. impl<'a, TX: DbTx> TrieCursorFactory for &'a TX { - fn account_trie_cursor(&self) -> Result, DatabaseError> { - Ok(Box::new(DatabaseAccountTrieCursor::new(self.cursor_read::()?))) + type AccountTrieCursor = DatabaseAccountTrieCursor<::Cursor>; + type StorageTrieCursor = + DatabaseStorageTrieCursor<::DupCursor>; + + fn account_trie_cursor(&self) -> Result { + Ok(DatabaseAccountTrieCursor::new(self.cursor_read::()?)) } fn storage_trie_cursor( &self, hashed_address: B256, - ) -> Result, DatabaseError> { - Ok(Box::new(DatabaseStorageTrieCursor::new( + ) -> Result { + Ok(DatabaseStorageTrieCursor::new( self.cursor_dup_read::()?, hashed_address, - ))) + )) } } diff --git a/crates/trie/trie/src/trie_cursor/mod.rs b/crates/trie/trie/src/trie_cursor/mod.rs index e083be76411a..f5f50a0d0151 100644 --- a/crates/trie/trie/src/trie_cursor/mod.rs +++ b/crates/trie/trie/src/trie_cursor/mod.rs @@ -3,6 +3,7 @@ use reth_db::DatabaseError; use reth_primitives::B256; mod database_cursors; mod subnode; +mod update; /// Noop trie cursor implementations. pub mod noop; @@ -10,18 +11,24 @@ pub mod noop; pub use self::{ database_cursors::{DatabaseAccountTrieCursor, DatabaseStorageTrieCursor}, subnode::CursorSubNode, + update::*, }; /// Factory for creating trie cursors. pub trait TrieCursorFactory { + /// The account trie cursor type. + type AccountTrieCursor: TrieCursor; + /// The storage trie cursor type. + type StorageTrieCursor: TrieCursor; + /// Create an account trie cursor. - fn account_trie_cursor(&self) -> Result, DatabaseError>; + fn account_trie_cursor(&self) -> Result; /// Create a storage tries cursor. fn storage_trie_cursor( &self, hashed_address: B256, - ) -> Result, DatabaseError>; + ) -> Result; } /// A cursor for navigating a trie that works with both Tables and DupSort tables. diff --git a/crates/trie/trie/src/trie_cursor/noop.rs b/crates/trie/trie/src/trie_cursor/noop.rs index 98c19216e655..c55bdb80f2c5 100644 --- a/crates/trie/trie/src/trie_cursor/noop.rs +++ b/crates/trie/trie/src/trie_cursor/noop.rs @@ -9,17 +9,20 @@ use reth_primitives::B256; pub struct NoopTrieCursorFactory; impl TrieCursorFactory for NoopTrieCursorFactory { + type AccountTrieCursor = NoopAccountTrieCursor; + type StorageTrieCursor = NoopStorageTrieCursor; + /// Generates a Noop account trie cursor. - fn account_trie_cursor(&self) -> Result, DatabaseError> { - Ok(Box::::default()) + fn account_trie_cursor(&self) -> Result { + Ok(NoopAccountTrieCursor::default()) } /// Generates a Noop storage trie cursor. fn storage_trie_cursor( &self, _hashed_address: B256, - ) -> Result, DatabaseError> { - Ok(Box::::default()) + ) -> Result { + Ok(NoopStorageTrieCursor::default()) } } diff --git a/crates/trie/trie/src/trie_cursor/update.rs b/crates/trie/trie/src/trie_cursor/update.rs new file mode 100644 index 000000000000..2ee62bd66b18 --- /dev/null +++ b/crates/trie/trie/src/trie_cursor/update.rs @@ -0,0 +1,191 @@ +use super::{TrieCursor, TrieCursorFactory}; +use crate::updates::{TrieKey, TrieOp, TrieUpdatesSorted}; +use reth_db::DatabaseError; +use reth_primitives::B256; +use reth_trie_common::{BranchNodeCompact, Nibbles, StoredNibbles, StoredNibblesSubKey}; + +/// The trie cursor factory for the trie updates. +#[derive(Debug, Clone)] +pub struct TrieUpdatesCursorFactory<'a, CF> { + cursor_factory: CF, + trie_updates: &'a TrieUpdatesSorted, +} + +impl<'a, CF> TrieUpdatesCursorFactory<'a, CF> { + /// Create a new trie cursor factory. + pub const fn new(cursor_factory: CF, trie_updates: &'a TrieUpdatesSorted) -> Self { + Self { cursor_factory, trie_updates } + } +} + +impl<'a, CF: TrieCursorFactory> TrieCursorFactory for TrieUpdatesCursorFactory<'a, CF> { + type AccountTrieCursor = TrieUpdatesAccountTrieCursor<'a, CF::AccountTrieCursor>; + type StorageTrieCursor = TrieUpdatesStorageTrieCursor<'a, CF::StorageTrieCursor>; + + fn account_trie_cursor(&self) -> Result { + let cursor = self.cursor_factory.account_trie_cursor()?; + Ok(TrieUpdatesAccountTrieCursor::new(cursor, self.trie_updates)) + } + + fn storage_trie_cursor( + &self, + hashed_address: B256, + ) -> Result { + let cursor = self.cursor_factory.storage_trie_cursor(hashed_address)?; + Ok(TrieUpdatesStorageTrieCursor::new(cursor, hashed_address, self.trie_updates)) + } +} + +/// The cursor to iterate over account trie updates and corresponding database entries. +/// It will always give precedence to the data from the trie updates. +#[derive(Debug)] +pub struct TrieUpdatesAccountTrieCursor<'a, C> { + cursor: C, + trie_updates: &'a TrieUpdatesSorted, + last_key: Option, +} + +impl<'a, C> TrieUpdatesAccountTrieCursor<'a, C> { + const fn new(cursor: C, trie_updates: &'a TrieUpdatesSorted) -> Self { + Self { cursor, trie_updates, last_key: None } + } +} + +impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesAccountTrieCursor<'a, C> { + fn seek_exact( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + if let Some((trie_key, trie_op)) = self.trie_updates.find_account_node(&key) { + self.last_key = Some(trie_key); + match trie_op { + TrieOp::Update(node) => Ok(Some((key, node))), + TrieOp::Delete => Ok(None), + } + } else { + let result = self.cursor.seek_exact(key)?; + self.last_key = + result.as_ref().map(|(k, _)| TrieKey::AccountNode(StoredNibbles(k.clone()))); + Ok(result) + } + } + + fn seek( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + let stored_nibbles = StoredNibbles(key.clone()); + let trie_update_entry = self + .trie_updates + .trie_operations + .iter() + .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if nibbles <= &stored_nibbles)) + .cloned(); + + if let Some((trie_key, trie_op)) = trie_update_entry { + let nibbles = match &trie_key { + TrieKey::AccountNode(nibbles) => nibbles.clone(), + _ => panic!("Invalid trie key"), + }; + self.last_key = Some(trie_key); + match trie_op { + TrieOp::Update(node) => return Ok(Some((nibbles.0, node))), + TrieOp::Delete => return Ok(None), + } + } + + let result = self.cursor.seek(key)?; + self.last_key = + result.as_ref().map(|(k, _)| TrieKey::AccountNode(StoredNibbles(k.clone()))); + Ok(result) + } + + fn current(&mut self) -> Result, DatabaseError> { + if self.last_key.is_some() { + Ok(self.last_key.clone()) + } else { + self.cursor.current() + } + } +} + +/// The cursor to iterate over storage trie updates and corresponding database entries. +/// It will always give precedence to the data from the trie updates. +#[derive(Debug)] +pub struct TrieUpdatesStorageTrieCursor<'a, C> { + cursor: C, + trie_update_index: usize, + trie_updates: &'a TrieUpdatesSorted, + hashed_address: B256, + last_key: Option, +} + +impl<'a, C> TrieUpdatesStorageTrieCursor<'a, C> { + const fn new(cursor: C, hashed_address: B256, trie_updates: &'a TrieUpdatesSorted) -> Self { + Self { cursor, trie_updates, trie_update_index: 0, hashed_address, last_key: None } + } +} + +impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesStorageTrieCursor<'a, C> { + fn seek_exact( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + if let Some((trie_key, trie_op)) = + self.trie_updates.find_storage_node(&self.hashed_address, &key) + { + self.last_key = Some(trie_key); + match trie_op { + TrieOp::Update(node) => Ok(Some((key, node))), + TrieOp::Delete => Ok(None), + } + } else { + let result = self.cursor.seek_exact(key)?; + self.last_key = result.as_ref().map(|(k, _)| { + TrieKey::StorageNode(self.hashed_address, StoredNibblesSubKey(k.clone())) + }); + Ok(result) + } + } + + fn seek( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + let mut trie_update_entry = self.trie_updates.trie_operations.get(self.trie_update_index); + while trie_update_entry + .filter(|(k, _)| matches!(k, TrieKey::StorageNode(address, nibbles) if address == &self.hashed_address && nibbles.0 < key)).is_some() + { + self.trie_update_index += 1; + trie_update_entry = self.trie_updates.trie_operations.get(self.trie_update_index); + } + + if let Some((trie_key, trie_op)) = + trie_update_entry.filter(|(k, _)| matches!(k, TrieKey::StorageNode(_, _))) + { + let nibbles = match trie_key { + TrieKey::StorageNode(_, nibbles) => nibbles.clone(), + _ => panic!("this should not happen!"), + }; + self.last_key = Some(trie_key.clone()); + match trie_op { + TrieOp::Update(node) => return Ok(Some((nibbles.0, node.clone()))), + TrieOp::Delete => return Ok(None), + } + } + + let result = self.cursor.seek(key)?; + self.last_key = result.as_ref().map(|(k, _)| { + TrieKey::StorageNode(self.hashed_address, StoredNibblesSubKey(k.clone())) + }); + Ok(result) + } + + fn current(&mut self) -> Result, DatabaseError> { + if self.last_key.is_some() { + Ok(self.last_key.clone()) + } else { + self.cursor.current() + } + } +} diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 53830fd8a3c2..ac5797855133 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -222,4 +222,46 @@ impl TrieUpdates { Ok(()) } + + /// creates [`TrieUpdatesSorted`] by sorting the `trie_operations`. + pub fn sorted(&self) -> TrieUpdatesSorted { + let mut trie_operations = Vec::from_iter(self.trie_operations.clone()); + trie_operations.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + TrieUpdatesSorted { trie_operations } + } + + /// converts trie updates into [`TrieUpdatesSorted`]. + pub fn into_sorted(self) -> TrieUpdatesSorted { + let mut trie_operations = Vec::from_iter(self.trie_operations); + trie_operations.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + TrieUpdatesSorted { trie_operations } + } +} + +/// The aggregation of trie updates. +#[derive(Debug, Default, Clone, PartialEq, Eq, Deref)] +pub struct TrieUpdatesSorted { + /// Sorted collection of trie operations. + pub(crate) trie_operations: Vec<(TrieKey, TrieOp)>, +} + +impl TrieUpdatesSorted { + /// Find the account node with the given nibbles. + pub fn find_account_node(&self, key: &Nibbles) -> Option<(TrieKey, TrieOp)> { + self.trie_operations + .iter() + .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if &nibbles.0 == key)) + .cloned() + } + + /// Find the storage node with the given hashed address and key. + pub fn find_storage_node( + &self, + hashed_address: &B256, + key: &Nibbles, + ) -> Option<(TrieKey, TrieOp)> { + self.trie_operations.iter().find(|(k, _)| { + matches!(k, TrieKey::StorageNode(address, nibbles) if address == hashed_address && &nibbles.0 == key) + }).cloned() + } } From ce8bcd8f1cc8f88ebaa69f15aab240c9334b777f Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 28 Jun 2024 09:02:25 +0100 Subject: [PATCH 250/405] chore(storage, provider): rename bundle state with receipts (#9160) --- ...{bundle_state_with_receipts.rs => execution_outcome.rs} | 0 crates/storage/provider/src/bundle_state/mod.rs | 7 +++---- 2 files changed, 3 insertions(+), 4 deletions(-) rename crates/storage/provider/src/bundle_state/{bundle_state_with_receipts.rs => execution_outcome.rs} (100%) diff --git a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs b/crates/storage/provider/src/bundle_state/execution_outcome.rs similarity index 100% rename from crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs rename to crates/storage/provider/src/bundle_state/execution_outcome.rs diff --git a/crates/storage/provider/src/bundle_state/mod.rs b/crates/storage/provider/src/bundle_state/mod.rs index d1a9e9b2a14d..eaf3dab43ee9 100644 --- a/crates/storage/provider/src/bundle_state/mod.rs +++ b/crates/storage/provider/src/bundle_state/mod.rs @@ -1,13 +1,12 @@ //! Bundle state module. //! This module contains all the logic related to bundle state. -mod bundle_state_with_receipts; + +mod execution_outcome; mod hashed_state_changes; mod state_changes; mod state_reverts; -pub use bundle_state_with_receipts::{ - AccountRevertInit, BundleStateInit, OriginalValuesKnown, RevertsInit, -}; +pub use execution_outcome::{AccountRevertInit, BundleStateInit, OriginalValuesKnown, RevertsInit}; pub use hashed_state_changes::HashedStateChanges; pub use state_changes::StateChanges; pub use state_reverts::{StateReverts, StorageRevertsIter}; From c12ca994388018f5c3f20a78d0d0bfc84edf38e9 Mon Sep 17 00:00:00 2001 From: greged93 <82421016+greged93@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:13:40 +0200 Subject: [PATCH 251/405] dev: update `NodeExitFuture` (#9153) Co-authored-by: Matthias Seitz --- Cargo.lock | 1 - crates/node-core/Cargo.toml | 2 -- crates/node-core/src/exit.rs | 52 ++++++++++++++------------- crates/node/builder/src/launch/mod.rs | 5 ++- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1131239a8d73..07cd06f18ae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7597,7 +7597,6 @@ dependencies = [ "procfs", "proptest", "rand 0.8.5", - "reth-beacon-consensus", "reth-chainspec", "reth-config", "reth-consensus-common", diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index ec86bb440893..db72a5e00ee7 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -36,7 +36,6 @@ reth-net-nat.workspace = true reth-network-peers.workspace = true reth-tasks.workspace = true reth-consensus-common.workspace = true -reth-beacon-consensus.workspace = true reth-prune-types.workspace = true reth-stages-types.workspace = true @@ -102,7 +101,6 @@ optimism = [ "reth-primitives/optimism", "reth-provider/optimism", "reth-rpc-types-compat/optimism", - "reth-beacon-consensus/optimism", "reth-rpc-eth-api/optimism", "reth-rpc-eth-types/optimism" ] diff --git a/crates/node-core/src/exit.rs b/crates/node-core/src/exit.rs index 7957af1854fc..5dc6e5638d80 100644 --- a/crates/node-core/src/exit.rs +++ b/crates/node-core/src/exit.rs @@ -1,32 +1,39 @@ //! Helper types for waiting for the node to exit. -use futures::FutureExt; -use reth_beacon_consensus::BeaconConsensusEngineError; +use futures::{future::BoxFuture, FutureExt}; use std::{ + fmt, future::Future, pin::Pin, task::{ready, Context, Poll}, }; -use tokio::sync::oneshot; /// A Future which resolves when the node exits -#[derive(Debug)] pub struct NodeExitFuture { - /// The receiver half of the channel for the consensus engine. - /// This can be used to wait for the consensus engine to exit. - consensus_engine_rx: Option>>, + /// The consensus engine future. + /// This can be polled to wait for the consensus engine to exit. + consensus_engine_fut: Option>>, /// Flag indicating whether the node should be terminated after the pipeline sync. terminate: bool, } +impl fmt::Debug for NodeExitFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NodeExitFuture") + .field("consensus_engine_fut", &"...") + .field("terminate", &self.terminate) + .finish() + } +} + impl NodeExitFuture { /// Create a new `NodeExitFuture`. - pub const fn new( - consensus_engine_rx: oneshot::Receiver>, - terminate: bool, - ) -> Self { - Self { consensus_engine_rx: Some(consensus_engine_rx), terminate } + pub fn new(consensus_engine_fut: F, terminate: bool) -> Self + where + F: Future> + 'static + Send, + { + Self { consensus_engine_fut: Some(Box::pin(consensus_engine_fut)), terminate } } } @@ -35,18 +42,17 @@ impl Future for NodeExitFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - if let Some(rx) = this.consensus_engine_rx.as_mut() { + if let Some(rx) = this.consensus_engine_fut.as_mut() { match ready!(rx.poll_unpin(cx)) { - Ok(res) => { - this.consensus_engine_rx.take(); - res?; + Ok(_) => { + this.consensus_engine_fut.take(); if this.terminate { Poll::Ready(Ok(())) } else { Poll::Pending } } - Err(err) => Poll::Ready(Err(err.into())), + Err(err) => Poll::Ready(Err(err)), } } else { Poll::Pending @@ -61,11 +67,9 @@ mod tests { #[tokio::test] async fn test_node_exit_future_terminate_true() { - let (tx, rx) = oneshot::channel::>(); + let fut = async { Ok(()) }; - let _ = tx.send(Ok(())); - - let node_exit_future = NodeExitFuture::new(rx, true); + let node_exit_future = NodeExitFuture::new(fut, true); let res = node_exit_future.await; @@ -74,11 +78,9 @@ mod tests { #[tokio::test] async fn test_node_exit_future_terminate_false() { - let (tx, rx) = oneshot::channel::>(); - - let _ = tx.send(Ok(())); + let fut = async { Ok(()) }; - let mut node_exit_future = NodeExitFuture::new(rx, false); + let mut node_exit_future = NodeExitFuture::new(fut, false); poll_fn(|cx| { assert!(node_exit_future.poll_unpin(cx).is_pending()); Poll::Ready(()) diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 532e87fecba5..99dc04310d82 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -389,7 +389,10 @@ where on_node_started.on_event(full_node.clone())?; let handle = NodeHandle { - node_exit_future: NodeExitFuture::new(rx, full_node.config.debug.terminate), + node_exit_future: NodeExitFuture::new( + async { Ok(rx.await??) }, + full_node.config.debug.terminate, + ), node: full_node, }; From e81c0798cf0923c7d8eb2243e848ff985d485c47 Mon Sep 17 00:00:00 2001 From: Huber Date: Fri, 28 Jun 2024 10:15:18 +0200 Subject: [PATCH 252/405] chore: fix wrong function name (#9164) --- crates/config/src/config.rs | 2 +- crates/node/builder/src/launch/common.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 5d79549d4e47..c3ee8bf3d5fb 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -53,7 +53,7 @@ impl Config { } /// Sets the pruning configuration. - pub fn update_prune_confing(&mut self, prune_config: PruneConfig) { + pub fn update_prune_config(&mut self, prune_config: PruneConfig) { self.prune = Some(prune_config); } } diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index a1512e25798f..83e48927df57 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -107,7 +107,7 @@ impl LaunchContext { ) -> eyre::Result<()> { if reth_config.prune.is_none() { if let Some(prune_config) = config.prune_config() { - reth_config.update_prune_confing(prune_config); + reth_config.update_prune_config(prune_config); info!(target: "reth::cli", "Saving prune config to toml file"); reth_config.save(config_path.as_ref())?; } From 87cdfb185eaa721f18bc691ace87456fa348dbad Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:49:17 +0200 Subject: [PATCH 253/405] refactor: reduce number of args for `post_block_balance_increments` (#9154) --- crates/ethereum/evm/src/execute.rs | 14 +++----------- crates/optimism/evm/src/execute.rs | 16 +++------------- crates/revm/src/state_change.rs | 24 +++++++++--------------- 3 files changed, 15 insertions(+), 39 deletions(-) diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 503034e1aa6d..76ec292396df 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -15,7 +15,7 @@ use reth_evm::{ }; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ - BlockNumber, BlockWithSenders, EthereumHardfork, Header, Receipt, Request, Withdrawals, U256, + BlockNumber, BlockWithSenders, EthereumHardfork, Header, Receipt, Request, U256, }; use reth_prune_types::PruneModes; use reth_revm::{ @@ -328,16 +328,8 @@ where block: &BlockWithSenders, total_difficulty: U256, ) -> Result<(), BlockExecutionError> { - let mut balance_increments = post_block_balance_increments( - self.chain_spec(), - block.number, - block.difficulty, - block.beneficiary, - block.timestamp, - total_difficulty, - &block.ommers, - block.withdrawals.as_ref().map(Withdrawals::as_ref), - ); + let mut balance_increments = + post_block_balance_increments(self.chain_spec(), block, total_difficulty); // Irregular state change at Ethereum DAO hardfork if self.chain_spec().fork(EthereumHardfork::Dao).transitions_at_block(block.number) { diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index bf9fbc57f525..1f873d234a46 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -11,9 +11,7 @@ use reth_evm::{ }; use reth_execution_types::ExecutionOutcome; use reth_optimism_consensus::validate_block_post_execution; -use reth_primitives::{ - BlockNumber, BlockWithSenders, Header, Receipt, Receipts, TxType, Withdrawals, U256, -}; +use reth_primitives::{BlockNumber, BlockWithSenders, Header, Receipt, Receipts, TxType, U256}; use reth_prune_types::PruneModes; use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, @@ -324,16 +322,8 @@ where block: &BlockWithSenders, total_difficulty: U256, ) -> Result<(), BlockExecutionError> { - let balance_increments = post_block_balance_increments( - self.chain_spec(), - block.number, - block.difficulty, - block.beneficiary, - block.timestamp, - total_difficulty, - &block.ommers, - block.withdrawals.as_ref().map(Withdrawals::as_ref), - ); + let balance_increments = + post_block_balance_increments(self.chain_spec(), block, total_difficulty); // increment balances self.state .increment_balances(balance_increments) diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index fdbb4c461398..5fad6ae42563 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -11,7 +11,7 @@ use reth_primitives::{ fill_tx_env_with_beacon_root_contract_call, fill_tx_env_with_withdrawal_requests_contract_call, }, - Address, Header, Request, Withdrawal, B256, U256, + Address, Block, Request, Withdrawal, Withdrawals, B256, U256, }; use reth_storage_errors::provider::ProviderError; use revm::{ @@ -36,40 +36,34 @@ use std::collections::HashMap; /// /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular /// state changes (DAO fork). -#[allow(clippy::too_many_arguments)] #[inline] pub fn post_block_balance_increments( chain_spec: &ChainSpec, - block_number: u64, - block_difficulty: U256, - beneficiary: Address, - block_timestamp: u64, + block: &Block, total_difficulty: U256, - ommers: &[Header], - withdrawals: Option<&[Withdrawal]>, ) -> HashMap { let mut balance_increments = HashMap::new(); // Add block rewards if they are enabled. if let Some(base_block_reward) = - calc::base_block_reward(chain_spec, block_number, block_difficulty, total_difficulty) + calc::base_block_reward(chain_spec, block.number, block.difficulty, total_difficulty) { // Ommer rewards - for ommer in ommers { + for ommer in &block.ommers { *balance_increments.entry(ommer.beneficiary).or_default() += - calc::ommer_reward(base_block_reward, block_number, ommer.number); + calc::ommer_reward(base_block_reward, block.number, ommer.number); } // Full block reward - *balance_increments.entry(beneficiary).or_default() += - calc::block_reward(base_block_reward, ommers.len()); + *balance_increments.entry(block.beneficiary).or_default() += + calc::block_reward(base_block_reward, block.ommers.len()); } // process withdrawals insert_post_block_withdrawals_balance_increments( chain_spec, - block_timestamp, - withdrawals, + block.timestamp, + block.withdrawals.as_ref().map(Withdrawals::as_ref), &mut balance_increments, ); From 3bf3b9e3eccda6572e158a49328651732f930278 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 28 Jun 2024 13:24:58 +0200 Subject: [PATCH 254/405] fix: derive arbitrary for tests (#9167) --- crates/primitives-traits/src/header/mod.rs | 2 +- crates/primitives/src/block.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/primitives-traits/src/header/mod.rs b/crates/primitives-traits/src/header/mod.rs index e817965a6d52..21a596ce7bd2 100644 --- a/crates/primitives-traits/src/header/mod.rs +++ b/crates/primitives-traits/src/header/mod.rs @@ -489,7 +489,7 @@ impl Decodable for Header { } } -#[cfg(feature = "arbitrary")] +#[cfg(any(test, feature = "test-utils", feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for Header { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { // Generate an arbitrary header, passing it to the generate_valid_header function to make diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 9b5e172e8fc2..345b301666d7 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -171,7 +171,7 @@ impl Block { } } -#[cfg(feature = "arbitrary")] +#[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for Block { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { // first generate up to 100 txs @@ -614,7 +614,7 @@ impl From for BlockBody { } } -#[cfg(feature = "arbitrary")] +#[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for BlockBody { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { // first generate up to 100 txs From 9a2cfe5a5c9c2f58c8775752db77cb289eceb80e Mon Sep 17 00:00:00 2001 From: Delweng Date: Fri, 28 Jun 2024 23:15:40 +0800 Subject: [PATCH 255/405] fix(net/peer): remove the duplicated disconnect logic (#9162) Signed-off-by: jsvisa --- crates/net/network/src/peers.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/crates/net/network/src/peers.rs b/crates/net/network/src/peers.rs index c7e6a05a57dc..03082e19b4ee 100644 --- a/crates/net/network/src/peers.rs +++ b/crates/net/network/src/peers.rs @@ -325,15 +325,6 @@ impl PeersManager { peer.state = PeerConnectionState::In; is_trusted = is_trusted || peer.is_trusted(); - - // if a peer is not trusted and we don't have capacity for more inbound connections, - // disconnecting the peer - if !is_trusted && !has_in_capacity { - self.queued_actions.push_back(PeerAction::Disconnect { - peer_id, - reason: Some(DisconnectReason::TooManyPeers), - }); - } } Entry::Vacant(entry) => { // peer is missing in the table, we add it but mark it as to be removed after @@ -342,16 +333,16 @@ impl PeersManager { peer.remove_after_disconnect = true; entry.insert(peer); self.queued_actions.push_back(PeerAction::PeerAdded(peer_id)); - - // disconnect the peer if we don't have capacity for more inbound connections - if !is_trusted && !has_in_capacity { - self.queued_actions.push_back(PeerAction::Disconnect { - peer_id, - reason: Some(DisconnectReason::TooManyPeers), - }); - } } } + + // disconnect the peer if we don't have capacity for more inbound connections + if !is_trusted && !has_in_capacity { + self.queued_actions.push_back(PeerAction::Disconnect { + peer_id, + reason: Some(DisconnectReason::TooManyPeers), + }); + } } /// Bans the peer temporarily with the configured ban timeout From 9129b97c5bf9bb0bd0a617b32b723681923c25dc Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 28 Jun 2024 16:22:42 +0100 Subject: [PATCH 256/405] feat(exex): backfill executor (#9123) --- Cargo.lock | 12 + bin/reth/src/commands/debug_cmd/execution.rs | 5 +- bin/reth/src/commands/stage/dump/merkle.rs | 6 +- bin/reth/src/commands/stage/run.rs | 8 +- bin/reth/src/commands/stage/unwind.rs | 4 +- crates/config/Cargo.toml | 1 + crates/config/src/config.rs | 12 + crates/evm/execution-types/src/chain.rs | 5 + crates/exex/exex/Cargo.toml | 16 + crates/exex/exex/src/backfill.rs | 344 ++++++++++++++++++ crates/exex/exex/src/lib.rs | 3 + crates/exex/types/Cargo.toml | 2 +- crates/stages/api/src/metrics/execution.rs | 20 + crates/stages/api/src/metrics/mod.rs | 2 + crates/stages/stages/src/stages/execution.rs | 90 +---- crates/stages/stages/src/stages/mod.rs | 4 +- crates/stages/types/src/execution.rs | 50 +++ crates/stages/types/src/lib.rs | 3 + .../provider/src/providers/database/mod.rs | 8 + .../src/providers/database/provider.rs | 134 ++++--- crates/storage/provider/src/providers/mod.rs | 8 + .../src/providers/static_file/manager.rs | 9 + .../storage/provider/src/test_utils/mock.rs | 8 + .../storage/provider/src/test_utils/noop.rs | 8 + crates/storage/storage-api/src/block.rs | 11 + 25 files changed, 633 insertions(+), 140 deletions(-) create mode 100644 crates/exex/exex/src/backfill.rs create mode 100644 crates/stages/api/src/metrics/execution.rs create mode 100644 crates/stages/types/src/execution.rs diff --git a/Cargo.lock b/Cargo.lock index 07cd06f18ae7..556f8da69d76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6636,6 +6636,7 @@ dependencies = [ "humantime-serde", "reth-network-types", "reth-prune-types", + "reth-stages-types", "serde", "tempfile", "toml", @@ -7216,7 +7217,13 @@ version = "1.0.0" dependencies = [ "eyre", "metrics", + "reth-blockchain-tree", + "reth-chainspec", "reth-config", + "reth-db-api", + "reth-db-common", + "reth-evm", + "reth-evm-ethereum", "reth-exex-types", "reth-metrics", "reth-network", @@ -7225,8 +7232,13 @@ dependencies = [ "reth-payload-builder", "reth-primitives", "reth-provider", + "reth-prune-types", + "reth-revm", + "reth-stages-api", "reth-tasks", + "reth-testing-utils", "reth-tracing", + "secp256k1", "serde", "tokio", "tokio-util", diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index 2df188c73404..2172a2b4c7ba 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -28,9 +28,8 @@ use reth_provider::{ }; use reth_prune::PruneModes; use reth_stages::{ - sets::DefaultStages, - stages::{ExecutionStage, ExecutionStageThresholds}, - Pipeline, StageId, StageSet, + sets::DefaultStages, stages::ExecutionStage, ExecutionStageThresholds, Pipeline, StageId, + StageSet, }; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; diff --git a/bin/reth/src/commands/stage/dump/merkle.rs b/bin/reth/src/commands/stage/dump/merkle.rs index 85fd0bfcab57..4e2541b60ea2 100644 --- a/bin/reth/src/commands/stage/dump/merkle.rs +++ b/bin/reth/src/commands/stage/dump/merkle.rs @@ -12,10 +12,10 @@ use reth_provider::{providers::StaticFileProvider, ProviderFactory}; use reth_prune::PruneModes; use reth_stages::{ stages::{ - AccountHashingStage, ExecutionStage, ExecutionStageThresholds, MerkleStage, - StorageHashingStage, MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, + AccountHashingStage, ExecutionStage, MerkleStage, StorageHashingStage, + MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, }, - Stage, StageCheckpoint, UnwindInput, + ExecutionStageThresholds, Stage, StageCheckpoint, UnwindInput, }; use tracing::info; diff --git a/bin/reth/src/commands/stage/run.rs b/bin/reth/src/commands/stage/run.rs index b8dcc7c9194d..55824bd79c69 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/bin/reth/src/commands/stage/run.rs @@ -20,11 +20,11 @@ use reth_provider::{ }; use reth_stages::{ stages::{ - AccountHashingStage, BodyStage, ExecutionStage, ExecutionStageThresholds, - IndexAccountHistoryStage, IndexStorageHistoryStage, MerkleStage, SenderRecoveryStage, - StorageHashingStage, TransactionLookupStage, + AccountHashingStage, BodyStage, ExecutionStage, IndexAccountHistoryStage, + IndexStorageHistoryStage, MerkleStage, SenderRecoveryStage, StorageHashingStage, + TransactionLookupStage, }, - ExecInput, ExecOutput, Stage, StageExt, UnwindInput, UnwindOutput, + ExecInput, ExecOutput, ExecutionStageThresholds, Stage, StageExt, UnwindInput, UnwindOutput, }; use std::{any::Any, net::SocketAddr, sync::Arc, time::Instant}; use tracing::*; diff --git a/bin/reth/src/commands/stage/unwind.rs b/bin/reth/src/commands/stage/unwind.rs index 57088eaf2e2d..e3cb0cc8fcd5 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/bin/reth/src/commands/stage/unwind.rs @@ -16,8 +16,8 @@ use reth_provider::{ use reth_prune::PruneModes; use reth_stages::{ sets::{DefaultStages, OfflineStages}, - stages::{ExecutionStage, ExecutionStageThresholds}, - Pipeline, StageSet, + stages::ExecutionStage, + ExecutionStageThresholds, Pipeline, StageSet, }; use reth_static_file::StaticFileProducer; use std::{ops::RangeInclusive, sync::Arc}; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 1468fccd32d0..91dcfc772e9c 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # reth reth-network-types = { workspace = true, features = ["serde"] } reth-prune-types.workspace = true +reth-stages-types.workspace = true # serde serde.workspace = true diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index c3ee8bf3d5fb..fa498f66c9c8 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -2,6 +2,7 @@ use reth_network_types::{PeersConfig, SessionsConfig}; use reth_prune_types::PruneModes; +use reth_stages_types::ExecutionStageThresholds; use serde::{Deserialize, Deserializer, Serialize}; use std::{ ffi::OsStr, @@ -216,6 +217,17 @@ impl Default for ExecutionConfig { } } +impl From for ExecutionStageThresholds { + fn from(config: ExecutionConfig) -> Self { + Self { + max_blocks: config.max_blocks, + max_changes: config.max_changes, + max_cumulative_gas: config.max_cumulative_gas, + max_duration: config.max_duration, + } + } +} + /// Hashing stage configuration. #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Serialize)] #[serde(default)] diff --git a/crates/evm/execution-types/src/chain.rs b/crates/evm/execution-types/src/chain.rs index 8833615bbc38..8ccf0f480e9d 100644 --- a/crates/evm/execution-types/src/chain.rs +++ b/crates/evm/execution-types/src/chain.rs @@ -94,6 +94,11 @@ impl Chain { &self.execution_outcome } + /// Get mutable execution outcome of this chain + pub fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome { + &mut self.execution_outcome + } + /// Prepends the given state to the current state. pub fn prepend_state(&mut self, state: BundleState) { self.execution_outcome.prepend_state(state); diff --git a/crates/exex/exex/Cargo.toml b/crates/exex/exex/Cargo.toml index 5bbf177d098b..a911f45997a7 100644 --- a/crates/exex/exex/Cargo.toml +++ b/crates/exex/exex/Cargo.toml @@ -24,6 +24,11 @@ reth-tasks.workspace = true reth-tracing.workspace = true reth-network.workspace = true reth-payload-builder.workspace = true +reth-evm.workspace = true +reth-prune-types.workspace = true +reth-revm.workspace = true +reth-stages-api.workspace = true +reth-db-api.workspace = true ## async tokio.workspace = true @@ -34,6 +39,17 @@ eyre.workspace = true metrics.workspace = true serde = { workspace = true, optional = true } +[dev-dependencies] +reth-chainspec.workspace = true +reth-evm-ethereum.workspace = true +reth-testing-utils.workspace = true +reth-blockchain-tree.workspace = true +reth-db-common.workspace = true +reth-node-api.workspace = true +reth-provider = { workspace = true, features = ["test-utils"] } + +secp256k1.workspace = true + [features] default = [] serde = ["dep:serde", "reth-provider/serde"] diff --git a/crates/exex/exex/src/backfill.rs b/crates/exex/exex/src/backfill.rs new file mode 100644 index 000000000000..f46c82d24101 --- /dev/null +++ b/crates/exex/exex/src/backfill.rs @@ -0,0 +1,344 @@ +use reth_db_api::database::Database; +use reth_evm::execute::{BatchExecutor, BlockExecutionError, BlockExecutorProvider}; +use reth_node_api::FullNodeComponents; +use reth_primitives::{Block, BlockNumber}; +use reth_provider::{Chain, FullProvider, ProviderError, TransactionVariant}; +use reth_prune_types::PruneModes; +use reth_revm::database::StateProviderDatabase; +use reth_stages_api::{format_gas_throughput, ExecutionStageThresholds}; +use reth_tracing::tracing::{debug, trace}; +use std::{ + marker::PhantomData, + ops::RangeInclusive, + time::{Duration, Instant}, +}; + +/// Factory for creating new backfill jobs. +#[derive(Debug, Clone)] +pub struct BackfillJobFactory { + executor: E, + provider: P, + prune_modes: PruneModes, + thresholds: ExecutionStageThresholds, +} + +impl BackfillJobFactory { + /// Creates a new [`BackfillJobFactory`]. + pub fn new(executor: E, provider: P, prune_modes: PruneModes) -> Self { + Self { executor, provider, prune_modes, thresholds: ExecutionStageThresholds::default() } + } + + /// Sets the thresholds + pub const fn with_thresholds(mut self, thresholds: ExecutionStageThresholds) -> Self { + self.thresholds = thresholds; + self + } +} + +impl BackfillJobFactory { + /// Creates a new backfill job for the given range. + pub fn backfill(&self, range: RangeInclusive) -> BackfillJob { + BackfillJob { + executor: self.executor.clone(), + provider: self.provider.clone(), + prune_modes: self.prune_modes.clone(), + range, + thresholds: self.thresholds.clone(), + _db: PhantomData, + } + } +} + +impl BackfillJobFactory<(), ()> { + /// Creates a new [`BackfillJobFactory`] from [`FullNodeComponents`]. + pub fn new_from_components( + components: Node, + prune_modes: PruneModes, + ) -> BackfillJobFactory { + BackfillJobFactory::<_, _>::new( + components.block_executor().clone(), + components.provider().clone(), + prune_modes, + ) + } +} + +/// Backfill job started for a specific range. +/// +/// It implements [`Iterator`] that executes blocks in batches according to the provided thresholds +/// and yields [`Chain`] +#[derive(Debug)] +pub struct BackfillJob { + executor: E, + provider: P, + prune_modes: PruneModes, + range: RangeInclusive, + thresholds: ExecutionStageThresholds, + _db: PhantomData, +} + +impl Iterator for BackfillJob +where + E: BlockExecutorProvider, + DB: Database, + P: FullProvider, +{ + type Item = Result; + + fn next(&mut self) -> Option { + if self.range.is_empty() { + return None + } + + Some(self.execute_range()) + } +} + +impl BackfillJob +where + E: BlockExecutorProvider, + DB: Database, + P: FullProvider, +{ + fn execute_range(&mut self) -> Result { + let mut executor = self.executor.batch_executor( + StateProviderDatabase::new( + self.provider.history_by_block_number(self.range.start().saturating_sub(1))?, + ), + self.prune_modes.clone(), + ); + + let mut fetch_block_duration = Duration::default(); + let mut execution_duration = Duration::default(); + let mut cumulative_gas = 0; + let batch_start = Instant::now(); + + let mut blocks = Vec::new(); + for block_number in self.range.clone() { + // Fetch the block + let fetch_block_start = Instant::now(); + + let td = self + .provider + .header_td_by_number(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + + // we need the block's transactions along with their hashes + let block = self + .provider + .sealed_block_with_senders(block_number.into(), TransactionVariant::WithHash)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + + fetch_block_duration += fetch_block_start.elapsed(); + + cumulative_gas += block.gas_used; + + // Configure the executor to use the current state. + trace!(target: "exex::backfill", number = block_number, txs = block.body.len(), "Executing block"); + + // Execute the block + let execute_start = Instant::now(); + + // Unseal the block for execution + let (block, senders) = block.into_components(); + let (unsealed_header, hash) = block.header.split(); + let block = Block { + header: unsealed_header, + body: block.body, + ommers: block.ommers, + withdrawals: block.withdrawals, + requests: block.requests, + } + .with_senders_unchecked(senders); + + executor.execute_and_verify_one((&block, td).into())?; + execution_duration += execute_start.elapsed(); + + // TODO(alexey): report gas metrics using `block.header.gas_used` + + // Seal the block back and save it + blocks.push(block.seal(hash)); + + // Check if we should commit now + let bundle_size_hint = executor.size_hint().unwrap_or_default() as u64; + if self.thresholds.is_end_of_batch( + block_number - *self.range.start(), + bundle_size_hint, + cumulative_gas, + batch_start.elapsed(), + ) { + break + } + } + + let last_block_number = blocks.last().expect("blocks should not be empty").number; + debug!( + target: "exex::backfill", + range = ?*self.range.start()..=last_block_number, + block_fetch = ?fetch_block_duration, + execution = ?execution_duration, + throughput = format_gas_throughput(cumulative_gas, execution_duration), + "Finished executing block range" + ); + self.range = last_block_number + 1..=*self.range.end(); + + let chain = Chain::new(blocks, executor.finalize(), None); + Ok(chain) + } +} + +#[cfg(test)] +mod tests { + use crate::BackfillJobFactory; + use eyre::OptionExt; + use reth_blockchain_tree::noop::NoopBlockchainTree; + use reth_chainspec::{ChainSpecBuilder, EthereumHardfork, MAINNET}; + use reth_db_common::init::init_genesis; + use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; + use reth_evm_ethereum::execute::EthExecutorProvider; + use reth_primitives::{ + b256, constants::ETH_TO_WEI, public_key_to_address, Address, Block, Genesis, + GenesisAccount, Header, Transaction, TxEip2930, TxKind, U256, + }; + use reth_provider::{ + providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, + BlockWriter, LatestStateProviderRef, + }; + use reth_prune_types::PruneModes; + use reth_revm::database::StateProviderDatabase; + use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; + use secp256k1::Keypair; + use std::sync::Arc; + + #[tokio::test] + async fn test_backfill() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + // Create a key pair for the sender + let key_pair = Keypair::new_global(&mut generators::rng()); + let address = public_key_to_address(key_pair.public_key()); + + // Create a chain spec with a genesis state that contains the sender + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(Genesis { + alloc: [( + address, + GenesisAccount { balance: U256::from(ETH_TO_WEI), ..Default::default() }, + )] + .into(), + ..MAINNET.genesis.clone() + }) + .paris_activated() + .build(), + ); + + let executor = EthExecutorProvider::ethereum(chain_spec.clone()); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(provider_factory.clone())?; + let blockchain_db = BlockchainProvider::new( + provider_factory.clone(), + Arc::new(NoopBlockchainTree::default()), + )?; + + // First block has a transaction that transfers some ETH to zero address + let block1 = Block { + header: Header { + parent_hash: chain_spec.genesis_hash(), + receipts_root: b256!( + "d3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" + ), + difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), + number: 1, + gas_limit: 21000, + gas_used: 21000, + ..Default::default() + }, + body: vec![sign_tx_with_key_pair( + key_pair, + Transaction::Eip2930(TxEip2930 { + chain_id: chain_spec.chain.id(), + nonce: 0, + gas_limit: 21000, + gas_price: 1_500_000_000, + to: TxKind::Call(Address::ZERO), + value: U256::from(0.1 * ETH_TO_WEI as f64), + ..Default::default() + }), + )], + ..Default::default() + } + .with_recovered_senders() + .ok_or_eyre("failed to recover senders")?; + + // Second block has no state changes + let block2 = Block { + header: Header { + parent_hash: block1.hash_slow(), + difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), + number: 2, + ..Default::default() + }, + ..Default::default() + } + .with_recovered_senders() + .ok_or_eyre("failed to recover senders")?; + + let provider = provider_factory.provider()?; + // Execute only the first block on top of genesis state + let mut outcome_single = EthExecutorProvider::ethereum(chain_spec.clone()) + .batch_executor( + StateProviderDatabase::new(LatestStateProviderRef::new( + provider.tx_ref(), + provider.static_file_provider().clone(), + )), + PruneModes::none(), + ) + .execute_and_verify_batch([(&block1, U256::ZERO).into()])?; + outcome_single.bundle.reverts.sort(); + // Execute both blocks on top of the genesis state + let outcome_batch = EthExecutorProvider::ethereum(chain_spec) + .batch_executor( + StateProviderDatabase::new(LatestStateProviderRef::new( + provider.tx_ref(), + provider.static_file_provider().clone(), + )), + PruneModes::none(), + ) + .execute_and_verify_batch([ + (&block1, U256::ZERO).into(), + (&block2, U256::ZERO).into(), + ])?; + drop(provider); + + let block1 = block1.seal_slow(); + let block2 = block2.seal_slow(); + + // Update the state with the execution results of both blocks + let provider_rw = provider_factory.provider_rw()?; + provider_rw.append_blocks_with_state( + vec![block1.clone(), block2], + outcome_batch, + Default::default(), + Default::default(), + None, + )?; + provider_rw.commit()?; + + // Backfill the first block + let factory = BackfillJobFactory::new(executor, blockchain_db, PruneModes::none()); + let job = factory.backfill(1..=1); + let chains = job.collect::, _>>()?; + + // Assert that the backfill job produced the same chain as we got before when we were + // executing only the first block + assert_eq!(chains.len(), 1); + let mut chain = chains.into_iter().next().unwrap(); + chain.execution_outcome_mut().bundle.reverts.sort(); + assert_eq!(chain.blocks(), &[(1, block1)].into()); + assert_eq!(chain.execution_outcome(), &outcome_single); + + Ok(()) + } +} diff --git a/crates/exex/exex/src/lib.rs b/crates/exex/exex/src/lib.rs index a7661d855f41..5f859accca42 100644 --- a/crates/exex/exex/src/lib.rs +++ b/crates/exex/exex/src/lib.rs @@ -34,6 +34,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] +mod backfill; +pub use backfill::*; + mod context; pub use context::*; diff --git a/crates/exex/types/Cargo.toml b/crates/exex/types/Cargo.toml index 8797376da74b..e03b63342f72 100644 --- a/crates/exex/types/Cargo.toml +++ b/crates/exex/types/Cargo.toml @@ -12,4 +12,4 @@ description = "Commonly used types for exex usage in reth." workspace = true [dependencies] -alloy-primitives.workspace = true \ No newline at end of file +alloy-primitives.workspace = true diff --git a/crates/stages/api/src/metrics/execution.rs b/crates/stages/api/src/metrics/execution.rs new file mode 100644 index 000000000000..b54ed02f653c --- /dev/null +++ b/crates/stages/api/src/metrics/execution.rs @@ -0,0 +1,20 @@ +use std::time::Duration; + +use reth_primitives_traits::constants::gas_units::{GIGAGAS, KILOGAS, MEGAGAS}; + +/// Returns a formatted gas throughput log, showing either: +/// * "Kgas/s", or 1,000 gas per second +/// * "Mgas/s", or 1,000,000 gas per second +/// * "Ggas/s", or 1,000,000,000 gas per second +/// +/// Depending on the magnitude of the gas throughput. +pub fn format_gas_throughput(gas: u64, execution_duration: Duration) -> String { + let gas_per_second = gas as f64 / execution_duration.as_secs_f64(); + if gas_per_second < MEGAGAS as f64 { + format!("{:.} Kgas/second", gas_per_second / KILOGAS as f64) + } else if gas_per_second < GIGAGAS as f64 { + format!("{:.} Mgas/second", gas_per_second / MEGAGAS as f64) + } else { + format!("{:.} Ggas/second", gas_per_second / GIGAGAS as f64) + } +} diff --git a/crates/stages/api/src/metrics/mod.rs b/crates/stages/api/src/metrics/mod.rs index bed2742c25fc..983247ae9c0b 100644 --- a/crates/stages/api/src/metrics/mod.rs +++ b/crates/stages/api/src/metrics/mod.rs @@ -1,5 +1,7 @@ +mod execution; mod listener; mod sync_metrics; +pub use execution::format_gas_throughput; pub use listener::{MetricEvent, MetricEventsSender, MetricsListener}; use sync_metrics::*; diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index a37c081b4da7..26db6f50f8ae 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -6,10 +6,7 @@ use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; use reth_execution_types::{Chain, ExecutionOutcome}; use reth_exex::{ExExManagerHandle, ExExNotification}; -use reth_primitives::{ - constants::gas_units::{GIGAGAS, KILOGAS, MEGAGAS}, - BlockNumber, Header, StaticFileSegment, -}; +use reth_primitives::{BlockNumber, Header, StaticFileSegment}; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, BlockReader, DatabaseProviderRW, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, @@ -18,9 +15,9 @@ use reth_provider::{ use reth_prune_types::PruneModes; use reth_revm::database::StateProviderDatabase; use reth_stages_api::{ - BlockErrorKind, CheckpointBlockRange, EntitiesCheckpoint, ExecInput, ExecOutput, - ExecutionCheckpoint, MetricEvent, MetricEventsSender, Stage, StageCheckpoint, StageError, - StageId, UnwindInput, UnwindOutput, + format_gas_throughput, BlockErrorKind, CheckpointBlockRange, EntitiesCheckpoint, ExecInput, + ExecOutput, ExecutionCheckpoint, ExecutionStageThresholds, MetricEvent, MetricEventsSender, + Stage, StageCheckpoint, StageError, StageId, UnwindInput, UnwindOutput, }; use std::{ cmp::Ordering, @@ -546,83 +543,6 @@ fn calculate_gas_used_from_headers( Ok(gas_total) } -/// The thresholds at which the execution stage writes state changes to the database. -/// -/// If either of the thresholds (`max_blocks` and `max_changes`) are hit, then the execution stage -/// commits all pending changes to the database. -/// -/// A third threshold, `max_changesets`, can be set to periodically write changesets to the -/// current database transaction, which frees up memory. -#[derive(Debug, Clone)] -pub struct ExecutionStageThresholds { - /// The maximum number of blocks to execute before the execution stage commits. - pub max_blocks: Option, - /// The maximum number of state changes to keep in memory before the execution stage commits. - pub max_changes: Option, - /// The maximum cumulative amount of gas to process before the execution stage commits. - pub max_cumulative_gas: Option, - /// The maximum spent on blocks processing before the execution stage commits. - pub max_duration: Option, -} - -impl Default for ExecutionStageThresholds { - fn default() -> Self { - Self { - max_blocks: Some(500_000), - max_changes: Some(5_000_000), - // 50k full blocks of 30M gas - max_cumulative_gas: Some(30_000_000 * 50_000), - // 10 minutes - max_duration: Some(Duration::from_secs(10 * 60)), - } - } -} - -impl ExecutionStageThresholds { - /// Check if the batch thresholds have been hit. - #[inline] - pub fn is_end_of_batch( - &self, - blocks_processed: u64, - changes_processed: u64, - cumulative_gas_used: u64, - elapsed: Duration, - ) -> bool { - blocks_processed >= self.max_blocks.unwrap_or(u64::MAX) || - changes_processed >= self.max_changes.unwrap_or(u64::MAX) || - cumulative_gas_used >= self.max_cumulative_gas.unwrap_or(u64::MAX) || - elapsed >= self.max_duration.unwrap_or(Duration::MAX) - } -} - -impl From for ExecutionStageThresholds { - fn from(config: ExecutionConfig) -> Self { - Self { - max_blocks: config.max_blocks, - max_changes: config.max_changes, - max_cumulative_gas: config.max_cumulative_gas, - max_duration: config.max_duration, - } - } -} - -/// Returns a formatted gas throughput log, showing either: -/// * "Kgas/s", or 1,000 gas per second -/// * "Mgas/s", or 1,000,000 gas per second -/// * "Ggas/s", or 1,000,000,000 gas per second -/// -/// Depending on the magnitude of the gas throughput. -pub fn format_gas_throughput(gas: u64, execution_duration: Duration) -> String { - let gas_per_second = gas as f64 / execution_duration.as_secs_f64(); - if gas_per_second < MEGAGAS as f64 { - format!("{:.} Kgas/second", gas_per_second / KILOGAS as f64) - } else if gas_per_second < GIGAGAS as f64 { - format!("{:.} Mgas/second", gas_per_second / MEGAGAS as f64) - } else { - format!("{:.} Ggas/second", gas_per_second / GIGAGAS as f64) - } -} - /// Returns a `StaticFileProviderRWRefMut` static file producer after performing a consistency /// check. /// @@ -720,7 +640,7 @@ mod tests { StaticFileProviderFactory, }; use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig}; - use reth_stages_api::StageUnitCheckpoint; + use reth_stages_api::{format_gas_throughput, StageUnitCheckpoint}; use std::collections::BTreeMap; fn stage() -> ExecutionStage { diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index 8d850b8ba13a..9c96f4b30a5b 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -66,7 +66,9 @@ mod tests { StaticFileProviderFactory, StorageReader, }; use reth_prune_types::{PruneMode, PruneModes}; - use reth_stages_api::{ExecInput, PipelineTarget, Stage, StageCheckpoint, StageId}; + use reth_stages_api::{ + ExecInput, ExecutionStageThresholds, PipelineTarget, Stage, StageCheckpoint, StageId, + }; use reth_testing_utils::generators::{self, random_block, random_block_range, random_receipt}; use std::{io::Write, sync::Arc}; diff --git a/crates/stages/types/src/execution.rs b/crates/stages/types/src/execution.rs new file mode 100644 index 000000000000..61f7313a380a --- /dev/null +++ b/crates/stages/types/src/execution.rs @@ -0,0 +1,50 @@ +use std::time::Duration; + +/// The thresholds at which the execution stage writes state changes to the database. +/// +/// If either of the thresholds (`max_blocks` and `max_changes`) are hit, then the execution stage +/// commits all pending changes to the database. +/// +/// A third threshold, `max_changesets`, can be set to periodically write changesets to the +/// current database transaction, which frees up memory. +#[derive(Debug, Clone)] +pub struct ExecutionStageThresholds { + /// The maximum number of blocks to execute before the execution stage commits. + pub max_blocks: Option, + /// The maximum number of state changes to keep in memory before the execution stage commits. + pub max_changes: Option, + /// The maximum cumulative amount of gas to process before the execution stage commits. + pub max_cumulative_gas: Option, + /// The maximum spent on blocks processing before the execution stage commits. + pub max_duration: Option, +} + +impl Default for ExecutionStageThresholds { + fn default() -> Self { + Self { + max_blocks: Some(500_000), + max_changes: Some(5_000_000), + // 50k full blocks of 30M gas + max_cumulative_gas: Some(30_000_000 * 50_000), + // 10 minutes + max_duration: Some(Duration::from_secs(10 * 60)), + } + } +} + +impl ExecutionStageThresholds { + /// Check if the batch thresholds have been hit. + #[inline] + pub fn is_end_of_batch( + &self, + blocks_processed: u64, + changes_processed: u64, + cumulative_gas_used: u64, + elapsed: Duration, + ) -> bool { + blocks_processed >= self.max_blocks.unwrap_or(u64::MAX) || + changes_processed >= self.max_changes.unwrap_or(u64::MAX) || + cumulative_gas_used >= self.max_cumulative_gas.unwrap_or(u64::MAX) || + elapsed >= self.max_duration.unwrap_or(Duration::MAX) + } +} diff --git a/crates/stages/types/src/lib.rs b/crates/stages/types/src/lib.rs index 00355b023bc8..0132c8b410d8 100644 --- a/crates/stages/types/src/lib.rs +++ b/crates/stages/types/src/lib.rs @@ -19,6 +19,9 @@ pub use checkpoints::{ StageUnitCheckpoint, StorageHashingCheckpoint, }; +mod execution; +pub use execution::*; + /// Direction and target block for pipeline operations. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PipelineTarget { diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 31332377d64a..9c65103a4226 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -330,6 +330,14 @@ impl BlockReader for ProviderFactory { self.provider()?.block_with_senders(id, transaction_kind) } + fn sealed_block_with_senders( + &self, + id: BlockHashOrNumber, + transaction_kind: TransactionVariant, + ) -> ProviderResult> { + self.provider()?.sealed_block_with_senders(id, transaction_kind) + } + fn block_range(&self, range: RangeInclusive) -> ProviderResult> { self.provider()?.block_range(range) } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index e5fa31642142..32b666c50182 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -357,6 +357,65 @@ impl DatabaseProvider { ) } + fn block_with_senders( + &self, + id: BlockHashOrNumber, + transaction_kind: TransactionVariant, + header_by_number: HF, + construct_block: BF, + ) -> ProviderResult> + where + H: AsRef
, + HF: FnOnce(BlockNumber) -> ProviderResult>, + BF: FnOnce( + H, + Vec, + Vec
, + Vec
, + Option, + Option, + ) -> ProviderResult>, + { + let Some(block_number) = self.convert_hash_or_number(id)? else { return Ok(None) }; + let Some(header) = header_by_number(block_number)? else { return Ok(None) }; + + let ommers = self.ommers(block_number.into())?.unwrap_or_default(); + let withdrawals = + self.withdrawals_by_block(block_number.into(), header.as_ref().timestamp)?; + let requests = self.requests_by_block(block_number.into(), header.as_ref().timestamp)?; + + // Get the block body + // + // 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 information to return the block anyways, so we return + // `None`. + let Some(body) = self.block_body_indices(block_number)? else { return Ok(None) }; + + let tx_range = body.tx_num_range(); + + let (transactions, senders) = if tx_range.is_empty() { + (vec![], vec![]) + } else { + (self.transactions_by_tx_range(tx_range.clone())?, self.senders_by_tx_range(tx_range)?) + }; + + let body = transactions + .into_iter() + .map(|tx| match transaction_kind { + TransactionVariant::NoHash => TransactionSigned { + // Caller explicitly asked for no hash, so we don't calculate it + hash: B256::ZERO, + signature: tx.signature, + transaction: tx.transaction, + }, + TransactionVariant::WithHash => tx.with_hash(), + }) + .collect(); + + construct_block(header, body, senders, ommers, withdrawals, requests) + } + /// Returns a range of blocks from the database. /// /// Uses the provided `headers_range` to get the headers for the range, and `assemble_block` to @@ -1550,48 +1609,41 @@ impl BlockReader for DatabaseProvider { id: BlockHashOrNumber, transaction_kind: TransactionVariant, ) -> ProviderResult> { - let Some(block_number) = self.convert_hash_or_number(id)? else { return Ok(None) }; - let Some(header) = self.header_by_number(block_number)? else { return Ok(None) }; - - 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 - // - // 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 information to return the block anyways, so we return - // `None`. - let Some(body) = self.block_body_indices(block_number)? else { return Ok(None) }; - - let tx_range = body.tx_num_range(); - - let (transactions, senders) = if tx_range.is_empty() { - (vec![], vec![]) - } else { - (self.transactions_by_tx_range(tx_range.clone())?, self.senders_by_tx_range(tx_range)?) - }; - - let body = transactions - .into_iter() - .map(|tx| match transaction_kind { - TransactionVariant::NoHash => TransactionSigned { - // Caller explicitly asked for no hash, so we don't calculate it - hash: B256::ZERO, - signature: tx.signature, - transaction: tx.transaction, - }, - TransactionVariant::WithHash => tx.with_hash(), - }) - .collect(); + self.block_with_senders( + id, + transaction_kind, + |block_number| self.header_by_number(block_number), + |header, body, senders, ommers, withdrawals, requests| { + 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) + .map(Some) + .map_err(|_| ProviderError::SenderRecoveryError) + }, + ) + } - 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) - .map(Some) - .map_err(|_| ProviderError::SenderRecoveryError) + fn sealed_block_with_senders( + &self, + id: BlockHashOrNumber, + transaction_kind: TransactionVariant, + ) -> ProviderResult> { + self.block_with_senders( + id, + transaction_kind, + |block_number| self.sealed_header(block_number), + |header, body, senders, ommers, withdrawals, requests| { + SealedBlock { 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) + .map(Some) + .map_err(|_| ProviderError::SenderRecoveryError) + }, + ) } fn block_range(&self, range: RangeInclusive) -> ProviderResult> { diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 69116517f7cd..4788db247e33 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -328,6 +328,14 @@ where self.database.block_with_senders(id, transaction_kind) } + fn sealed_block_with_senders( + &self, + id: BlockHashOrNumber, + transaction_kind: TransactionVariant, + ) -> ProviderResult> { + self.database.sealed_block_with_senders(id, transaction_kind) + } + fn block_range(&self, range: RangeInclusive) -> ProviderResult> { self.database.block_range(range) } diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 39e588c7f5f4..19d6b06843d7 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -1457,6 +1457,15 @@ impl BlockReader for StaticFileProvider { Err(ProviderError::UnsupportedProvider) } + fn sealed_block_with_senders( + &self, + _id: BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> ProviderResult> { + // Required data not present in static_files + Err(ProviderError::UnsupportedProvider) + } + fn block_range(&self, _range: RangeInclusive) -> ProviderResult> { // Required data not present in static_files Err(ProviderError::UnsupportedProvider) diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 73ee53eeca6b..0184f57559bd 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -474,6 +474,14 @@ impl BlockReader for MockEthProvider { Ok(None) } + fn sealed_block_with_senders( + &self, + _id: BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> ProviderResult> { + Ok(None) + } + fn block_range(&self, range: RangeInclusive) -> ProviderResult> { let lock = self.blocks.lock(); diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 4f7bdabce70d..d52da187f7dd 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -113,6 +113,14 @@ impl BlockReader for NoopProvider { Ok(None) } + fn sealed_block_with_senders( + &self, + _id: BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> ProviderResult> { + Ok(None) + } + fn block_range(&self, _range: RangeInclusive) -> ProviderResult> { Ok(vec![]) } diff --git a/crates/storage/storage-api/src/block.rs b/crates/storage/storage-api/src/block.rs index 42ab05f22503..3dc22de8ae4f 100644 --- a/crates/storage/storage-api/src/block.rs +++ b/crates/storage/storage-api/src/block.rs @@ -118,6 +118,17 @@ pub trait BlockReader: transaction_kind: TransactionVariant, ) -> ProviderResult>; + /// Returns the sealed block with senders with matching number or hash from database. + /// + /// Returns the block's transactions in the requested variant. + /// + /// Returns `None` if block is not found. + fn sealed_block_with_senders( + &self, + id: BlockHashOrNumber, + transaction_kind: TransactionVariant, + ) -> ProviderResult>; + /// Returns all blocks in the given inclusive range. /// /// Note: returns only available blocks From 5416adf97b8e333c9a04ed9f3a18cdffd1a4f5f2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 28 Jun 2024 17:42:25 +0200 Subject: [PATCH 257/405] chore: rm redundant clone (#9174) --- crates/rpc/rpc/src/eth/bundle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index b72d548b1fbc..82395e6df3ca 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -140,7 +140,7 @@ where let gas_price = tx .effective_tip_per_gas(basefee) .ok_or_else(|| RpcInvalidTransactionError::FeeCapTooLow)?; - Call::evm_config(ð_api).fill_tx_env(evm.tx_mut(), &tx.clone(), signer); + Call::evm_config(ð_api).fill_tx_env(evm.tx_mut(), &tx, signer); let ResultAndState { result, state } = evm.transact()?; let gas_used = result.gas_used(); From b24ca7637fb2674024050b26dbd4eff6a2f4be4f Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:43:55 +0200 Subject: [PATCH 258/405] chore: remove `tx_env_with_recovered` from rpc crates (#9158) --- crates/evm/src/lib.rs | 11 ++++++- crates/rpc/rpc-eth-api/src/helpers/call.rs | 16 ++++++---- .../rpc-eth-api/src/helpers/pending_block.rs | 10 ++++--- crates/rpc/rpc-eth-api/src/helpers/trace.rs | 13 +++++---- crates/rpc/rpc/src/debug.rs | 29 ++++++++++++++----- crates/rpc/rpc/src/trace.rs | 13 ++++++--- 6 files changed, 64 insertions(+), 28 deletions(-) diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index f86219eec7fa..20d2f67ed6f2 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -12,10 +12,12 @@ #[cfg(not(feature = "std"))] extern crate alloc; +use core::ops::Deref; + use reth_chainspec::ChainSpec; use reth_primitives::{ revm::env::{fill_block_env, fill_tx_env}, - Address, Header, TransactionSigned, U256, + Address, Header, TransactionSigned, TransactionSignedEcRecovered, U256, }; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; @@ -106,6 +108,13 @@ pub trait ConfigureEvm: ConfigureEvmEnv { /// Default trait method implementation is done w.r.t. L1. #[auto_impl::auto_impl(&, Arc)] pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { + /// Returns a [`TxEnv`] from a [`TransactionSignedEcRecovered`]. + fn tx_env(&self, transaction: &TransactionSignedEcRecovered) -> TxEnv { + let mut tx_env = TxEnv::default(); + self.fill_tx_env(&mut tx_env, transaction.deref(), transaction.signer()); + tx_env + } + /// Fill transaction environment from a [`TransactionSigned`] and the given sender address. fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { fill_tx_env(tx_env, transaction, sender) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 888b739945ee..5f6aebaa85b3 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -4,7 +4,6 @@ use futures::Future; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_primitives::{ - revm::env::tx_env_with_recovered, revm_primitives::{ BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason, ResultAndState, TransactTo, @@ -119,9 +118,11 @@ pub trait EthCall: Call + LoadPendingBlock { // to be replayed let transactions = block.into_transactions_ecrecovered().take(num_txs); for tx in transactions { - let tx = tx_env_with_recovered(&tx); - let env = - EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx); + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg.clone(), + block_env.clone(), + Call::evm_config(&this).tx_env(&tx), + ); let (res, _) = this.transact(&mut db, env)?; db.commit(res.state); } @@ -422,8 +423,11 @@ pub trait Call: LoadState + SpawnBlocking { tx.hash, )?; - let env = - EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx)); + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg, + block_env, + Call::evm_config(&this).tx_env(&tx), + ); let (res, _) = this.transact(&mut db, env)?; f(tx_info, res, db) diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index f30e397e030f..8f3e38817695 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -5,12 +5,11 @@ use std::time::{Duration, Instant}; use futures::Future; use reth_chainspec::EthereumHardforks; -use reth_evm::ConfigureEvm; +use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH}, proofs::calculate_transaction_root, - revm::env::tx_env_with_recovered, revm_primitives::{ BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EVMError, Env, ExecutionResult, InvalidTransaction, ResultAndState, SpecId, @@ -292,8 +291,11 @@ pub trait LoadPendingBlock { } // Configure the environment for the block. - let env = - Env::boxed(cfg.cfg_env.clone(), block_env.clone(), tx_env_with_recovered(&tx)); + let env = Env::boxed( + cfg.cfg_env.clone(), + block_env.clone(), + Self::evm_config(self).tx_env(&tx), + ); let mut evm = revm::Evm::builder().with_env(env).with_db(&mut db).build(); diff --git a/crates/rpc/rpc-eth-api/src/helpers/trace.rs b/crates/rpc/rpc-eth-api/src/helpers/trace.rs index 63b210204065..d48e566ed51d 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/trace.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/trace.rs @@ -1,8 +1,8 @@ //! Loads a pending block from database. Helper trait for `eth_` call and trace RPC methods. use futures::Future; -use reth_evm::ConfigureEvm; -use reth_primitives::{revm::env::tx_env_with_recovered, B256}; +use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; +use reth_primitives::B256; use reth_revm::database::StateProviderDatabase; use reth_rpc_eth_types::{ cache::db::{StateCacheDb, StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, @@ -196,8 +196,11 @@ pub trait Trace: LoadState { tx.hash, )?; - let env = - EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx)); + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg, + block_env, + Call::evm_config(&this).tx_env(&tx), + ); let (res, _) = this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?; f(tx_info, inspector, res, db) @@ -308,7 +311,7 @@ pub trait Trace: LoadState { block_number: Some(block_number), base_fee: Some(base_fee), }; - let tx_env = tx_env_with_recovered(&tx); + let tx_env = Trace::evm_config(&this).tx_env(&tx); (tx_info, tx_env) }) .peekable(); diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 2658547b1963..67363dff31c1 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -4,9 +4,10 @@ use alloy_rlp::{Decodable, Encodable}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_chainspec::EthereumHardforks; +use reth_evm::ConfigureEvmEnv; use reth_primitives::{ - revm::env::tx_env_with_recovered, Address, Block, BlockId, BlockNumberOrTag, Bytes, - TransactionSignedEcRecovered, Withdrawals, B256, U256, + Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, Withdrawals, + B256, U256, }; use reth_provider::{ BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, @@ -14,7 +15,7 @@ use reth_provider::{ }; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::DebugApiServer; -use reth_rpc_eth_api::helpers::{EthApiSpec, EthTransactions, TraceExt}; +use reth_rpc_eth_api::helpers::{Call, EthApiSpec, EthTransactions, TraceExt}; use reth_rpc_eth_types::{revm_utils::prepare_call_env, EthApiError, EthResult, StateCacheDb}; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use reth_rpc_types::{ @@ -99,9 +100,13 @@ where let mut transactions = transactions.into_iter().enumerate().peekable(); while let Some((index, tx)) = transactions.next() { let tx_hash = tx.hash; - let tx = tx_env_with_recovered(&tx); + let env = EnvWithHandlerCfg { - env: Env::boxed(cfg.cfg_env.clone(), block_env.clone(), tx), + env: Env::boxed( + cfg.cfg_env.clone(), + block_env.clone(), + Call::evm_config(this.eth_api()).tx_env(&tx), + ), handler_cfg: cfg.handler_cfg, }; let (result, state_changes) = this.trace_transaction( @@ -240,7 +245,11 @@ where )?; let env = EnvWithHandlerCfg { - env: Env::boxed(cfg.cfg_env.clone(), block_env, tx_env_with_recovered(&tx)), + env: Env::boxed( + cfg.cfg_env.clone(), + block_env, + Call::evm_config(this.eth_api()).tx_env(&tx), + ), handler_cfg: cfg.handler_cfg, }; @@ -453,6 +462,7 @@ where } let this = self.clone(); + self.inner .eth_api .spawn_with_state_at_block(at.into(), move |state| { @@ -467,9 +477,12 @@ where // Execute all transactions until index for tx in transactions { - let tx = tx_env_with_recovered(&tx); let env = EnvWithHandlerCfg { - env: Env::boxed(cfg.cfg_env.clone(), block_env.clone(), tx), + env: Env::boxed( + cfg.cfg_env.clone(), + block_env.clone(), + Call::evm_config(this.eth_api()).tx_env(&tx), + ), handler_cfg: cfg.handler_cfg, }; let (res, _) = this.inner.eth_api.transact(&mut db, env)?; diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 110f35dbd1eb..ff98194b91fb 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -6,11 +6,12 @@ use reth_chainspec::EthereumHardforks; use reth_consensus_common::calc::{ base_block_reward, base_block_reward_pre_merge, block_reward, ommer_reward, }; -use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, Header, B256, U256}; +use reth_evm::ConfigureEvmEnv; +use reth_primitives::{BlockId, Bytes, Header, B256, U256}; use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_rpc_api::TraceApiServer; -use reth_rpc_eth_api::helpers::TraceExt; +use reth_rpc_eth_api::helpers::{Call, TraceExt}; use reth_rpc_eth_types::{ error::{EthApiError, EthResult}, revm_utils::prepare_call_env, @@ -113,8 +114,12 @@ where let tx = recover_raw_transaction(tx)?; let (cfg, block, at) = self.inner.eth_api.evm_env_at(block_id.unwrap_or_default()).await?; - let tx = tx_env_with_recovered(&tx.into_ecrecovered_transaction()); - let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, block, tx); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg, + block, + Call::evm_config(self.eth_api()).tx_env(&tx.into_ecrecovered_transaction()), + ); let config = TracingInspectorConfig::from_parity_config(&trace_types); From 6e564cd0642254391d1819e6f84e6cef01576b0e Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 28 Jun 2024 09:45:06 -0700 Subject: [PATCH 259/405] chore(trie): remove database-related types from trie keys (#9175) --- .../commands/debug_cmd/in_memory_merkle.rs | 2 +- crates/trie/trie/src/trie.rs | 4 +- .../trie/src/trie_cursor/database_cursors.rs | 4 +- crates/trie/trie/src/trie_cursor/update.rs | 27 +++++------- crates/trie/trie/src/updates.rs | 42 ++++++++++--------- 5 files changed, 39 insertions(+), 40 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index d7a5b42bc201..8d138d2c2c1b 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -196,7 +196,7 @@ impl Command { (Some(in_mem), Some(incr)) => { similar_asserts::assert_eq!(in_mem.0, incr.0, "Nibbles don't match"); if in_mem.1 != incr.1 && - matches!(in_mem.0, TrieKey::AccountNode(ref nibbles) if nibbles.0.len() > self.skip_node_depth.unwrap_or_default()) + matches!(in_mem.0, TrieKey::AccountNode(ref nibbles) if nibbles.len() > self.skip_node_depth.unwrap_or_default()) { in_mem_mismatched.push(in_mem); incremental_mismatched.push(incr); diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 6671840f1b41..b3ac9dec1d07 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -1208,7 +1208,7 @@ mod tests { .iter() .filter_map(|entry| match entry { (TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => { - Some((nibbles.0.clone(), node.clone())) + Some((nibbles.clone(), node.clone())) } _ => None, }) @@ -1295,7 +1295,7 @@ mod tests { .iter() .filter_map(|entry| match entry { (TrieKey::StorageNode(_, nibbles), TrieOp::Update(node)) => { - Some((nibbles.0.clone(), node.clone())) + Some((nibbles.clone(), node.clone())) } _ => None, }) diff --git a/crates/trie/trie/src/trie_cursor/database_cursors.rs b/crates/trie/trie/src/trie_cursor/database_cursors.rs index a425c70f8c34..61e43c19b12c 100644 --- a/crates/trie/trie/src/trie_cursor/database_cursors.rs +++ b/crates/trie/trie/src/trie_cursor/database_cursors.rs @@ -61,7 +61,7 @@ where /// Retrieves the current key in the cursor. fn current(&mut self) -> Result, DatabaseError> { - Ok(self.0.current()?.map(|(k, _)| TrieKey::AccountNode(k))) + Ok(self.0.current()?.map(|(k, _)| TrieKey::AccountNode(k.0))) } } @@ -110,7 +110,7 @@ where /// Retrieves the current value in the storage trie cursor. fn current(&mut self) -> Result, DatabaseError> { - Ok(self.cursor.current()?.map(|(k, v)| TrieKey::StorageNode(k, v.nibbles))) + Ok(self.cursor.current()?.map(|(k, v)| TrieKey::StorageNode(k, v.nibbles.0))) } } diff --git a/crates/trie/trie/src/trie_cursor/update.rs b/crates/trie/trie/src/trie_cursor/update.rs index 2ee62bd66b18..0c5d9b046882 100644 --- a/crates/trie/trie/src/trie_cursor/update.rs +++ b/crates/trie/trie/src/trie_cursor/update.rs @@ -2,7 +2,7 @@ use super::{TrieCursor, TrieCursorFactory}; use crate::updates::{TrieKey, TrieOp, TrieUpdatesSorted}; use reth_db::DatabaseError; use reth_primitives::B256; -use reth_trie_common::{BranchNodeCompact, Nibbles, StoredNibbles, StoredNibblesSubKey}; +use reth_trie_common::{BranchNodeCompact, Nibbles}; /// The trie cursor factory for the trie updates. #[derive(Debug, Clone)] @@ -64,8 +64,7 @@ impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesAccountTrieCursor<'a, C> { } } else { let result = self.cursor.seek_exact(key)?; - self.last_key = - result.as_ref().map(|(k, _)| TrieKey::AccountNode(StoredNibbles(k.clone()))); + self.last_key = result.as_ref().map(|(k, _)| TrieKey::AccountNode(k.clone())); Ok(result) } } @@ -74,12 +73,11 @@ impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesAccountTrieCursor<'a, C> { &mut self, key: Nibbles, ) -> Result, DatabaseError> { - let stored_nibbles = StoredNibbles(key.clone()); let trie_update_entry = self .trie_updates .trie_operations .iter() - .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if nibbles <= &stored_nibbles)) + .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if nibbles <= &key)) .cloned(); if let Some((trie_key, trie_op)) = trie_update_entry { @@ -89,14 +87,13 @@ impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesAccountTrieCursor<'a, C> { }; self.last_key = Some(trie_key); match trie_op { - TrieOp::Update(node) => return Ok(Some((nibbles.0, node))), + TrieOp::Update(node) => return Ok(Some((nibbles, node))), TrieOp::Delete => return Ok(None), } } let result = self.cursor.seek(key)?; - self.last_key = - result.as_ref().map(|(k, _)| TrieKey::AccountNode(StoredNibbles(k.clone()))); + self.last_key = result.as_ref().map(|(k, _)| TrieKey::AccountNode(k.clone())); Ok(result) } @@ -141,9 +138,8 @@ impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesStorageTrieCursor<'a, C> { } } else { let result = self.cursor.seek_exact(key)?; - self.last_key = result.as_ref().map(|(k, _)| { - TrieKey::StorageNode(self.hashed_address, StoredNibblesSubKey(k.clone())) - }); + self.last_key = + result.as_ref().map(|(k, _)| TrieKey::StorageNode(self.hashed_address, k.clone())); Ok(result) } } @@ -154,7 +150,7 @@ impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesStorageTrieCursor<'a, C> { ) -> Result, DatabaseError> { let mut trie_update_entry = self.trie_updates.trie_operations.get(self.trie_update_index); while trie_update_entry - .filter(|(k, _)| matches!(k, TrieKey::StorageNode(address, nibbles) if address == &self.hashed_address && nibbles.0 < key)).is_some() + .filter(|(k, _)| matches!(k, TrieKey::StorageNode(address, nibbles) if address == &self.hashed_address && nibbles < &key)).is_some() { self.trie_update_index += 1; trie_update_entry = self.trie_updates.trie_operations.get(self.trie_update_index); @@ -169,15 +165,14 @@ impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesStorageTrieCursor<'a, C> { }; self.last_key = Some(trie_key.clone()); match trie_op { - TrieOp::Update(node) => return Ok(Some((nibbles.0, node.clone()))), + TrieOp::Update(node) => return Ok(Some((nibbles, node.clone()))), TrieOp::Delete => return Ok(None), } } let result = self.cursor.seek(key)?; - self.last_key = result.as_ref().map(|(k, _)| { - TrieKey::StorageNode(self.hashed_address, StoredNibblesSubKey(k.clone())) - }); + self.last_key = + result.as_ref().map(|(k, _)| TrieKey::StorageNode(self.hashed_address, k.clone())); Ok(result) } diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index ac5797855133..4ae4eb309089 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -16,16 +16,16 @@ use std::collections::{hash_map::IntoIter, HashMap, HashSet}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TrieKey { /// A node in the account trie. - AccountNode(StoredNibbles), + AccountNode(Nibbles), /// A node in the storage trie. - StorageNode(B256, StoredNibblesSubKey), + StorageNode(B256, Nibbles), /// Storage trie of an account. StorageTrie(B256), } impl TrieKey { /// Returns reference to account node key if the key is for [`Self::AccountNode`]. - pub const fn as_account_node_key(&self) -> Option<&StoredNibbles> { + pub const fn as_account_node_key(&self) -> Option<&Nibbles> { if let Self::AccountNode(nibbles) = &self { Some(nibbles) } else { @@ -34,7 +34,7 @@ impl TrieKey { } /// Returns reference to storage node key if the key is for [`Self::StorageNode`]. - pub const fn as_storage_node_key(&self) -> Option<(&B256, &StoredNibblesSubKey)> { + pub const fn as_storage_node_key(&self) -> Option<(&B256, &Nibbles)> { if let Self::StorageNode(key, subkey) = &self { Some((key, subkey)) } else { @@ -121,9 +121,9 @@ impl TrieUpdates { /// Extend the updates with account trie updates. pub fn extend_with_account_updates(&mut self, updates: HashMap) { self.extend( - updates.into_iter().map(|(nibbles, node)| { - (TrieKey::AccountNode(nibbles.into()), TrieOp::Update(node)) - }), + updates + .into_iter() + .map(|(nibbles, node)| (TrieKey::AccountNode(nibbles), TrieOp::Update(node))), ); } @@ -162,7 +162,7 @@ impl TrieUpdates { // Add storage node updates from hash builder. let (_, hash_builder_updates) = hash_builder.split(); self.extend(hash_builder_updates.into_iter().map(|(nibbles, node)| { - (TrieKey::StorageNode(hashed_address, nibbles.into()), TrieOp::Update(node)) + (TrieKey::StorageNode(hashed_address, nibbles), TrieOp::Update(node)) })); } @@ -179,18 +179,21 @@ impl TrieUpdates { trie_operations.sort_unstable_by(|a, b| a.0.cmp(&b.0)); for (key, operation) in trie_operations { match key { - TrieKey::AccountNode(nibbles) => match operation { - TrieOp::Delete => { - if account_trie_cursor.seek_exact(nibbles)?.is_some() { - account_trie_cursor.delete_current()?; + TrieKey::AccountNode(nibbles) => { + let nibbles = StoredNibbles(nibbles); + match operation { + TrieOp::Delete => { + if account_trie_cursor.seek_exact(nibbles)?.is_some() { + account_trie_cursor.delete_current()?; + } } - } - TrieOp::Update(node) => { - if !nibbles.0.is_empty() { - account_trie_cursor.upsert(nibbles, StoredBranchNode(node))?; + TrieOp::Update(node) => { + if !nibbles.0.is_empty() { + account_trie_cursor.upsert(nibbles, StoredBranchNode(node))?; + } } } - }, + } TrieKey::StorageTrie(hashed_address) => match operation { TrieOp::Delete => { if storage_trie_cursor.seek_exact(hashed_address)?.is_some() { @@ -201,6 +204,7 @@ impl TrieUpdates { }, TrieKey::StorageNode(hashed_address, nibbles) => { if !nibbles.is_empty() { + let nibbles = StoredNibblesSubKey(nibbles); // Delete the old entry if it exists. if storage_trie_cursor .seek_by_key_subkey(hashed_address, nibbles.clone())? @@ -250,7 +254,7 @@ impl TrieUpdatesSorted { pub fn find_account_node(&self, key: &Nibbles) -> Option<(TrieKey, TrieOp)> { self.trie_operations .iter() - .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if &nibbles.0 == key)) + .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if nibbles == key)) .cloned() } @@ -261,7 +265,7 @@ impl TrieUpdatesSorted { key: &Nibbles, ) -> Option<(TrieKey, TrieOp)> { self.trie_operations.iter().find(|(k, _)| { - matches!(k, TrieKey::StorageNode(address, nibbles) if address == hashed_address && &nibbles.0 == key) + matches!(k, TrieKey::StorageNode(address, nibbles) if address == hashed_address && nibbles == key) }).cloned() } } From d1efe2b19b3c69b5c534d0afaa5fe3eaf7d53a92 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:01:02 -0400 Subject: [PATCH 260/405] chore(ecies): expose ECIESCodec for fuzzing (#9182) --- crates/net/ecies/src/codec.rs | 11 +++++++++-- crates/net/ecies/src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/net/ecies/src/codec.rs b/crates/net/ecies/src/codec.rs index 54250e10210e..c3e9b8d58cc9 100644 --- a/crates/net/ecies/src/codec.rs +++ b/crates/net/ecies/src/codec.rs @@ -1,3 +1,5 @@ +//! This contains the main codec for `RLPx` ECIES messages + use crate::{algorithm::ECIES, ECIESError, EgressECIESValue, IngressECIESValue}; use alloy_primitives::{bytes::BytesMut, B512 as PeerId}; use secp256k1::SecretKey; @@ -7,14 +9,14 @@ use tracing::{instrument, trace}; /// Tokio codec for ECIES #[derive(Debug)] -pub(crate) struct ECIESCodec { +pub struct ECIESCodec { ecies: ECIES, state: ECIESState, } /// Current ECIES state of a connection #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum ECIESState { +pub enum ECIESState { /// The first stage of the ECIES handshake, where each side of the connection sends an auth /// message containing the ephemeral public key, signature of the public key, nonce, and other /// metadata. @@ -23,7 +25,12 @@ enum ECIESState { /// The second stage of the ECIES handshake, where each side of the connection sends an ack /// message containing the nonce and other metadata. Ack, + + /// The third stage of the ECIES handshake, where header is parsed, message integrity checks + /// performed, and message is decrypted. Header, + + /// The final stage, where the ECIES message is actually read and returned by the ECIES codec. Body, } diff --git a/crates/net/ecies/src/lib.rs b/crates/net/ecies/src/lib.rs index 378398d6ba30..f766b48b21cb 100644 --- a/crates/net/ecies/src/lib.rs +++ b/crates/net/ecies/src/lib.rs @@ -16,7 +16,7 @@ pub mod util; mod error; pub use error::ECIESError; -mod codec; +pub mod codec; use alloy_primitives::{ bytes::{Bytes, BytesMut}, From f4689f35b2013da9c48190ca3ef738e82ac7c67d Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Sat, 29 Jun 2024 01:20:27 -0400 Subject: [PATCH 261/405] chore: update audit doc to v2 (#9177) --- README.md | 2 +- ..._audit_v1.pdf => sigma_prime_audit_v2.pdf} | Bin 647595 -> 668280 bytes book/intro.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename audit/{sigma_prime_audit_v1.pdf => sigma_prime_audit_v2.pdf} (51%) diff --git a/README.md b/README.md index 3b845c951b35..ad98ba8529f3 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Reth is production ready, and suitable for usage in mission-critical environment More historical context below: * We released 1.0 "production-ready" stable Reth in June 2024. - * Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v1.pdf). + * Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v2.pdf). * Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. * We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3rd 2024 the last beta release. * We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4th 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives. diff --git a/audit/sigma_prime_audit_v1.pdf b/audit/sigma_prime_audit_v2.pdf similarity index 51% rename from audit/sigma_prime_audit_v1.pdf rename to audit/sigma_prime_audit_v2.pdf index 4b31fb2b44482344e79aa6513e465912135b2a74..50da37d23f461d6aad63c6bead35acc6e6178c46 100644 GIT binary patch delta 341457 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 cyQ!i_@% delta 321215 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≺>8_S8wBjvqRTJSR8P!UXnJA@43N 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

*R2!r#bRC!PAtbrw^bq0!!4<;-l}`K~!_2 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�%gfJ69P>tIt{FxhTelP;Qe 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 XGPQ&NbhsiSqT+%8Ha4J^GT{FK<)1tA diff --git a/book/intro.md b/book/intro.md index 9b57849eb53e..f945d694e6ce 100644 --- a/book/intro.md +++ b/book/intro.md @@ -76,7 +76,7 @@ Reth implements the specification of Ethereum as defined in the [ethereum/execut 1. We operate multiple nodes at the tip of Ethereum mainnet and various testnets. 1. We extensively unit test, fuzz test and document all our code, while also restricting PRs with aggressive lint rules. -We have completed an audit of the [Reth v1.0.0-rc.2](https://github.com/paradigmxyz/reth/releases/tag/v1.0.0-rc.2) with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/audit/sigma_prime_audit_v1.pdf). +We have completed an audit of the [Reth v1.0.0-rc.2](https://github.com/paradigmxyz/reth/releases/tag/v1.0.0-rc.2) with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](https://github.com/paradigmxyz/reth/blob/main/audit/sigma_prime_audit_v2.pdf). [Revm](https://github.com/bluealloy/revm) (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. From 57c4f7e570c7fac06929c7bc035ae3e3df87ea81 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 29 Jun 2024 10:32:58 +0200 Subject: [PATCH 262/405] chore: rm leftover mods (#9188) --- .../rpc/rpc/src/eth/helpers/blocking_task.rs | 55 --- crates/rpc/rpc/src/eth/helpers/fee.rs | 346 ------------------ 2 files changed, 401 deletions(-) delete mode 100644 crates/rpc/rpc/src/eth/helpers/blocking_task.rs delete mode 100644 crates/rpc/rpc/src/eth/helpers/fee.rs diff --git a/crates/rpc/rpc/src/eth/helpers/blocking_task.rs b/crates/rpc/rpc/src/eth/helpers/blocking_task.rs deleted file mode 100644 index 1dae8dc6bb1e..000000000000 --- a/crates/rpc/rpc/src/eth/helpers/blocking_task.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Spawns a blocking task. CPU heavy tasks are executed with the `rayon` library. IO heavy tasks -//! are executed on the `tokio` runtime. - -use futures::Future; -use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; -use tokio::sync::oneshot; - -use crate::{EthApiError, EthResult}; - -/// Executes code on a blocking thread. -pub trait SpawnBlocking: Clone + Send + Sync + 'static { - /// Returns a handle for spawning IO heavy blocking tasks. - /// - /// Runtime access in default trait method implementations. - fn io_task_spawner(&self) -> impl TaskSpawner; - - /// Returns a handle for spawning CPU heavy blocking tasks. - /// - /// Thread pool access in default trait method implementations. - fn tracing_task_pool(&self) -> &BlockingTaskPool; - - /// Executes the future on a new blocking task. - /// - /// Note: This is expected for futures that are dominated by blocking IO operations, for tracing - /// or CPU bound operations in general use [`spawn_tracing`](Self::spawn_tracing). - fn spawn_blocking_io(&self, f: F) -> impl Future> + Send - where - F: FnOnce(Self) -> EthResult + Send + 'static, - R: Send + 'static, - { - let (tx, rx) = oneshot::channel(); - let this = self.clone(); - self.io_task_spawner().spawn_blocking(Box::pin(async move { - let res = async move { f(this) }.await; - let _ = tx.send(res); - })); - - async move { rx.await.map_err(|_| EthApiError::InternalEthError)? } - } - - /// Executes a blocking task on the tracing pool. - /// - /// Note: This is expected for futures that are predominantly CPU bound, as it uses `rayon` - /// under the hood, for blocking IO futures use [`spawn_blocking`](Self::spawn_blocking_io). See - /// . - fn spawn_tracing(&self, f: F) -> impl Future> + Send - where - F: FnOnce(Self) -> EthResult + Send + 'static, - R: Send + 'static, - { - let this = self.clone(); - let fut = self.tracing_task_pool().spawn(move || f(this)); - async move { fut.await.map_err(|_| EthApiError::InternalBlockingTaskError)? } - } -} diff --git a/crates/rpc/rpc/src/eth/helpers/fee.rs b/crates/rpc/rpc/src/eth/helpers/fee.rs deleted file mode 100644 index 7d795e52f38b..000000000000 --- a/crates/rpc/rpc/src/eth/helpers/fee.rs +++ /dev/null @@ -1,346 +0,0 @@ -//! Loads fee history from database. Helper trait for `eth_` fee and transaction RPC methods. - -use futures::Future; -use reth_primitives::U256; -use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider}; -use reth_rpc_types::{BlockNumberOrTag, FeeHistory}; -use tracing::debug; - -use crate::{ - fee_history::calculate_reward_percentiles_for_block, api::LoadBlock, EthApiError, - EthResult, EthStateCache, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, - RpcInvalidTransactionError, -}; - -/// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the -/// `eth_` namespace. -pub trait EthFees: LoadFee { - /// Returns a suggestion for a gas price for legacy transactions. - /// - /// See also: - fn gas_price(&self) -> impl Future> + Send - where - Self: LoadBlock, - { - LoadFee::gas_price(self) - } - - /// Returns a suggestion for a base fee for blob transactions. - fn blob_base_fee(&self) -> impl Future> + Send - where - Self: LoadBlock, - { - LoadFee::blob_base_fee(self) - } - - /// Returns a suggestion for the priority fee (the tip) - fn suggested_priority_fee(&self) -> impl Future> + Send - where - Self: 'static, - { - LoadFee::suggested_priority_fee(self) - } - - /// Reports the fee history, for the given amount of blocks, up until the given newest block. - /// - /// If `reward_percentiles` are provided the [`FeeHistory`] will include the _approximated_ - /// rewards for the requested range. - fn fee_history( - &self, - mut block_count: u64, - newest_block: BlockNumberOrTag, - reward_percentiles: Option>, - ) -> impl Future> + Send { - async move { - if block_count == 0 { - return Ok(FeeHistory::default()) - } - - // See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225 - let max_fee_history = if reward_percentiles.is_none() { - self.gas_oracle().config().max_header_history - } else { - self.gas_oracle().config().max_block_history - }; - - if block_count > max_fee_history { - debug!( - requested = block_count, - truncated = max_fee_history, - "Sanitizing fee history block count" - ); - block_count = max_fee_history - } - - let Some(end_block) = - LoadFee::provider(self).block_number_for_id(newest_block.into())? - else { - return Err(EthApiError::UnknownBlockNumber) - }; - - // need to add 1 to the end block to get the correct (inclusive) range - let end_block_plus = end_block + 1; - // Ensure that we would not be querying outside of genesis - if end_block_plus < block_count { - block_count = end_block_plus; - } - - // If reward percentiles were specified, we - // need to validate that they are monotonically - // increasing and 0 <= p <= 100 - // Note: The types used ensure that the percentiles are never < 0 - if let Some(percentiles) = &reward_percentiles { - if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) { - return Err(EthApiError::InvalidRewardPercentiles) - } - } - - // Fetch the headers and ensure we got all of them - // - // Treat a request for 1 block as a request for `newest_block..=newest_block`, - // otherwise `newest_block - 2 - // NOTE: We ensured that block count is capped - let start_block = end_block_plus - block_count; - - // Collect base fees, gas usage ratios and (optionally) reward percentile data - let mut base_fee_per_gas: Vec = Vec::new(); - let mut gas_used_ratio: Vec = Vec::new(); - - let mut base_fee_per_blob_gas: Vec = Vec::new(); - let mut blob_gas_used_ratio: Vec = Vec::new(); - - let mut rewards: Vec> = Vec::new(); - - // Check if the requested range is within the cache bounds - let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await; - - if let Some(fee_entries) = fee_entries { - if fee_entries.len() != block_count as usize { - return Err(EthApiError::InvalidBlockRange) - } - - for entry in &fee_entries { - base_fee_per_gas.push(entry.base_fee_per_gas as u128); - gas_used_ratio.push(entry.gas_used_ratio); - base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default()); - blob_gas_used_ratio.push(entry.blob_gas_used_ratio); - - if let Some(percentiles) = &reward_percentiles { - let mut block_rewards = Vec::with_capacity(percentiles.len()); - for &percentile in percentiles { - block_rewards.push(self.approximate_percentile(entry, percentile)); - } - rewards.push(block_rewards); - } - } - let last_entry = fee_entries.last().expect("is not empty"); - - // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the - // next block - base_fee_per_gas - .push(last_entry.next_block_base_fee(&LoadFee::provider(self).chain_spec()) - as u128); - - base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); - } else { - // read the requested header range - let headers = LoadFee::provider(self).sealed_headers_range(start_block..=end_block)?; - if headers.len() != block_count as usize { - return Err(EthApiError::InvalidBlockRange) - } - - for header in &headers { - base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default() as u128); - gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64); - base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default()); - blob_gas_used_ratio.push( - header.blob_gas_used.unwrap_or_default() as f64 / - reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64, - ); - - // Percentiles were specified, so we need to collect reward percentile ino - if let Some(percentiles) = &reward_percentiles { - let (transactions, receipts) = LoadFee::cache(self) - .get_transactions_and_receipts(header.hash()) - .await? - .ok_or(EthApiError::InvalidBlockRange)?; - rewards.push( - calculate_reward_percentiles_for_block( - percentiles, - header.gas_used, - header.base_fee_per_gas.unwrap_or_default(), - &transactions, - &receipts, - ) - .unwrap_or_default(), - ); - } - } - - // The spec states that `base_fee_per_gas` "[..] includes the next block after the - // newest of the returned range, because this value can be derived from the - // newest block" - // - // The unwrap is safe since we checked earlier that we got at least 1 header. - let last_header = headers.last().expect("is present"); - base_fee_per_gas.push( - LoadFee::provider(self).chain_spec().base_fee_params_at_timestamp(last_header.timestamp).next_block_base_fee( - last_header.gas_used as u128, - last_header.gas_limit as u128, - last_header.base_fee_per_gas.unwrap_or_default() as u128, - )); - - // Same goes for the `base_fee_per_blob_gas`: - // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. - base_fee_per_blob_gas - .push(last_header.next_block_blob_fee().unwrap_or_default()); - }; - - Ok(FeeHistory { - base_fee_per_gas, - gas_used_ratio, - base_fee_per_blob_gas, - blob_gas_used_ratio, - oldest_block: start_block, - reward: reward_percentiles.map(|_| rewards), - }) - } - } - - /// Approximates reward at a given percentile for a specific block - /// Based on the configured resolution - fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 { - let resolution = self.fee_history_cache().resolution(); - let rounded_percentile = - (requested_percentile * resolution as f64).round() / resolution as f64; - let clamped_percentile = rounded_percentile.clamp(0.0, 100.0); - - // Calculate the index in the precomputed rewards array - let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize; - // Fetch the reward from the FeeHistoryEntry - entry.rewards.get(index).cloned().unwrap_or_default() - } -} - -/// Loads fee from database. -/// -/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` fees RPC methods. -pub trait LoadFee: LoadBlock { - // Returns a handle for reading data from disk. - /// - /// Data access in default (L1) trait method implementations. - fn provider(&self) -> impl BlockIdReader + HeaderProvider + ChainSpecProvider; - - /// Returns a handle for reading data from memory. - /// - /// Data access in default (L1) trait method implementations. - fn cache(&self) -> &EthStateCache; - - /// Returns a handle for reading gas price. - /// - /// Data access in default (L1) trait method implementations. - fn gas_oracle(&self) -> &GasPriceOracle; - - /// Returns a handle for reading fee history data from memory. - /// - /// Data access in default (L1) trait method implementations. - fn fee_history_cache(&self) -> &FeeHistoryCache; - - /// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy - /// transactions. - fn legacy_gas_price( - &self, - gas_price: Option, - ) -> impl Future> + Send { - async move { - match gas_price { - Some(gas_price) => Ok(gas_price), - None => { - // fetch a suggested gas price - self.gas_price().await - } - } - } - } - - /// Returns the EIP-1559 fees if they are set, otherwise fetches a suggested gas price for - /// EIP-1559 transactions. - /// - /// Returns (`max_fee`, `priority_fee`) - fn eip1559_fees( - &self, - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - ) -> impl Future> + Send { - async move { - let max_fee_per_gas = match max_fee_per_gas { - Some(max_fee_per_gas) => max_fee_per_gas, - None => { - // fetch pending base fee - let base_fee = self - .block(BlockNumberOrTag::Pending) - .await? - .ok_or(EthApiError::UnknownBlockNumber)? - .base_fee_per_gas - .ok_or_else(|| { - EthApiError::InvalidTransaction( - RpcInvalidTransactionError::TxTypeNotSupported, - ) - })?; - U256::from(base_fee) - } - }; - - let max_priority_fee_per_gas = match max_priority_fee_per_gas { - Some(max_priority_fee_per_gas) => max_priority_fee_per_gas, - None => self.suggested_priority_fee().await?, - }; - Ok((max_fee_per_gas, max_priority_fee_per_gas)) - } - } - - /// Returns the EIP-4844 blob fee if it is set, otherwise fetches a blob fee. - fn eip4844_blob_fee( - &self, - blob_fee: Option, - ) -> impl Future> + Send { - async move { - match blob_fee { - Some(blob_fee) => Ok(blob_fee), - None => self.blob_base_fee().await, - } - } - } - - /// Returns a suggestion for a gas price for legacy transactions. - /// - /// See also: - fn gas_price(&self) -> impl Future> + Send { - let header = self.block(BlockNumberOrTag::Latest); - let suggested_tip = self.suggested_priority_fee(); - async move { - let (header, suggested_tip) = futures::try_join!(header, suggested_tip)?; - let base_fee = header.and_then(|h| h.base_fee_per_gas).unwrap_or_default(); - Ok(suggested_tip + U256::from(base_fee)) - } - } - - /// Returns a suggestion for a base fee for blob transactions. - fn blob_base_fee(&self) -> impl Future> + Send { - async move { - self.block(BlockNumberOrTag::Latest) - .await? - .and_then(|h: reth_primitives::SealedBlock| h.next_block_blob_fee()) - .ok_or(EthApiError::ExcessBlobGasNotSet) - .map(U256::from) - } - } - - /// Returns a suggestion for the priority fee (the tip) - fn suggested_priority_fee(&self) -> impl Future> + Send - where - Self: 'static, - { - self.gas_oracle().suggest_tip_cap() - } -} From b93e70c429a804fc7ba4bbf24107b4e3146a871a Mon Sep 17 00:00:00 2001 From: Delweng Date: Sat, 29 Jun 2024 17:02:09 +0800 Subject: [PATCH 263/405] feat(net/peer): add peer with udp socket (#9156) Signed-off-by: jsvisa --- crates/net/network-api/src/lib.rs | 30 +++-- crates/net/network-api/src/noop.rs | 9 +- crates/net/network/src/discovery.rs | 21 ++-- crates/net/network/src/manager.rs | 4 +- crates/net/network/src/network.rs | 23 +++- crates/net/network/src/peers.rs | 189 ++++++++++++++++++++-------- crates/net/network/src/state.rs | 14 +-- crates/net/network/src/swarm.rs | 4 +- crates/net/peers/src/node_record.rs | 11 ++ crates/rpc/rpc/src/admin.rs | 4 +- 10 files changed, 221 insertions(+), 88 deletions(-) diff --git a/crates/net/network-api/src/lib.rs b/crates/net/network-api/src/lib.rs index 6c6f8036daff..895fbc089234 100644 --- a/crates/net/network-api/src/lib.rs +++ b/crates/net/network-api/src/lib.rs @@ -66,9 +66,14 @@ pub trait PeersInfo: Send + Sync { /// Provides an API for managing the peers of the network. pub trait Peers: PeersInfo { - /// Adds a peer to the peer set. - fn add_peer(&self, peer: PeerId, addr: SocketAddr) { - self.add_peer_kind(peer, PeerKind::Basic, addr); + /// Adds a peer to the peer set with UDP `SocketAddr`. + fn add_peer(&self, peer: PeerId, tcp_addr: SocketAddr) { + self.add_peer_kind(peer, PeerKind::Basic, tcp_addr, None); + } + + /// Adds a peer to the peer set with TCP and UDP `SocketAddr`. + fn add_peer_with_udp(&self, peer: PeerId, tcp_addr: SocketAddr, udp_addr: SocketAddr) { + self.add_peer_kind(peer, PeerKind::Basic, tcp_addr, Some(udp_addr)); } /// Adds a trusted [`PeerId`] to the peer set. @@ -76,13 +81,24 @@ pub trait Peers: PeersInfo { /// This allows marking a peer as trusted without having to know the peer's address. fn add_trusted_peer_id(&self, peer: PeerId); - /// Adds a trusted peer to the peer set. - fn add_trusted_peer(&self, peer: PeerId, addr: SocketAddr) { - self.add_peer_kind(peer, PeerKind::Trusted, addr); + /// Adds a trusted peer to the peer set with UDP `SocketAddr`. + fn add_trusted_peer(&self, peer: PeerId, tcp_addr: SocketAddr) { + self.add_peer_kind(peer, PeerKind::Trusted, tcp_addr, None); + } + + /// Adds a trusted peer with TCP and UDP `SocketAddr` to the peer set. + fn add_trusted_peer_with_udp(&self, peer: PeerId, tcp_addr: SocketAddr, udp_addr: SocketAddr) { + self.add_peer_kind(peer, PeerKind::Trusted, tcp_addr, Some(udp_addr)); } /// Adds a peer to the known peer set, with the given kind. - fn add_peer_kind(&self, peer: PeerId, kind: PeerKind, addr: SocketAddr); + fn add_peer_kind( + &self, + peer: PeerId, + kind: PeerKind, + tcp_addr: SocketAddr, + udp_addr: Option, + ); /// Returns the rpc [`PeerInfo`] for all connected [`PeerKind::Trusted`] peers. fn get_trusted_peers( diff --git a/crates/net/network-api/src/noop.rs b/crates/net/network-api/src/noop.rs index 745613f40655..a74204a3f742 100644 --- a/crates/net/network-api/src/noop.rs +++ b/crates/net/network-api/src/noop.rs @@ -71,7 +71,14 @@ impl PeersInfo for NoopNetwork { impl Peers for NoopNetwork { fn add_trusted_peer_id(&self, _peer: PeerId) {} - fn add_peer_kind(&self, _peer: PeerId, _kind: PeerKind, _addr: SocketAddr) {} + fn add_peer_kind( + &self, + _peer: PeerId, + _kind: PeerKind, + _tcp_addr: SocketAddr, + _udp_addr: Option, + ) { + } async fn get_peers_by_kind(&self, _kind: PeerKind) -> Result, NetworkError> { Ok(vec![]) diff --git a/crates/net/network/src/discovery.rs b/crates/net/network/src/discovery.rs index c320c7fe1575..2e51a6b71cb4 100644 --- a/crates/net/network/src/discovery.rs +++ b/crates/net/network/src/discovery.rs @@ -4,6 +4,7 @@ use crate::{ cache::LruMap, error::{NetworkError, ServiceKind}, manager::DiscoveredEvent, + peers::PeerAddr, }; use enr::Enr; use futures::StreamExt; @@ -40,7 +41,7 @@ pub struct Discovery { /// All nodes discovered via discovery protocol. /// /// These nodes can be ephemeral and are updated via the discovery protocol. - discovered_nodes: LruMap, + discovered_nodes: LruMap, /// Local ENR of the discovery v4 service (discv5 ENR has same [`PeerId`]). local_enr: NodeRecord, /// Handler to interact with the Discovery v4 service @@ -204,12 +205,14 @@ impl Discovery { /// Processes an incoming [`NodeRecord`] update from a discovery service fn on_node_record_update(&mut self, record: NodeRecord, fork_id: Option) { - let id = record.id; - let addr = record.tcp_addr(); + let peer_id = record.id; + let tcp_addr = record.tcp_addr(); + let udp_addr = record.udp_addr(); + let addr = PeerAddr::new(tcp_addr, Some(udp_addr)); _ = - self.discovered_nodes.get_or_insert(id, || { + self.discovered_nodes.get_or_insert(peer_id, || { self.queued_events.push_back(DiscoveryEvent::NewNode( - DiscoveredEvent::EventQueued { peer_id: id, socket_addr: addr, fork_id }, + DiscoveredEvent::EventQueued { peer_id, addr, fork_id }, )); addr @@ -224,8 +227,8 @@ impl Discovery { DiscoveryUpdate::EnrForkId(node, fork_id) => { self.queued_events.push_back(DiscoveryEvent::EnrForkId(node.id, fork_id)) } - DiscoveryUpdate::Removed(node) => { - self.discovered_nodes.remove(&node); + DiscoveryUpdate::Removed(peer_id) => { + self.discovered_nodes.remove(&peer_id); } DiscoveryUpdate::Batch(updates) => { for update in updates { @@ -427,7 +430,7 @@ mod tests { assert_eq!( DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id: discv4_id_2, - socket_addr: discv4_enr_2.tcp_addr(), + addr: PeerAddr::new(discv4_enr_2.tcp_addr(), Some(discv4_enr_2.udp_addr())), fork_id: None }), event_node_1 @@ -435,7 +438,7 @@ mod tests { assert_eq!( DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id: discv4_id_1, - socket_addr: discv4_enr_1.tcp_addr(), + addr: PeerAddr::new(discv4_enr_1.tcp_addr(), Some(discv4_enr_1.udp_addr())), fork_id: None }), event_node_2 diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index c957738e0ce3..1093bc8c50e5 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -26,7 +26,7 @@ use crate::{ message::{NewBlockMessage, PeerMessage, PeerRequest, PeerRequestSender}, metrics::{DisconnectMetrics, NetworkMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE}, network::{NetworkHandle, NetworkHandleMessage}, - peers::{PeersHandle, PeersManager}, + peers::{PeerAddr, PeersHandle, PeersManager}, poll_nested_stream_with_budget, protocol::IntoRlpxSubProtocol, session::SessionManager, @@ -1030,7 +1030,7 @@ pub enum NetworkEvent { #[derive(Debug, Clone, PartialEq, Eq)] pub enum DiscoveredEvent { - EventQueued { peer_id: PeerId, socket_addr: SocketAddr, fork_id: Option }, + EventQueued { peer_id: PeerId, addr: PeerAddr, fork_id: Option }, } #[derive(Debug, Default)] diff --git a/crates/net/network/src/network.rs b/crates/net/network/src/network.rs index 632a6028795a..ab092a164406 100644 --- a/crates/net/network/src/network.rs +++ b/crates/net/network/src/network.rs @@ -1,7 +1,13 @@ use crate::{ - config::NetworkMode, discovery::DiscoveryEvent, manager::NetworkEvent, message::PeerRequest, - peers::PeersHandle, protocol::RlpxSubProtocol, swarm::NetworkConnectionState, - transactions::TransactionsHandle, FetchClient, + config::NetworkMode, + discovery::DiscoveryEvent, + manager::NetworkEvent, + message::PeerRequest, + peers::{PeerAddr, PeersHandle}, + protocol::RlpxSubProtocol, + swarm::NetworkConnectionState, + transactions::TransactionsHandle, + FetchClient, }; use enr::Enr; use parking_lot::Mutex; @@ -257,7 +263,14 @@ impl Peers for NetworkHandle { /// Sends a message to the [`NetworkManager`](crate::NetworkManager) to add a peer to the known /// set, with the given kind. - fn add_peer_kind(&self, peer: PeerId, kind: PeerKind, addr: SocketAddr) { + fn add_peer_kind( + &self, + peer: PeerId, + kind: PeerKind, + tcp_addr: SocketAddr, + udp_addr: Option, + ) { + let addr = PeerAddr::new(tcp_addr, udp_addr); self.send_message(NetworkHandleMessage::AddPeerAddress(peer, kind, addr)); } @@ -420,7 +433,7 @@ pub(crate) enum NetworkHandleMessage { /// Marks a peer as trusted. AddTrustedPeerId(PeerId), /// Adds an address for a peer, including its ID, kind, and socket address. - AddPeerAddress(PeerId, PeerKind, SocketAddr), + AddPeerAddress(PeerId, PeerKind, PeerAddr), /// Removes a peer from the peerset corresponding to the given kind. RemovePeer(PeerId, PeerKind), /// Disconnects a connection to a peer if it exists, optionally providing a disconnect reason. diff --git a/crates/net/network/src/peers.rs b/crates/net/network/src/peers.rs index 03082e19b4ee..ca80faf5c1bb 100644 --- a/crates/net/network/src/peers.rs +++ b/crates/net/network/src/peers.rs @@ -155,13 +155,17 @@ impl PeersManager { let mut peers = HashMap::with_capacity(trusted_nodes.len() + basic_nodes.len()); let mut trusted_peer_ids = HashSet::with_capacity(trusted_nodes.len()); - for NodeRecord { address, tcp_port, udp_port: _, id } in trusted_nodes { + for NodeRecord { address, tcp_port, udp_port, id } in trusted_nodes { trusted_peer_ids.insert(id); - peers.entry(id).or_insert_with(|| Peer::trusted(SocketAddr::from((address, tcp_port)))); + peers.entry(id).or_insert_with(|| { + Peer::trusted(PeerAddr::new_with_ports(address, tcp_port, Some(udp_port))) + }); } - for NodeRecord { address, tcp_port, udp_port: _, id } in basic_nodes { - peers.entry(id).or_insert_with(|| Peer::new(SocketAddr::from((address, tcp_port)))); + for NodeRecord { address, tcp_port, udp_port, id } in basic_nodes { + peers.entry(id).or_insert_with(|| { + Peer::new(PeerAddr::new_with_ports(address, tcp_port, Some(udp_port))) + }); } Self { @@ -198,7 +202,27 @@ impl PeersManager { /// Returns an iterator over all peers pub(crate) fn iter_peers(&self) -> impl Iterator + '_ { - self.peers.iter().map(|(peer_id, v)| NodeRecord::new(v.addr, *peer_id)) + self.peers.iter().map(|(peer_id, v)| { + NodeRecord::new_with_ports( + v.addr.tcp.ip(), + v.addr.tcp.port(), + v.addr.udp.map(|addr| addr.port()), + *peer_id, + ) + }) + } + + /// Returns the [`NodeRecord`] for the given peer id + #[allow(dead_code)] + fn peer_by_id(&self, peer_id: PeerId) -> Option { + self.peers.get(&peer_id).map(|v| { + NodeRecord::new_with_ports( + v.addr.tcp.ip(), + v.addr.tcp.port(), + v.addr.udp.map(|addr| addr.port()), + peer_id, + ) + }) } /// Returns an iterator over all peer ids for peers with the given kind @@ -329,7 +353,7 @@ impl PeersManager { Entry::Vacant(entry) => { // peer is missing in the table, we add it but mark it as to be removed after // disconnect, because we only know the outgoing port - let mut peer = Peer::with_state(addr, PeerConnectionState::In); + let mut peer = Peer::with_state(PeerAddr::tcp(addr), PeerConnectionState::In); peer.remove_after_disconnect = true; entry.insert(peer); self.queued_actions.push_back(PeerAction::PeerAdded(peer_id)); @@ -654,7 +678,7 @@ impl PeersManager { /// Called for a newly discovered peer. /// /// If the peer already exists, then the address, kind and `fork_id` will be updated. - pub(crate) fn add_peer(&mut self, peer_id: PeerId, addr: SocketAddr, fork_id: Option) { + pub(crate) fn add_peer(&mut self, peer_id: PeerId, addr: PeerAddr, fork_id: Option) { self.add_peer_kind(peer_id, PeerKind::Basic, addr, fork_id) } @@ -667,7 +691,7 @@ impl PeersManager { /// /// If the peer already exists, then the address and kind will be updated. #[allow(dead_code)] - pub(crate) fn add_trusted_peer(&mut self, peer_id: PeerId, addr: SocketAddr) { + pub(crate) fn add_trusted_peer(&mut self, peer_id: PeerId, addr: PeerAddr) { self.add_peer_kind(peer_id, PeerKind::Trusted, addr, None) } @@ -678,10 +702,10 @@ impl PeersManager { &mut self, peer_id: PeerId, kind: PeerKind, - addr: SocketAddr, + addr: PeerAddr, fork_id: Option, ) { - if self.ban_list.is_banned(&peer_id, &addr.ip()) { + if self.ban_list.is_banned(&peer_id, &addr.tcp.ip()) { return } @@ -700,7 +724,7 @@ impl PeersManager { } } Entry::Vacant(entry) => { - trace!(target: "net::peers", ?peer_id, ?addr, "discovered new node"); + trace!(target: "net::peers", ?peer_id, ?addr.tcp, "discovered new node"); let mut peer = Peer::with_kind(addr, kind); peer.fork_id = fork_id; entry.insert(peer); @@ -804,7 +828,7 @@ impl PeersManager { return } - // as long as there a slots available fill them with the best peers + // as long as there are slots available fill them with the best peers while self.connection_info.has_out_capacity() { let action = { let (peer_id, peer) = match self.best_unconnected() { @@ -815,7 +839,7 @@ impl PeersManager { trace!(target: "net::peers", ?peer_id, addr=?peer.addr, "schedule outbound connection"); peer.state = PeerConnectionState::PendingOut; - PeerAction::Connect { peer_id, remote_addr: peer.addr } + PeerAction::Connect { peer_id, remote_addr: peer.addr.tcp } }; self.connection_info.inc_pending_out(); @@ -853,7 +877,7 @@ impl PeersManager { while let Poll::Ready(Some(cmd)) = self.handle_rx.poll_next_unpin(cx) { match cmd { PeerCommand::Add(peer_id, addr) => { - self.add_peer(peer_id, addr, None); + self.add_peer(peer_id, PeerAddr::tcp(addr), None); } PeerCommand::Remove(peer) => self.remove_peer(peer), PeerCommand::ReputationChange(peer_id, rep) => { @@ -985,11 +1009,43 @@ impl ConnectionInfo { } } +/// Represents a peer's address information. +/// +/// # Fields +/// +/// - `tcp`: A `SocketAddr` representing the peer's data transfer address. +/// - `udp`: An optional `SocketAddr` representing the peer's discover address. `None` if the peer +/// is directly connecting to us or the port is the same to `tcp`'s +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct PeerAddr { + tcp: SocketAddr, + udp: Option, +} + +impl PeerAddr { + /// Returns a new `PeerAddr` with the given `tcp` and `udp` addresses. + pub const fn new(tcp: SocketAddr, udp: Option) -> Self { + Self { tcp, udp } + } + + /// Returns a new `PeerAddr` with a `tcp` address only. + pub const fn tcp(tcp: SocketAddr) -> Self { + Self { tcp, udp: None } + } + + /// Returns a new `PeerAddr` with the given `tcp` and `udp` ports. + fn new_with_ports(ip: IpAddr, tcp_port: u16, udp_port: Option) -> Self { + let tcp = SocketAddr::new(ip, tcp_port); + let udp = udp_port.map(|port| SocketAddr::new(ip, port)); + Self::new(tcp, udp) + } +} + /// Tracks info about a single peer. #[derive(Debug, Clone)] pub struct Peer { - /// Where to reach the peer - addr: SocketAddr, + /// Where to reach the peer. + addr: PeerAddr, /// Reputation of the peer. reputation: i32, /// The state of the connection, if any. @@ -1010,11 +1066,11 @@ pub struct Peer { // === impl Peer === impl Peer { - fn new(addr: SocketAddr) -> Self { + fn new(addr: PeerAddr) -> Self { Self::with_state(addr, Default::default()) } - fn trusted(addr: SocketAddr) -> Self { + fn trusted(addr: PeerAddr) -> Self { Self { kind: PeerKind::Trusted, ..Self::new(addr) } } @@ -1023,7 +1079,7 @@ impl Peer { self.reputation } - fn with_state(addr: SocketAddr, state: PeerConnectionState) -> Self { + fn with_state(addr: PeerAddr, state: PeerConnectionState) -> Self { Self { addr, state, @@ -1036,7 +1092,7 @@ impl Peer { } } - fn with_kind(addr: SocketAddr, kind: PeerKind) -> Self { + fn with_kind(addr: PeerAddr, kind: PeerKind) -> Self { Self { kind, ..Self::new(addr) } } @@ -1257,7 +1313,7 @@ mod tests { use super::PeersManager; use crate::{ peers::{ - ConnectionInfo, InboundConnectionError, PeerAction, PeerBackoffDurations, + ConnectionInfo, InboundConnectionError, PeerAction, PeerAddr, PeerBackoffDurations, PeerConnectionState, }, session::PendingSessionHandshakeError, @@ -1306,7 +1362,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -1321,6 +1377,37 @@ mod tests { } _ => unreachable!(), } + + let record = peers.peer_by_id(peer).unwrap(); + assert_eq!(record.tcp_addr(), socket_addr); + assert_eq!(record.udp_addr(), socket_addr); + } + + #[tokio::test] + async fn test_insert_udp() { + let peer = PeerId::random(); + let tcp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); + let udp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8009); + let mut peers = PeersManager::default(); + peers.add_peer(peer, PeerAddr::new(tcp_addr, Some(udp_addr)), None); + + match event!(peers) { + PeerAction::PeerAdded(peer_id) => { + assert_eq!(peer_id, peer); + } + _ => unreachable!(), + } + match event!(peers) { + PeerAction::Connect { peer_id, remote_addr } => { + assert_eq!(peer_id, peer); + assert_eq!(remote_addr, tcp_addr); + } + _ => unreachable!(), + } + + let record = peers.peer_by_id(peer).unwrap(); + assert_eq!(record.tcp_addr(), tcp_addr); + assert_eq!(record.udp_addr(), udp_addr); } #[tokio::test] @@ -1329,7 +1416,7 @@ mod tests { let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); peers.ban_peer(peer); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::BanPeer { peer_id } => { @@ -1351,7 +1438,7 @@ mod tests { let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); peers.ban_peer(peer); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::BanPeer { peer_id } => { @@ -1388,7 +1475,7 @@ mod tests { let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::new(PeersConfig::test()); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -1447,7 +1534,7 @@ mod tests { let backoff_durations = PeerBackoffDurations::test(); let config = PeersConfig { backoff_durations, ..PeersConfig::test() }; let mut peers = PeersManager::new(config); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -1504,7 +1591,7 @@ mod tests { let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let config = PeersConfig::test(); let mut peers = PeersManager::new(config); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); let peer_struct = peers.peers.get_mut(&peer).unwrap(); let backoff_timestamp = peers @@ -1521,7 +1608,7 @@ mod tests { let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let config = PeersConfig::default(); let mut peers = PeersManager::new(config); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); let peer_struct = peers.peers.get_mut(&peer).unwrap(); // Simulate a peer that was already backed off once @@ -1549,7 +1636,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -1606,7 +1693,7 @@ mod tests { let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let config = PeersConfig::test(); let mut peers = PeersManager::new(config.clone()); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); let peer_struct = peers.peers.get_mut(&peer).unwrap(); // Simulate a peer that was already backed off once @@ -1660,7 +1747,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -1772,7 +1859,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -1890,7 +1977,7 @@ mod tests { // to increase by 1 peers.on_incoming_session_established(peer, socket_addr); let p = peers.peers.get_mut(&peer).expect("peer not found"); - assert_eq!(p.addr, socket_addr); + assert_eq!(p.addr.tcp, socket_addr); assert_eq!(peers.connection_info.num_pending_in, 0); assert_eq!(peers.connection_info.num_inbound, 1); @@ -1905,7 +1992,7 @@ mod tests { peers.on_already_connected(Direction::Incoming); let p = peers.peers.get_mut(&peer).expect("peer not found"); - assert_eq!(p.addr, socket_addr); + assert_eq!(p.addr.tcp, socket_addr); assert_eq!(peers.connection_info.num_pending_in, 0); assert_eq!(peers.connection_info.num_inbound, 1); } @@ -1915,7 +2002,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_trusted_peer(peer, socket_addr); + peers.add_trusted_peer(peer, PeerAddr::tcp(socket_addr)); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -1967,7 +2054,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); assert_eq!(peers.get_reputation(&peer), Some(0)); peers.apply_reputation_change(&peer, ReputationChangeKind::Other(1024)); @@ -1982,7 +2069,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -2019,7 +2106,7 @@ mod tests { let p = peers.peers.get(&peer).unwrap(); assert_eq!(p.state, PeerConnectionState::PendingOut); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); let p = peers.peers.get(&peer).unwrap(); assert_eq!(p.state, PeerConnectionState::PendingOut); @@ -2032,7 +2119,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -2067,7 +2154,7 @@ mod tests { let peer = PeerId::random(); let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8008); let mut peers = PeersManager::default(); - peers.add_peer(peer, socket_addr, None); + peers.add_peer(peer, PeerAddr::tcp(socket_addr), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -2101,7 +2188,7 @@ mod tests { let ban_list = BanList::new(HashSet::new(), vec![ip]); let config = PeersConfig::default().with_ban_list(ban_list); let mut peer_manager = PeersManager::new(config); - peer_manager.add_peer(B512::default(), socket_addr, None); + peer_manager.add_peer(B512::default(), PeerAddr::tcp(socket_addr), None); assert!(peer_manager.peers.is_empty()); } @@ -2204,7 +2291,7 @@ mod tests { let basic_peer = PeerId::random(); let basic_sock = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8009); - peers.add_peer(basic_peer, basic_sock, None); + peers.add_peer(basic_peer, PeerAddr::tcp(basic_sock), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -2244,7 +2331,7 @@ mod tests { let basic_peer = PeerId::random(); let basic_sock = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)), 8009); - peers.add_peer(basic_peer, basic_sock, None); + peers.add_peer(basic_peer, PeerAddr::tcp(basic_sock), None); match event!(peers) { PeerAction::PeerAdded(peer_id) => { @@ -2352,7 +2439,7 @@ mod tests { let config = PeersConfig::test(); let mut peer_manager = PeersManager::new(config); let peer_id = PeerId::random(); - peer_manager.add_peer(peer_id, socket_addr, None); + peer_manager.add_peer(peer_id, PeerAddr::tcp(socket_addr), None); tokio::time::sleep(Duration::from_secs(1)).await; peer_manager.tick(); @@ -2407,7 +2494,7 @@ mod tests { assert!(peer.remove_after_disconnect); // trigger discovery manually while the peer is still connected - peers.add_peer(peer_id, addr, None); + peers.add_peer(peer_id, PeerAddr::tcp(addr), None); peers.on_active_session_gracefully_closed(peer_id); @@ -2423,7 +2510,7 @@ mod tests { let mut peers = PeersManager::default(); peers.on_incoming_pending_session(addr.ip()).unwrap(); - peers.add_peer(peer_id, addr, None); + peers.add_peer(peer_id, PeerAddr::tcp(addr), None); match event!(peers) { PeerAction::PeerAdded(_) => {} @@ -2451,7 +2538,7 @@ mod tests { let mut peers = PeersManager::default(); peers.on_incoming_pending_session(addr.ip()).unwrap(); - peers.add_peer(peer_id, addr, None); + peers.add_peer(peer_id, PeerAddr::tcp(addr), None); match event!(peers) { PeerAction::PeerAdded(_) => {} @@ -2482,9 +2569,9 @@ mod tests { let config = PeersConfig::default(); let mut peer_manager = PeersManager::new(config); let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)); - let socket_addr = SocketAddr::new(ip, 8008); + let peer_addr = PeerAddr::tcp(SocketAddr::new(ip, 8008)); for _ in 0..peer_manager.connection_info.config.max_concurrent_outbound_dials * 2 { - peer_manager.add_peer(PeerId::random(), socket_addr, None); + peer_manager.add_peer(PeerId::random(), peer_addr, None); } peer_manager.fill_outbound_slots(); @@ -2501,11 +2588,11 @@ mod tests { let config = PeersConfig::default(); let mut peer_manager = PeersManager::new(config); let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2)); - let socket_addr = SocketAddr::new(ip, 8008); + let peer_addr = PeerAddr::tcp(SocketAddr::new(ip, 8008)); // add more peers than allowed for _ in 0..peer_manager.connection_info.config.max_concurrent_outbound_dials * 2 { - peer_manager.add_peer(PeerId::random(), socket_addr, None); + peer_manager.add_peer(PeerId::random(), peer_addr, None); } for _ in 0..peer_manager.connection_info.config.max_concurrent_outbound_dials * 2 { diff --git a/crates/net/network/src/state.rs b/crates/net/network/src/state.rs index afbf05dde691..7fa5b6d2c08f 100644 --- a/crates/net/network/src/state.rs +++ b/crates/net/network/src/state.rs @@ -9,7 +9,7 @@ use crate::{ BlockRequest, NewBlockMessage, PeerRequest, PeerRequestSender, PeerResponse, PeerResponseResult, }, - peers::{PeerAction, PeersManager}, + peers::{PeerAction, PeerAddr, PeersManager}, FetchClient, }; use rand::seq::SliceRandom; @@ -274,7 +274,7 @@ where } /// Adds a peer and its address with the given kind to the peerset. - pub(crate) fn add_peer_kind(&mut self, peer_id: PeerId, kind: PeerKind, addr: SocketAddr) { + pub(crate) fn add_peer_kind(&mut self, peer_id: PeerId, kind: PeerKind, addr: PeerAddr) { self.peers_manager.add_peer_kind(peer_id, kind, addr, None) } @@ -288,14 +288,10 @@ where /// Event hook for events received from the discovery service. fn on_discovery_event(&mut self, event: DiscoveryEvent) { match event { - DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { - peer_id, - socket_addr, - fork_id, - }) => { + DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, addr, fork_id }) => { self.queued_messages.push_back(StateAction::DiscoveredNode { peer_id, - socket_addr, + addr, fork_id, }); } @@ -516,7 +512,7 @@ pub(crate) enum StateAction { fork_id: ForkId, }, /// A new node was found through the discovery, possibly with a `ForkId` - DiscoveredNode { peer_id: PeerId, socket_addr: SocketAddr, fork_id: Option }, + DiscoveredNode { peer_id: PeerId, addr: PeerAddr, fork_id: Option }, /// A peer was added PeerAdded(PeerId), /// A peer was dropped diff --git a/crates/net/network/src/swarm.rs b/crates/net/network/src/swarm.rs index cfc1f841713c..057cd1143726 100644 --- a/crates/net/network/src/swarm.rs +++ b/crates/net/network/src/swarm.rs @@ -247,14 +247,14 @@ where } StateAction::PeerAdded(peer_id) => return Some(SwarmEvent::PeerAdded(peer_id)), StateAction::PeerRemoved(peer_id) => return Some(SwarmEvent::PeerRemoved(peer_id)), - StateAction::DiscoveredNode { peer_id, socket_addr, fork_id } => { + StateAction::DiscoveredNode { peer_id, addr, fork_id } => { // Don't try to connect to peer if node is shutting down if self.is_shutting_down() { return None } // Insert peer only if no fork id or a valid fork id if fork_id.map_or_else(|| true, |f| self.sessions.is_valid_fork_id(f)) { - self.state_mut().peers_mut().add_peer(peer_id, socket_addr, fork_id); + self.state_mut().peers_mut().add_peer(peer_id, addr, fork_id); } } StateAction::DiscoveredEnrForkId { peer_id, fork_id } => { diff --git a/crates/net/peers/src/node_record.rs b/crates/net/peers/src/node_record.rs index 3b6c38170d56..5f268c253919 100644 --- a/crates/net/peers/src/node_record.rs +++ b/crates/net/peers/src/node_record.rs @@ -92,6 +92,17 @@ impl NodeRecord { Self { address: addr.ip(), tcp_port: addr.port(), udp_port: addr.port(), id } } + /// Creates a new record from an ip address and ports. + pub fn new_with_ports( + ip_addr: IpAddr, + tcp_port: u16, + udp_port: Option, + id: PeerId, + ) -> Self { + let udp_port = udp_port.unwrap_or(tcp_port); + Self { address: ip_addr, tcp_port, udp_port, id } + } + /// The TCP socket address of this node #[must_use] pub const fn tcp_addr(&self) -> SocketAddr { diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 772bc77e3f76..f294a52c1bf3 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -38,7 +38,7 @@ where { /// Handler for `admin_addPeer` fn add_peer(&self, record: NodeRecord) -> RpcResult { - self.network.add_peer(record.id, record.tcp_addr()); + self.network.add_peer_with_udp(record.id, record.tcp_addr(), record.udp_addr()); Ok(true) } @@ -51,7 +51,7 @@ where /// Handler for `admin_addTrustedPeer` fn add_trusted_peer(&self, record: AnyNode) -> RpcResult { if let Some(record) = record.node_record() { - self.network.add_trusted_peer(record.id, record.tcp_addr()) + self.network.add_trusted_peer_with_udp(record.id, record.tcp_addr(), record.udp_addr()) } self.network.add_trusted_peer_id(record.peer_id()); Ok(true) From 1ce76f2e998ae45843abe0ce20a8fff1f005ad69 Mon Sep 17 00:00:00 2001 From: sboou Date: Sat, 29 Jun 2024 14:43:36 +0100 Subject: [PATCH 264/405] chore: replace raw usage of revm evm builder with EvmConfig usage (#9190) --- crates/rpc/rpc/src/eth/bundle.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index 82395e6df3ca..894f2f80404b 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use jsonrpsee::core::RpcResult; -use reth_evm::ConfigureEvmEnv; +use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_primitives::{ keccak256, revm_primitives::db::{DatabaseCommit, DatabaseRef}, @@ -120,8 +120,7 @@ where let mut total_gas_fess = U256::ZERO; let mut hash_bytes = Vec::with_capacity(32 * transactions.len()); - let mut evm = - revm::Evm::builder().with_db(db).with_env_with_handler_cfg(env).build(); + let mut evm = Call::evm_config(ð_api).evm_with_env(db, env); let mut results = Vec::with_capacity(transactions.len()); let mut transactions = transactions.into_iter().peekable(); From fffeefbca3ad1e8a6a01d825db58e222509652f8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 29 Jun 2024 19:28:46 +0200 Subject: [PATCH 265/405] chore: finally move node-core to node folder (#9191) --- Cargo.toml | 4 ++-- crates/{node-core => node/core}/Cargo.toml | 0 crates/{node-core => node/core}/build.rs | 0 crates/{node-core => node/core}/src/args/benchmark_args.rs | 0 crates/{node-core => node/core}/src/args/database.rs | 0 crates/{node-core => node/core}/src/args/datadir_args.rs | 0 crates/{node-core => node/core}/src/args/debug.rs | 0 crates/{node-core => node/core}/src/args/dev.rs | 0 crates/{node-core => node/core}/src/args/gas_price_oracle.rs | 0 crates/{node-core => node/core}/src/args/log.rs | 0 crates/{node-core => node/core}/src/args/mod.rs | 0 crates/{node-core => node/core}/src/args/network.rs | 0 crates/{node-core => node/core}/src/args/payload_builder.rs | 0 crates/{node-core => node/core}/src/args/pruning.rs | 0 crates/{node-core => node/core}/src/args/rpc_server.rs | 0 crates/{node-core => node/core}/src/args/rpc_state_cache.rs | 0 crates/{node-core => node/core}/src/args/secret_key.rs | 0 crates/{node-core => node/core}/src/args/stage.rs | 0 crates/{node-core => node/core}/src/args/txpool.rs | 0 crates/{node-core => node/core}/src/args/types.rs | 0 crates/{node-core => node/core}/src/args/utils.rs | 0 crates/{node-core => node/core}/src/cli/config.rs | 0 crates/{node-core => node/core}/src/cli/mod.rs | 0 crates/{node-core => node/core}/src/dirs.rs | 0 crates/{node-core => node/core}/src/exit.rs | 0 crates/{node-core => node/core}/src/lib.rs | 0 crates/{node-core => node/core}/src/metrics/mod.rs | 0 .../core}/src/metrics/prometheus_exporter.rs | 0 .../{node-core => node/core}/src/metrics/version_metrics.rs | 0 crates/{node-core => node/core}/src/node_config.rs | 0 crates/{node-core => node/core}/src/utils.rs | 0 crates/{node-core => node/core}/src/version.rs | 0 32 files changed, 2 insertions(+), 2 deletions(-) rename crates/{node-core => node/core}/Cargo.toml (100%) rename crates/{node-core => node/core}/build.rs (100%) rename crates/{node-core => node/core}/src/args/benchmark_args.rs (100%) rename crates/{node-core => node/core}/src/args/database.rs (100%) rename crates/{node-core => node/core}/src/args/datadir_args.rs (100%) rename crates/{node-core => node/core}/src/args/debug.rs (100%) rename crates/{node-core => node/core}/src/args/dev.rs (100%) rename crates/{node-core => node/core}/src/args/gas_price_oracle.rs (100%) rename crates/{node-core => node/core}/src/args/log.rs (100%) rename crates/{node-core => node/core}/src/args/mod.rs (100%) rename crates/{node-core => node/core}/src/args/network.rs (100%) rename crates/{node-core => node/core}/src/args/payload_builder.rs (100%) rename crates/{node-core => node/core}/src/args/pruning.rs (100%) rename crates/{node-core => node/core}/src/args/rpc_server.rs (100%) rename crates/{node-core => node/core}/src/args/rpc_state_cache.rs (100%) rename crates/{node-core => node/core}/src/args/secret_key.rs (100%) rename crates/{node-core => node/core}/src/args/stage.rs (100%) rename crates/{node-core => node/core}/src/args/txpool.rs (100%) rename crates/{node-core => node/core}/src/args/types.rs (100%) rename crates/{node-core => node/core}/src/args/utils.rs (100%) rename crates/{node-core => node/core}/src/cli/config.rs (100%) rename crates/{node-core => node/core}/src/cli/mod.rs (100%) rename crates/{node-core => node/core}/src/dirs.rs (100%) rename crates/{node-core => node/core}/src/exit.rs (100%) rename crates/{node-core => node/core}/src/lib.rs (100%) rename crates/{node-core => node/core}/src/metrics/mod.rs (100%) rename crates/{node-core => node/core}/src/metrics/prometheus_exporter.rs (100%) rename crates/{node-core => node/core}/src/metrics/version_metrics.rs (100%) rename crates/{node-core => node/core}/src/node_config.rs (100%) rename crates/{node-core => node/core}/src/utils.rs (100%) rename crates/{node-core => node/core}/src/version.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 32360341f024..4c7f2ad92e9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ members = [ "crates/net/network/", "crates/net/p2p/", "crates/net/peers/", - "crates/node-core/", + "crates/node/core/", "crates/node/api/", "crates/node/builder/", "crates/node/events/", @@ -320,7 +320,7 @@ reth-network-p2p = { path = "crates/net/p2p" } reth-nippy-jar = { path = "crates/storage/nippy-jar" } reth-node-api = { path = "crates/node/api" } reth-node-builder = { path = "crates/node/builder" } -reth-node-core = { path = "crates/node-core" } +reth-node-core = { path = "crates/node/core" } reth-node-ethereum = { path = "crates/ethereum/node" } reth-node-events = { path = "crates/node/events" } reth-node-optimism = { path = "crates/optimism/node" } diff --git a/crates/node-core/Cargo.toml b/crates/node/core/Cargo.toml similarity index 100% rename from crates/node-core/Cargo.toml rename to crates/node/core/Cargo.toml diff --git a/crates/node-core/build.rs b/crates/node/core/build.rs similarity index 100% rename from crates/node-core/build.rs rename to crates/node/core/build.rs diff --git a/crates/node-core/src/args/benchmark_args.rs b/crates/node/core/src/args/benchmark_args.rs similarity index 100% rename from crates/node-core/src/args/benchmark_args.rs rename to crates/node/core/src/args/benchmark_args.rs diff --git a/crates/node-core/src/args/database.rs b/crates/node/core/src/args/database.rs similarity index 100% rename from crates/node-core/src/args/database.rs rename to crates/node/core/src/args/database.rs diff --git a/crates/node-core/src/args/datadir_args.rs b/crates/node/core/src/args/datadir_args.rs similarity index 100% rename from crates/node-core/src/args/datadir_args.rs rename to crates/node/core/src/args/datadir_args.rs diff --git a/crates/node-core/src/args/debug.rs b/crates/node/core/src/args/debug.rs similarity index 100% rename from crates/node-core/src/args/debug.rs rename to crates/node/core/src/args/debug.rs diff --git a/crates/node-core/src/args/dev.rs b/crates/node/core/src/args/dev.rs similarity index 100% rename from crates/node-core/src/args/dev.rs rename to crates/node/core/src/args/dev.rs diff --git a/crates/node-core/src/args/gas_price_oracle.rs b/crates/node/core/src/args/gas_price_oracle.rs similarity index 100% rename from crates/node-core/src/args/gas_price_oracle.rs rename to crates/node/core/src/args/gas_price_oracle.rs diff --git a/crates/node-core/src/args/log.rs b/crates/node/core/src/args/log.rs similarity index 100% rename from crates/node-core/src/args/log.rs rename to crates/node/core/src/args/log.rs diff --git a/crates/node-core/src/args/mod.rs b/crates/node/core/src/args/mod.rs similarity index 100% rename from crates/node-core/src/args/mod.rs rename to crates/node/core/src/args/mod.rs diff --git a/crates/node-core/src/args/network.rs b/crates/node/core/src/args/network.rs similarity index 100% rename from crates/node-core/src/args/network.rs rename to crates/node/core/src/args/network.rs diff --git a/crates/node-core/src/args/payload_builder.rs b/crates/node/core/src/args/payload_builder.rs similarity index 100% rename from crates/node-core/src/args/payload_builder.rs rename to crates/node/core/src/args/payload_builder.rs diff --git a/crates/node-core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs similarity index 100% rename from crates/node-core/src/args/pruning.rs rename to crates/node/core/src/args/pruning.rs diff --git a/crates/node-core/src/args/rpc_server.rs b/crates/node/core/src/args/rpc_server.rs similarity index 100% rename from crates/node-core/src/args/rpc_server.rs rename to crates/node/core/src/args/rpc_server.rs diff --git a/crates/node-core/src/args/rpc_state_cache.rs b/crates/node/core/src/args/rpc_state_cache.rs similarity index 100% rename from crates/node-core/src/args/rpc_state_cache.rs rename to crates/node/core/src/args/rpc_state_cache.rs diff --git a/crates/node-core/src/args/secret_key.rs b/crates/node/core/src/args/secret_key.rs similarity index 100% rename from crates/node-core/src/args/secret_key.rs rename to crates/node/core/src/args/secret_key.rs diff --git a/crates/node-core/src/args/stage.rs b/crates/node/core/src/args/stage.rs similarity index 100% rename from crates/node-core/src/args/stage.rs rename to crates/node/core/src/args/stage.rs diff --git a/crates/node-core/src/args/txpool.rs b/crates/node/core/src/args/txpool.rs similarity index 100% rename from crates/node-core/src/args/txpool.rs rename to crates/node/core/src/args/txpool.rs diff --git a/crates/node-core/src/args/types.rs b/crates/node/core/src/args/types.rs similarity index 100% rename from crates/node-core/src/args/types.rs rename to crates/node/core/src/args/types.rs diff --git a/crates/node-core/src/args/utils.rs b/crates/node/core/src/args/utils.rs similarity index 100% rename from crates/node-core/src/args/utils.rs rename to crates/node/core/src/args/utils.rs diff --git a/crates/node-core/src/cli/config.rs b/crates/node/core/src/cli/config.rs similarity index 100% rename from crates/node-core/src/cli/config.rs rename to crates/node/core/src/cli/config.rs diff --git a/crates/node-core/src/cli/mod.rs b/crates/node/core/src/cli/mod.rs similarity index 100% rename from crates/node-core/src/cli/mod.rs rename to crates/node/core/src/cli/mod.rs diff --git a/crates/node-core/src/dirs.rs b/crates/node/core/src/dirs.rs similarity index 100% rename from crates/node-core/src/dirs.rs rename to crates/node/core/src/dirs.rs diff --git a/crates/node-core/src/exit.rs b/crates/node/core/src/exit.rs similarity index 100% rename from crates/node-core/src/exit.rs rename to crates/node/core/src/exit.rs diff --git a/crates/node-core/src/lib.rs b/crates/node/core/src/lib.rs similarity index 100% rename from crates/node-core/src/lib.rs rename to crates/node/core/src/lib.rs diff --git a/crates/node-core/src/metrics/mod.rs b/crates/node/core/src/metrics/mod.rs similarity index 100% rename from crates/node-core/src/metrics/mod.rs rename to crates/node/core/src/metrics/mod.rs diff --git a/crates/node-core/src/metrics/prometheus_exporter.rs b/crates/node/core/src/metrics/prometheus_exporter.rs similarity index 100% rename from crates/node-core/src/metrics/prometheus_exporter.rs rename to crates/node/core/src/metrics/prometheus_exporter.rs diff --git a/crates/node-core/src/metrics/version_metrics.rs b/crates/node/core/src/metrics/version_metrics.rs similarity index 100% rename from crates/node-core/src/metrics/version_metrics.rs rename to crates/node/core/src/metrics/version_metrics.rs diff --git a/crates/node-core/src/node_config.rs b/crates/node/core/src/node_config.rs similarity index 100% rename from crates/node-core/src/node_config.rs rename to crates/node/core/src/node_config.rs diff --git a/crates/node-core/src/utils.rs b/crates/node/core/src/utils.rs similarity index 100% rename from crates/node-core/src/utils.rs rename to crates/node/core/src/utils.rs diff --git a/crates/node-core/src/version.rs b/crates/node/core/src/version.rs similarity index 100% rename from crates/node-core/src/version.rs rename to crates/node/core/src/version.rs From f16fd02d940504e9f2de42093af9152c8b83aca7 Mon Sep 17 00:00:00 2001 From: fdsuuo <161194610+fdsuuo@users.noreply.github.com> Date: Sun, 30 Jun 2024 05:36:20 +0100 Subject: [PATCH 266/405] chore(deps): remove igd-next (#9200) --- Cargo.toml | 1 - deny.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c7f2ad92e9b..7110a39197ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -484,7 +484,6 @@ tower-http = "0.5" # p2p discv5 = "0.6.0" -igd-next = "0.14.3" # rpc jsonrpsee = "0.23" diff --git a/deny.toml b/deny.toml index bfe4169798b0..431698495969 100644 --- a/deny.toml +++ b/deny.toml @@ -64,7 +64,6 @@ exceptions = [ { allow = ["CC0-1.0"], name = "aurora-engine-modexp" }, # TODO: decide on MPL-2.0 handling # These dependencies are grandfathered in in https://github.com/paradigmxyz/reth/pull/6980 - { allow = ["MPL-2.0"], name = "attohttpc" }, { allow = ["MPL-2.0"], name = "option-ext" }, { allow = ["MPL-2.0"], name = "webpki-roots" }, ] From a31b8b0d3f249c154d52b4fc4212f10fbefd67b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Jun 2024 05:47:15 +0000 Subject: [PATCH 267/405] chore(deps): weekly `cargo update` (#9199) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- Cargo.lock | 390 ++++++++++++++++++++++++++--------------------------- 1 file changed, 195 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 556f8da69d76..962e60497108 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,9 +109,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9a1892803b02f53e25bea3e414ddd0501f12d97456c9d5ade4edf88f9516f" +checksum = "1752d7d62e2665da650a36d84abbf239f812534475d51f072a49a533513b7cdd" dependencies = [ "alloy-rlp", "arbitrary", @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a016bfa21193744d4c38b3f3ab845462284d129e5e23c7cc0fafca7e92d9db37" +checksum = "3f63a6c9eb45684a5468536bc55379a2af0f45ffa5d756e4e4964532737e1836" dependencies = [ "alloy-eips", "alloy-primitives", @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d6d8118b83b0489cfb7e6435106948add2b35217f4a5004ef895f613f60299" +checksum = "aa4b0fc6a572ef2eebda0a31a5e393d451abda703fec917c75d9615d8c978cf2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "894f33a7822abb018db56b10ab90398e63273ce1b5a33282afd186c132d764a6" +checksum = "48450f9c6f0821c1eee00ed912942492ed4f11dd69532825833de23ecc7a2256" dependencies = [ "alloy-primitives", "alloy-serde", @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f0ae6e93b885cc70fe8dae449e7fd629751dbee8f59767eaaa7285333c5727" +checksum = "d484c2a934d0a4d86f8ad4db8113cb1d607707a6c54f6e78f4f1b4451b47aa70" dependencies = [ "alloy-primitives", "serde", @@ -213,9 +213,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc122cbee2b8523854cc11d87bcd5773741602c553d2d2d106d82eeb9c16924a" +checksum = "7a20eba9bc551037f0626d6d29e191888638d979943fa4e842e9e6fc72bf0565" dependencies = [ "alloy-consensus", "alloy-eips", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0e005ecc1b41f0b3bf90f68df5a446971e7eb34e1ea051da401e7e8eeef8fd" +checksum = "22e07c66b8b0ba8c87461a15fe3247c5b46fb500e103111b0ad4798738e45b1e" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5af289798fe8783acd0c5f10644d9d26f54a12bc52a083e4f3b31718e9bf92" +checksum = "ad5d89acb7339fad13bc69e7b925232f242835bfd91c82fcb9326b36481bd0f0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702f330b7da123a71465ab9d39616292f8344a2811c28f2cc8d8438a69d79e35" +checksum = "034258dfaa51c278e1f7fcc46e587d10079ec9372866fa48c5df9d908fc1f6b1" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b155716bab55763c95ba212806cf43d05bcc70e5f35b02bad20cf5ec7fe11fed" +checksum = "a43b18702501396fa9bcdeecd533bc85fac75150d308fc0f6800a01e6234a003" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -342,20 +342,20 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8037e03c7f462a063f28daec9fda285a9a89da003c552f8637a80b9c8fd96241" +checksum = "d83524c1f6162fcb5b0decf775498a125066c86dda6066ed609531b0e912f85a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] name = "alloy-rpc-client" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40fcb53b2a9d0a78a4968b2eca8805a4b7011b9ee3fdfa2acaf137c5128f36b" +checksum = "479ce003e8c74bbbc7d4235131c1d6b7eaf14a533ae850295b90d240340989cb" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f2fbe956a3e0f0975c798f488dc6be96b669544df3737e18f4a325b42f4c86" +checksum = "0dfa1dd3e0bc3a3d89744fba8d1511216e83257160da2cd028a18b7d9c026030" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334a8c00cde17a48e073031f1534e71a75b529dbf25552178c43c2337632e0ab" +checksum = "bae99de76a362c4311f0892e286eb752cf2a3a6ef6555dff6d93f51de2c24648" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87f724e6170f558b809a520e37bdb34d99123092b78118bff31fb5b21dc2a2e" +checksum = "f67aec11f9f3bc5e96c2b7f342dba6e9541a8a48d2cfbe27b6b195136aa18eee" dependencies = [ "alloy-primitives", "alloy-serde", @@ -412,9 +412,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb383cd3981cee0031aeacbd394c4e726e907f3a0180fe36d5fc76d37c41cd82" +checksum = "dd2c363d49f460538899aaeb3325918f55fa01841fd7f3f11f58d438343ea083" dependencies = [ "alloy-eips", "alloy-primitives", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd473d98ec552f8229cd6d566bd2b0bbfc5bb4efcefbb5288c834aa8fd832020" +checksum = "cc40df2dda7561d1406d0bee1d19c8787483a2cf2ee8011c05909475e7bc102d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -445,9 +445,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "083f443a83b9313373817236a8f4bea09cca862618e9177d822aee579640a5d6" +checksum = "13bd7aa9ff9e67f1ba7ee0dd8cebfc95831d1649b0e4eeefae940dc3681079fa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -467,9 +467,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7a838f9a34aae7022c6cb53ecf21bc0a5a30c82f8d9eb0afed701ab5fd88de" +checksum = "535d26db98ac320a0d1637faf3e210328c3df3b1998abd7e72343d3857058efe" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1572267dbc660843d87c02994029d1654c2c32867e186b266d1c03644b43af97" +checksum = "5971c92989c6a5588d3f6d1e99e5328fba6e68694efbe969d6ec96ae5b9d1037" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -493,9 +493,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d94da1c0c4e27cc344b05626fe22a89dc6b8b531b9475f3b7691dbf6913e4109" +checksum = "8913f9e825068d77c516188c221c44f78fd814fce8effe550a783295a2757d19" dependencies = [ "alloy-primitives", "arbitrary", @@ -507,9 +507,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58d876be3afd8b78979540084ff63995292a26aa527ad0d44276405780aa0ffd" +checksum = "f740e13eb4c6a0e4d0e49738f1e86f31ad2d7ef93be499539f492805000f7237" dependencies = [ "alloy-primitives", "async-trait", @@ -521,9 +521,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40a37dc216c269b8a7244047cb1c18a9c69f7a0332ab2c4c2aa4cbb1a31468b" +checksum = "87db68d926887393a1d0f9c43833b44446ea29d603291e7b20e5d115f31aa4e3" dependencies = [ "alloy-consensus", "alloy-network", @@ -548,7 +548,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -565,7 +565,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", "syn-solidity", "tiny-keccak", ] @@ -583,7 +583,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.67", + "syn 2.0.68", "syn-solidity", ] @@ -611,9 +611,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245af9541f0a0dbd5258669c80dfe3af118164cacec978a520041fc130550deb" +checksum = "dd9773e4ec6832346171605c776315544bd06e40f803e7b5b7824b325d5442ca" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5619c017e1fdaa1db87f9182f4f0ed97c53d674957f4902fba655e972d359c6c" +checksum = "ff8ef947b901c0d4e97370f9fa25844cf8b63b1a58fd4011ee82342dc8a9fc6b" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173cefa110afac7a53cf2e75519327761f2344d305eea2993f3af1b2c1fc1c44" +checksum = "bb40ee66887a66d875a5bb5e01cee4c9a467c263ef28c865cd4b0ebf15f705af" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0aff8af5be5e58856c5cdd1e46db2c67c7ecd3a652d9100b4822c96c899947" +checksum = "3d92049d6642a18c9849ce7659430151e7c92b51552a0cabdc038c1af4cd7308" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -786,7 +786,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -1006,7 +1006,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -1017,7 +1017,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -1055,7 +1055,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -1163,7 +1163,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -1176,7 +1176,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.67", + "syn 2.0.68", "which", ] @@ -1209,9 +1209,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "arbitrary", "serde", @@ -1284,7 +1284,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b6fb81ca0f301f33aff7401e2ffab37dc9e0e4a1cf0ccf6b34f4d9e60aa0682" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "boa_interner", "boa_macros", "indexmap 2.2.6", @@ -1299,7 +1299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "600e4e4a65b26efcef08a7b1cf2899d3845a32e82e067ee3b75eaf7e413ff31c" dependencies = [ "arrayvec", - "bitflags 2.5.0", + "bitflags 2.6.0", "boa_ast", "boa_gc", "boa_interner", @@ -1373,7 +1373,7 @@ checksum = "6be9c93793b60dac381af475b98634d4b451e28336e72218cad9a20176218dbc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", "synstructure", ] @@ -1383,7 +1383,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8592556849f0619ed142ce2b3a19086769314a8d657f93a5765d06dbce4818" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "boa_ast", "boa_interner", "boa_macros", @@ -1482,7 +1482,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -1569,9 +1569,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "779e6b7d17797c0b42023d417228c02889300190e700cb074c3438d9c541d332" dependencies = [ "jobserver", "libc", @@ -1673,9 +1673,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -1683,9 +1683,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -1695,14 +1695,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2045,7 +2045,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "crossterm_winapi", "libc", "mio", @@ -2188,7 +2188,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2212,7 +2212,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2223,7 +2223,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2329,7 +2329,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2342,7 +2342,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2456,7 +2456,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2549,9 +2549,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -2613,7 +2613,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -2624,7 +2624,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -3317,7 +3317,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -3847,7 +3847,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -3953,9 +3953,9 @@ checksum = "e3744fecc0df9ce19999cdaf1f9f3a48c253431ce1d67ef499128fe9d0b607ab" [[package]] name = "icu_properties" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8173ba888885d250016e957b8ebfd5a65cdb690123d8833a19f6833f9c2b579" +checksum = "db9e559598096627aeca8cdfb98138a70eb4078025f8d1d5f2416a361241f756" dependencies = [ "displaydoc", "icu_collections", @@ -3997,7 +3997,7 @@ checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -4296,9 +4296,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a130d27083a4001b7b2d72a19f08786299550f76c9bd5307498dce2c2b20fa" +checksum = "62b089779ad7f80768693755a031cc14a7766aba707cbe886674e3f79e9b7e47" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -4314,9 +4314,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039db9fe25cd63b7221c3f8788c1ef4ea07987d40ec25a1e7d7a3c3e3e3fd130" +checksum = "08163edd8bcc466c33d79e10f695cdc98c00d1e6ddfb95cec41b6b0279dd5432" dependencies = [ "base64 0.22.1", "futures-channel", @@ -4339,9 +4339,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21545a9445fbd582840ff5160a9a3e12b8e6da582151cdb07bde9a1970ba3a24" +checksum = "79712302e737d23ca0daa178e752c9334846b08321d439fd89af9a384f8c830b" dependencies = [ "anyhow", "async-trait", @@ -4368,9 +4368,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb25cab482c8512c4f3323a5c90b95a3b8f7c90681a87bf7a68b942d52f08933" +checksum = "2d90064e04fb9d7282b1c71044ea94d0bbc6eff5621c66f1a0bce9e9de7cf3ac" dependencies = [ "async-trait", "base64 0.22.1", @@ -4393,22 +4393,22 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c18184cd09b386feb18085609e8bf77bdc942482bdd82777b433b8d015edf561" +checksum = "7895f186d5921065d96e16bd795e5ca89ac8356ec423fafc6e3d7cf8ec11aee4" dependencies = [ "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] name = "jsonrpsee-server" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810f63eff0f78fa8d413d678c0e55b702e2ea61d4587774c0db4ea2fc554ef92" +checksum = "654afab2e92e5d88ebd8a39d6074483f3f2bfdf91c5ac57fe285e7127cdd4f51" dependencies = [ "anyhow", "futures-util", @@ -4434,9 +4434,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f511b714bca46f9a3e97c0e0eb21d2c112e83e444d2db535b5ec7093f5836d73" +checksum = "d9c465fbe385238e861fdc4d1c85e04ada6c1fd246161d26385c1b311724d2af" dependencies = [ "beef", "http 1.1.0", @@ -4447,9 +4447,9 @@ dependencies = [ [[package]] name = "jsonrpsee-wasm-client" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c8a6dfa0c35c8549fa8e003ce0bbcf37b051ab7ef85fce587e8f0ed7881c84d" +checksum = "4727ac037f834c6f04c0912cada7532dbddb54e92fbc64e33d6cb8c24af313c9" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -4458,9 +4458,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786c100eb67df2f2d863d231c2c6978bcf80ff4bf606ffc40e7e68ef562da7bf" +checksum = "1c28759775f5cb2f1ea9667672d3fe2b0e701d1f4b7b67954e60afe7fd058b5e" dependencies = [ "http 1.1.0", "jsonrpsee-client-transport", @@ -4555,9 +4555,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", "windows-targets 0.52.5", @@ -4703,7 +4703,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", ] @@ -4805,9 +4805,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" @@ -4893,9 +4893,9 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26eb45aff37b45cff885538e1dcbd6c2b462c04fe84ce0155ea469f325672c98" +checksum = "bf0af7a0d7ced10c0151f870e5e3f3f8bc9ffc5992d32873566ca1f9169ae776" dependencies = [ "base64 0.22.1", "indexmap 2.2.6", @@ -4941,9 +4941,9 @@ dependencies = [ [[package]] name = "mev-share-sse" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cbf228751922258c86a8492b39f987bb22338bef0b09426c106853be0c9fc7" +checksum = "e00cdd87dab765e7dac55c21eb680bfd10655b6c2530f6fe578acdfbb66c757c" dependencies = [ "alloy-primitives", "async-sse", @@ -4967,9 +4967,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -5026,7 +5026,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -5175,9 +5175,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -5278,7 +5278,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -5306,9 +5306,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -5345,9 +5345,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "4.2.0" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" +checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be" dependencies = [ "num-traits", ] @@ -5557,7 +5557,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -5586,7 +5586,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -5745,7 +5745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -5818,7 +5818,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "chrono", "flate2", "hex", @@ -5833,7 +5833,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "chrono", "hex", ] @@ -5846,7 +5846,7 @@ checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.5.0", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand 0.8.5", @@ -5887,7 +5887,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -6087,7 +6087,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cassowary", "compact_str", "crossterm", @@ -6108,7 +6108,7 @@ version = "11.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -6152,7 +6152,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -6625,7 +6625,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -7315,7 +7315,7 @@ dependencies = [ name = "reth-libmdbx" version = "1.0.0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "criterion", "dashmap", @@ -7360,7 +7360,7 @@ dependencies = [ "quote", "regex", "serial_test", - "syn 2.0.67", + "syn 2.0.68", "trybuild", ] @@ -8526,7 +8526,7 @@ dependencies = [ "aquamarine", "assert_matches", "auto_impl", - "bitflags 2.5.0", + "bitflags 2.6.0", "criterion", "futures-util", "metrics", @@ -8716,7 +8716,7 @@ checksum = "902184a7a781550858d4b96707098da357429f1e4545806fd5b589f455555cf2" dependencies = [ "alloy-primitives", "auto_impl", - "bitflags 2.5.0", + "bitflags 2.6.0", "bitvec", "c-kzg", "cfg-if", @@ -8741,9 +8741,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.37" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" dependencies = [ "bytemuck", ] @@ -8859,7 +8859,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink 0.9.1", @@ -8915,7 +8915,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -8968,9 +8968,9 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-platform-verifier" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +checksum = "3e3beb939bcd33c269f4bf946cc829fcd336370267c4a927ac0399c84a3151a1" dependencies = [ "core-foundation", "core-foundation-sys", @@ -9135,7 +9135,7 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -9203,9 +9203,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] @@ -9218,14 +9218,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ "indexmap 2.2.6", "itoa", @@ -9292,7 +9292,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9317,7 +9317,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9573,7 +9573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" dependencies = [ "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9602,9 +9602,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] @@ -9619,7 +9619,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9637,9 +9637,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sucds" @@ -9687,9 +9687,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.67" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -9705,7 +9705,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9722,7 +9722,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9807,7 +9807,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9846,7 +9846,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -9984,9 +9984,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ "tinyvec_macros", ] @@ -10024,7 +10024,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -10155,7 +10155,7 @@ checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", "base64 0.21.7", - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "futures-core", "futures-util", @@ -10222,7 +10222,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -10561,9 +10561,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "getrandom 0.2.15", ] @@ -10673,7 +10673,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -10707,7 +10707,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10848,7 +10848,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -10859,7 +10859,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -11117,7 +11117,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", "synstructure", ] @@ -11138,7 +11138,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] @@ -11158,7 +11158,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", "synstructure", ] @@ -11179,14 +11179,14 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] name = "zerovec" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", @@ -11195,13 +11195,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.68", ] [[package]] From 2a9fa4869e658ca89e050d0d985a7ccbfa360bfa Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Sun, 30 Jun 2024 13:09:41 -0700 Subject: [PATCH 268/405] chore(trie): rename in-memory trie cursors (#9203) --- crates/trie/trie/src/trie.rs | 4 +-- .../trie_cursor/{update.rs => in_memory.rs} | 26 +++++++++---------- crates/trie/trie/src/trie_cursor/mod.rs | 10 +++++-- 3 files changed, 23 insertions(+), 17 deletions(-) rename crates/trie/trie/src/trie_cursor/{update.rs => in_memory.rs} (87%) diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index b3ac9dec1d07..0ae43cdf849a 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -549,7 +549,7 @@ mod tests { hashed_cursor::HashedPostStateCursorFactory, prefix_set::PrefixSetMut, test_utils::{state_root, state_root_prehashed, storage_root, storage_root_prehashed}, - trie_cursor::TrieUpdatesCursorFactory, + trie_cursor::InMemoryTrieCursorFactory, BranchNodeCompact, HashedPostState, HashedStorage, TrieMask, }; use proptest::{prelude::ProptestConfig, proptest}; @@ -1488,7 +1488,7 @@ mod tests { tx, &post_state.clone().into_sorted(), )) - .with_trie_cursor_factory(TrieUpdatesCursorFactory::new(tx, &update.sorted())) + .with_trie_cursor_factory(InMemoryTrieCursorFactory::new(tx, &update.sorted())) .with_prefix_set(prefix_sets.storage_prefix_sets.remove(&keccak256(address)).unwrap()) .root_with_updates() .unwrap(); diff --git a/crates/trie/trie/src/trie_cursor/update.rs b/crates/trie/trie/src/trie_cursor/in_memory.rs similarity index 87% rename from crates/trie/trie/src/trie_cursor/update.rs rename to crates/trie/trie/src/trie_cursor/in_memory.rs index 0c5d9b046882..5f607f314db0 100644 --- a/crates/trie/trie/src/trie_cursor/update.rs +++ b/crates/trie/trie/src/trie_cursor/in_memory.rs @@ -6,25 +6,25 @@ use reth_trie_common::{BranchNodeCompact, Nibbles}; /// The trie cursor factory for the trie updates. #[derive(Debug, Clone)] -pub struct TrieUpdatesCursorFactory<'a, CF> { +pub struct InMemoryTrieCursorFactory<'a, CF> { cursor_factory: CF, trie_updates: &'a TrieUpdatesSorted, } -impl<'a, CF> TrieUpdatesCursorFactory<'a, CF> { +impl<'a, CF> InMemoryTrieCursorFactory<'a, CF> { /// Create a new trie cursor factory. pub const fn new(cursor_factory: CF, trie_updates: &'a TrieUpdatesSorted) -> Self { Self { cursor_factory, trie_updates } } } -impl<'a, CF: TrieCursorFactory> TrieCursorFactory for TrieUpdatesCursorFactory<'a, CF> { - type AccountTrieCursor = TrieUpdatesAccountTrieCursor<'a, CF::AccountTrieCursor>; - type StorageTrieCursor = TrieUpdatesStorageTrieCursor<'a, CF::StorageTrieCursor>; +impl<'a, CF: TrieCursorFactory> TrieCursorFactory for InMemoryTrieCursorFactory<'a, CF> { + type AccountTrieCursor = InMemoryAccountTrieCursor<'a, CF::AccountTrieCursor>; + type StorageTrieCursor = InMemoryStorageTrieCursor<'a, CF::StorageTrieCursor>; fn account_trie_cursor(&self) -> Result { let cursor = self.cursor_factory.account_trie_cursor()?; - Ok(TrieUpdatesAccountTrieCursor::new(cursor, self.trie_updates)) + Ok(InMemoryAccountTrieCursor::new(cursor, self.trie_updates)) } fn storage_trie_cursor( @@ -32,26 +32,26 @@ impl<'a, CF: TrieCursorFactory> TrieCursorFactory for TrieUpdatesCursorFactory<' hashed_address: B256, ) -> Result { let cursor = self.cursor_factory.storage_trie_cursor(hashed_address)?; - Ok(TrieUpdatesStorageTrieCursor::new(cursor, hashed_address, self.trie_updates)) + Ok(InMemoryStorageTrieCursor::new(cursor, hashed_address, self.trie_updates)) } } /// The cursor to iterate over account trie updates and corresponding database entries. /// It will always give precedence to the data from the trie updates. #[derive(Debug)] -pub struct TrieUpdatesAccountTrieCursor<'a, C> { +pub struct InMemoryAccountTrieCursor<'a, C> { cursor: C, trie_updates: &'a TrieUpdatesSorted, last_key: Option, } -impl<'a, C> TrieUpdatesAccountTrieCursor<'a, C> { +impl<'a, C> InMemoryAccountTrieCursor<'a, C> { const fn new(cursor: C, trie_updates: &'a TrieUpdatesSorted) -> Self { Self { cursor, trie_updates, last_key: None } } } -impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesAccountTrieCursor<'a, C> { +impl<'a, C: TrieCursor> TrieCursor for InMemoryAccountTrieCursor<'a, C> { fn seek_exact( &mut self, key: Nibbles, @@ -109,7 +109,7 @@ impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesAccountTrieCursor<'a, C> { /// The cursor to iterate over storage trie updates and corresponding database entries. /// It will always give precedence to the data from the trie updates. #[derive(Debug)] -pub struct TrieUpdatesStorageTrieCursor<'a, C> { +pub struct InMemoryStorageTrieCursor<'a, C> { cursor: C, trie_update_index: usize, trie_updates: &'a TrieUpdatesSorted, @@ -117,13 +117,13 @@ pub struct TrieUpdatesStorageTrieCursor<'a, C> { last_key: Option, } -impl<'a, C> TrieUpdatesStorageTrieCursor<'a, C> { +impl<'a, C> InMemoryStorageTrieCursor<'a, C> { const fn new(cursor: C, hashed_address: B256, trie_updates: &'a TrieUpdatesSorted) -> Self { Self { cursor, trie_updates, trie_update_index: 0, hashed_address, last_key: None } } } -impl<'a, C: TrieCursor> TrieCursor for TrieUpdatesStorageTrieCursor<'a, C> { +impl<'a, C: TrieCursor> TrieCursor for InMemoryStorageTrieCursor<'a, C> { fn seek_exact( &mut self, key: Nibbles, diff --git a/crates/trie/trie/src/trie_cursor/mod.rs b/crates/trie/trie/src/trie_cursor/mod.rs index f5f50a0d0151..a8e0a01cf093 100644 --- a/crates/trie/trie/src/trie_cursor/mod.rs +++ b/crates/trie/trie/src/trie_cursor/mod.rs @@ -1,17 +1,23 @@ use crate::{updates::TrieKey, BranchNodeCompact, Nibbles}; use reth_db::DatabaseError; use reth_primitives::B256; + +/// Database implementations of trie cursors. mod database_cursors; + +/// In-memory implementations of trie cursors. +mod in_memory; + +/// Cursor for iterating over a subtrie. mod subnode; -mod update; /// Noop trie cursor implementations. pub mod noop; pub use self::{ database_cursors::{DatabaseAccountTrieCursor, DatabaseStorageTrieCursor}, + in_memory::*, subnode::CursorSubNode, - update::*, }; /// Factory for creating trie cursors. From 84e385753ecba84113e72f0c5c741f3837ff3fb2 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:39:48 +0200 Subject: [PATCH 269/405] refactor(net): some refactor in eth requests (#9205) --- crates/net/network/src/eth_requests.rs | 33 ++++++++------------------ 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 2e403a517dc0..3b7e9500e077 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -66,8 +66,12 @@ pub struct EthRequestHandler { impl EthRequestHandler { /// Create a new instance pub fn new(client: C, peers: PeersHandle, incoming: Receiver) -> Self { - let metrics = Default::default(); - Self { client, peers, incoming_requests: ReceiverStream::new(incoming), metrics } + Self { + client, + peers, + incoming_requests: ReceiverStream::new(incoming), + metrics: Default::default(), + } } } @@ -124,11 +128,7 @@ where total_bytes += header.length(); headers.push(header); - if headers.len() >= MAX_HEADERS_SERVE { - break - } - - if total_bytes > SOFT_RESPONSE_LIMIT { + if headers.len() >= MAX_HEADERS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT { break } } else { @@ -163,21 +163,12 @@ where for hash in request.0 { if let Some(block) = self.client.block_by_hash(hash).unwrap_or_default() { - let body = BlockBody { - transactions: block.body, - ommers: block.ommers, - withdrawals: block.withdrawals, - requests: block.requests, - }; + let body: BlockBody = block.into(); total_bytes += body.length(); bodies.push(body); - if bodies.len() >= MAX_BODIES_SERVE { - break - } - - if total_bytes > SOFT_RESPONSE_LIMIT { + if bodies.len() >= MAX_BODIES_SERVE || total_bytes > SOFT_RESPONSE_LIMIT { break } } else { @@ -212,11 +203,7 @@ where total_bytes += receipt.length(); receipts.push(receipt); - if receipts.len() >= MAX_RECEIPTS_SERVE { - break - } - - if total_bytes > SOFT_RESPONSE_LIMIT { + if receipts.len() >= MAX_RECEIPTS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT { break } } else { From 91d45871b29dd1f6ed9af83aab92c7307e9d09bc Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:46:43 +0200 Subject: [PATCH 270/405] refactor(revm): simplify `fill_tx_env` (#9206) --- crates/primitives/src/revm/env.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index bbd4cffd35fb..ada7894513ed 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -245,10 +245,7 @@ where tx_env.gas_limit = tx.gas_limit; tx_env.gas_price = U256::from(tx.gas_price); tx_env.gas_priority_fee = None; - tx_env.transact_to = match tx.to { - TxKind::Call(to) => TxKind::Call(to), - TxKind::Create => TxKind::Create, - }; + tx_env.transact_to = tx.to; tx_env.value = tx.value; tx_env.data = tx.input.clone(); tx_env.chain_id = tx.chain_id; @@ -261,17 +258,13 @@ where tx_env.gas_limit = tx.gas_limit; tx_env.gas_price = U256::from(tx.gas_price); tx_env.gas_priority_fee = None; - tx_env.transact_to = match tx.to { - TxKind::Call(to) => TxKind::Call(to), - TxKind::Create => TxKind::Create, - }; + tx_env.transact_to = tx.to; tx_env.value = tx.value; tx_env.data = tx.input.clone(); tx_env.chain_id = Some(tx.chain_id); tx_env.nonce = Some(tx.nonce); tx_env.access_list = tx .access_list - .0 .iter() .map(|l| { (l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect()) @@ -284,17 +277,13 @@ where tx_env.gas_limit = tx.gas_limit; tx_env.gas_price = U256::from(tx.max_fee_per_gas); tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); - tx_env.transact_to = match tx.to { - TxKind::Call(to) => TxKind::Call(to), - TxKind::Create => TxKind::Create, - }; + tx_env.transact_to = tx.to; tx_env.value = tx.value; tx_env.data = tx.input.clone(); tx_env.chain_id = Some(tx.chain_id); tx_env.nonce = Some(tx.nonce); tx_env.access_list = tx .access_list - .0 .iter() .map(|l| { (l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect()) @@ -314,7 +303,6 @@ where tx_env.nonce = Some(tx.nonce); tx_env.access_list = tx .access_list - .0 .iter() .map(|l| { (l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect()) From 9cd377881caaaef2a470fda30fe25fa3c77cecb4 Mon Sep 17 00:00:00 2001 From: Ninja Date: Mon, 1 Jul 2024 12:06:29 +0200 Subject: [PATCH 271/405] docs: fix the links to code in discv4 docs (#9204) --- docs/crates/discv4.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/crates/discv4.md b/docs/crates/discv4.md index 5abe7c439b99..348c68e06b13 100644 --- a/docs/crates/discv4.md +++ b/docs/crates/discv4.md @@ -126,7 +126,7 @@ The `NodeRecord::from_secret_key()` takes the socket address used for discovery If the `discv4_config` supplied to the `Discovery::new()` function is `None`, the discv4 service will not be spawned. In this case, no new peers will be discovered across the network. The node will have to rely on manually added peers. However, if the `discv4_config` contains a `Some(Discv4Config)` value, then the `Discv4::bind()` function is called to bind to a new UdpSocket and create the disc_v4 service. -[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/main/crates/net/discv4/src/lib.rs#L188) +[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/530e7e8961b8f82ae2c675d16c368dd266ceba7d/crates/net/discv4/src/lib.rs#L178) ```rust ignore impl Discv4 { //--snip-- @@ -155,7 +155,7 @@ impl Discv4 { To better understand what is actually happening when the disc_v4 service is created, lets take a deeper look at the `Discv4Service::new()` function. -[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/main/crates/net/discv4/src/lib.rs#L392) +[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/530e7e8961b8f82ae2c675d16c368dd266ceba7d/crates/net/discv4/src/lib.rs#L495) ```rust ignore impl Discv4Service { /// Create a new instance for a bound [`UdpSocket`]. @@ -216,7 +216,7 @@ In Rust, the owner of a [`Future`](https://doc.rust-lang.org/std/future/trait.Fu Lets take a detailed look at how `Discv4Service::poll` works under the hood. This function has many moving parts, so we will break it up into smaller sections. -[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/main/crates/net/discv4/src/lib.rs#L1302) +[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/530e7e8961b8f82ae2c675d16c368dd266ceba7d/crates/net/discv4/src/lib.rs#L495) ```rust ignore impl Discv4Service { //--snip-- @@ -259,7 +259,7 @@ impl Discv4Service { As the function starts, a `loop` is entered and the `Discv4Service.queued_events` are evaluated to see if there are any events ready to be processed. If there is an event ready, the function immediately returns the event wrapped in `Poll::Ready()`. The `queued_events` field is a `VecDeque` where `Discv4Event` is an enum containing one of the following variants. -[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/main/crates/net/discv4/src/lib.rs#L1455) +[File: crates/net/discv4/src/lib.rs](https://github.com/paradigmxyz/reth/blob/530e7e8961b8f82ae2c675d16c368dd266ceba7d/crates/net/discv4/src/lib.rs#L1770) ```rust ignore pub enum Discv4Event { /// A `Ping` message was handled. @@ -285,7 +285,7 @@ Next, the Discv4Service handles all incoming `Discv4Command`s until there are no In Reth, once a new `NetworkState` is initialized as the node starts up and a new task is spawned to handle the network, the `poll()` function is used to advance the state of the network. -[File: crates/net/network/src/state.rs](https://github.com/paradigmxyz/reth/blob/main/crates/net/network/src/state.rs#L377) +[File: crates/net/network/src/state.rs](https://github.com/paradigmxyz/reth/blob/530e7e8961b8f82ae2c675d16c368dd266ceba7d/crates/net/network/src/state.rs#L396) ```rust ignore impl NetworkState where From d4fa9defbd3180aababad03ea7477328f25af902 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 1 Jul 2024 18:23:28 +0800 Subject: [PATCH 272/405] feat(net/peer): set rpc added peer as static (#9201) Signed-off-by: jsvisa --- crates/net/network-api/src/lib.rs | 11 +++++++++-- crates/net/network/src/manager.rs | 2 +- crates/net/network/src/state.rs | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/net/network-api/src/lib.rs b/crates/net/network-api/src/lib.rs index 895fbc089234..e9cce0866fde 100644 --- a/crates/net/network-api/src/lib.rs +++ b/crates/net/network-api/src/lib.rs @@ -68,12 +68,12 @@ pub trait PeersInfo: Send + Sync { pub trait Peers: PeersInfo { /// Adds a peer to the peer set with UDP `SocketAddr`. fn add_peer(&self, peer: PeerId, tcp_addr: SocketAddr) { - self.add_peer_kind(peer, PeerKind::Basic, tcp_addr, None); + self.add_peer_kind(peer, PeerKind::Static, tcp_addr, None); } /// Adds a peer to the peer set with TCP and UDP `SocketAddr`. fn add_peer_with_udp(&self, peer: PeerId, tcp_addr: SocketAddr, udp_addr: SocketAddr) { - self.add_peer_kind(peer, PeerKind::Basic, tcp_addr, Some(udp_addr)); + self.add_peer_kind(peer, PeerKind::Static, tcp_addr, Some(udp_addr)); } /// Adds a trusted [`PeerId`] to the peer set. @@ -163,6 +163,8 @@ pub enum PeerKind { /// Basic peer kind. #[default] Basic, + /// Static peer, added via JSON-RPC. + Static, /// Trusted peer. Trusted, } @@ -173,6 +175,11 @@ impl PeerKind { matches!(self, Self::Trusted) } + /// Returns `true` if the peer is static. + pub const fn is_static(&self) -> bool { + matches!(self, Self::Static) + } + /// Returns `true` if the peer is basic. pub const fn is_basic(&self) -> bool { matches!(self, Self::Basic) diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 1093bc8c50e5..b3fa43252ec1 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -562,7 +562,7 @@ where } } NetworkHandleMessage::RemovePeer(peer_id, kind) => { - self.swarm.state_mut().remove_peer(peer_id, kind); + self.swarm.state_mut().remove_peer_kind(peer_id, kind); } NetworkHandleMessage::DisconnectPeer(peer_id, reason) => { self.swarm.sessions_mut().disconnect(peer_id, reason); diff --git a/crates/net/network/src/state.rs b/crates/net/network/src/state.rs index 7fa5b6d2c08f..7334e483b2b4 100644 --- a/crates/net/network/src/state.rs +++ b/crates/net/network/src/state.rs @@ -278,9 +278,10 @@ where self.peers_manager.add_peer_kind(peer_id, kind, addr, None) } - pub(crate) fn remove_peer(&mut self, peer_id: PeerId, kind: PeerKind) { + /// Removes a peer and its address with the given kind from the peerset. + pub(crate) fn remove_peer_kind(&mut self, peer_id: PeerId, kind: PeerKind) { match kind { - PeerKind::Basic => self.peers_manager.remove_peer(peer_id), + PeerKind::Basic | PeerKind::Static => self.peers_manager.remove_peer(peer_id), PeerKind::Trusted => self.peers_manager.remove_peer_from_trusted_set(peer_id), } } From 3a9fbbc8e4d3b76d2b41aff4f4f2727da3485ab4 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:30:40 +0200 Subject: [PATCH 273/405] refactor: small refactoring (#9208) --- crates/consensus/common/src/validation.rs | 7 ++++--- crates/net/downloaders/src/file_client.rs | 10 +--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index dc04d74dc9ac..a2ebd3f6c545 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -29,7 +29,7 @@ pub fn validate_header_base_fee( header: &SealedHeader, chain_spec: &ChainSpec, ) -> Result<(), ConsensusError> { - if chain_spec.fork(EthereumHardfork::London).active_at_block(header.number) && + if chain_spec.is_fork_active_at_block(EthereumHardfork::London, header.number) && header.base_fee_per_gas.is_none() { return Err(ConsensusError::BaseFeeMissing) @@ -152,8 +152,9 @@ pub fn validate_4844_header_standalone(header: &SealedHeader) -> Result<(), Cons /// This must be 32 bytes or fewer; formally Hx. #[inline] pub fn validate_header_extradata(header: &Header) -> Result<(), ConsensusError> { - if header.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE { - Err(ConsensusError::ExtraDataExceedsMax { len: header.extra_data.len() }) + let extradata_len = header.extra_data.len(); + if extradata_len > MAXIMUM_EXTRA_DATA_SIZE { + Err(ConsensusError::ExtraDataExceedsMax { len: extradata_len }) } else { Ok(()) } diff --git a/crates/net/downloaders/src/file_client.rs b/crates/net/downloaders/src/file_client.rs index 3464baf5d6ff..3d00c389237b 100644 --- a/crates/net/downloaders/src/file_client.rs +++ b/crates/net/downloaders/src/file_client.rs @@ -228,15 +228,7 @@ impl FromReader for FileClient { // add to the internal maps headers.insert(block.header.number, block.header.clone()); hash_to_number.insert(block_hash, block.header.number); - bodies.insert( - block_hash, - BlockBody { - transactions: block.body, - ommers: block.ommers, - withdrawals: block.withdrawals, - requests: block.requests, - }, - ); + bodies.insert(block_hash, block.into()); if log_interval == 0 { trace!(target: "downloaders::file", From 90c60cb26c2d2b5542c8ccb5f720717a30cdc4ae Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:44:16 +0200 Subject: [PATCH 274/405] refactor: some simplifications around revm database (#9212) --- crates/revm/src/database.rs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/crates/revm/src/database.rs b/crates/revm/src/database.rs index 3b31788dbdaf..a1296ae39c24 100644 --- a/crates/revm/src/database.rs +++ b/crates/revm/src/database.rs @@ -1,6 +1,6 @@ use crate::primitives::alloy_primitives::{BlockNumber, StorageKey, StorageValue}; use core::ops::{Deref, DerefMut}; -use reth_primitives::{Account, Address, B256, KECCAK_EMPTY, U256}; +use reth_primitives::{Account, Address, B256, U256}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use revm::{ db::DatabaseRef, @@ -134,12 +134,7 @@ impl DatabaseRef for StateProviderDatabase { /// Returns `Ok` with `Some(AccountInfo)` if the account exists, /// `None` if it doesn't, or an error if encountered. fn basic_ref(&self, address: Address) -> Result, Self::Error> { - Ok(self.basic_account(address)?.map(|account| AccountInfo { - balance: account.balance, - nonce: account.nonce, - code_hash: account.bytecode_hash.unwrap_or(KECCAK_EMPTY), - code: None, - })) + Ok(self.basic_account(address)?.map(Into::into)) } /// Retrieves the bytecode associated with a given code hash. @@ -160,13 +155,10 @@ impl DatabaseRef for StateProviderDatabase { /// /// Returns `Ok` with the block hash if found, or the default hash otherwise. fn block_hash_ref(&self, number: U256) -> Result { - // Attempt to convert U256 to u64 - let block_number = match number.try_into() { - Ok(value) => value, - Err(_) => return Err(Self::Error::BlockNumberOverflow(number)), - }; - - // Get the block hash or default hash - Ok(self.0.block_hash(block_number)?.unwrap_or_default()) + // Get the block hash or default hash with an attempt to convert U256 block number to u64 + Ok(self + .0 + .block_hash(number.try_into().map_err(|_| Self::Error::BlockNumberOverflow(number))?)? + .unwrap_or_default()) } } From e6842fb8bd614daa56d9109b7d604355a40c6f30 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:24:52 +0200 Subject: [PATCH 275/405] refactor(chainspec): simplify `genesis_header` using default pattern (#9198) --- crates/chainspec/src/spec.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 44c363c6ce77..33ca2f4f0244 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -19,10 +19,7 @@ use reth_ethereum_forks::{ }; use reth_network_peers::NodeRecord; use reth_primitives_traits::{ - constants::{ - EIP1559_INITIAL_BASE_FEE, EMPTY_OMMER_ROOT_HASH, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, - EMPTY_WITHDRAWALS, - }, + constants::{EIP1559_INITIAL_BASE_FEE, EMPTY_WITHDRAWALS}, Header, SealedHeader, }; use reth_trie_common::root::state_root_ref_unhashed; @@ -443,12 +440,6 @@ impl ChainSpec { }; Header { - parent_hash: B256::ZERO, - number: 0, - transactions_root: EMPTY_TRANSACTIONS, - ommers_hash: EMPTY_OMMER_ROOT_HASH, - receipts_root: EMPTY_RECEIPTS, - logs_bloom: Default::default(), gas_limit: self.genesis.gas_limit as u64, difficulty: self.genesis.difficulty, nonce: self.genesis.nonce, @@ -457,13 +448,13 @@ impl ChainSpec { timestamp: self.genesis.timestamp, mix_hash: self.genesis.mix_hash, beneficiary: self.genesis.coinbase, - gas_used: Default::default(), base_fee_per_gas, withdrawals_root, parent_beacon_block_root, blob_gas_used, excess_blob_gas, requests_root, + ..Default::default() } } From 068bf57c06de0764b214e2f10c99e125825947c7 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 1 Jul 2024 04:26:44 -0700 Subject: [PATCH 276/405] fix: ambiguous deposit mint value in arbitrary (#9216) --- crates/primitives/src/transaction/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index c23d454f868d..1edafaaa2c0a 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1478,6 +1478,16 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned { if tx_eip_4844.to != Address::default() { Some(()) } else { None }; } + #[cfg(feature = "optimism")] + // Both `Some(0)` and `None` values are encoded as empty string byte. This introduces + // ambiguity in roundtrip tests. Patch the mint value of deposit transaction here, so that + // it's `None` if zero. + if let Transaction::Deposit(ref mut tx_deposit) = transaction { + if tx_deposit.mint == Some(0) { + tx_deposit.mint = None; + } + } + let signature = Signature::arbitrary(u)?; #[cfg(feature = "optimism")] From 01979c4bdea2297fdba08acdaeca8a33c3294c1a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 1 Jul 2024 14:03:44 +0200 Subject: [PATCH 277/405] feat: new engine API handler (#8559) Co-authored-by: Roman Krasiuk Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> Co-authored-by: Federico Gimenez --- CODEOWNERS | 3 +- Cargo.lock | 44 ++ Cargo.toml | 2 + .../consensus/beacon/src/engine/forkchoice.rs | 9 +- .../beacon/src/engine/invalid_headers.rs | 7 +- crates/consensus/beacon/src/engine/mod.rs | 7 +- crates/engine/tree/Cargo.toml | 64 ++ crates/engine/tree/src/backfill.rs | 342 +++++++++++ crates/engine/tree/src/chain.rs | 218 +++++++ crates/engine/tree/src/download.rs | 414 +++++++++++++ crates/engine/tree/src/engine.rs | 212 +++++++ crates/engine/tree/src/lib.rs | 31 + crates/engine/tree/src/metrics.rs | 9 + crates/engine/tree/src/persistence.rs | 139 +++++ crates/engine/tree/src/test_utils.rs | 21 + crates/engine/tree/src/tree/memory_overlay.rs | 123 ++++ crates/engine/tree/src/tree/mod.rs | 574 ++++++++++++++++++ 17 files changed, 2208 insertions(+), 11 deletions(-) create mode 100644 crates/engine/tree/Cargo.toml create mode 100644 crates/engine/tree/src/backfill.rs create mode 100644 crates/engine/tree/src/chain.rs create mode 100644 crates/engine/tree/src/download.rs create mode 100644 crates/engine/tree/src/engine.rs create mode 100644 crates/engine/tree/src/lib.rs create mode 100644 crates/engine/tree/src/metrics.rs create mode 100644 crates/engine/tree/src/persistence.rs create mode 100644 crates/engine/tree/src/test_utils.rs create mode 100644 crates/engine/tree/src/tree/memory_overlay.rs create mode 100644 crates/engine/tree/src/tree/mod.rs diff --git a/CODEOWNERS b/CODEOWNERS index 11c19aa43454..225d0f08b174 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,8 +6,9 @@ crates/chainspec/ @Rjected @joshieDo @mattsse crates/cli/ @onbjerg @mattsse crates/config/ @onbjerg crates/consensus/ @rkrasiuk @mattsse @Rjected +crates/engine @rkrasiuk @mattsse @Rjected crates/e2e-test-utils/ @mattsse @Rjected -crates/engine-primitives/ @rkrasiuk @mattsse @Rjected +crates/engine/ @rkrasiuk @mattsse @Rjected @fgimenez crates/errors/ @mattsse crates/ethereum/ @mattsse @Rjected crates/ethereum-forks/ @mattsse @Rjected diff --git a/Cargo.lock b/Cargo.lock index 962e60497108..0bdada0efe7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6966,6 +6966,50 @@ dependencies = [ "serde", ] +[[package]] +name = "reth-engine-tree" +version = "1.0.0" +dependencies = [ + "aquamarine", + "assert_matches", + "futures", + "metrics", + "parking_lot 0.12.3", + "reth-beacon-consensus", + "reth-blockchain-tree", + "reth-blockchain-tree-api", + "reth-chainspec", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-engine-primitives", + "reth-errors", + "reth-ethereum-consensus", + "reth-evm", + "reth-metrics", + "reth-network-p2p", + "reth-payload-builder", + "reth-payload-primitives", + "reth-payload-validator", + "reth-primitives", + "reth-provider", + "reth-prune", + "reth-prune-types", + "reth-revm", + "reth-rpc-types", + "reth-stages", + "reth-stages-api", + "reth-static-file", + "reth-tasks", + "reth-tokio-util", + "reth-tracing", + "reth-trie", + "revm", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "reth-engine-util" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 7110a39197ae..1308361d4a3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "crates/ethereum-forks/", "crates/e2e-test-utils/", "crates/engine/primitives/", + "crates/engine/tree/", "crates/engine/util/", "crates/errors/", "crates/ethereum-forks/", @@ -287,6 +288,7 @@ reth-downloaders = { path = "crates/net/downloaders" } reth-e2e-test-utils = { path = "crates/e2e-test-utils" } reth-ecies = { path = "crates/net/ecies" } reth-engine-primitives = { path = "crates/engine/primitives" } +reth-engine-tree = { path = "crates/engine/tree" } reth-engine-util = { path = "crates/engine/util" } reth-errors = { path = "crates/errors" } reth-eth-wire = { path = "crates/net/eth-wire" } diff --git a/crates/consensus/beacon/src/engine/forkchoice.rs b/crates/consensus/beacon/src/engine/forkchoice.rs index afd19f6079eb..491d0ff8aade 100644 --- a/crates/consensus/beacon/src/engine/forkchoice.rs +++ b/crates/consensus/beacon/src/engine/forkchoice.rs @@ -3,7 +3,7 @@ use reth_rpc_types::engine::{ForkchoiceState, PayloadStatusEnum}; /// The struct that keeps track of the received forkchoice state and their status. #[derive(Debug, Clone, Default)] -pub(crate) struct ForkchoiceStateTracker { +pub struct ForkchoiceStateTracker { /// The latest forkchoice state that we received. /// /// Caution: this can be invalid. @@ -76,7 +76,7 @@ impl ForkchoiceStateTracker { } /// Returns the last received `ForkchoiceState` to which we need to sync. - pub(crate) const fn sync_target_state(&self) -> Option { + pub const fn sync_target_state(&self) -> Option { self.last_syncing } @@ -139,9 +139,12 @@ impl From for ForkchoiceStatus { /// A helper type to check represent hashes of a [`ForkchoiceState`] #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum ForkchoiceStateHash { +pub enum ForkchoiceStateHash { + /// Head hash of the [`ForkchoiceState`]. Head(B256), + /// Safe hash of the [`ForkchoiceState`]. Safe(B256), + /// Finalized hash of the [`ForkchoiceState`]. Finalized(B256), } diff --git a/crates/consensus/beacon/src/engine/invalid_headers.rs b/crates/consensus/beacon/src/engine/invalid_headers.rs index 2a37c60014da..ebce1faf92dc 100644 --- a/crates/consensus/beacon/src/engine/invalid_headers.rs +++ b/crates/consensus/beacon/src/engine/invalid_headers.rs @@ -14,7 +14,8 @@ use tracing::warn; const INVALID_HEADER_HIT_EVICTION_THRESHOLD: u8 = 128; /// Keeps track of invalid headers. -pub(crate) struct InvalidHeaderCache { +#[derive(Debug)] +pub struct InvalidHeaderCache { /// This maps a header hash to a reference to its invalid ancestor. headers: LruMap, /// Metrics for the cache. @@ -34,7 +35,7 @@ impl InvalidHeaderCache { /// /// If this is called, the hit count for the entry is incremented. /// If the hit count exceeds the threshold, the entry is evicted and `None` is returned. - pub(crate) fn get(&mut self, hash: &B256) -> Option> { + pub fn get(&mut self, hash: &B256) -> Option> { { let entry = self.headers.get(hash)?; entry.hit_count += 1; @@ -49,7 +50,7 @@ impl InvalidHeaderCache { } /// Inserts an invalid block into the cache, with a given invalid ancestor. - pub(crate) fn insert_with_invalid_ancestor( + pub fn insert_with_invalid_ancestor( &mut self, header_hash: B256, invalid_ancestor: Arc

, diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index d08dcc259231..eba82c295d80 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -53,7 +53,7 @@ pub use error::{ }; mod invalid_headers; -use invalid_headers::InvalidHeaderCache; +pub use invalid_headers::InvalidHeaderCache; mod event; pub use event::{BeaconConsensusEngineEvent, ConsensusEngineLiveSyncProgress}; @@ -62,13 +62,12 @@ mod handle; pub use handle::BeaconConsensusEngineHandle; mod forkchoice; -pub use forkchoice::ForkchoiceStatus; -use forkchoice::{ForkchoiceStateHash, ForkchoiceStateTracker}; +pub use forkchoice::{ForkchoiceStateHash, ForkchoiceStateTracker, ForkchoiceStatus}; mod metrics; use metrics::EngineMetrics; -pub(crate) mod sync; +pub mod sync; use sync::{EngineSyncController, EngineSyncEvent}; /// Hooks for running during the main loop of diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml new file mode 100644 index 000000000000..bcc8ae34bddd --- /dev/null +++ b/crates/engine/tree/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "reth-engine-tree" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-beacon-consensus.workspace = true +reth-blockchain-tree.workspace = true +reth-blockchain-tree-api.workspace = true +reth-chainspec.workspace = true +reth-consensus.workspace = true +reth-db.workspace = true +reth-db-api.workspace = true +reth-engine-primitives.workspace = true +reth-errors.workspace = true +reth-ethereum-consensus.workspace = true +reth-evm.workspace = true +reth-network-p2p.workspace = true +reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true +reth-payload-validator.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true +reth-prune.workspace = true +reth-revm.workspace = true +reth-rpc-types.workspace = true +reth-stages-api.workspace = true +reth-static-file.workspace = true +reth-tasks.workspace = true +reth-tokio-util.workspace = true +reth-trie.workspace = true +revm.workspace = true + +# common +futures.workspace = true +tokio = { workspace = true, features = ["macros", "sync"] } +tokio-stream = { workspace = true, features = ["sync"] } + + +# metrics +metrics.workspace = true +reth-metrics = { workspace = true, features = ["common"] } + +# misc +aquamarine.workspace = true +parking_lot.workspace = true +tracing.workspace = true + +[dev-dependencies] +# reth +reth-network-p2p = { workspace = true, features = ["test-utils"] } +reth-prune-types.workspace = true +reth-stages = { workspace = true, features = ["test-utils"] } +reth-tracing.workspace = true + +assert_matches.workspace = true \ No newline at end of file diff --git a/crates/engine/tree/src/backfill.rs b/crates/engine/tree/src/backfill.rs new file mode 100644 index 000000000000..ec0b4ef06cc7 --- /dev/null +++ b/crates/engine/tree/src/backfill.rs @@ -0,0 +1,342 @@ +//! It is expected that the node has two sync modes: +//! +//! - Backfill sync: Sync to a certain block height in stages, e.g. download data from p2p then +//! execute that range. +//! - Live sync: In this mode the nodes is keeping up with the latest tip and listens for new +//! requests from the consensus client. +//! +//! These modes are mutually exclusive and the node can only be in one mode at a time. + +use futures::FutureExt; +use reth_db_api::database::Database; +use reth_stages_api::{ControlFlow, Pipeline, PipelineError, PipelineTarget, PipelineWithResult}; +use reth_tasks::TaskSpawner; +use std::task::{ready, Context, Poll}; +use tokio::sync::oneshot; +use tracing::trace; + +/// Backfill sync mode functionality. +pub trait BackfillSync: Send + Sync { + /// Performs a backfill action. + fn on_action(&mut self, action: BackfillAction); + + /// Polls the pipeline for completion. + fn poll(&mut self, cx: &mut Context<'_>) -> Poll; +} + +/// The backfill actions that can be performed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BackfillAction { + /// Start backfilling with the given target. + Start(PipelineTarget), +} + +/// The events that can be emitted on backfill sync. +#[derive(Debug)] +pub enum BackfillEvent { + /// Backfill sync idle. + Idle, + /// Backfill sync started. + Started(PipelineTarget), + /// Backfill sync finished. + /// + /// If this is returned, backfill sync is idle. + Finished(Result), + /// Sync task was dropped after it was started, unable to receive it because + /// channel closed. This would indicate a panicked task. + TaskDropped(String), +} + +/// Pipeline sync. +#[derive(Debug)] +pub struct PipelineSync +where + DB: Database, +{ + /// The type that can spawn the pipeline task. + pipeline_task_spawner: Box, + /// The current state of the pipeline. + /// The pipeline is used for large ranges. + pipeline_state: PipelineState, + /// Pending target block for the pipeline to sync + pending_pipeline_target: Option, +} + +impl PipelineSync +where + DB: Database + 'static, +{ + /// Create a new instance. + pub fn new(pipeline: Pipeline, pipeline_task_spawner: Box) -> Self { + Self { + pipeline_task_spawner, + pipeline_state: PipelineState::Idle(Some(pipeline)), + pending_pipeline_target: None, + } + } + + /// Returns `true` if a pipeline target is queued and will be triggered on the next `poll`. + #[allow(dead_code)] + const fn is_pipeline_sync_pending(&self) -> bool { + self.pending_pipeline_target.is_some() && self.pipeline_state.is_idle() + } + + /// Returns `true` if the pipeline is idle. + const fn is_pipeline_idle(&self) -> bool { + self.pipeline_state.is_idle() + } + + /// Returns `true` if the pipeline is active. + const fn is_pipeline_active(&self) -> bool { + !self.is_pipeline_idle() + } + + /// Sets a new target to sync the pipeline to. + /// + /// But ensures the target is not the zero hash. + fn set_pipeline_sync_target(&mut self, target: PipelineTarget) { + if target.sync_target().is_some_and(|target| target.is_zero()) { + trace!( + target: "consensus::engine::sync", + "Pipeline target cannot be zero hash." + ); + // precaution to never sync to the zero hash + return + } + self.pending_pipeline_target = Some(target); + } + + /// This will spawn the pipeline if it is idle and a target is set or if the pipeline is set to + /// run continuously. + fn try_spawn_pipeline(&mut self) -> Option { + match &mut self.pipeline_state { + PipelineState::Idle(pipeline) => { + let target = self.pending_pipeline_target.take()?; + let (tx, rx) = oneshot::channel(); + + let pipeline = pipeline.take().expect("exists"); + self.pipeline_task_spawner.spawn_critical_blocking( + "pipeline task", + Box::pin(async move { + let result = pipeline.run_as_fut(Some(target)).await; + let _ = tx.send(result); + }), + ); + self.pipeline_state = PipelineState::Running(rx); + + Some(BackfillEvent::Started(target)) + } + PipelineState::Running(_) => None, + } + } + + /// Advances the pipeline state. + /// + /// This checks for the result in the channel, or returns pending if the pipeline is idle. + fn poll_pipeline(&mut self, cx: &mut Context<'_>) -> Poll { + let res = match self.pipeline_state { + PipelineState::Idle(_) => return Poll::Pending, + PipelineState::Running(ref mut fut) => { + ready!(fut.poll_unpin(cx)) + } + }; + let ev = match res { + Ok((_, result)) => BackfillEvent::Finished(result), + Err(why) => { + // failed to receive the pipeline + BackfillEvent::TaskDropped(why.to_string()) + } + }; + Poll::Ready(ev) + } +} + +impl BackfillSync for PipelineSync +where + DB: Database + 'static, +{ + fn on_action(&mut self, event: BackfillAction) { + match event { + BackfillAction::Start(target) => self.set_pipeline_sync_target(target), + } + } + + fn poll(&mut self, cx: &mut Context<'_>) -> Poll { + // try to spawn a pipeline if a target is set + if let Some(event) = self.try_spawn_pipeline() { + return Poll::Ready(event) + } + + // make sure we poll the pipeline if it's active, and return any ready pipeline events + if !self.is_pipeline_idle() { + // advance the pipeline + if let Poll::Ready(event) = self.poll_pipeline(cx) { + return Poll::Ready(event) + } + } + + Poll::Pending + } +} + +/// The possible pipeline states within the sync controller. +/// +/// [`PipelineState::Idle`] means that the pipeline is currently idle. +/// [`PipelineState::Running`] means that the pipeline is currently running. +/// +/// NOTE: The differentiation between these two states is important, because when the pipeline is +/// running, it acquires the write lock over the database. This means that we cannot forward to the +/// blockchain tree any messages that would result in database writes, since it would result in a +/// deadlock. +#[derive(Debug)] +enum PipelineState { + /// Pipeline is idle. + Idle(Option>), + /// Pipeline is running and waiting for a response + Running(oneshot::Receiver>), +} + +impl PipelineState { + /// Returns `true` if the state matches idle. + const fn is_idle(&self) -> bool { + matches!(self, Self::Idle(_)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::insert_headers_into_client; + use assert_matches::assert_matches; + use futures::poll; + use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET}; + use reth_db::{mdbx::DatabaseEnv, test_utils::TempDatabase}; + use reth_network_p2p::test_utils::TestFullBlockClient; + use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, BlockNumber, Header, B256}; + use reth_provider::{ + test_utils::create_test_provider_factory_with_chain_spec, ExecutionOutcome, + }; + use reth_prune_types::PruneModes; + use reth_stages::{test_utils::TestStages, ExecOutput, StageError}; + use reth_stages_api::StageCheckpoint; + use reth_static_file::StaticFileProducer; + use reth_tasks::TokioTaskExecutor; + use std::{collections::VecDeque, future::poll_fn, sync::Arc}; + use tokio::sync::watch; + + struct TestHarness { + pipeline_sync: PipelineSync>>, + tip: B256, + } + + impl TestHarness { + fn new(total_blocks: usize, pipeline_done_after: u64) -> Self { + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(MAINNET.genesis.clone()) + .paris_activated() + .build(), + ); + + // force the pipeline to be "done" after 5 blocks + let pipeline = TestPipelineBuilder::new() + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { + checkpoint: StageCheckpoint::new(BlockNumber::from(pipeline_done_after)), + done: true, + })])) + .build(chain_spec); + + let pipeline_sync = PipelineSync::new(pipeline, Box::::default()); + let client = TestFullBlockClient::default(); + let header = Header { + base_fee_per_gas: Some(7), + gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, + ..Default::default() + } + .seal_slow(); + insert_headers_into_client(&client, header, 0..total_blocks); + + let tip = client.highest_block().expect("there should be blocks here").hash(); + + Self { pipeline_sync, tip } + } + } + + struct TestPipelineBuilder { + pipeline_exec_outputs: VecDeque>, + executor_results: Vec, + } + + impl TestPipelineBuilder { + /// Create a new [`TestPipelineBuilder`]. + const fn new() -> Self { + Self { pipeline_exec_outputs: VecDeque::new(), executor_results: Vec::new() } + } + + /// Set the pipeline execution outputs to use for the test consensus engine. + fn with_pipeline_exec_outputs( + mut self, + pipeline_exec_outputs: VecDeque>, + ) -> Self { + self.pipeline_exec_outputs = pipeline_exec_outputs; + self + } + + /// Set the executor results to use for the test consensus engine. + #[allow(dead_code)] + fn with_executor_results(mut self, executor_results: Vec) -> Self { + self.executor_results = executor_results; + self + } + + /// Builds the pipeline. + fn build(self, chain_spec: Arc) -> Pipeline>> { + reth_tracing::init_test_tracing(); + + // Setup pipeline + let (tip_tx, _tip_rx) = watch::channel(B256::default()); + let pipeline = Pipeline::builder() + .add_stages(TestStages::new(self.pipeline_exec_outputs, Default::default())) + .with_tip_sender(tip_tx); + + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); + + let static_file_producer = + StaticFileProducer::new(provider_factory.clone(), PruneModes::default()); + + pipeline.build(provider_factory, static_file_producer) + } + } + + #[tokio::test] + async fn pipeline_started_and_finished() { + const TOTAL_BLOCKS: usize = 10; + const PIPELINE_DONE_AFTER: u64 = 5; + let TestHarness { mut pipeline_sync, tip } = + TestHarness::new(TOTAL_BLOCKS, PIPELINE_DONE_AFTER); + + let sync_future = poll_fn(|cx| pipeline_sync.poll(cx)); + let next_event = poll!(sync_future); + + // sync target not set, pipeline not started + assert_matches!(next_event, Poll::Pending); + + pipeline_sync.on_action(BackfillAction::Start(PipelineTarget::Sync(tip))); + + let sync_future = poll_fn(|cx| pipeline_sync.poll(cx)); + let next_event = poll!(sync_future); + + // sync target set, pipeline started + assert_matches!(next_event, Poll::Ready(BackfillEvent::Started(target)) => { + assert_eq!(target.sync_target().unwrap(), tip); + }); + + // the next event should be the pipeline finishing in a good state + let sync_future = poll_fn(|cx| pipeline_sync.poll(cx)); + let next_ready = sync_future.await; + assert_matches!(next_ready, BackfillEvent::Finished(result) => { + assert_matches!(result, Ok(control_flow) => assert_eq!(control_flow, ControlFlow::Continue { block_number: PIPELINE_DONE_AFTER })); + }); + } +} diff --git a/crates/engine/tree/src/chain.rs b/crates/engine/tree/src/chain.rs new file mode 100644 index 000000000000..97c6d615c118 --- /dev/null +++ b/crates/engine/tree/src/chain.rs @@ -0,0 +1,218 @@ +use crate::backfill::{BackfillAction, BackfillEvent, BackfillSync}; +use futures::Stream; +use reth_stages_api::PipelineTarget; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +/// The type that drives the chain forward. +/// +/// A state machine that orchestrates the components responsible for advancing the chain +/// +/// +/// ## Control flow +/// +/// The [`ChainOrchestrator`] is responsible for controlling the pipeline sync and additional hooks. +/// It polls the given `handler`, which is responsible for advancing the chain, how is up to the +/// handler. However, due to database restrictions (e.g. exclusive write access), following +/// invariants apply: +/// - If the handler requests a backfill run (e.g. [`BackfillAction::Start`]), the handler must +/// ensure that while the pipeline is running, no other write access is granted. +/// - At any time the [`ChainOrchestrator`] can request exclusive write access to the database +/// (e.g. if pruning is required), but will not do so until the handler has acknowledged the +/// request for write access. +/// +/// The [`ChainOrchestrator`] polls the [`ChainHandler`] to advance the chain and handles the +/// emitted events. Requests and events are passed to the [`ChainHandler`] via +/// [`ChainHandler::on_event`]. +#[must_use = "Stream does nothing unless polled"] +#[derive(Debug)] +pub struct ChainOrchestrator +where + T: ChainHandler, + P: BackfillSync, +{ + /// The handler for advancing the chain. + handler: T, + /// Controls pipeline sync. + pipeline: P, +} + +impl ChainOrchestrator +where + T: ChainHandler + Unpin, + P: BackfillSync + Unpin, +{ + /// Creates a new [`ChainOrchestrator`] with the given handler and pipeline. + pub const fn new(handler: T, pipeline: P) -> Self { + Self { handler, pipeline } + } + + /// Returns the handler + pub const fn handler(&self) -> &T { + &self.handler + } + + /// Returns a mutable reference to the handler + pub fn handler_mut(&mut self) -> &mut T { + &mut self.handler + } + + /// Internal function used to advance the chain. + /// + /// Polls the `ChainOrchestrator` for the next event. + #[tracing::instrument(level = "debug", name = "ChainOrchestrator::poll", skip(self, cx))] + fn poll_next_event(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + // This loop polls the components + // + // 1. Polls the pipeline to completion, if active. + // 2. Advances the chain by polling the handler. + 'outer: loop { + // try to poll the pipeline to completion, if active + match this.pipeline.poll(cx) { + Poll::Ready(pipeline_event) => match pipeline_event { + BackfillEvent::Idle => {} + BackfillEvent::Started(_) => { + // notify handler that pipeline started + this.handler.on_event(FromOrchestrator::PipelineStarted); + return Poll::Ready(ChainEvent::PipelineStarted); + } + BackfillEvent::Finished(res) => { + return match res { + Ok(event) => { + tracing::debug!(?event, "pipeline finished"); + // notify handler that pipeline finished + this.handler.on_event(FromOrchestrator::PipelineFinished); + Poll::Ready(ChainEvent::PipelineFinished) + } + Err(err) => { + tracing::error!( %err, "pipeline failed"); + Poll::Ready(ChainEvent::FatalError) + } + } + } + BackfillEvent::TaskDropped(err) => { + tracing::error!( %err, "pipeline task dropped"); + return Poll::Ready(ChainEvent::FatalError); + } + }, + Poll::Pending => {} + } + + // poll the handler for the next event + match this.handler.poll(cx) { + Poll::Ready(handler_event) => { + match handler_event { + HandlerEvent::Pipeline(target) => { + // trigger pipeline and start polling it + this.pipeline.on_action(BackfillAction::Start(target)); + continue 'outer + } + HandlerEvent::Event(ev) => { + // bubble up the event + return Poll::Ready(ChainEvent::Handler(ev)); + } + } + } + Poll::Pending => { + // no more events to process + break 'outer + } + } + } + + Poll::Pending + } +} + +impl Stream for ChainOrchestrator +where + T: ChainHandler + Unpin, + P: BackfillSync + Unpin, +{ + type Item = ChainEvent; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.as_mut().poll_next_event(cx).map(Some) + } +} + +/// Represents the sync mode the chain is operating in. +#[derive(Debug, Default)] +enum SyncMode { + #[default] + Handler, + Backfill, +} + +/// Event emitted by the [`ChainOrchestrator`] +/// +/// These are meant to be used for observability and debugging purposes. +#[derive(Debug)] +pub enum ChainEvent { + /// Pipeline sync started + PipelineStarted, + /// Pipeline sync finished + PipelineFinished, + /// Fatal error + FatalError, + /// Event emitted by the handler + Handler(T), +} + +/// A trait that advances the chain by handling actions. +/// +/// This is intended to be implement the chain consensus logic, for example `engine` API. +pub trait ChainHandler: Send + Sync { + /// Event generated by this handler that orchestrator can bubble up; + type Event: Send; + + /// Informs the handler about an event from the [`ChainOrchestrator`]. + fn on_event(&mut self, event: FromOrchestrator); + + /// Polls for actions that [`ChainOrchestrator`] should handle. + fn poll(&mut self, cx: &mut Context<'_>) -> Poll>; +} + +/// Events/Requests that the [`ChainHandler`] can emit to the [`ChainOrchestrator`]. +#[derive(Clone, Debug)] +pub enum HandlerEvent { + /// Request to start a pipeline sync + Pipeline(PipelineTarget), + /// Other event emitted by the handler + Event(T), +} + +/// Internal events issued by the [`ChainOrchestrator`]. +#[derive(Clone, Debug)] +pub enum FromOrchestrator { + /// Invoked when pipeline sync finished + PipelineFinished, + /// Invoked when pipeline started + PipelineStarted, +} + +/// Represents the state of the chain. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +pub enum OrchestratorState { + /// Orchestrator has exclusive write access to the database. + PipelineActive, + /// Node is actively processing the chain. + #[default] + Idle, +} + +impl OrchestratorState { + /// Returns `true` if the state is [`OrchestratorState::PipelineActive`]. + pub const fn is_pipeline_active(&self) -> bool { + matches!(self, Self::PipelineActive) + } + + /// Returns `true` if the state is [`OrchestratorState::Idle`]. + pub const fn is_idle(&self) -> bool { + matches!(self, Self::Idle) + } +} diff --git a/crates/engine/tree/src/download.rs b/crates/engine/tree/src/download.rs new file mode 100644 index 000000000000..12b2bd189288 --- /dev/null +++ b/crates/engine/tree/src/download.rs @@ -0,0 +1,414 @@ +//! Handler that can download blocks on demand (e.g. from the network). + +use crate::{engine::DownloadRequest, metrics::BlockDownloaderMetrics}; +use futures::FutureExt; +use reth_consensus::Consensus; +use reth_network_p2p::{ + bodies::client::BodiesClient, + full_block::{FetchFullBlockFuture, FetchFullBlockRangeFuture, FullBlockClient}, + headers::client::HeadersClient, +}; +use reth_primitives::{SealedBlock, SealedBlockWithSenders, B256}; +use std::{ + cmp::{Ordering, Reverse}, + collections::{binary_heap::PeekMut, BinaryHeap, HashSet}, + sync::Arc, + task::{Context, Poll}, +}; +use tracing::trace; + +/// A trait that can download blocks on demand. +pub trait BlockDownloader: Send + Sync { + /// Handle an action. + fn on_action(&mut self, event: DownloadAction); + + /// Advance in progress requests if any + fn poll(&mut self, cx: &mut Context<'_>) -> Poll; +} + +/// Actions that can be performed by the block downloader. +#[derive(Debug)] +pub enum DownloadAction { + /// Stop downloading blocks. + Clear, + /// Download given blocks + Download(DownloadRequest), +} + +/// Outcome of downloaded blocks. +#[derive(Debug)] +pub enum DownloadOutcome { + /// Downloaded blocks. + Blocks(Vec), +} + +/// Basic [`BlockDownloader`]. +pub struct BasicBlockDownloader +where + Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, +{ + /// A downloader that can download full blocks from the network. + full_block_client: FullBlockClient, + /// In-flight full block requests in progress. + inflight_full_block_requests: Vec>, + /// In-flight full block _range_ requests in progress. + inflight_block_range_requests: Vec>, + /// Buffered blocks from downloads - this is a min-heap of blocks, using the block number for + /// ordering. This means the blocks will be popped from the heap with ascending block numbers. + set_buffered_blocks: BinaryHeap>, + /// Engine download metrics. + metrics: BlockDownloaderMetrics, +} + +impl BasicBlockDownloader +where + Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, +{ + /// Create a new instance + pub(crate) fn new(client: Client, consensus: Arc) -> Self { + Self { + full_block_client: FullBlockClient::new(client, consensus), + inflight_full_block_requests: Vec::new(), + inflight_block_range_requests: Vec::new(), + set_buffered_blocks: BinaryHeap::new(), + metrics: BlockDownloaderMetrics::default(), + } + } + + /// Clears the stored inflight requests. + fn clear(&mut self) { + self.inflight_full_block_requests.clear(); + self.inflight_block_range_requests.clear(); + self.set_buffered_blocks.clear(); + self.update_block_download_metrics(); + } + + /// Processes a download request. + fn download(&mut self, request: DownloadRequest) { + match request { + DownloadRequest::BlockSet(hashes) => self.download_block_set(hashes), + DownloadRequest::BlockRange(hash, count) => self.download_block_range(hash, count), + } + } + + /// Processes a block set download request. + fn download_block_set(&mut self, hashes: HashSet) { + for hash in hashes { + self.download_full_block(hash); + } + } + + /// Processes a block range download request. + fn download_block_range(&mut self, hash: B256, count: u64) { + if count == 1 { + self.download_full_block(hash); + } else { + trace!( + target: "consensus::engine", + ?hash, + ?count, + "start downloading full block range." + ); + + let request = self.full_block_client.get_full_block_range(hash, count); + self.inflight_block_range_requests.push(request); + } + } + + /// Starts requesting a full block from the network. + /// + /// Returns `true` if the request was started, `false` if there's already a request for the + /// given hash. + fn download_full_block(&mut self, hash: B256) -> bool { + if self.is_inflight_request(hash) { + return false + } + trace!( + target: "consensus::engine::sync", + ?hash, + "Start downloading full block" + ); + + let request = self.full_block_client.get_full_block(hash); + self.inflight_full_block_requests.push(request); + + self.update_block_download_metrics(); + + true + } + + /// Returns true if there's already a request for the given hash. + fn is_inflight_request(&self, hash: B256) -> bool { + self.inflight_full_block_requests.iter().any(|req| *req.hash() == hash) + } + + /// Sets the metrics for the active downloads + fn update_block_download_metrics(&self) { + self.metrics.active_block_downloads.set(self.inflight_full_block_requests.len() as f64); + // TODO: full block range metrics + } +} + +impl BlockDownloader for BasicBlockDownloader +where + Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, +{ + /// Handles incoming download actions. + fn on_action(&mut self, event: DownloadAction) { + match event { + DownloadAction::Clear => self.clear(), + DownloadAction::Download(request) => self.download(request), + } + } + + /// Advances the download process. + fn poll(&mut self, cx: &mut Context<'_>) -> Poll { + // advance all full block requests + for idx in (0..self.inflight_full_block_requests.len()).rev() { + let mut request = self.inflight_full_block_requests.swap_remove(idx); + if let Poll::Ready(block) = request.poll_unpin(cx) { + trace!(target: "consensus::engine", block=?block.num_hash(), "Received single full block, buffering"); + self.set_buffered_blocks.push(Reverse(block.into())); + } else { + // still pending + self.inflight_full_block_requests.push(request); + } + } + + // advance all full block range requests + for idx in (0..self.inflight_block_range_requests.len()).rev() { + let mut request = self.inflight_block_range_requests.swap_remove(idx); + if let Poll::Ready(blocks) = request.poll_unpin(cx) { + trace!(target: "consensus::engine", len=?blocks.len(), first=?blocks.first().map(|b| b.num_hash()), last=?blocks.last().map(|b| b.num_hash()), "Received full block range, buffering"); + self.set_buffered_blocks.extend( + blocks + .into_iter() + .map(|b| { + let senders = b.senders().unwrap_or_default(); + OrderedSealedBlockWithSenders(SealedBlockWithSenders { + block: b, + senders, + }) + }) + .map(Reverse), + ); + } else { + // still pending + self.inflight_block_range_requests.push(request); + } + } + + self.update_block_download_metrics(); + + if self.set_buffered_blocks.is_empty() { + return Poll::Pending; + } + + // drain all unique element of the block buffer if there are any + let mut downloaded_blocks: Vec = + Vec::with_capacity(self.set_buffered_blocks.len()); + while let Some(block) = self.set_buffered_blocks.pop() { + // peek ahead and pop duplicates + while let Some(peek) = self.set_buffered_blocks.peek_mut() { + if peek.0 .0.hash() == block.0 .0.hash() { + PeekMut::pop(peek); + } else { + break + } + } + downloaded_blocks.push(block.0.into()); + } + Poll::Ready(DownloadOutcome::Blocks(downloaded_blocks)) + } +} + +/// A wrapper type around [`SealedBlockWithSenders`] that implements the [Ord] +/// trait by block number. +#[derive(Debug, Clone, PartialEq, Eq)] +struct OrderedSealedBlockWithSenders(SealedBlockWithSenders); + +impl PartialOrd for OrderedSealedBlockWithSenders { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for OrderedSealedBlockWithSenders { + fn cmp(&self, other: &Self) -> Ordering { + self.0.number.cmp(&other.0.number) + } +} + +impl From for OrderedSealedBlockWithSenders { + fn from(block: SealedBlock) -> Self { + let senders = block.senders().unwrap_or_default(); + Self(SealedBlockWithSenders { block, senders }) + } +} + +impl From for SealedBlockWithSenders { + fn from(value: OrderedSealedBlockWithSenders) -> Self { + let senders = value.0.senders; + Self { block: value.0.block, senders } + } +} + +/// A [`BlockDownloader`] that does nothing. +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct NoopBlockDownloader; + +impl BlockDownloader for NoopBlockDownloader { + fn on_action(&mut self, _event: DownloadAction) {} + + fn poll(&mut self, _cx: &mut Context<'_>) -> Poll { + Poll::Pending + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::insert_headers_into_client; + use assert_matches::assert_matches; + use reth_beacon_consensus::EthBeaconConsensus; + use reth_chainspec::{ChainSpecBuilder, MAINNET}; + use reth_network_p2p::test_utils::TestFullBlockClient; + use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, Header}; + use std::{future::poll_fn, sync::Arc}; + + struct TestHarness { + block_downloader: BasicBlockDownloader, + client: TestFullBlockClient, + } + + impl TestHarness { + fn new(total_blocks: usize) -> Self { + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(MAINNET.genesis.clone()) + .paris_activated() + .build(), + ); + + let client = TestFullBlockClient::default(); + let header = Header { + base_fee_per_gas: Some(7), + gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, + ..Default::default() + } + .seal_slow(); + + insert_headers_into_client(&client, header, 0..total_blocks); + let consensus = Arc::new(EthBeaconConsensus::new(chain_spec)); + + let block_downloader = BasicBlockDownloader::new(client.clone(), consensus); + Self { block_downloader, client } + } + } + + #[tokio::test] + async fn block_downloader_range_request() { + const TOTAL_BLOCKS: usize = 10; + let TestHarness { mut block_downloader, client } = TestHarness::new(TOTAL_BLOCKS); + let tip = client.highest_block().expect("there should be blocks here"); + + // send block range download request + block_downloader.on_action(DownloadAction::Download(DownloadRequest::BlockRange( + tip.hash(), + tip.number, + ))); + + // ensure we have one in flight range request + assert_eq!(block_downloader.inflight_block_range_requests.len(), 1); + + // ensure the range request is made correctly + let first_req = block_downloader.inflight_block_range_requests.first().unwrap(); + assert_eq!(first_req.start_hash(), tip.hash()); + assert_eq!(first_req.count(), tip.number); + + // poll downloader + let sync_future = poll_fn(|cx| block_downloader.poll(cx)); + let next_ready = sync_future.await; + + assert_matches!(next_ready, DownloadOutcome::Blocks(blocks) => { + // ensure all blocks were obtained + assert_eq!(blocks.len(), TOTAL_BLOCKS); + + // ensure they are in ascending order + for num in 1..=TOTAL_BLOCKS { + assert_eq!(blocks[num-1].number, num as u64); + } + }); + } + + #[tokio::test] + async fn block_downloader_set_request() { + const TOTAL_BLOCKS: usize = 2; + let TestHarness { mut block_downloader, client } = TestHarness::new(TOTAL_BLOCKS); + + let tip = client.highest_block().expect("there should be blocks here"); + + // send block set download request + block_downloader.on_action(DownloadAction::Download(DownloadRequest::BlockSet( + HashSet::from([tip.hash(), tip.parent_hash]), + ))); + + // ensure we have TOTAL_BLOCKS in flight full block request + assert_eq!(block_downloader.inflight_full_block_requests.len(), TOTAL_BLOCKS); + + // poll downloader + let sync_future = poll_fn(|cx| block_downloader.poll(cx)); + let next_ready = sync_future.await; + + assert_matches!(next_ready, DownloadOutcome::Blocks(blocks) => { + // ensure all blocks were obtained + assert_eq!(blocks.len(), TOTAL_BLOCKS); + + // ensure they are in ascending order + for num in 1..=TOTAL_BLOCKS { + assert_eq!(blocks[num-1].number, num as u64); + } + }); + } + + #[tokio::test] + async fn block_downloader_clear_request() { + const TOTAL_BLOCKS: usize = 10; + let TestHarness { mut block_downloader, client } = TestHarness::new(TOTAL_BLOCKS); + + let tip = client.highest_block().expect("there should be blocks here"); + + // send block range download request + block_downloader.on_action(DownloadAction::Download(DownloadRequest::BlockRange( + tip.hash(), + tip.number, + ))); + + // send block set download request + let download_set = HashSet::from([tip.hash(), tip.parent_hash]); + block_downloader + .on_action(DownloadAction::Download(DownloadRequest::BlockSet(download_set.clone()))); + + // ensure we have one in flight range request + assert_eq!(block_downloader.inflight_block_range_requests.len(), 1); + + // ensure the range request is made correctly + let first_req = block_downloader.inflight_block_range_requests.first().unwrap(); + assert_eq!(first_req.start_hash(), tip.hash()); + assert_eq!(first_req.count(), tip.number); + + // ensure we have download_set.len() in flight full block request + assert_eq!(block_downloader.inflight_full_block_requests.len(), download_set.len()); + + // send clear request + block_downloader.on_action(DownloadAction::Clear); + + // ensure we have no in flight range request + assert_eq!(block_downloader.inflight_block_range_requests.len(), 0); + + // ensure we have no in flight full block request + assert_eq!(block_downloader.inflight_full_block_requests.len(), 0); + } +} diff --git a/crates/engine/tree/src/engine.rs b/crates/engine/tree/src/engine.rs new file mode 100644 index 000000000000..d77162e4cd0a --- /dev/null +++ b/crates/engine/tree/src/engine.rs @@ -0,0 +1,212 @@ +//! An engine API handler for the chain. + +use crate::{ + chain::{ChainHandler, FromOrchestrator, HandlerEvent}, + download::{BlockDownloader, DownloadAction, DownloadOutcome}, +}; +use futures::{Stream, StreamExt}; +use reth_beacon_consensus::BeaconEngineMessage; +use reth_engine_primitives::EngineTypes; +use reth_primitives::{SealedBlockWithSenders, B256}; +use std::{ + collections::HashSet, + task::{Context, Poll}, +}; +use tokio::sync::mpsc; + +/// Advances the chain based on incoming requests. +/// +/// This is a general purpose request handler with network access. +/// This type listens for incoming messages and processes them via the configured request handler. +/// +/// ## Overview +/// +/// This type is an orchestrator for incoming messages and responsible for delegating requests +/// received from the CL to the handler. +/// +/// It is responsible for handling the following: +/// - Downloading blocks on demand from the network if requested by the [`EngineApiRequestHandler`]. +/// +/// The core logic is part of the [`EngineRequestHandler`], which is responsible for processing the +/// incoming requests. +#[derive(Debug)] +pub struct EngineHandler { + /// Processes requests. + /// + /// This type is responsible for processing incoming requests. + handler: T, + /// Receiver for incoming requests that need to be processed. + incoming_requests: S, + /// A downloader to download blocks on demand. + downloader: D, +} + +impl EngineHandler { + /// Creates a new [`EngineHandler`] with the given handler and downloader. + pub const fn new(handler: T, downloader: D, incoming_requests: S) -> Self + where + T: EngineRequestHandler, + { + Self { handler, incoming_requests, downloader } + } +} + +impl ChainHandler for EngineHandler +where + T: EngineRequestHandler, + S: Stream + Send + Sync + Unpin + 'static, + D: BlockDownloader, +{ + type Event = T::Event; + + fn on_event(&mut self, event: FromOrchestrator) { + // delegate event to the handler + self.handler.on_event(event.into()); + } + + fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { + loop { + // drain the handler first + while let Poll::Ready(ev) = self.handler.poll(cx) { + match ev { + RequestHandlerEvent::Idle => break, + RequestHandlerEvent::HandlerEvent(ev) => { + return match ev { + HandlerEvent::Pipeline(target) => { + // bubble up pipeline request + self.downloader.on_action(DownloadAction::Clear); + Poll::Ready(HandlerEvent::Pipeline(target)) + } + HandlerEvent::Event(ev) => { + // bubble up the event + Poll::Ready(HandlerEvent::Event(ev)) + } + } + } + RequestHandlerEvent::Download(req) => { + // delegate download request to the downloader + self.downloader.on_action(DownloadAction::Download(req)); + } + } + } + + // pop the next incoming request + if let Poll::Ready(Some(req)) = self.incoming_requests.poll_next_unpin(cx) { + // and delegate the request to the handler + self.handler.on_event(FromEngine::Request(req)); + // skip downloading in this iteration to allow the handler to process the request + continue + } + + // advance the downloader + if let Poll::Ready(DownloadOutcome::Blocks(blocks)) = self.downloader.poll(cx) { + // delegate the downloaded blocks to the handler + self.handler.on_event(FromEngine::DownloadedBlocks(blocks)); + continue + } + + return Poll::Pending + } + } +} + +/// A type that processes incoming requests (e.g. requests from the consensus layer, engine API) +pub trait EngineRequestHandler: Send + Sync { + /// Even type this handler can emit + type Event: Send; + /// The request type this handler can process. + type Request; + + /// Informs the handler about an event from the [`EngineHandler`]. + fn on_event(&mut self, event: FromEngine); + + /// Advances the handler. + fn poll(&mut self, cx: &mut Context<'_>) -> Poll>; +} + +/// An [`EngineRequestHandler`] that processes engine API requests by delegating to an execution +/// task. +/// +/// This type is responsible for advancing the chain during live sync (following the tip of the +/// chain). +/// +/// It advances the chain based on received engine API requests by delegating them to the tree +/// executor. +/// +/// There are two types of requests that can be processed: +/// +/// - `on_new_payload`: Executes the payload and inserts it into the tree. These are allowed to be +/// processed concurrently. +/// - `on_forkchoice_updated`: Updates the fork choice based on the new head. These require write +/// access to the database and are skipped if the handler can't acquire exclusive access to the +/// database. +/// +/// In case required blocks are missing, the handler will request them from the network, by emitting +/// a download request upstream. +#[derive(Debug)] +pub struct EngineApiRequestHandler { + /// channel to send messages to the tree to execute the payload. + to_tree: std::sync::mpsc::Sender>>, + /// channel to receive messages from the tree. + from_tree: mpsc::UnboundedReceiver, + // TODO add db controller +} + +impl EngineApiRequestHandler where T: EngineTypes {} + +impl EngineRequestHandler for EngineApiRequestHandler +where + T: EngineTypes, +{ + type Event = EngineApiEvent; + type Request = BeaconEngineMessage; + + fn on_event(&mut self, event: FromEngine) { + // delegate to the tree + let _ = self.to_tree.send(event); + } + + fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { + todo!("poll tree and handle db") + } +} + +/// Events emitted by the engine API handler. +#[derive(Debug)] +pub enum EngineApiEvent {} + +#[derive(Debug)] +pub enum FromEngine { + /// Event from the top level orchestrator. + Event(FromOrchestrator), + /// Request from the engine + Request(Req), + /// Downloaded blocks from the network. + DownloadedBlocks(Vec), +} + +impl From for FromEngine { + fn from(event: FromOrchestrator) -> Self { + Self::Event(event) + } +} + +/// Requests produced by a [`EngineRequestHandler`]. +#[derive(Debug)] +pub enum RequestHandlerEvent { + /// The handler is idle. + Idle, + /// An event emitted by the handler. + HandlerEvent(HandlerEvent), + /// Request to download blocks. + Download(DownloadRequest), +} + +/// A request to download blocks from the network. +#[derive(Debug)] +pub enum DownloadRequest { + /// Download the given set of blocks. + BlockSet(HashSet), + /// Download the given range of blocks. + BlockRange(B256, u64), +} diff --git a/crates/engine/tree/src/lib.rs b/crates/engine/tree/src/lib.rs new file mode 100644 index 000000000000..52ef90e4b637 --- /dev/null +++ b/crates/engine/tree/src/lib.rs @@ -0,0 +1,31 @@ +//! This crate includes the core components for advancing a reth chain. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +// #![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![allow(missing_docs, dead_code, missing_debug_implementations, unused_variables)] // TODO rm + +/// Re-export of the blockchain tree API. +pub use reth_blockchain_tree_api::*; + +/// Support for backfill sync mode. +pub mod backfill; +/// The type that drives the chain forward. +pub mod chain; +/// Support for downloading blocks on demand for live sync. +pub mod download; +/// Engine Api chain handler support. +pub mod engine; +/// Metrics support. +pub mod metrics; +/// The background writer task for batch db writes. +pub mod persistence; +/// Support for interacting with the blockchain tree. +pub mod tree; + +#[cfg(test)] +mod test_utils; diff --git a/crates/engine/tree/src/metrics.rs b/crates/engine/tree/src/metrics.rs new file mode 100644 index 000000000000..9579affe690f --- /dev/null +++ b/crates/engine/tree/src/metrics.rs @@ -0,0 +1,9 @@ +use reth_metrics::{metrics::Gauge, Metrics}; + +/// Metrics for the `BasicBlockDownloader`. +#[derive(Metrics)] +#[metrics(scope = "consensus.engine.beacon")] +pub(crate) struct BlockDownloaderMetrics { + /// How many blocks are currently being downloaded. + pub(crate) active_block_downloads: Gauge, +} diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs new file mode 100644 index 000000000000..e7bfadcc1bef --- /dev/null +++ b/crates/engine/tree/src/persistence.rs @@ -0,0 +1,139 @@ +#![allow(dead_code)] + +use crate::tree::ExecutedBlock; +use reth_db::database::Database; +use reth_errors::ProviderResult; +use reth_primitives::B256; +use reth_provider::ProviderFactory; +use std::sync::mpsc::{Receiver, Sender}; +use tokio::sync::oneshot; + +/// Writes parts of reth's in memory tree state to the database. +/// +/// This is meant to be a spawned task that listens for various incoming persistence operations, +/// performing those actions on disk, and returning the result in a channel. +/// +/// There are two types of operations this task can perform: +/// - Writing executed blocks to disk, returning the hash of the latest block that was inserted. +/// - Removing blocks from disk, returning the removed blocks. +/// +/// This should be spawned in its own thread with [`std::thread::spawn`], since this performs +/// blocking database operations in an endless loop. +#[derive(Debug)] +pub struct Persistence { + /// The db / static file provider to use + provider: ProviderFactory, + /// Incoming requests to persist stuff + incoming: Receiver, +} + +impl Persistence { + /// Create a new persistence task + const fn new(provider: ProviderFactory, incoming: Receiver) -> Self { + Self { provider, incoming } + } + + /// Writes the cloned tree state to the database + fn write(&self, _blocks: Vec) -> ProviderResult<()> { + let mut _rw = self.provider.provider_rw()?; + todo!("implement this") + } + + /// Removes the blocks above the give block number from the database, returning them. + fn remove_blocks_above(&self, _block_number: u64) -> Vec { + todo!("implement this") + } +} + +impl Persistence +where + DB: Database + 'static, +{ + /// Create a new persistence task, spawning it, and returning a [`PersistenceHandle`]. + fn spawn_new(provider: ProviderFactory) -> PersistenceHandle { + let (tx, rx) = std::sync::mpsc::channel(); + let task = Self::new(provider, rx); + std::thread::Builder::new() + .name("Persistence Task".to_string()) + .spawn(|| task.run()) + .unwrap(); + + PersistenceHandle::new(tx) + } +} + +impl Persistence +where + DB: Database, +{ + /// This is the main loop, that will listen to persistence events and perform the requested + /// database actions + fn run(self) { + // If the receiver errors then senders have disconnected, so the loop should then end. + while let Ok(action) = self.incoming.recv() { + match action { + PersistenceAction::RemoveBlocksAbove((new_tip_num, sender)) => { + // spawn blocking so we can poll the thread later + let output = self.remove_blocks_above(new_tip_num); + sender.send(output).unwrap(); + } + PersistenceAction::SaveBlocks((blocks, sender)) => { + if blocks.is_empty() { + todo!("return error or something"); + } + let last_block_hash = blocks.last().unwrap().block().hash(); + self.write(blocks).unwrap(); + sender.send(last_block_hash).unwrap(); + } + } + } + } +} + +/// A signal to the persistence task that part of the tree state can be persisted. +#[derive(Debug)] +pub enum PersistenceAction { + /// The section of tree state that should be persisted. These blocks are expected in order of + /// increasing block number. + SaveBlocks((Vec, oneshot::Sender)), + + /// Removes the blocks above the given block number from the database. + RemoveBlocksAbove((u64, oneshot::Sender>)), +} + +/// A handle to the persistence task +#[derive(Debug, Clone)] +pub struct PersistenceHandle { + /// The channel used to communicate with the persistence task + sender: Sender, +} + +impl PersistenceHandle { + /// Create a new [`PersistenceHandle`] from a [`Sender`]. + pub const fn new(sender: Sender) -> Self { + Self { sender } + } + + /// Tells the persistence task to save a certain list of finalized blocks. The blocks are + /// assumed to be ordered by block number. + /// + /// This returns the latest hash that has been saved, allowing removal of that block and any + /// previous blocks from in-memory data structures. + pub async fn save_blocks(&self, blocks: Vec) -> B256 { + let (tx, rx) = oneshot::channel(); + self.sender + .send(PersistenceAction::SaveBlocks((blocks, tx))) + .expect("should be able to send"); + rx.await.expect("todo: err handling") + } + + /// Tells the persistence task to remove blocks above a certain block number. The removed blocks + /// are returned by the task. + pub async fn remove_blocks_above(&self, block_num: u64) -> Vec { + let (tx, rx) = oneshot::channel(); + self.sender + .send(PersistenceAction::RemoveBlocksAbove((block_num, tx))) + .expect("should be able to send"); + rx.await.expect("todo: err handling") + } +} diff --git a/crates/engine/tree/src/test_utils.rs b/crates/engine/tree/src/test_utils.rs new file mode 100644 index 000000000000..eed483e29932 --- /dev/null +++ b/crates/engine/tree/src/test_utils.rs @@ -0,0 +1,21 @@ +use reth_network_p2p::test_utils::TestFullBlockClient; +use reth_primitives::{BlockBody, SealedHeader}; +use std::ops::Range; + +pub(crate) fn insert_headers_into_client( + client: &TestFullBlockClient, + genesis_header: SealedHeader, + range: Range, +) { + let mut sealed_header = genesis_header; + let body = BlockBody::default(); + for _ in range { + let (mut header, hash) = sealed_header.split(); + // update to the next header + header.parent_hash = hash; + header.number += 1; + header.timestamp += 1; + sealed_header = header.seal_slow(); + client.insert(sealed_header.clone(), body.clone()); + } +} diff --git a/crates/engine/tree/src/tree/memory_overlay.rs b/crates/engine/tree/src/tree/memory_overlay.rs new file mode 100644 index 000000000000..7e0e1d52e5be --- /dev/null +++ b/crates/engine/tree/src/tree/memory_overlay.rs @@ -0,0 +1,123 @@ +use super::ExecutedBlock; +use reth_errors::ProviderResult; +use reth_primitives::{Account, Address, BlockNumber, Bytecode, StorageKey, StorageValue, B256}; +use reth_provider::{AccountReader, BlockHashReader, StateProvider, StateRootProvider}; +use reth_trie::{updates::TrieUpdates, AccountProof}; +use revm::db::BundleState; + +/// A state provider that stores references to in-memory blocks along with their state as well as +/// the historical state provider for fallback lookups. +#[derive(Debug)] +pub struct MemoryOverlayStateProvider { + /// The collection of executed parent blocks. + in_memory: Vec, + /// Historical state provider for state lookups that are not found in in-memory blocks. + historical: H, +} + +impl MemoryOverlayStateProvider { + /// Create new memory overlay state provider. + pub const fn new(in_memory: Vec, historical: H) -> Self { + Self { in_memory, historical } + } +} + +impl BlockHashReader for MemoryOverlayStateProvider +where + H: BlockHashReader, +{ + fn block_hash(&self, number: BlockNumber) -> ProviderResult> { + for block in self.in_memory.iter().rev() { + if block.block.number == number { + return Ok(Some(block.block.hash())) + } + } + + self.historical.block_hash(number) + } + + fn canonical_hashes_range( + &self, + start: BlockNumber, + end: BlockNumber, + ) -> ProviderResult> { + let range = start..end; + let mut earliest_block_number = None; + let mut in_memory_hashes = Vec::new(); + for block in self.in_memory.iter().rev() { + if range.contains(&block.block.number) { + in_memory_hashes.insert(0, block.block.hash()); + earliest_block_number = Some(block.block.number); + } + } + + let mut hashes = + self.historical.canonical_hashes_range(start, earliest_block_number.unwrap_or(end))?; + hashes.append(&mut in_memory_hashes); + Ok(hashes) + } +} + +impl AccountReader for MemoryOverlayStateProvider +where + H: AccountReader + Send, +{ + fn basic_account(&self, address: Address) -> ProviderResult> { + for block in self.in_memory.iter().rev() { + if let Some(account) = block.execution_output.account(&address) { + return Ok(account) + } + } + + self.historical.basic_account(address) + } +} + +impl StateRootProvider for MemoryOverlayStateProvider +where + H: StateRootProvider + Send, +{ + fn state_root(&self, bundle_state: &BundleState) -> ProviderResult { + todo!() + } + + fn state_root_with_updates( + &self, + bundle_state: &BundleState, + ) -> ProviderResult<(B256, TrieUpdates)> { + todo!() + } +} + +impl StateProvider for MemoryOverlayStateProvider +where + H: StateProvider + Send, +{ + fn storage( + &self, + address: Address, + storage_key: StorageKey, + ) -> ProviderResult> { + for block in self.in_memory.iter().rev() { + if let Some(value) = block.execution_output.storage(&address, storage_key.into()) { + return Ok(Some(value)) + } + } + + self.historical.storage(address, storage_key) + } + + fn bytecode_by_hash(&self, code_hash: B256) -> ProviderResult> { + for block in self.in_memory.iter().rev() { + if let Some(contract) = block.execution_output.bytecode(&code_hash) { + return Ok(Some(contract)) + } + } + + self.historical.bytecode_by_hash(code_hash) + } + + fn proof(&self, address: Address, keys: &[B256]) -> ProviderResult { + todo!() + } +} diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs new file mode 100644 index 000000000000..e4cf33923cfd --- /dev/null +++ b/crates/engine/tree/src/tree/mod.rs @@ -0,0 +1,574 @@ +use crate::{backfill::BackfillAction, engine::DownloadRequest}; +use reth_beacon_consensus::{ForkchoiceStateTracker, InvalidHeaderCache, OnForkChoiceUpdated}; +use reth_blockchain_tree::{ + error::InsertBlockErrorKind, BlockAttachment, BlockBuffer, BlockStatus, +}; +use reth_blockchain_tree_api::{error::InsertBlockError, InsertPayloadOk}; +use reth_consensus::{Consensus, PostExecutionInput}; +use reth_engine_primitives::EngineTypes; +use reth_errors::{ConsensusError, ProviderResult}; +use reth_evm::execute::{BlockExecutorProvider, Executor}; +use reth_payload_primitives::PayloadTypes; +use reth_payload_validator::ExecutionPayloadValidator; +use reth_primitives::{ + Address, Block, BlockNumber, Receipts, Requests, SealedBlock, SealedBlockWithSenders, B256, + U256, +}; +use reth_provider::{BlockReader, ExecutionOutcome, StateProvider, StateProviderFactory}; +use reth_revm::database::StateProviderDatabase; +use reth_rpc_types::{ + engine::{ + CancunPayloadFields, ForkchoiceState, PayloadStatus, PayloadStatusEnum, + PayloadValidationError, + }, + ExecutionPayload, +}; +use reth_trie::{updates::TrieUpdates, HashedPostState}; +use std::{ + collections::{BTreeMap, HashMap}, + marker::PhantomData, + sync::Arc, +}; +use tracing::*; + +mod memory_overlay; +pub use memory_overlay::MemoryOverlayStateProvider; + +/// Represents an executed block stored in-memory. +#[derive(Clone, Debug)] +pub struct ExecutedBlock { + block: Arc, + senders: Arc>, + execution_output: Arc, + hashed_state: Arc, + trie: Arc, +} + +impl ExecutedBlock { + /// Returns a reference to the executed block. + pub(crate) fn block(&self) -> &SealedBlock { + &self.block + } +} + +/// Keeps track of the state of the tree. +#[derive(Debug)] +pub struct TreeState { + /// All executed blocks by hash. + blocks_by_hash: HashMap, + /// Executed blocks grouped by their respective block number. + blocks_by_number: BTreeMap>, +} + +impl TreeState { + fn block_by_hash(&self, hash: B256) -> Option> { + self.blocks_by_hash.get(&hash).map(|b| b.block.clone()) + } + + /// Insert executed block into the state. + fn insert_executed(&mut self, executed: ExecutedBlock) { + self.blocks_by_number.entry(executed.block.number).or_default().push(executed.clone()); + let existing = self.blocks_by_hash.insert(executed.block.hash(), executed); + debug_assert!(existing.is_none(), "inserted duplicate block"); + } + + /// Remove blocks before specified block number. + pub(crate) fn remove_before(&mut self, block_number: BlockNumber) { + while self + .blocks_by_number + .first_key_value() + .map(|entry| entry.0 < &block_number) + .unwrap_or_default() + { + let (_, to_remove) = self.blocks_by_number.pop_first().unwrap(); + for block in to_remove { + let block_hash = block.block.hash(); + let removed = self.blocks_by_hash.remove(&block_hash); + debug_assert!( + removed.is_some(), + "attempted to remove non-existing block {block_hash}" + ); + } + } + } +} + +/// Tracks the state of the engine api internals. +/// +/// This type is shareable. +#[derive(Debug)] +pub struct EngineApiTreeState { + /// Tracks the state of the blockchain tree. + tree_state: TreeState, + /// Tracks the received forkchoice state updates received by the CL. + forkchoice_state_tracker: ForkchoiceStateTracker, + /// Buffer of detached blocks. + buffer: BlockBuffer, + /// Tracks the header of invalid payloads that were rejected by the engine because they're + /// invalid. + invalid_headers: InvalidHeaderCache, +} + +/// The type responsible for processing engine API requests. +/// +/// TODO: design: should the engine handler functions also accept the response channel or return the +/// result and the caller redirects the response +pub trait EngineApiTreeHandler: Send + Sync { + /// The engine type that this handler is for. + type Engine: EngineTypes; + + /// Invoked when previously requested blocks were downloaded. + fn on_downloaded(&mut self, blocks: Vec) -> Option; + + /// When the Consensus layer receives a new block via the consensus gossip protocol, + /// the transactions in the block are sent to the execution layer in the form of a + /// [`ExecutionPayload`]. The Execution layer executes the transactions and validates the + /// state in the block header, then passes validation data back to Consensus layer, that + /// adds the block to the head of its own blockchain and attests to it. The block is then + /// broadcast over the consensus p2p network in the form of a "Beacon block". + /// + /// These responses should adhere to the [Engine API Spec for + /// `engine_newPayload`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification). + /// + /// This returns a [`PayloadStatus`] that represents the outcome of a processed new payload and + /// returns an error if an internal error occurred. + fn on_new_payload( + &mut self, + payload: ExecutionPayload, + cancun_fields: Option, + ) -> ProviderResult>; + + /// Invoked when we receive a new forkchoice update message. Calls into the blockchain tree + /// to resolve chain forks and ensure that the Execution Layer is working with the latest valid + /// chain. + /// + /// These responses should adhere to the [Engine API Spec for + /// `engine_forkchoiceUpdated`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification-1). + /// + /// Returns an error if an internal error occurred like a database error. + fn on_forkchoice_updated( + &mut self, + state: ForkchoiceState, + attrs: Option<::PayloadAttributes>, + ) -> TreeOutcome>; +} + +/// The outcome of a tree operation. +#[derive(Debug)] +pub struct TreeOutcome { + /// The outcome of the operation. + pub outcome: T, + /// An optional event to tell the caller to do something. + pub event: Option, +} + +impl TreeOutcome { + /// Create new tree outcome. + pub const fn new(outcome: T) -> Self { + Self { outcome, event: None } + } + + /// Set event on the outcome. + pub fn with_event(mut self, event: TreeEvent) -> Self { + self.event = Some(event); + self + } +} + +/// Events that can be emitted by the [`EngineApiTreeHandler`]. +#[derive(Debug)] +pub enum TreeEvent { + /// Tree action is needed. + TreeAction(TreeAction), + /// Backfill action is needed. + BackfillAction(BackfillAction), + /// Block download is needed. + Download(DownloadRequest), +} + +/// The actions that can be performed on the tree. +#[derive(Debug)] +pub enum TreeAction { + /// Make target canonical. + MakeCanonical(B256), +} + +#[derive(Debug)] +pub struct EngineApiTreeHandlerImpl { + provider: P, + executor_provider: E, + consensus: Arc, + payload_validator: ExecutionPayloadValidator, + state: EngineApiTreeState, + /// (tmp) The flag indicating whether the pipeline is active. + is_pipeline_active: bool, + _marker: PhantomData, +} + +impl EngineApiTreeHandlerImpl +where + P: BlockReader + StateProviderFactory, + E: BlockExecutorProvider, + T: EngineTypes, +{ + /// Return block from database or in-memory state by hash. + fn block_by_hash(&self, hash: B256) -> ProviderResult> { + // check database first + let mut block = self.provider.block_by_hash(hash)?; + if block.is_none() { + // Note: it's fine to return the unsealed block because the caller already has + // the hash + block = self + .state + .tree_state + .block_by_hash(hash) + // TODO: clone for compatibility. should we return an Arc here? + .map(|block| block.as_ref().clone().unseal()); + } + Ok(block) + } + + /// Return state provider with reference to in-memory blocks that overlay database state. + fn state_provider( + &self, + hash: B256, + ) -> ProviderResult>> { + let mut in_memory = Vec::new(); + let mut parent_hash = hash; + while let Some(executed) = self.state.tree_state.blocks_by_hash.get(&parent_hash) { + parent_hash = executed.block.parent_hash; + in_memory.insert(0, executed.clone()); + } + + let historical = self.provider.state_by_block_hash(parent_hash)?; + Ok(MemoryOverlayStateProvider::new(in_memory, historical)) + } + + /// Return the parent hash of the lowest buffered ancestor for the requested block, if there + /// are any buffered ancestors. If there are no buffered ancestors, and the block itself does + /// not exist in the buffer, this returns the hash that is passed in. + /// + /// Returns the parent hash of the block itself if the block is buffered and has no other + /// buffered ancestors. + fn lowest_buffered_ancestor_or(&self, hash: B256) -> B256 { + self.state + .buffer + .lowest_ancestor(&hash) + .map(|block| block.parent_hash) + .unwrap_or_else(|| hash) + } + + /// If validation fails, the response MUST contain the latest valid hash: + /// + /// - The block hash of the ancestor of the invalid payload satisfying the following two + /// conditions: + /// - It is fully validated and deemed VALID + /// - Any other ancestor of the invalid payload with a higher blockNumber is INVALID + /// - 0x0000000000000000000000000000000000000000000000000000000000000000 if the above + /// conditions are satisfied by a `PoW` block. + /// - null if client software cannot determine the ancestor of the invalid payload satisfying + /// the above conditions. + fn latest_valid_hash_for_invalid_payload( + &mut self, + parent_hash: B256, + ) -> ProviderResult> { + // Check if parent exists in side chain or in canonical chain. + if self.block_by_hash(parent_hash)?.is_some() { + return Ok(Some(parent_hash)) + } + + // iterate over ancestors in the invalid cache + // until we encounter the first valid ancestor + let mut current_hash = parent_hash; + let mut current_header = self.state.invalid_headers.get(¤t_hash); + while let Some(header) = current_header { + current_hash = header.parent_hash; + current_header = self.state.invalid_headers.get(¤t_hash); + + // If current_header is None, then the current_hash does not have an invalid + // ancestor in the cache, check its presence in blockchain tree + if current_header.is_none() && self.block_by_hash(current_hash)?.is_some() { + return Ok(Some(current_hash)) + } + } + Ok(None) + } + + /// Prepares the invalid payload response for the given hash, checking the + /// database for the parent hash and populating the payload status with the latest valid hash + /// according to the engine api spec. + fn prepare_invalid_response(&mut self, mut parent_hash: B256) -> ProviderResult { + // Edge case: the `latestValid` field is the zero hash if the parent block is the terminal + // PoW block, which we need to identify by looking at the parent's block difficulty + if let Some(parent) = self.block_by_hash(parent_hash)? { + if !parent.is_zero_difficulty() { + parent_hash = B256::ZERO; + } + } + + let valid_parent_hash = self.latest_valid_hash_for_invalid_payload(parent_hash)?; + Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid { + validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(), + }) + .with_latest_valid_hash(valid_parent_hash.unwrap_or_default())) + } + + /// Checks if the given `check` hash points to an invalid header, inserting the given `head` + /// block into the invalid header cache if the `check` hash has a known invalid ancestor. + /// + /// Returns a payload status response according to the engine API spec if the block is known to + /// be invalid. + fn check_invalid_ancestor_with_head( + &mut self, + check: B256, + head: B256, + ) -> ProviderResult> { + // check if the check hash was previously marked as invalid + let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) }; + + // populate the latest valid hash field + let status = self.prepare_invalid_response(header.parent_hash)?; + + // insert the head block into the invalid header cache + self.state.invalid_headers.insert_with_invalid_ancestor(head, header); + + Ok(Some(status)) + } + + /// Validate if block is correct and satisfies all the consensus rules that concern the header + /// and block body itself. + fn validate_block(&self, block: &SealedBlockWithSenders) -> Result<(), ConsensusError> { + if let Err(e) = self.consensus.validate_header_with_total_difficulty(block, U256::MAX) { + error!( + ?block, + "Failed to validate total difficulty for block {}: {e}", + block.header.hash() + ); + return Err(e) + } + + if let Err(e) = self.consensus.validate_header(block) { + error!(?block, "Failed to validate header {}: {e}", block.header.hash()); + return Err(e) + } + + if let Err(e) = self.consensus.validate_block_pre_execution(block) { + error!(?block, "Failed to validate block {}: {e}", block.header.hash()); + return Err(e) + } + + Ok(()) + } + + fn buffer_block_without_senders(&mut self, block: SealedBlock) -> Result<(), InsertBlockError> { + match block.try_seal_with_senders() { + Ok(block) => self.buffer_block(block), + Err(block) => Err(InsertBlockError::sender_recovery_error(block)), + } + } + + fn buffer_block(&mut self, block: SealedBlockWithSenders) -> Result<(), InsertBlockError> { + if let Err(err) = self.validate_block(&block) { + return Err(InsertBlockError::consensus_error(err, block.block)) + } + self.state.buffer.insert_block(block); + Ok(()) + } + + fn insert_block_without_senders( + &mut self, + block: SealedBlock, + ) -> Result { + match block.try_seal_with_senders() { + Ok(block) => self.insert_block(block), + Err(block) => Err(InsertBlockError::sender_recovery_error(block)), + } + } + + fn insert_block( + &mut self, + block: SealedBlockWithSenders, + ) -> Result { + self.insert_block_inner(block.clone()) + .map_err(|kind| InsertBlockError::new(block.block, kind)) + } + + fn insert_block_inner( + &mut self, + block: SealedBlockWithSenders, + ) -> Result { + if self.block_by_hash(block.hash())?.is_some() { + let attachment = BlockAttachment::Canonical; // TODO: remove or revise attachment + return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid(attachment))) + } + + // validate block consensus rules + self.validate_block(&block)?; + + let state_provider = self.state_provider(block.parent_hash).unwrap(); + let executor = self.executor_provider.executor(StateProviderDatabase::new(&state_provider)); + + let block_number = block.number; + let block_hash = block.hash(); + let block = block.unseal(); + let output = executor.execute((&block, U256::MAX).into()).unwrap(); + self.consensus.validate_block_post_execution( + &block, + PostExecutionInput::new(&output.receipts, &output.requests), + )?; + + let hashed_state = HashedPostState::from_bundle_state(&output.state.state); + + // TODO: compute and validate state root + let trie_output = TrieUpdates::default(); + + let executed = ExecutedBlock { + block: Arc::new(block.block.seal(block_hash)), + senders: Arc::new(block.senders), + execution_output: Arc::new(ExecutionOutcome::new( + output.state, + Receipts::from(output.receipts), + block_number, + vec![Requests::from(output.requests)], + )), + hashed_state: Arc::new(hashed_state), + trie: Arc::new(trie_output), + }; + self.state.tree_state.insert_executed(executed); + + let attachment = BlockAttachment::Canonical; // TODO: remove or revise attachment + Ok(InsertPayloadOk::Inserted(BlockStatus::Valid(attachment))) + } +} + +impl EngineApiTreeHandler for EngineApiTreeHandlerImpl +where + P: BlockReader + StateProviderFactory + Clone, + E: BlockExecutorProvider, + T: EngineTypes, +{ + type Engine = T; + + fn on_downloaded(&mut self, _blocks: Vec) -> Option { + todo!() + } + + fn on_new_payload( + &mut self, + payload: ExecutionPayload, + cancun_fields: Option, + ) -> ProviderResult> { + // Ensures that the given payload does not violate any consensus rules that concern the + // block's layout, like: + // - missing or invalid base fee + // - invalid extra data + // - invalid transactions + // - incorrect hash + // - the versioned hashes passed with the payload do not exactly match transaction + // versioned hashes + // - the block does not contain blob transactions if it is pre-cancun + // + // This validates the following engine API rule: + // + // 3. Given the expected array of blob versioned hashes client software **MUST** run its + // validation by taking the following steps: + // + // 1. Obtain the actual array by concatenating blob versioned hashes lists + // (`tx.blob_versioned_hashes`) of each [blob + // transaction](https://eips.ethereum.org/EIPS/eip-4844#new-transaction-type) included + // in the payload, respecting the order of inclusion. If the payload has no blob + // transactions the expected array **MUST** be `[]`. + // + // 2. Return `{status: INVALID, latestValidHash: null, validationError: errorMessage | + // null}` if the expected and the actual arrays don't match. + // + // This validation **MUST** be instantly run in all cases even during active sync process. + let parent_hash = payload.parent_hash(); + let block = match self + .payload_validator + .ensure_well_formed_payload(payload, cancun_fields.into()) + { + Ok(block) => block, + Err(error) => { + error!(target: "engine::tree", %error, "Invalid payload"); + // we need to convert the error to a payload status (response to the CL) + + let latest_valid_hash = + if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() { + // Engine-API rules: + // > `latestValidHash: null` if the blockHash validation has failed () + // > `latestValidHash: null` if the expected and the actual arrays don't match () + None + } else { + self.latest_valid_hash_for_invalid_payload(parent_hash)? + }; + + let status = PayloadStatusEnum::from(error); + return Ok(TreeOutcome::new(PayloadStatus::new(status, latest_valid_hash))) + } + }; + + let block_hash = block.hash(); + let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash); + if lowest_buffered_ancestor == block_hash { + lowest_buffered_ancestor = block.parent_hash; + } + + // now check the block itself + if let Some(status) = + self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, block_hash)? + { + return Ok(TreeOutcome::new(status)) + } + + let status = if self.is_pipeline_active { + self.buffer_block_without_senders(block).unwrap(); + PayloadStatus::from_status(PayloadStatusEnum::Syncing) + } else { + let mut latest_valid_hash = None; + let status = match self.insert_block_without_senders(block).unwrap() { + InsertPayloadOk::Inserted(BlockStatus::Valid(_)) | + InsertPayloadOk::AlreadySeen(BlockStatus::Valid(_)) => { + latest_valid_hash = Some(block_hash); + PayloadStatusEnum::Valid + } + InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) | + InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => { + // TODO: isn't this check redundant? + // check if the block's parent is already marked as invalid + // if let Some(status) = self + // .check_invalid_ancestor_with_head(block.parent_hash, block.hash()) + // .map_err(|error| { + // InsertBlockError::new(block, InsertBlockErrorKind::Provider(error)) + // })? + // { + // return Ok(status) + // } + + // not known to be invalid, but we don't know anything else + PayloadStatusEnum::Syncing + } + }; + PayloadStatus::new(status, latest_valid_hash) + }; + + let mut outcome = TreeOutcome::new(status); + if outcome.outcome.is_valid() { + if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() { + if target.head_block_hash == block_hash { + outcome = outcome + .with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical(block_hash))); + } + } + } + Ok(outcome) + } + + fn on_forkchoice_updated( + &mut self, + state: ForkchoiceState, + attrs: Option<::PayloadAttributes>, + ) -> TreeOutcome> { + todo!() + } +} From feb6a37a185bb3cbe096d081799592ea060f1ae8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 1 Jul 2024 14:07:05 +0200 Subject: [PATCH 278/405] chore: remove unused `MIN_LENGTH_EIPXXXX_ENCODED` (#9211) Co-authored-by: Matthias Seitz --- crates/primitives/src/transaction/mod.rs | 118 +---------------------- 1 file changed, 2 insertions(+), 116 deletions(-) diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 1edafaaa2c0a..7a5ae73ba743 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -75,18 +75,6 @@ pub(crate) static PARALLEL_SENDER_RECOVERY_THRESHOLD: Lazy = _ => 5, }); -/// Minimum length of a rlp-encoded legacy transaction. -pub const MIN_LENGTH_LEGACY_TX_ENCODED: usize = 10; -/// Minimum length of a rlp-encoded eip2930 transaction. -pub const MIN_LENGTH_EIP2930_TX_ENCODED: usize = 14; -/// Minimum length of a rlp-encoded eip1559 transaction. -pub const MIN_LENGTH_EIP1559_TX_ENCODED: usize = 15; -/// Minimum length of a rlp-encoded eip4844 transaction. -pub const MIN_LENGTH_EIP4844_TX_ENCODED: usize = 37; -/// Minimum length of a rlp-encoded deposit transaction. -#[cfg(feature = "optimism")] -pub const MIN_LENGTH_DEPOSIT_TX_ENCODED: usize = 65; - /// A raw transaction. /// /// Transaction types were introduced in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718). @@ -1627,12 +1615,10 @@ mod tests { hex, sign_message, transaction::{ from_compact_zstd_unaware, signature::Signature, to_compact_ztd_unaware, TxEip1559, - TxKind, TxLegacy, MIN_LENGTH_EIP1559_TX_ENCODED, MIN_LENGTH_EIP2930_TX_ENCODED, - MIN_LENGTH_EIP4844_TX_ENCODED, MIN_LENGTH_LEGACY_TX_ENCODED, - PARALLEL_SENDER_RECOVERY_THRESHOLD, + TxKind, TxLegacy, PARALLEL_SENDER_RECOVERY_THRESHOLD, }, Address, Bytes, Transaction, TransactionSigned, TransactionSignedEcRecovered, - TransactionSignedNoHash, TxEip2930, TxEip4844, B256, U256, + TransactionSignedNoHash, B256, U256, }; use alloy_primitives::{address, b256, bytes}; use alloy_rlp::{Decodable, Encodable, Error as RlpError}; @@ -1973,106 +1959,6 @@ mod tests { assert_eq!(sender, address!("7e9e359edf0dbacf96a9952fa63092d919b0842b")); } - #[test] - fn min_length_encoded_legacy_transaction() { - let transaction = TxLegacy::default(); - let signature = Signature::default(); - - let signed_tx = TransactionSigned::from_transaction_and_signature( - Transaction::Legacy(transaction), - signature, - ); - - let encoded = &alloy_rlp::encode(signed_tx); - assert_eq!( - if cfg!(feature = "optimism") { - hex!("c9808080808080808080") - } else { - hex!("c98080808080801b8080") - }, - &encoded[..] - ); - assert_eq!(MIN_LENGTH_LEGACY_TX_ENCODED, encoded.len()); - - TransactionSigned::decode(&mut &encoded[..]).unwrap(); - } - - #[test] - fn min_length_encoded_eip2930_transaction() { - let transaction = TxEip2930::default(); - let signature = Signature::default(); - - let signed_tx = TransactionSigned::from_transaction_and_signature( - Transaction::Eip2930(transaction), - signature, - ); - - let encoded = &alloy_rlp::encode(signed_tx); - assert_eq!(hex!("8d01cb80808080808080c0808080"), encoded[..]); - assert_eq!(MIN_LENGTH_EIP2930_TX_ENCODED, encoded.len()); - - TransactionSigned::decode(&mut &encoded[..]).unwrap(); - } - - #[test] - fn min_length_encoded_eip1559_transaction() { - let transaction = TxEip1559::default(); - let signature = Signature::default(); - - let signed_tx = TransactionSigned::from_transaction_and_signature( - Transaction::Eip1559(transaction), - signature, - ); - - let encoded = &alloy_rlp::encode(signed_tx); - assert_eq!(hex!("8e02cc8080808080808080c0808080"), encoded[..]); - assert_eq!(MIN_LENGTH_EIP1559_TX_ENCODED, encoded.len()); - - TransactionSigned::decode(&mut &encoded[..]).unwrap(); - } - - #[test] - fn min_length_encoded_eip4844_transaction() { - let transaction = TxEip4844::default(); - let signature = Signature::default(); - - let signed_tx = TransactionSigned::from_transaction_and_signature( - Transaction::Eip4844(transaction), - signature, - ); - - let encoded = alloy_rlp::encode(signed_tx); - assert_eq!( - hex!("a403e280808080809400000000000000000000000000000000000000008080c080c0808080"), - encoded[..] - ); - assert_eq!(MIN_LENGTH_EIP4844_TX_ENCODED, encoded.len()); - - TransactionSigned::decode(&mut &encoded[..]).unwrap(); - } - - #[cfg(feature = "optimism")] - #[test] - fn min_length_encoded_deposit_transaction() { - use super::MIN_LENGTH_DEPOSIT_TX_ENCODED; - use crate::TxDeposit; - - let transaction = TxDeposit::default(); - let signature = Signature::default(); - - let signed_tx = TransactionSigned::from_transaction_and_signature( - Transaction::Deposit(transaction), - signature, - ); - - let encoded = &alloy_rlp::encode(signed_tx); - - assert_eq!(b"\xb8?~\xf8<\xa0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x94\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x80\x80\x80\x80\x80\x80", &encoded[..]); - assert_eq!(MIN_LENGTH_DEPOSIT_TX_ENCODED, encoded.len()); - - TransactionSigned::decode(&mut &encoded[..]).unwrap(); - } - #[test] fn transaction_signed_no_hash_zstd_codec() { // will use same signature everywhere. From db191c82a57dd3eed8cf982dee197526433e7d09 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 1 Jul 2024 06:07:23 -0700 Subject: [PATCH 279/405] chore(trie): clean up trie update operation matching (#9202) --- crates/trie/trie/src/trie_cursor/in_memory.rs | 22 +++++-------------- crates/trie/trie/src/updates.rs | 9 ++++++++ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/crates/trie/trie/src/trie_cursor/in_memory.rs b/crates/trie/trie/src/trie_cursor/in_memory.rs index 5f607f314db0..f7c521f69b6b 100644 --- a/crates/trie/trie/src/trie_cursor/in_memory.rs +++ b/crates/trie/trie/src/trie_cursor/in_memory.rs @@ -1,5 +1,5 @@ use super::{TrieCursor, TrieCursorFactory}; -use crate::updates::{TrieKey, TrieOp, TrieUpdatesSorted}; +use crate::updates::{TrieKey, TrieUpdatesSorted}; use reth_db::DatabaseError; use reth_primitives::B256; use reth_trie_common::{BranchNodeCompact, Nibbles}; @@ -58,10 +58,7 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryAccountTrieCursor<'a, C> { ) -> Result, DatabaseError> { if let Some((trie_key, trie_op)) = self.trie_updates.find_account_node(&key) { self.last_key = Some(trie_key); - match trie_op { - TrieOp::Update(node) => Ok(Some((key, node))), - TrieOp::Delete => Ok(None), - } + Ok(trie_op.into_update().map(|node| (key, node))) } else { let result = self.cursor.seek_exact(key)?; self.last_key = result.as_ref().map(|(k, _)| TrieKey::AccountNode(k.clone())); @@ -86,10 +83,7 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryAccountTrieCursor<'a, C> { _ => panic!("Invalid trie key"), }; self.last_key = Some(trie_key); - match trie_op { - TrieOp::Update(node) => return Ok(Some((nibbles, node))), - TrieOp::Delete => return Ok(None), - } + return Ok(trie_op.into_update().map(|node| (nibbles, node))) } let result = self.cursor.seek(key)?; @@ -132,10 +126,7 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryStorageTrieCursor<'a, C> { self.trie_updates.find_storage_node(&self.hashed_address, &key) { self.last_key = Some(trie_key); - match trie_op { - TrieOp::Update(node) => Ok(Some((key, node))), - TrieOp::Delete => Ok(None), - } + Ok(trie_op.into_update().map(|node| (key, node))) } else { let result = self.cursor.seek_exact(key)?; self.last_key = @@ -164,10 +155,7 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryStorageTrieCursor<'a, C> { _ => panic!("this should not happen!"), }; self.last_key = Some(trie_key.clone()); - match trie_op { - TrieOp::Update(node) => return Ok(Some((nibbles, node.clone()))), - TrieOp::Delete => return Ok(None), - } + return Ok(trie_op.as_update().map(|node| (nibbles, node.clone()))) } let result = self.cursor.seek(key)?; diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 4ae4eb309089..d0027d658523 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -76,6 +76,15 @@ impl TrieOp { None } } + + /// Returns owned updated branch node if operation is [`Self::Update`]. + pub fn into_update(self) -> Option { + if let Self::Update(node) = self { + Some(node) + } else { + None + } + } } /// The aggregation of trie updates. From d317b4a0fbe54fed247b626e8d0d0928497d812b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 1 Jul 2024 15:07:49 +0200 Subject: [PATCH 280/405] chore: add builder with rng secret key fn (#9218) --- crates/net/network/src/config.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index b6c95521245c..3f7887a9b8db 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -88,6 +88,11 @@ impl NetworkConfig<()> { pub fn builder(secret_key: SecretKey) -> NetworkConfigBuilder { NetworkConfigBuilder::new(secret_key) } + + /// Convenience method for creating the corresponding builder type with a random secret key. + pub fn builder_with_rng_secret_key() -> NetworkConfigBuilder { + NetworkConfigBuilder::with_rng_secret_key() + } } impl NetworkConfig { @@ -176,6 +181,12 @@ pub struct NetworkConfigBuilder { #[allow(missing_docs)] impl NetworkConfigBuilder { + /// Create a new builder instance with a random secret key. + pub fn with_rng_secret_key() -> Self { + Self::new(rng_secret_key()) + } + + /// Create a new builder instance with the given secret key. pub fn new(secret_key: SecretKey) -> Self { Self { secret_key, @@ -212,6 +223,11 @@ impl NetworkConfigBuilder { pk2id(&self.secret_key.public_key(SECP256K1)) } + /// Returns the configured [`SecretKey`], from which the node's identity is derived. + pub const fn secret_key(&self) -> &SecretKey { + &self.secret_key + } + /// Sets the chain spec. pub fn chain_spec(mut self, chain_spec: Arc) -> Self { self.chain_spec = chain_spec; From cf8a9163afb9bbdab821e8f0a843ec195785432b Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:12:36 +0200 Subject: [PATCH 281/405] chore: remove usage of `tx_env_with_recovered` (#9222) --- crates/ethereum/evm/src/lib.rs | 8 +- crates/ethereum/payload/src/lib.rs | 3 +- crates/evm/src/lib.rs | 8 +- crates/optimism/evm/src/lib.rs | 6 +- crates/optimism/payload/src/builder.rs | 10 +- crates/primitives/src/revm/env.rs | 162 +------------------- crates/primitives/src/transaction/compat.rs | 131 ++++++++++++++++ crates/primitives/src/transaction/mod.rs | 2 + examples/custom-evm/src/main.rs | 9 +- examples/exex/rollup/src/execution.rs | 3 +- 10 files changed, 158 insertions(+), 184 deletions(-) create mode 100644 crates/primitives/src/transaction/compat.rs diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 17c0c7370b35..86386ceb5af0 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -14,9 +14,9 @@ extern crate alloc; use reth_chainspec::{ChainSpec, Head}; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; -use reth_primitives::{Header, U256}; +use reth_primitives::{transaction::FillTxEnv, Address, Header, TransactionSigned, U256}; use reth_revm::{Database, EvmBuilder}; -use revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg}; +use revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}; mod config; pub use config::{revm_spec, revm_spec_by_timestamp_after_merge}; @@ -57,6 +57,10 @@ impl ConfigureEvmEnv for EthEvmConfig { cfg_env.handler_cfg.spec_id = spec_id; } + + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + transaction.fill_tx_env(tx_env, sender); + } } impl ConfigureEvm for EthEvmConfig { diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 43a00fb98cff..84d43b33c149 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -27,7 +27,6 @@ use reth_primitives::{ }, eip4844::calculate_excess_blob_gas, proofs::{self, calculate_requests_root}, - revm::env::tx_env_with_recovered, Block, EthereumHardforks, Header, IntoRecoveredTransaction, Receipt, EMPTY_OMMER_ROOT_HASH, U256, }; @@ -343,7 +342,7 @@ where let env = EnvWithHandlerCfg::new_with_cfg_env( initialized_cfg.clone(), initialized_block_env.clone(), - tx_env_with_recovered(&tx), + evm_config.tx_env(&tx), ); // Configure the environment for the block. diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 20d2f67ed6f2..b57969de8e61 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -16,8 +16,8 @@ use core::ops::Deref; use reth_chainspec::ChainSpec; use reth_primitives::{ - revm::env::{fill_block_env, fill_tx_env}, - Address, Header, TransactionSigned, TransactionSignedEcRecovered, U256, + revm::env::fill_block_env, Address, Header, TransactionSigned, TransactionSignedEcRecovered, + U256, }; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; @@ -116,9 +116,7 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { } /// Fill transaction environment from a [`TransactionSigned`] and the given sender address. - fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { - fill_tx_env(tx_env, transaction, sender) - } + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address); /// Fill [`CfgEnvWithHandlerCfg`] fields according to the chain spec and given header fn fill_cfg_env( diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 5dff8d5de312..2d1b0afb07f7 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -12,8 +12,8 @@ use reth_chainspec::ChainSpec; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_primitives::{ - revm::env::fill_op_tx_env, revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, + transaction::FillTxEnv, Address, Head, Header, TransactionSigned, U256, }; use reth_revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; @@ -35,9 +35,7 @@ pub struct OptimismEvmConfig; impl ConfigureEvmEnv for OptimismEvmConfig { fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { - let mut buf = Vec::with_capacity(transaction.length_without_header()); - transaction.encode_enveloped(&mut buf); - fill_op_tx_env(tx_env, transaction, sender, buf.into()); + transaction.fill_tx_env(tx_env, sender); } fn fill_cfg_env( diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 3b08b8df0a18..9bb8d3cdcdf5 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -12,9 +12,7 @@ use reth_payload_builder::error::PayloadBuilderError; use reth_primitives::{ constants::{BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS}, eip4844::calculate_excess_blob_gas, - proofs, - revm::env::tx_env_with_recovered, - Block, Header, IntoRecoveredTransaction, Receipt, TxType, EMPTY_OMMER_ROOT_HASH, U256, + proofs, Block, Header, IntoRecoveredTransaction, Receipt, TxType, EMPTY_OMMER_ROOT_HASH, U256, }; use reth_provider::StateProviderFactory; use reth_revm::database::StateProviderDatabase; @@ -324,7 +322,7 @@ where } // Convert the transaction to a [TransactionSignedEcRecovered]. This is - // purely for the purposes of utilizing the [tx_env_with_recovered] function. + // purely for the purposes of utilizing the `evm_config.tx_env`` function. // Deposit transactions do not have signatures, so if the tx is a deposit, this // will just pull in its `from` address. let sequencer_tx = sequencer_tx.clone().try_into_ecrecovered().map_err(|_| { @@ -351,7 +349,7 @@ where let env = EnvWithHandlerCfg::new_with_cfg_env( initialized_cfg.clone(), initialized_block_env.clone(), - tx_env_with_recovered(&sequencer_tx), + evm_config.tx_env(&sequencer_tx), ); let mut evm = evm_config.evm_with_env(&mut db, env); @@ -430,7 +428,7 @@ where let env = EnvWithHandlerCfg::new_with_cfg_env( initialized_cfg.clone(), initialized_block_env.clone(), - tx_env_with_recovered(&tx), + evm_config.tx_env(&tx), ); // Configure the environment for the block. diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index ada7894513ed..6cc81128c2a0 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -1,7 +1,7 @@ use crate::{ recover_signer_unchecked, revm_primitives::{BlockEnv, Env, TxEnv}, - Address, Bytes, Header, Transaction, TransactionSignedEcRecovered, TxKind, B256, U256, + Address, Bytes, Header, TxKind, B256, U256, }; use reth_chainspec::{Chain, ChainSpec}; @@ -107,28 +107,6 @@ pub fn recover_header_signer(header: &Header) -> Result TxEnv { - let mut tx_env = TxEnv::default(); - - #[cfg(not(feature = "optimism"))] - fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer()); - - #[cfg(feature = "optimism")] - { - let mut envelope_buf = Vec::with_capacity(transaction.length_without_header()); - transaction.encode_enveloped(&mut envelope_buf); - fill_op_tx_env( - &mut tx_env, - transaction.as_ref(), - transaction.signer(), - envelope_buf.into(), - ); - } - - tx_env -} - /// Fill transaction environment with the EIP-4788 system contract message data. /// /// This requirements for the beacon root contract call defined by @@ -218,144 +196,6 @@ fn fill_tx_env_with_system_contract_call( env.block.basefee = U256::ZERO; } -/// Fill transaction environment from [`TransactionSignedEcRecovered`]. -#[cfg(not(feature = "optimism"))] -pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) { - fill_tx_env(tx_env, transaction.as_ref(), transaction.signer()); -} - -/// Fill transaction environment from [`TransactionSignedEcRecovered`] and the given envelope. -#[cfg(feature = "optimism")] -pub fn fill_tx_env_with_recovered( - tx_env: &mut TxEnv, - transaction: &TransactionSignedEcRecovered, - envelope: Bytes, -) { - fill_op_tx_env(tx_env, transaction.as_ref(), transaction.signer(), envelope); -} - -/// Fill transaction environment from a [Transaction] and the given sender address. -pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: T, sender: Address) -where - T: AsRef, -{ - tx_env.caller = sender; - match transaction.as_ref() { - Transaction::Legacy(tx) => { - tx_env.gas_limit = tx.gas_limit; - tx_env.gas_price = U256::from(tx.gas_price); - tx_env.gas_priority_fee = None; - tx_env.transact_to = tx.to; - tx_env.value = tx.value; - tx_env.data = tx.input.clone(); - tx_env.chain_id = tx.chain_id; - tx_env.nonce = Some(tx.nonce); - tx_env.access_list.clear(); - tx_env.blob_hashes.clear(); - tx_env.max_fee_per_blob_gas.take(); - } - Transaction::Eip2930(tx) => { - tx_env.gas_limit = tx.gas_limit; - tx_env.gas_price = U256::from(tx.gas_price); - tx_env.gas_priority_fee = None; - tx_env.transact_to = tx.to; - tx_env.value = tx.value; - tx_env.data = tx.input.clone(); - tx_env.chain_id = Some(tx.chain_id); - tx_env.nonce = Some(tx.nonce); - tx_env.access_list = tx - .access_list - .iter() - .map(|l| { - (l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect()) - }) - .collect(); - tx_env.blob_hashes.clear(); - tx_env.max_fee_per_blob_gas.take(); - } - Transaction::Eip1559(tx) => { - tx_env.gas_limit = tx.gas_limit; - tx_env.gas_price = U256::from(tx.max_fee_per_gas); - tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); - tx_env.transact_to = tx.to; - tx_env.value = tx.value; - tx_env.data = tx.input.clone(); - tx_env.chain_id = Some(tx.chain_id); - tx_env.nonce = Some(tx.nonce); - tx_env.access_list = tx - .access_list - .iter() - .map(|l| { - (l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect()) - }) - .collect(); - tx_env.blob_hashes.clear(); - tx_env.max_fee_per_blob_gas.take(); - } - Transaction::Eip4844(tx) => { - tx_env.gas_limit = tx.gas_limit; - tx_env.gas_price = U256::from(tx.max_fee_per_gas); - tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); - tx_env.transact_to = TxKind::Call(tx.to); - tx_env.value = tx.value; - tx_env.data = tx.input.clone(); - tx_env.chain_id = Some(tx.chain_id); - tx_env.nonce = Some(tx.nonce); - tx_env.access_list = tx - .access_list - .iter() - .map(|l| { - (l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect()) - }) - .collect(); - tx_env.blob_hashes.clone_from(&tx.blob_versioned_hashes); - tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas)); - } - #[cfg(feature = "optimism")] - Transaction::Deposit(tx) => { - tx_env.access_list.clear(); - tx_env.gas_limit = tx.gas_limit; - tx_env.gas_price = U256::ZERO; - tx_env.gas_priority_fee = None; - tx_env.transact_to = tx.to; - tx_env.value = tx.value; - tx_env.data = tx.input.clone(); - tx_env.chain_id = None; - tx_env.nonce = None; - } - } -} - -/// Fill transaction environment from a [Transaction], envelope, and the given sender address. -#[cfg(feature = "optimism")] -#[inline(always)] -pub fn fill_op_tx_env>( - tx_env: &mut TxEnv, - transaction: T, - sender: Address, - envelope: Bytes, -) { - fill_tx_env(tx_env, &transaction, sender); - match transaction.as_ref() { - Transaction::Deposit(tx) => { - tx_env.optimism = OptimismFields { - source_hash: Some(tx.source_hash), - mint: tx.mint, - is_system_transaction: Some(tx.is_system_transaction), - enveloped_tx: Some(envelope), - }; - } - _ => { - tx_env.optimism = OptimismFields { - source_hash: None, - mint: None, - is_system_transaction: Some(false), - enveloped_tx: Some(envelope), - } - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/primitives/src/transaction/compat.rs b/crates/primitives/src/transaction/compat.rs new file mode 100644 index 000000000000..ec4f7ad8255d --- /dev/null +++ b/crates/primitives/src/transaction/compat.rs @@ -0,0 +1,131 @@ +use crate::{Address, Transaction, TransactionSigned, TxKind, U256}; +use revm_primitives::TxEnv; + +/// Implements behaviour to fill a [`TxEnv`] from another transaction. +pub trait FillTxEnv { + /// Fills [`TxEnv`] with an [`Address`] and transaction. + fn fill_tx_env(&self, tx_env: &mut TxEnv, sender: Address); +} + +impl FillTxEnv for TransactionSigned { + fn fill_tx_env(&self, tx_env: &mut TxEnv, sender: Address) { + #[cfg(feature = "optimism")] + let envelope = { + let mut envelope = Vec::with_capacity(self.length_without_header()); + self.encode_enveloped(&mut envelope); + envelope + }; + + tx_env.caller = sender; + match self.as_ref() { + Transaction::Legacy(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.gas_price); + tx_env.gas_priority_fee = None; + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = tx.chain_id; + tx_env.nonce = Some(tx.nonce); + tx_env.access_list.clear(); + tx_env.blob_hashes.clear(); + tx_env.max_fee_per_blob_gas.take(); + } + Transaction::Eip2930(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.gas_price); + tx_env.gas_priority_fee = None; + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = Some(tx.chain_id); + tx_env.nonce = Some(tx.nonce); + tx_env.access_list = tx + .access_list + .iter() + .map(|l| { + ( + l.address, + l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect(), + ) + }) + .collect(); + tx_env.blob_hashes.clear(); + tx_env.max_fee_per_blob_gas.take(); + } + Transaction::Eip1559(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.max_fee_per_gas); + tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = Some(tx.chain_id); + tx_env.nonce = Some(tx.nonce); + tx_env.access_list = tx + .access_list + .iter() + .map(|l| { + ( + l.address, + l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect(), + ) + }) + .collect(); + tx_env.blob_hashes.clear(); + tx_env.max_fee_per_blob_gas.take(); + } + Transaction::Eip4844(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.max_fee_per_gas); + tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); + tx_env.transact_to = TxKind::Call(tx.to); + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = Some(tx.chain_id); + tx_env.nonce = Some(tx.nonce); + tx_env.access_list = tx + .access_list + .iter() + .map(|l| { + ( + l.address, + l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect(), + ) + }) + .collect(); + tx_env.blob_hashes.clone_from(&tx.blob_versioned_hashes); + tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas)); + } + #[cfg(feature = "optimism")] + Transaction::Deposit(tx) => { + tx_env.access_list.clear(); + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::ZERO; + tx_env.gas_priority_fee = None; + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = None; + tx_env.nonce = None; + tx_env.optimism = revm_primitives::OptimismFields { + source_hash: Some(tx.source_hash), + mint: tx.mint, + is_system_transaction: Some(tx.is_system_transaction), + enveloped_tx: Some(envelope.into()), + }; + return; + } + } + + #[cfg(feature = "optimism")] + if !self.is_deposit() { + tx_env.optimism = revm_primitives::OptimismFields { + source_hash: None, + mint: None, + is_system_transaction: Some(false), + enveloped_tx: Some(envelope.into()), + } + } + } +} diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 7a5ae73ba743..b6f8ba72c71d 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -32,6 +32,7 @@ pub use sidecar::generate_blob_sidecar; pub use sidecar::BlobTransactionValidationError; pub use sidecar::{BlobTransaction, BlobTransactionSidecar}; +pub use compat::FillTxEnv; pub use signature::{extract_chain_id, Signature}; pub use tx_type::{ TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, @@ -39,6 +40,7 @@ pub use tx_type::{ pub use variant::TransactionSignedVariant; mod access_list; +mod compat; mod eip1559; mod eip2930; mod eip4844; diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 968f0beff1a7..388438857500 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -19,12 +19,13 @@ use reth::{ tasks::TaskManager, }; use reth_chainspec::{Chain, ChainSpec, Head}; +use reth_evm_ethereum::EthEvmConfig; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::{EthExecutorProvider, EthereumNode}; use reth_primitives::{ - revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg}, - Header, U256, + revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, + Address, Header, TransactionSigned, U256, }; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; @@ -88,6 +89,10 @@ impl ConfigureEvmEnv for MyEvmConfig { cfg_env.handler_cfg.spec_id = spec_id; } + + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + EthEvmConfig::default().fill_tx_env(tx_env, transaction, sender) + } } impl ConfigureEvm for MyEvmConfig { diff --git a/examples/exex/rollup/src/execution.rs b/examples/exex/rollup/src/execution.rs index 2f755183fab4..2746553872c9 100644 --- a/examples/exex/rollup/src/execution.rs +++ b/examples/exex/rollup/src/execution.rs @@ -10,7 +10,6 @@ use reth_primitives::{ constants, eip4844::kzg_to_versioned_hash, keccak256, - revm::env::fill_tx_env, revm_primitives::{CfgEnvWithHandlerCfg, EVMError, ExecutionResult, ResultAndState}, Address, Block, BlockWithSenders, Bytes, EthereumHardfork, Header, Receipt, TransactionSigned, TxType, B256, U256, @@ -217,7 +216,7 @@ fn execute_transactions( } // Execute transaction. // Fill revm structure. - fill_tx_env(evm.tx_mut(), &transaction, sender); + EthEvmConfig::default().fill_tx_env(evm.tx_mut(), &transaction, sender); let ResultAndState { result, state } = match evm.transact() { Ok(result) => result, From 9d4722eb650563aed650c325407f306b5770b858 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:24:51 +0200 Subject: [PATCH 282/405] chore: remove unused `static-file` code (#9178) --- .../static-file/src/segments/headers.rs | 77 +----------- .../static-file/src/segments/mod.rs | 93 +------------- .../static-file/src/segments/receipts.rs | 64 +--------- .../static-file/src/segments/transactions.rs | 64 +--------- .../storage/db/src/static_file/generation.rs | 115 ------------------ crates/storage/db/src/static_file/mod.rs | 3 - .../storage/nippy-jar/src/compression/mod.rs | 2 + .../storage/nippy-jar/src/compression/zstd.rs | 4 +- crates/storage/nippy-jar/src/cursor.rs | 4 +- crates/storage/nippy-jar/src/lib.rs | 102 ++++++++-------- .../provider/src/providers/static_file/mod.rs | 67 +++------- 11 files changed, 96 insertions(+), 499 deletions(-) delete mode 100644 crates/storage/db/src/static_file/generation.rs diff --git a/crates/static-file/static-file/src/segments/headers.rs b/crates/static-file/static-file/src/segments/headers.rs index e87c1fdc58e8..5824d1d1ac7d 100644 --- a/crates/static-file/static-file/src/segments/headers.rs +++ b/crates/static-file/static-file/src/segments/headers.rs @@ -1,14 +1,14 @@ -use crate::segments::{dataset_for_compression, prepare_jar, Segment, SegmentHeader}; +use crate::segments::Segment; use alloy_primitives::BlockNumber; -use reth_db::{static_file::create_static_file_T1_T2_T3, tables, RawKey, RawTable}; +use reth_db::tables; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, DatabaseProviderRO, }; -use reth_static_file_types::{SegmentConfig, StaticFileSegment}; +use reth_static_file_types::StaticFileSegment; use reth_storage_errors::provider::ProviderResult; -use std::{ops::RangeInclusive, path::Path}; +use std::ops::RangeInclusive; /// Static File segment responsible for [`StaticFileSegment::Headers`] part of data. #[derive(Debug, Default)] @@ -56,73 +56,4 @@ impl Segment for Headers { Ok(()) } - - fn create_static_file_file( - &self, - provider: &DatabaseProviderRO, - directory: &Path, - config: SegmentConfig, - block_range: RangeInclusive, - ) -> ProviderResult<()> { - let range_len = block_range.clone().count(); - let jar = prepare_jar::( - provider, - directory, - StaticFileSegment::Headers, - config, - block_range.clone(), - range_len, - || { - Ok([ - dataset_for_compression::( - provider, - &block_range, - range_len, - )?, - dataset_for_compression::( - provider, - &block_range, - range_len, - )?, - dataset_for_compression::( - provider, - &block_range, - range_len, - )?, - ]) - }, - )?; - - // Generate list of hashes for filters & PHF - let mut cursor = provider.tx_ref().cursor_read::>()?; - let hashes = if config.filters.has_filters() { - Some( - cursor - .walk(Some(RawKey::from(*block_range.start())))? - .take(range_len) - .map(|row| row.map(|(_key, value)| value.into_value()).map_err(|e| e.into())), - ) - } else { - None - }; - - create_static_file_T1_T2_T3::< - tables::Headers, - tables::HeaderTerminalDifficulties, - tables::CanonicalHeaders, - BlockNumber, - SegmentHeader, - >( - provider.tx_ref(), - block_range, - None, - // We already prepared the dictionary beforehand - None::>>>, - hashes, - range_len, - jar, - )?; - - Ok(()) - } } diff --git a/crates/static-file/static-file/src/segments/mod.rs b/crates/static-file/static-file/src/segments/mod.rs index 77798dd08542..1125b2085d99 100644 --- a/crates/static-file/static-file/src/segments/mod.rs +++ b/crates/static-file/static-file/src/segments/mod.rs @@ -10,20 +10,11 @@ mod receipts; pub use receipts::Receipts; use alloy_primitives::BlockNumber; -use reth_db::{RawKey, RawTable}; -use reth_db_api::{cursor::DbCursorRO, database::Database, table::Table, transaction::DbTx}; -use reth_nippy_jar::NippyJar; -use reth_provider::{ - providers::StaticFileProvider, DatabaseProviderRO, ProviderError, TransactionsProviderExt, -}; -use reth_static_file_types::{ - find_fixed_range, Compression, Filters, InclusionFilter, PerfectHashingFunction, SegmentConfig, - SegmentHeader, StaticFileSegment, -}; +use reth_db_api::database::Database; +use reth_provider::{providers::StaticFileProvider, DatabaseProviderRO}; +use reth_static_file_types::StaticFileSegment; use reth_storage_errors::provider::ProviderResult; -use std::{ops::RangeInclusive, path::Path}; - -pub(crate) type Rows = [Vec>; COLUMNS]; +use std::ops::RangeInclusive; /// A segment represents moving some portion of the data to static files. pub trait Segment: Send + Sync { @@ -38,80 +29,4 @@ pub trait Segment: Send + Sync { static_file_provider: StaticFileProvider, block_range: RangeInclusive, ) -> ProviderResult<()>; - - /// Create a static file of data for the provided block range. The `directory` parameter - /// determines the static file's save location. - fn create_static_file_file( - &self, - provider: &DatabaseProviderRO, - directory: &Path, - config: SegmentConfig, - block_range: RangeInclusive, - ) -> ProviderResult<()>; -} - -/// Returns a [`NippyJar`] according to the desired configuration. The `directory` parameter -/// determines the static file's save location. -pub(crate) fn prepare_jar( - provider: &DatabaseProviderRO, - directory: impl AsRef, - segment: StaticFileSegment, - segment_config: SegmentConfig, - block_range: RangeInclusive, - total_rows: usize, - prepare_compression: impl Fn() -> ProviderResult>, -) -> ProviderResult> { - let tx_range = match segment { - StaticFileSegment::Headers => None, - StaticFileSegment::Receipts | StaticFileSegment::Transactions => { - Some(provider.transaction_range_by_block_range(block_range.clone())?.into()) - } - }; - - let mut nippy_jar = NippyJar::new( - COLUMNS, - &directory.as_ref().join(segment.filename(&find_fixed_range(*block_range.end())).as_str()), - SegmentHeader::new(block_range.clone().into(), Some(block_range.into()), tx_range, segment), - ); - - nippy_jar = match segment_config.compression { - Compression::Lz4 => nippy_jar.with_lz4(), - Compression::Zstd => nippy_jar.with_zstd(false, 0), - Compression::ZstdWithDictionary => { - let dataset = prepare_compression()?; - - nippy_jar = nippy_jar.with_zstd(true, 5_000_000); - nippy_jar - .prepare_compression(dataset.to_vec()) - .map_err(|e| ProviderError::NippyJar(e.to_string()))?; - nippy_jar - } - Compression::Uncompressed => nippy_jar, - }; - - if let Filters::WithFilters(inclusion_filter, phf) = segment_config.filters { - nippy_jar = match inclusion_filter { - InclusionFilter::Cuckoo => nippy_jar.with_cuckoo_filter(total_rows), - }; - nippy_jar = match phf { - PerfectHashingFunction::Fmph => nippy_jar.with_fmph(), - PerfectHashingFunction::GoFmph => nippy_jar.with_gofmph(), - }; - } - - Ok(nippy_jar) -} - -/// Generates the dataset to train a zstd dictionary with the most recent rows (at most 1000). -pub(crate) fn dataset_for_compression>( - provider: &DatabaseProviderRO, - range: &RangeInclusive, - range_len: usize, -) -> ProviderResult>> { - let mut cursor = provider.tx_ref().cursor_read::>()?; - Ok(cursor - .walk_back(Some(RawKey::from(*range.end())))? - .take(range_len.min(1000)) - .map(|row| row.map(|(_key, value)| value.into_value()).expect("should exist")) - .collect::>()) } diff --git a/crates/static-file/static-file/src/segments/receipts.rs b/crates/static-file/static-file/src/segments/receipts.rs index e09b5e690df9..5548e9f99ddf 100644 --- a/crates/static-file/static-file/src/segments/receipts.rs +++ b/crates/static-file/static-file/src/segments/receipts.rs @@ -1,14 +1,14 @@ -use crate::segments::{dataset_for_compression, prepare_jar, Segment}; -use alloy_primitives::{BlockNumber, TxNumber}; -use reth_db::{static_file::create_static_file_T1, tables}; +use crate::segments::Segment; +use alloy_primitives::BlockNumber; +use reth_db::tables; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, - BlockReader, DatabaseProviderRO, TransactionsProviderExt, + BlockReader, DatabaseProviderRO, }; -use reth_static_file_types::{SegmentConfig, SegmentHeader, StaticFileSegment}; +use reth_static_file_types::StaticFileSegment; use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use std::{ops::RangeInclusive, path::Path}; +use std::ops::RangeInclusive; /// Static File segment responsible for [`StaticFileSegment::Receipts`] part of data. #[derive(Debug, Default)] @@ -47,56 +47,4 @@ impl Segment for Receipts { Ok(()) } - - fn create_static_file_file( - &self, - provider: &DatabaseProviderRO, - directory: &Path, - config: SegmentConfig, - block_range: RangeInclusive, - ) -> ProviderResult<()> { - let tx_range = provider.transaction_range_by_block_range(block_range.clone())?; - let tx_range_len = tx_range.clone().count(); - - let jar = prepare_jar::( - provider, - directory, - StaticFileSegment::Receipts, - config, - block_range, - tx_range_len, - || { - Ok([dataset_for_compression::( - provider, - &tx_range, - tx_range_len, - )?]) - }, - )?; - - // Generate list of hashes for filters & PHF - let hashes = if config.filters.has_filters() { - Some( - provider - .transaction_hashes_by_range(*tx_range.start()..(*tx_range.end() + 1))? - .into_iter() - .map(|(tx, _)| Ok(tx)), - ) - } else { - None - }; - - create_static_file_T1::( - provider.tx_ref(), - tx_range, - None, - // We already prepared the dictionary beforehand - None::>>>, - hashes, - tx_range_len, - jar, - )?; - - Ok(()) - } } diff --git a/crates/static-file/static-file/src/segments/transactions.rs b/crates/static-file/static-file/src/segments/transactions.rs index c7daeba0675f..4361f8ca661e 100644 --- a/crates/static-file/static-file/src/segments/transactions.rs +++ b/crates/static-file/static-file/src/segments/transactions.rs @@ -1,14 +1,14 @@ -use crate::segments::{dataset_for_compression, prepare_jar, Segment}; -use alloy_primitives::{BlockNumber, TxNumber}; -use reth_db::{static_file::create_static_file_T1, tables}; +use crate::segments::Segment; +use alloy_primitives::BlockNumber; +use reth_db::tables; use reth_db_api::{cursor::DbCursorRO, database::Database, transaction::DbTx}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, - BlockReader, DatabaseProviderRO, TransactionsProviderExt, + BlockReader, DatabaseProviderRO, }; -use reth_static_file_types::{SegmentConfig, SegmentHeader, StaticFileSegment}; +use reth_static_file_types::StaticFileSegment; use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use std::{ops::RangeInclusive, path::Path}; +use std::ops::RangeInclusive; /// Static File segment responsible for [`StaticFileSegment::Transactions`] part of data. #[derive(Debug, Default)] @@ -53,56 +53,4 @@ impl Segment for Transactions { Ok(()) } - - fn create_static_file_file( - &self, - provider: &DatabaseProviderRO, - directory: &Path, - config: SegmentConfig, - block_range: RangeInclusive, - ) -> ProviderResult<()> { - let tx_range = provider.transaction_range_by_block_range(block_range.clone())?; - let tx_range_len = tx_range.clone().count(); - - let jar = prepare_jar::( - provider, - directory, - StaticFileSegment::Transactions, - config, - block_range, - tx_range_len, - || { - Ok([dataset_for_compression::( - provider, - &tx_range, - tx_range_len, - )?]) - }, - )?; - - // Generate list of hashes for filters & PHF - let hashes = if config.filters.has_filters() { - Some( - provider - .transaction_hashes_by_range(*tx_range.start()..(*tx_range.end() + 1))? - .into_iter() - .map(|(tx, _)| Ok(tx)), - ) - } else { - None - }; - - create_static_file_T1::( - provider.tx_ref(), - tx_range, - None, - // We already prepared the dictionary beforehand - None::>>>, - hashes, - tx_range_len, - jar, - )?; - - Ok(()) - } } diff --git a/crates/storage/db/src/static_file/generation.rs b/crates/storage/db/src/static_file/generation.rs deleted file mode 100644 index 9c2a64a23b18..000000000000 --- a/crates/storage/db/src/static_file/generation.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::{RawKey, RawTable}; -use reth_db_api::{ - cursor::DbCursorRO, - table::{Key, Table}, - transaction::DbTx, -}; - -use reth_nippy_jar::{ColumnResult, NippyJar, NippyJarHeader, PHFKey}; -use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use reth_tracing::tracing::*; -use std::{error::Error as StdError, ops::RangeInclusive}; - -/// Macro that generates static file creation functions that take an arbitrary number of [`Table`] -/// and creates a [`NippyJar`] file out of their [`Table::Value`]. Each list of [`Table::Value`] -/// from a table is a column of values. -/// -/// Has membership filter set and compression dictionary support. -macro_rules! generate_static_file_func { - ($(($($tbl:ident),+)),+ $(,)? ) => { - $( - paste::item! { - /// Creates a static file from specified tables. Each table's `Value` iterator represents a column. - /// - /// **Ensure the range contains the same number of rows.** - /// - /// * `tx`: Database transaction. - /// * `range`: Data range for columns in tables. - /// * `additional`: Additional columns which can't be straight straightforwardly walked on. - /// * `keys`: IntoIterator of keys (eg. `TxHash` or `BlockHash`) with length equal to `row_count` and ordered by future column insertion from `range`. - /// * `dict_compression_set`: Sets of column data for compression dictionaries. Max size is 2GB. Row count is independent. - /// * `row_count`: Total rows to add to `NippyJar`. Must match row count in `range`. - /// * `nippy_jar`: Static File object responsible for file generation. - #[allow(non_snake_case)] - pub fn []< - $($tbl: Table,)+ - K, - H: NippyJarHeader - > - ( - tx: &impl DbTx, - range: RangeInclusive, - additional: Option, Box>>>>>, - dict_compression_set: Option>>>, - keys: Option>>, - row_count: usize, - mut nippy_jar: NippyJar - ) -> ProviderResult<()> - where K: Key + Copy - { - let additional = additional.unwrap_or_default(); - debug!(target: "reth::static_file", ?range, "Creating static file {:?} and {} more columns.", vec![$($tbl::NAME,)+], additional.len()); - - let range: RangeInclusive> = RawKey::new(*range.start())..=RawKey::new(*range.end()); - - // Create PHF and Filter if required - if let Some(keys) = keys { - debug!(target: "reth::static_file", "Calculating Filter, PHF and offset index list"); - match nippy_jar.prepare_index(keys, row_count) { - Ok(_) => { - debug!(target: "reth::static_file", "Filter, PHF and offset index list calculated."); - }, - Err(e) => { - return Err(ProviderError::NippyJar(e.to_string())); - } - } - } - - // Create compression dictionaries if required - if let Some(data_sets) = dict_compression_set { - debug!(target: "reth::static_file", "Creating compression dictionaries."); - match nippy_jar.prepare_compression(data_sets){ - Ok(_) => { - debug!(target: "reth::static_file", "Compression dictionaries created."); - }, - Err(e) => { - return Err(ProviderError::NippyJar(e.to_string())); - } - } - - } - - // Creates the cursors for the columns - $( - let mut [< $tbl _cursor>] = tx.cursor_read::>()?; - let [< $tbl _iter>] = [< $tbl _cursor>] - .walk_range(range.clone())? - .into_iter() - .map(|row| - row - .map(|(_key, val)| val.into_value()) - .map_err(|e| Box::new(e) as Box) - ); - - )+ - - // Create the static file from the data - let col_iterators: Vec,_>>>> = vec![ - $(Box::new([< $tbl _iter>]),)+ - ]; - - - debug!(target: "reth::static_file", jar=?nippy_jar, "Generating static file."); - - let nippy_jar = nippy_jar.freeze(col_iterators.into_iter().chain(additional).collect(), row_count as u64).map_err(|e| ProviderError::NippyJar(e.to_string())); - - debug!(target: "reth::static_file", jar=?nippy_jar, "Static file generated."); - - Ok(()) - } - } - )+ - }; -} - -generate_static_file_func!((T1), (T1, T2), (T1, T2, T3), (T1, T2, T3, T4), (T1, T2, T3, T4, T5),); diff --git a/crates/storage/db/src/static_file/mod.rs b/crates/storage/db/src/static_file/mod.rs index daa6f8a816bd..f27a574f640e 100644 --- a/crates/storage/db/src/static_file/mod.rs +++ b/crates/storage/db/src/static_file/mod.rs @@ -1,13 +1,10 @@ //! reth's static file database table import and access -mod generation; use std::{ collections::{hash_map::Entry, HashMap}, path::Path, }; -pub use generation::*; - mod cursor; pub use cursor::StaticFileCursor; diff --git a/crates/storage/nippy-jar/src/compression/mod.rs b/crates/storage/nippy-jar/src/compression/mod.rs index 76e8c6d16b69..28a92fe909f2 100644 --- a/crates/storage/nippy-jar/src/compression/mod.rs +++ b/crates/storage/nippy-jar/src/compression/mod.rs @@ -30,6 +30,7 @@ pub trait Compression: Serialize + for<'a> Deserialize<'a> { true } + #[cfg(test)] /// If required, prepares compression algorithm with an early pass on the data. fn prepare_compression( &mut self, @@ -95,6 +96,7 @@ impl Compression for Compressors { } } + #[cfg(test)] fn prepare_compression( &mut self, columns: Vec>>, diff --git a/crates/storage/nippy-jar/src/compression/zstd.rs b/crates/storage/nippy-jar/src/compression/zstd.rs index c55ca103aff5..494d79de52f6 100644 --- a/crates/storage/nippy-jar/src/compression/zstd.rs +++ b/crates/storage/nippy-jar/src/compression/zstd.rs @@ -185,6 +185,7 @@ impl Compression for Zstd { matches!(self.state, ZstdState::Ready) } + #[cfg(test)] /// If using it with dictionaries, prepares a dictionary for each column. fn prepare_compression( &mut self, @@ -208,7 +209,6 @@ impl Compression for Zstd { return Err(NippyJarError::ColumnLenMismatch(self.columns, columns.len())) } - // TODO: parallel calculation let mut dictionaries = vec![]; for column in columns { // ZSTD requires all training data to be continuous in memory, alongside the size of @@ -273,6 +273,7 @@ impl<'a> std::fmt::Debug for ZstdDictionaries<'a> { } impl<'a> ZstdDictionaries<'a> { + #[cfg(test)] /// Creates [`ZstdDictionaries`]. pub(crate) fn new(raw: Vec) -> Self { Self(raw.into_iter().map(ZstdDictionary::Raw).collect()) @@ -315,6 +316,7 @@ impl<'a> ZstdDictionaries<'a> { /// A Zstd dictionary. It's created and serialized with [`ZstdDictionary::Raw`], and deserialized as /// [`ZstdDictionary::Loaded`]. pub(crate) enum ZstdDictionary<'a> { + #[allow(dead_code)] Raw(RawDictionary), Loaded(DecoderDictionary<'a>), } diff --git a/crates/storage/nippy-jar/src/cursor.rs b/crates/storage/nippy-jar/src/cursor.rs index 434c40a9a69b..d42b0d364bf8 100644 --- a/crates/storage/nippy-jar/src/cursor.rs +++ b/crates/storage/nippy-jar/src/cursor.rs @@ -67,7 +67,7 @@ impl<'a, H: NippyJarHeader> NippyJarCursor<'a, H> { self.row = 0; } - /// Returns a row, searching it by a key used during [`NippyJar::prepare_index`]. + /// Returns a row, searching it by a key. /// /// **May return false positives.** /// @@ -130,7 +130,7 @@ impl<'a, H: NippyJarHeader> NippyJarCursor<'a, H> { )) } - /// Returns a row, searching it by a key used during [`NippyJar::prepare_index`] by using a + /// Returns a row, searching it by a key using a /// `mask` to only read certain columns from the row. /// /// **May return false positives.** diff --git a/crates/storage/nippy-jar/src/lib.rs b/crates/storage/nippy-jar/src/lib.rs index 8247599d7aeb..056f456eb2b7 100644 --- a/crates/storage/nippy-jar/src/lib.rs +++ b/crates/storage/nippy-jar/src/lib.rs @@ -28,7 +28,9 @@ pub mod filter; use filter::{Cuckoo, InclusionFilter, InclusionFilters}; pub mod compression; -use compression::{Compression, Compressors}; +#[cfg(test)] +use compression::Compression; +use compression::Compressors; pub mod phf; pub use phf::PHFKey; @@ -306,6 +308,56 @@ impl NippyJar { DataReader::new(self.data_path()) } + /// Writes all necessary configuration to file. + fn freeze_config(&self) -> Result<(), NippyJarError> { + // Atomic writes are hard: + let mut tmp_path = self.config_path(); + tmp_path.set_extension(".tmp"); + + // Write to temporary file + let mut file = File::create(&tmp_path)?; + bincode::serialize_into(&mut file, &self)?; + + // fsync() file + file.sync_all()?; + + // Rename file, not move + reth_fs_util::rename(&tmp_path, self.config_path())?; + + // fsync() dir + if let Some(parent) = tmp_path.parent() { + OpenOptions::new().read(true).open(parent)?.sync_all()?; + } + Ok(()) + } +} + +impl InclusionFilter for NippyJar { + fn add(&mut self, element: &[u8]) -> Result<(), NippyJarError> { + self.filter.as_mut().ok_or(NippyJarError::FilterMissing)?.add(element) + } + + fn contains(&self, element: &[u8]) -> Result { + self.filter.as_ref().ok_or(NippyJarError::FilterMissing)?.contains(element) + } + + fn size(&self) -> usize { + self.filter.as_ref().map(|f| f.size()).unwrap_or(0) + } +} + +impl PerfectHashingFunction for NippyJar { + fn set_keys(&mut self, keys: &[T]) -> Result<(), NippyJarError> { + self.phf.as_mut().ok_or(NippyJarError::PHFMissing)?.set_keys(keys) + } + + fn get_index(&self, key: &[u8]) -> Result, NippyJarError> { + self.phf.as_ref().ok_or(NippyJarError::PHFMissing)?.get_index(key) + } +} + +#[cfg(test)] +impl NippyJar { /// If required, prepares any compression algorithm to an early pass of the data. pub fn prepare_compression( &mut self, @@ -429,53 +481,6 @@ impl NippyJar { Ok(()) } - - /// Writes all necessary configuration to file. - fn freeze_config(&self) -> Result<(), NippyJarError> { - // Atomic writes are hard: - let mut tmp_path = self.config_path(); - tmp_path.set_extension(".tmp"); - - // Write to temporary file - let mut file = File::create(&tmp_path)?; - bincode::serialize_into(&mut file, &self)?; - - // fsync() file - file.sync_all()?; - - // Rename file, not move - reth_fs_util::rename(&tmp_path, self.config_path())?; - - // fsync() dir - if let Some(parent) = tmp_path.parent() { - OpenOptions::new().read(true).open(parent)?.sync_all()?; - } - Ok(()) - } -} - -impl InclusionFilter for NippyJar { - fn add(&mut self, element: &[u8]) -> Result<(), NippyJarError> { - self.filter.as_mut().ok_or(NippyJarError::FilterMissing)?.add(element) - } - - fn contains(&self, element: &[u8]) -> Result { - self.filter.as_ref().ok_or(NippyJarError::FilterMissing)?.contains(element) - } - - fn size(&self) -> usize { - self.filter.as_ref().map(|f| f.size()).unwrap_or(0) - } -} - -impl PerfectHashingFunction for NippyJar { - fn set_keys(&mut self, keys: &[T]) -> Result<(), NippyJarError> { - self.phf.as_mut().ok_or(NippyJarError::PHFMissing)?.set_keys(keys) - } - - fn get_index(&self, key: &[u8]) -> Result, NippyJarError> { - self.phf.as_ref().ok_or(NippyJarError::PHFMissing)?.get_index(key) - } } /// Manages the reading of static file data using memory-mapped files. @@ -581,6 +586,7 @@ impl DataReader { #[cfg(test)] mod tests { use super::*; + use compression::Compression; use rand::{rngs::SmallRng, seq::SliceRandom, RngCore, SeedableRng}; use std::{collections::HashSet, fs::OpenOptions}; diff --git a/crates/storage/provider/src/providers/static_file/mod.rs b/crates/storage/provider/src/providers/static_file/mod.rs index e7073defeed0..c5abdbe00c31 100644 --- a/crates/storage/provider/src/providers/static_file/mod.rs +++ b/crates/storage/provider/src/providers/static_file/mod.rs @@ -59,29 +59,16 @@ mod tests { use super::*; use crate::{test_utils::create_test_provider_factory, HeaderProvider}; use rand::seq::SliceRandom; - use reth_db::{ - static_file::create_static_file_T1_T2_T3, CanonicalHeaders, HeaderNumbers, - HeaderTerminalDifficulties, Headers, RawTable, - }; - use reth_db_api::{ - cursor::DbCursorRO, - transaction::{DbTx, DbTxMut}, - }; - use reth_primitives::{static_file::find_fixed_range, BlockNumber, B256, U256}; + use reth_db::{CanonicalHeaders, HeaderNumbers, HeaderTerminalDifficulties, Headers}; + use reth_db_api::transaction::DbTxMut; + use reth_primitives::{static_file::find_fixed_range, B256, U256}; use reth_testing_utils::generators::{self, random_header_range}; - use std::vec::IntoIter; #[test] fn test_snap() { // Ranges let row_count = 100u64; let range = 0..=(row_count - 1); - let segment_header = SegmentHeader::new( - range.clone().into(), - Some(range.clone().into()), - Some(range.clone().into()), - StaticFileSegment::Headers, - ); // Data sources let factory = create_test_provider_factory(); @@ -113,46 +100,22 @@ mod tests { // Create StaticFile { - let with_compression = true; - let with_filter = true; - - let mut nippy_jar = NippyJar::new(3, static_file.as_path(), segment_header); - - if with_compression { - nippy_jar = nippy_jar.with_zstd(false, 0); + let manager = StaticFileProvider::read_write(static_files_path.path()).unwrap(); + let mut writer = manager.latest_writer(StaticFileSegment::Headers).unwrap(); + let mut td = U256::ZERO; + + for header in headers.clone() { + td += header.header().difficulty; + let hash = header.hash(); + writer.append_header(header.unseal(), td, hash).unwrap(); } - - if with_filter { - nippy_jar = nippy_jar.with_cuckoo_filter(row_count as usize + 10).with_fmph(); - } - - let provider = factory.provider().unwrap(); - let tx = provider.tx_ref(); - - let none_vec: Option>>> = None; - - // Generate list of hashes for filters & PHF - let mut cursor = tx.cursor_read::>().unwrap(); - let hashes = cursor - .walk(None) - .unwrap() - .map(|row| row.map(|(_key, value)| value.into_value()).map_err(|e| e.into())); - - create_static_file_T1_T2_T3::< - Headers, - HeaderTerminalDifficulties, - CanonicalHeaders, - BlockNumber, - SegmentHeader, - >(tx, range, None, none_vec, Some(hashes), row_count as usize, nippy_jar) - .unwrap(); + writer.commit().unwrap(); } // Use providers to query Header data and compare if it matches { let db_provider = factory.provider().unwrap(); - let manager = - StaticFileProvider::read_write(static_files_path.path()).unwrap().with_filters(); + let manager = StaticFileProvider::read_write(static_files_path.path()).unwrap(); let jar_provider = manager .get_segment_provider_from_block(StaticFileSegment::Headers, 0, Some(&static_file)) .unwrap(); @@ -168,12 +131,12 @@ mod tests { // Compare Header assert_eq!(header, db_provider.header(&header_hash).unwrap().unwrap()); - assert_eq!(header, jar_provider.header(&header_hash).unwrap().unwrap()); + assert_eq!(header, jar_provider.header_by_number(header.number).unwrap().unwrap()); // Compare HeaderTerminalDifficulties assert_eq!( db_provider.header_td(&header_hash).unwrap().unwrap(), - jar_provider.header_td(&header_hash).unwrap().unwrap() + jar_provider.header_td_by_number(header.number).unwrap().unwrap() ); } } From 158377f457d71f0f6bfc267509db72912811722e Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Mon, 1 Jul 2024 17:37:58 +0200 Subject: [PATCH 283/405] chore: rename pipeline references to backfill sync (#9223) --- crates/engine/tree/src/backfill.rs | 2 +- crates/engine/tree/src/chain.rs | 74 +++++++++++++++--------------- crates/engine/tree/src/download.rs | 8 ++-- crates/engine/tree/src/engine.rs | 6 +-- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/crates/engine/tree/src/backfill.rs b/crates/engine/tree/src/backfill.rs index ec0b4ef06cc7..3060ddd1d4e7 100644 --- a/crates/engine/tree/src/backfill.rs +++ b/crates/engine/tree/src/backfill.rs @@ -239,7 +239,7 @@ mod tests { .build(), ); - // force the pipeline to be "done" after 5 blocks + // force the pipeline to be "done" after `pipeline_done_after` blocks let pipeline = TestPipelineBuilder::new() .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(BlockNumber::from(pipeline_done_after)), diff --git a/crates/engine/tree/src/chain.rs b/crates/engine/tree/src/chain.rs index 97c6d615c118..e3f764beab6b 100644 --- a/crates/engine/tree/src/chain.rs +++ b/crates/engine/tree/src/chain.rs @@ -13,12 +13,12 @@ use std::{ /// /// ## Control flow /// -/// The [`ChainOrchestrator`] is responsible for controlling the pipeline sync and additional hooks. +/// The [`ChainOrchestrator`] is responsible for controlling the backfill sync and additional hooks. /// It polls the given `handler`, which is responsible for advancing the chain, how is up to the /// handler. However, due to database restrictions (e.g. exclusive write access), following /// invariants apply: /// - If the handler requests a backfill run (e.g. [`BackfillAction::Start`]), the handler must -/// ensure that while the pipeline is running, no other write access is granted. +/// ensure that while the backfill sync is running, no other write access is granted. /// - At any time the [`ChainOrchestrator`] can request exclusive write access to the database /// (e.g. if pruning is required), but will not do so until the handler has acknowledged the /// request for write access. @@ -35,8 +35,8 @@ where { /// The handler for advancing the chain. handler: T, - /// Controls pipeline sync. - pipeline: P, + /// Controls backfill sync. + backfill_sync: P, } impl ChainOrchestrator @@ -44,9 +44,9 @@ where T: ChainHandler + Unpin, P: BackfillSync + Unpin, { - /// Creates a new [`ChainOrchestrator`] with the given handler and pipeline. - pub const fn new(handler: T, pipeline: P) -> Self { - Self { handler, pipeline } + /// Creates a new [`ChainOrchestrator`] with the given handler and backfill sync. + pub const fn new(handler: T, backfill_sync: P) -> Self { + Self { handler, backfill_sync } } /// Returns the handler @@ -68,34 +68,34 @@ where // This loop polls the components // - // 1. Polls the pipeline to completion, if active. + // 1. Polls the backfill sync to completion, if active. // 2. Advances the chain by polling the handler. 'outer: loop { - // try to poll the pipeline to completion, if active - match this.pipeline.poll(cx) { - Poll::Ready(pipeline_event) => match pipeline_event { + // try to poll the backfill sync to completion, if active + match this.backfill_sync.poll(cx) { + Poll::Ready(backfill_sync_event) => match backfill_sync_event { BackfillEvent::Idle => {} BackfillEvent::Started(_) => { - // notify handler that pipeline started - this.handler.on_event(FromOrchestrator::PipelineStarted); - return Poll::Ready(ChainEvent::PipelineStarted); + // notify handler that backfill sync started + this.handler.on_event(FromOrchestrator::BackfillSyncStarted); + return Poll::Ready(ChainEvent::BackfillSyncStarted); } BackfillEvent::Finished(res) => { return match res { Ok(event) => { - tracing::debug!(?event, "pipeline finished"); - // notify handler that pipeline finished - this.handler.on_event(FromOrchestrator::PipelineFinished); - Poll::Ready(ChainEvent::PipelineFinished) + tracing::debug!(?event, "backfill sync finished"); + // notify handler that backfill sync finished + this.handler.on_event(FromOrchestrator::BackfillSyncFinished); + Poll::Ready(ChainEvent::BackfillSyncFinished) } Err(err) => { - tracing::error!( %err, "pipeline failed"); + tracing::error!( %err, "backfill sync failed"); Poll::Ready(ChainEvent::FatalError) } } } BackfillEvent::TaskDropped(err) => { - tracing::error!( %err, "pipeline task dropped"); + tracing::error!( %err, "backfill sync task dropped"); return Poll::Ready(ChainEvent::FatalError); } }, @@ -106,9 +106,9 @@ where match this.handler.poll(cx) { Poll::Ready(handler_event) => { match handler_event { - HandlerEvent::Pipeline(target) => { - // trigger pipeline and start polling it - this.pipeline.on_action(BackfillAction::Start(target)); + HandlerEvent::BackfillSync(target) => { + // trigger backfill sync and start polling it + this.backfill_sync.on_action(BackfillAction::Start(target)); continue 'outer } HandlerEvent::Event(ev) => { @@ -153,10 +153,10 @@ enum SyncMode { /// These are meant to be used for observability and debugging purposes. #[derive(Debug)] pub enum ChainEvent { - /// Pipeline sync started - PipelineStarted, - /// Pipeline sync finished - PipelineFinished, + /// Backfill sync started + BackfillSyncStarted, + /// Backfill sync finished + BackfillSyncFinished, /// Fatal error FatalError, /// Event emitted by the handler @@ -180,8 +180,8 @@ pub trait ChainHandler: Send + Sync { /// Events/Requests that the [`ChainHandler`] can emit to the [`ChainOrchestrator`]. #[derive(Clone, Debug)] pub enum HandlerEvent { - /// Request to start a pipeline sync - Pipeline(PipelineTarget), + /// Request to start a backfill sync + BackfillSync(PipelineTarget), /// Other event emitted by the handler Event(T), } @@ -189,26 +189,26 @@ pub enum HandlerEvent { /// Internal events issued by the [`ChainOrchestrator`]. #[derive(Clone, Debug)] pub enum FromOrchestrator { - /// Invoked when pipeline sync finished - PipelineFinished, - /// Invoked when pipeline started - PipelineStarted, + /// Invoked when backfill sync finished + BackfillSyncFinished, + /// Invoked when backfill sync started + BackfillSyncStarted, } /// Represents the state of the chain. #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] pub enum OrchestratorState { /// Orchestrator has exclusive write access to the database. - PipelineActive, + BackfillSyncActive, /// Node is actively processing the chain. #[default] Idle, } impl OrchestratorState { - /// Returns `true` if the state is [`OrchestratorState::PipelineActive`]. - pub const fn is_pipeline_active(&self) -> bool { - matches!(self, Self::PipelineActive) + /// Returns `true` if the state is [`OrchestratorState::BackfillSyncActive`]. + pub const fn is_backfill_sync_active(&self) -> bool { + matches!(self, Self::BackfillSyncActive) } /// Returns `true` if the state is [`OrchestratorState::Idle`]. diff --git a/crates/engine/tree/src/download.rs b/crates/engine/tree/src/download.rs index 12b2bd189288..b8ebb8415c8d 100644 --- a/crates/engine/tree/src/download.rs +++ b/crates/engine/tree/src/download.rs @@ -20,7 +20,7 @@ use tracing::trace; /// A trait that can download blocks on demand. pub trait BlockDownloader: Send + Sync { /// Handle an action. - fn on_action(&mut self, event: DownloadAction); + fn on_action(&mut self, action: DownloadAction); /// Advance in progress requests if any fn poll(&mut self, cx: &mut Context<'_>) -> Poll; @@ -65,7 +65,7 @@ where Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, { /// Create a new instance - pub(crate) fn new(client: Client, consensus: Arc) -> Self { + pub fn new(client: Client, consensus: Arc) -> Self { Self { full_block_client: FullBlockClient::new(client, consensus), inflight_full_block_requests: Vec::new(), @@ -154,8 +154,8 @@ where Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, { /// Handles incoming download actions. - fn on_action(&mut self, event: DownloadAction) { - match event { + fn on_action(&mut self, action: DownloadAction) { + match action { DownloadAction::Clear => self.clear(), DownloadAction::Download(request) => self.download(request), } diff --git a/crates/engine/tree/src/engine.rs b/crates/engine/tree/src/engine.rs index d77162e4cd0a..2010c3768a42 100644 --- a/crates/engine/tree/src/engine.rs +++ b/crates/engine/tree/src/engine.rs @@ -72,10 +72,10 @@ where RequestHandlerEvent::Idle => break, RequestHandlerEvent::HandlerEvent(ev) => { return match ev { - HandlerEvent::Pipeline(target) => { - // bubble up pipeline request + HandlerEvent::BackfillSync(target) => { + // bubble up backfill sync request request self.downloader.on_action(DownloadAction::Clear); - Poll::Ready(HandlerEvent::Pipeline(target)) + Poll::Ready(HandlerEvent::BackfillSync(target)) } HandlerEvent::Event(ev) => { // bubble up the event From e5a634ceced373bd87e308025e754602113376e8 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 1 Jul 2024 08:41:26 -0700 Subject: [PATCH 284/405] chore(execution): verify cumulative gas used before receipts root (#9224) --- crates/ethereum/consensus/src/validation.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/ethereum/consensus/src/validation.rs b/crates/ethereum/consensus/src/validation.rs index fafd0ee4217b..523bed077dec 100644 --- a/crates/ethereum/consensus/src/validation.rs +++ b/crates/ethereum/consensus/src/validation.rs @@ -14,6 +14,16 @@ pub fn validate_block_post_execution( receipts: &[Receipt], requests: &[Request], ) -> Result<(), ConsensusError> { + // Check if gas used matches the value set in header. + let cumulative_gas_used = + receipts.last().map(|receipt| receipt.cumulative_gas_used).unwrap_or(0); + if block.gas_used != cumulative_gas_used { + return Err(ConsensusError::BlockGasUsed { + gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used }, + gas_spent_by_tx: gas_spent_by_transactions(receipts), + }) + } + // Before Byzantium, receipts contained state root that would mean that expensive // operation as hashing that is required for state root got calculated in every // transaction This was replaced with is_success flag. @@ -27,16 +37,6 @@ pub fn validate_block_post_execution( } } - // Check if gas used matches the value set in header. - let cumulative_gas_used = - receipts.last().map(|receipt| receipt.cumulative_gas_used).unwrap_or(0); - if block.gas_used != cumulative_gas_used { - return Err(ConsensusError::BlockGasUsed { - gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used }, - gas_spent_by_tx: gas_spent_by_transactions(receipts), - }) - } - // 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 { From 20713e978567e191605d032ecbbf4facb0c66211 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 1 Jul 2024 16:59:42 +0100 Subject: [PATCH 285/405] docs(book): remote ExEx chapter (#8992) Co-authored-by: Matthias Seitz --- book/developers/exex/assets/remote_exex.png | Bin 0 -> 1155305 bytes book/developers/exex/remote.md | 489 +++++++++++++++++++- 2 files changed, 488 insertions(+), 1 deletion(-) create mode 100644 book/developers/exex/assets/remote_exex.png diff --git a/book/developers/exex/assets/remote_exex.png b/book/developers/exex/assets/remote_exex.png new file mode 100644 index 0000000000000000000000000000000000000000..8606616e8113bcbff94402a29554059a31a87777 GIT binary patch literal 1155305 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�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

_b6cs71Yn`0kUXV&_a^{ro2xXcH}o;DQ;VbF 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�@=BcU^@0ABto~V7 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�*Rsu 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 z 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
l6Lyt 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 I?05dZ01j-GkpKVy literal 0 HcmV?d00001 diff --git a/book/developers/exex/remote.md b/book/developers/exex/remote.md index a3ac9ff2e867..e0caa72f62d3 100644 --- a/book/developers/exex/remote.md +++ b/book/developers/exex/remote.md @@ -1,3 +1,490 @@ # Remote Execution Extensions -WIP +In this chapter, we will learn how to create an ExEx that emits all notifications to an external process. + +We will use [Tonic](https://github.com/hyperium/tonic) to create a gRPC server and a client. +- The server binary will have the Reth client, our ExEx and the gRPC server. +- The client binary will have the gRPC client that connects to the server. + +## Prerequisites + +See [section](https://github.com/hyperium/tonic?tab=readme-ov-file#dependencies) of the Tonic documentation +to install the required dependencies. + +## Create a new project + +Let's create a new project. Don't forget to provide the `--lib` flag to `cargo new`, +because we will have two custom binaries in this project that we will create manually. + +```console +$ cargo new --lib exex-remote +$ cd exex-remote +``` + +We will also need a bunch of dependencies. Some of them you know from the [Hello World](./hello-world.md) chapter, +but some of specific to what we need now. + +```toml +[package] +name = "remote-exex" +version = "0.1.0" +edition = "2021" + +[dependencies] +# reth +reth = { git = "https://github.com/paradigmxyz/reth.git" } +reth-exex = { git = "https://github.com/paradigmxyz/reth.git", features = ["serde"] } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth.git"} +reth-tracing = { git = "https://github.com/paradigmxyz/reth.git" } + +# async +tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1" +futures-util = "0.3" + +# grpc +tonic = "0.11" +prost = "0.12" +bincode = "1" + +# misc +eyre = "0.6" + +[build-dependencies] +tonic-build = "0.11" + +[[bin]] +name = "exex" +path = "src/exex.rs" + +[[bin]] +name = "consumer" +path = "src/consumer.rs" +``` + +We also added a build dependency for Tonic. We will use it to generate the Rust code for our +Protobuf definitions at compile time. Read more about using Tonic in the +[introductory tutorial](https://github.com/hyperium/tonic/blob/6a213e9485965db0628591e30577ed81cdaeaf2b/examples/helloworld-tutorial.md). + +Also, we now have two separate binaries: +- `exex` is the server binary that will run the ExEx and the gRPC server. +- `consumer` is the client binary that will connect to the server and receive notifications. + +### Create the Protobuf definitions + +In the root directory of your project (not `src`), create a new directory called `proto` and a file called `exex.proto`. + +We define a service called `RemoteExEx` that exposes a single method called `Subscribe`. +This method streams notifications to the client. + +
+ +A proper way to represent the notification would be to define all fields in the schema, but it goes beyond the scope +of this chapter. + +For an example of a full schema, see the [Remote ExEx](https://github.com/paradigmxyz/reth-exex-grpc/blob/22b26f7beca1c74577d28be3b3838eb352747be0/proto/exex.proto) example. + +
+ +```protobuf +syntax = "proto3"; + +package exex; + +service RemoteExEx { + rpc Subscribe(SubscribeRequest) returns (stream ExExNotification) {} +} + +message SubscribeRequest {} + +message ExExNotification { + bytes data = 1; +} +``` + +To instruct Tonic to generate the Rust code using this `.proto`, add the following lines to your `lib.rs` file: +```rust,norun,noplayground,ignore +pub mod proto { + tonic::include_proto!("exex"); +} +``` + +## ExEx and gRPC server + +We will now create the ExEx and the gRPC server in our `src/exex.rs` file. + +### gRPC server + +Let's create a minimal gRPC server that listens on the port `:10000`, and spawn it using +the [NodeBuilder](https://reth.rs/docs/reth/builder/struct.NodeBuilder.html)'s [task executor](https://reth.rs/docs/reth/tasks/struct.TaskExecutor.html). + +```rust,norun,noplayground,ignore +use remote_exex::proto::{ + self, + remote_ex_ex_server::{RemoteExEx, RemoteExExServer}, +}; +use reth_exex::ExExNotification; +use reth_node_ethereum::EthereumNode; +use reth_tracing::tracing::info; +use std::sync::Arc; +use tokio::sync::{broadcast, mpsc}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{transport::Server, Request, Response, Status}; + +struct ExExService {} + +#[tonic::async_trait] +impl RemoteExEx for ExExService { + type SubscribeStream = ReceiverStream>; + + async fn subscribe( + &self, + _request: Request, + ) -> Result, Status> { + let (_tx, rx) = mpsc::channel(1); + + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let server = Server::builder() + .add_service(RemoteExExServer::new(ExExService {})) + .serve("[::1]:10000".parse().unwrap()); + + let handle = builder.node(EthereumNode::default()).launch().await?; + + handle + .node + .task_executor + .spawn_critical("gRPC server", async move { + server.await.expect("failed to start gRPC server") + }); + + handle.wait_for_node_exit().await + }) +} +``` + +Currently, it does not send anything on the stream. +We need to create a communication channel between our future ExEx and this gRPC server +to send new `ExExNotification` on it. + +Let's create this channel in the `main` function where we will have both gRPC server and ExEx initiated, +and save the sender part (that way we will be able to create new receivers) of this channel in our gRPC server. + +```rust,norun,noplayground,ignore +// ... +use reth_exex::{ExExNotification}; + +struct ExExService { + notifications: Arc>, +} + +... + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let notifications = Arc::new(broadcast::channel(1).0); + + let server = Server::builder() + .add_service(RemoteExExServer::new(ExExService { + notifications: notifications.clone(), + })) + .serve("[::1]:10000".parse().unwrap()); + + let handle = builder + .node(EthereumNode::default()) + .launch() + .await?; + + handle + .node + .task_executor + .spawn_critical("gRPC server", async move { + server.await.expect("failed to start gRPC server") + }); + + handle.wait_for_node_exit().await + }) +} +``` + +And with that, we're ready to handle incoming notifications, serialize them with [bincode](https://docs.rs/bincode/) +and send back to the client. + +For each incoming request, we spawn a separate tokio task that will run in the background, +and then return the stream receiver to the client. + +```rust,norun,noplayground,ignore +// ... + +#[tonic::async_trait] +impl RemoteExEx for ExExService { + type SubscribeStream = ReceiverStream>; + + async fn subscribe( + &self, + _request: Request, + ) -> Result, Status> { + let (tx, rx) = mpsc::channel(1); + + let mut notifications = self.notifications.subscribe(); + tokio::spawn(async move { + while let Ok(notification) = notifications.recv().await { + let proto_notification = proto::ExExNotification { + data: bincode::serialize(¬ification).expect("failed to serialize"), + }; + tx.send(Ok(proto_notification)) + .await + .expect("failed to send notification to client"); + + info!("Notification sent to the gRPC client"); + } + }); + + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +// ... +``` + +That's it for the gRPC server part! It doesn't receive anything on the `notifications` channel yet, +but we will fix it with our ExEx. + +### ExEx + +Now, let's define the ExEx part of our binary. + +Our ExEx accepts a `notifications` channel and redirects all incoming `ExExNotification`s to it. + +
+ +Don't forget to emit `ExExEvent::FinishedHeight` + +
+ +```rust,norun,noplayground,ignore +// ... +use reth_exex::{ExExContext, ExExEvent}; + +async fn remote_exex( + mut ctx: ExExContext, + notifications: Arc>, +) -> eyre::Result<()> { + while let Some(notification) = ctx.notifications.recv().await { + if let Some(committed_chain) = notification.committed_chain() { + ctx.events + .send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + + info!("Notification sent to the gRPC server"); + let _ = notifications.send(notification); + } + + Ok(()) +} + +// ... +``` + +All that's left is to connect all pieces together: install our ExEx in the node and pass the sender part +of communication channel to it. + +```rust,norun,noplayground,ignore +// ... + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let notifications = Arc::new(broadcast::channel(1).0); + + let server = Server::builder() + .add_service(RemoteExExServer::new(ExExService { + notifications: notifications.clone(), + })) + .serve("[::1]:10000".parse().unwrap()); + + let handle = builder + .node(EthereumNode::default()) + .install_exex("remote-exex", |ctx| async move { + Ok(remote_exex(ctx, notifications)) + }) + .launch() + .await?; + + handle + .node + .task_executor + .spawn_critical("gRPC server", async move { + server.await.expect("failed to start gRPC server") + }); + + handle.wait_for_node_exit().await + }) +} +``` + +### Full `exex.rs` code + +
+Click to expand + +```rust,norun,noplayground,ignore +use remote_exex::proto::{ + self, + remote_ex_ex_server::{RemoteExEx, RemoteExExServer}, +}; +use reth::api::FullNodeComponents; +use reth_exex::{ExExContext, ExExEvent, ExExNotification}; +use reth_node_ethereum::EthereumNode; +use reth_tracing::tracing::info; +use std::sync::Arc; +use tokio::sync::{broadcast, mpsc}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{transport::Server, Request, Response, Status}; + +struct ExExService { + notifications: Arc>, +} + +#[tonic::async_trait] +impl RemoteExEx for ExExService { + type SubscribeStream = ReceiverStream>; + + async fn subscribe( + &self, + _request: Request, + ) -> Result, Status> { + let (tx, rx) = mpsc::channel(1); + + let mut notifications = self.notifications.subscribe(); + tokio::spawn(async move { + while let Ok(notification) = notifications.recv().await { + let proto_notification = proto::ExExNotification { + data: bincode::serialize(¬ification).expect("failed to serialize"), + }; + tx.send(Ok(proto_notification)) + .await + .expect("failed to send notification to client"); + + info!(?notification, "Notification sent to the gRPC client"); + } + }); + + Ok(Response::new(ReceiverStream::new(rx))) + } +} + +async fn remote_exex( + mut ctx: ExExContext, + notifications: Arc>, +) -> eyre::Result<()> { + while let Some(notification) = ctx.notifications.recv().await { + if let Some(committed_chain) = notification.committed_chain() { + ctx.events + .send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + + info!(?notification, "Notification sent to the gRPC server"); + let _ = notifications.send(notification); + } + + Ok(()) +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let notifications = Arc::new(broadcast::channel(1).0); + + let server = Server::builder() + .add_service(RemoteExExServer::new(ExExService { + notifications: notifications.clone(), + })) + .serve("[::1]:10000".parse().unwrap()); + + let handle = builder + .node(EthereumNode::default()) + .install_exex("remote-exex", |ctx| async move { + Ok(remote_exex(ctx, notifications)) + }) + .launch() + .await?; + + handle + .node + .task_executor + .spawn_critical("gRPC server", async move { + server.await.expect("failed to start gRPC server") + }); + + handle.wait_for_node_exit().await + }) +} +``` +
+ +## Consumer + +Consumer will be a much simpler binary that just connects to our gRPC server and prints out all the notifications +it receives. + +
+ +We need to increase maximum message encoding and decoding sizes to `usize::MAX`, +because notifications can get very heavy + +
+ +```rust,norun,noplayground,ignore +use remote_exex::proto::{remote_ex_ex_client::RemoteExExClient, SubscribeRequest}; +use reth_exex::ExExNotification; +use reth_tracing::{tracing::info, RethTracer, Tracer}; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let _ = RethTracer::new().init()?; + + let mut client = RemoteExExClient::connect("http://[::1]:10000") + .await? + .max_encoding_message_size(usize::MAX) + .max_decoding_message_size(usize::MAX); + + let mut stream = client.subscribe(SubscribeRequest {}).await?.into_inner(); + while let Some(notification) = stream.message().await? { + let notification: ExExNotification = bincode::deserialize(¬ification.data)?; + + match notification { + ExExNotification::ChainCommitted { new } => { + info!(committed_chain = ?new.range(), "Received commit"); + } + ExExNotification::ChainReorged { old, new } => { + info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg"); + } + ExExNotification::ChainReverted { old } => { + info!(reverted_chain = ?old.range(), "Received revert"); + } + }; + } + + Ok(()) +} +``` + +## Running + +In one terminal window, we will run our ExEx and gRPC server. It will start syncing Reth on the Holesky chain +and use Etherscan in place of a real Consensus Client. + +```console +cargo run --bin exex --release -- node --chain holesky --debug.etherscan +``` + +And in the other, we will run our consumer: + +```console +cargo run --bin consumer --release +``` + + From 898d17bb91cf69fcef88cf7385b80aa1822e2c7f Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 1 Jul 2024 09:06:16 -0700 Subject: [PATCH 286/405] chore(trie): store only deleted keys in `TrieWalker` (#9226) --- crates/trie/parallel/src/async_root.rs | 2 +- crates/trie/parallel/src/parallel_root.rs | 2 +- crates/trie/trie/src/trie.rs | 15 +++++---- crates/trie/trie/src/updates.rs | 20 +++--------- crates/trie/trie/src/walker.rs | 40 ++++++++++------------- 5 files changed, 33 insertions(+), 46 deletions(-) diff --git a/crates/trie/parallel/src/async_root.rs b/crates/trie/parallel/src/async_root.rs index a36a01be5ecb..e1a031a038f5 100644 --- a/crates/trie/parallel/src/async_root.rs +++ b/crates/trie/parallel/src/async_root.rs @@ -132,7 +132,7 @@ where trie_cursor_factory.account_trie_cursor().map_err(ProviderError::Database)?, prefix_sets.account_prefix_set, ) - .with_updates(retain_updates); + .with_deletions_retained(retain_updates); let mut account_node_iter = TrieNodeIter::new( walker, hashed_cursor_factory.hashed_account_cursor().map_err(ProviderError::Database)?, diff --git a/crates/trie/parallel/src/parallel_root.rs b/crates/trie/parallel/src/parallel_root.rs index edf552096fca..e276d7e055a8 100644 --- a/crates/trie/parallel/src/parallel_root.rs +++ b/crates/trie/parallel/src/parallel_root.rs @@ -116,7 +116,7 @@ where trie_cursor_factory.account_trie_cursor().map_err(ProviderError::Database)?, prefix_sets.account_prefix_set, ) - .with_updates(retain_updates); + .with_deletions_retained(retain_updates); let mut account_node_iter = TrieNodeIter::new( walker, hashed_cursor_factory.hashed_account_cursor().map_err(ProviderError::Database)?, diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 0ae43cdf849a..4d23bfc98187 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -221,7 +221,7 @@ where state.walker_stack, self.prefix_sets.account_prefix_set, ) - .with_updates(retain_updates); + .with_deletions_retained(retain_updates); let node_iter = TrieNodeIter::new(walker, hashed_account_cursor) .with_last_hashed_key(state.last_account_key); (hash_builder, node_iter) @@ -229,7 +229,7 @@ where None => { let hash_builder = HashBuilder::default().with_updates(retain_updates); let walker = TrieWalker::new(trie_cursor, self.prefix_sets.account_prefix_set) - .with_updates(retain_updates); + .with_deletions_retained(retain_updates); let node_iter = TrieNodeIter::new(walker, hashed_account_cursor); (hash_builder, node_iter) } @@ -286,10 +286,10 @@ where // Decide if we need to return intermediate progress. let total_updates_len = trie_updates.len() + - account_node_iter.walker.updates_len() + + account_node_iter.walker.deleted_keys_len() + hash_builder.updates_len(); if retain_updates && total_updates_len as u64 >= self.threshold { - let (walker_stack, walker_updates) = account_node_iter.walker.split(); + let (walker_stack, walker_deleted_keys) = account_node_iter.walker.split(); let (hash_builder, hash_builder_updates) = hash_builder.split(); let state = IntermediateStateRootState { @@ -298,7 +298,9 @@ where last_account_key: hashed_address, }; - trie_updates.extend(walker_updates); + trie_updates.extend( + walker_deleted_keys.into_iter().map(|key| (key, TrieOp::Delete)), + ); trie_updates.extend_with_account_updates(hash_builder_updates); return Ok(StateRootProgress::Progress( @@ -492,7 +494,8 @@ where let mut tracker = TrieTracker::default(); let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(self.hashed_address)?; - let walker = TrieWalker::new(trie_cursor, self.prefix_set).with_updates(retain_updates); + let walker = + TrieWalker::new(trie_cursor, self.prefix_set).with_deletions_retained(retain_updates); let mut hash_builder = HashBuilder::default().with_updates(retain_updates); diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index d0027d658523..b982c945591d 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -110,18 +110,6 @@ impl IntoIterator for TrieUpdates { } impl TrieUpdates { - /// Schedule a delete operation on a trie key. - /// - /// # Panics - /// - /// If the key already exists and the operation is an update. - pub fn schedule_delete(&mut self, key: TrieKey) { - let existing = self.trie_operations.insert(key, TrieOp::Delete); - if let Some(op) = existing { - assert!(!op.is_update(), "Tried to delete a node that was already updated"); - } - } - /// Extend the updates with trie updates. pub fn extend(&mut self, updates: impl IntoIterator) { self.trie_operations.extend(updates); @@ -144,8 +132,8 @@ impl TrieUpdates { destroyed_accounts: HashSet, ) { // Add updates from trie walker. - let (_, walker_updates) = walker.split(); - self.extend(walker_updates); + let (_, deleted_keys) = walker.split(); + self.extend(deleted_keys.into_iter().map(|key| (key, TrieOp::Delete))); // Add account node updates from hash builder. let (_, hash_builder_updates) = hash_builder.split(); @@ -165,8 +153,8 @@ impl TrieUpdates { hash_builder: HashBuilder, ) { // Add updates from trie walker. - let (_, walker_updates) = walker.split(); - self.extend(walker_updates); + let (_, deleted_keys) = walker.split(); + self.extend(deleted_keys.into_iter().map(|key| (key, TrieOp::Delete))); // Add storage node updates from hash builder. let (_, hash_builder_updates) = hash_builder.split(); diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index 6486a9b08006..990b7fb74d52 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -1,11 +1,12 @@ use crate::{ prefix_set::PrefixSet, trie_cursor::{CursorSubNode, TrieCursor}, - updates::TrieUpdates, + updates::TrieKey, BranchNodeCompact, Nibbles, }; use reth_db::DatabaseError; use reth_primitives::B256; +use std::collections::HashSet; /// `TrieWalker` is a structure that enables traversal of a Merkle trie. /// It allows moving through the trie in a depth-first manner, skipping certain branches @@ -22,36 +23,31 @@ pub struct TrieWalker { pub can_skip_current_node: bool, /// A `PrefixSet` representing the changes to be applied to the trie. pub changes: PrefixSet, - /// The trie updates to be applied to the trie. - trie_updates: Option, + /// The retained trie node keys that need to be deleted. + deleted_keys: Option>, } impl TrieWalker { /// Constructs a new `TrieWalker` from existing stack and a cursor. pub fn from_stack(cursor: C, stack: Vec, changes: PrefixSet) -> Self { let mut this = - Self { cursor, changes, stack, can_skip_current_node: false, trie_updates: None }; + Self { cursor, changes, stack, can_skip_current_node: false, deleted_keys: None }; this.update_skip_node(); this } /// Sets the flag whether the trie updates should be stored. - pub fn with_updates(mut self, retain_updates: bool) -> Self { - self.set_updates(retain_updates); - self - } - - /// Sets the flag whether the trie updates should be stored. - pub fn set_updates(&mut self, retain_updates: bool) { - if retain_updates { - self.trie_updates = Some(TrieUpdates::default()); + pub fn with_deletions_retained(mut self, retained: bool) -> Self { + if retained { + self.deleted_keys = Some(HashSet::default()); } + self } /// Split the walker into stack and trie updates. - pub fn split(mut self) -> (Vec, TrieUpdates) { - let trie_updates = self.trie_updates.take(); - (self.stack, trie_updates.unwrap_or_default()) + pub fn split(mut self) -> (Vec, HashSet) { + let keys = self.deleted_keys.take(); + (self.stack, keys.unwrap_or_default()) } /// Prints the current stack of trie nodes. @@ -63,9 +59,9 @@ impl TrieWalker { println!("====================== END STACK ======================\n"); } - /// The current length of the trie updates. - pub fn updates_len(&self) -> usize { - self.trie_updates.as_ref().map(|u| u.len()).unwrap_or(0) + /// The current length of the deleted keys. + pub fn deleted_keys_len(&self) -> usize { + self.deleted_keys.as_ref().map_or(0, |u| u.len()) } /// Returns the current key in the trie. @@ -117,7 +113,7 @@ impl TrieWalker { changes, stack: vec![CursorSubNode::default()], can_skip_current_node: false, - trie_updates: None, + deleted_keys: None, }; // Set up the root node of the trie in the stack, if it exists. @@ -193,8 +189,8 @@ impl TrieWalker { // Delete the current node if it's included in the prefix set or it doesn't contain the root // hash. if !self.can_skip_current_node || nibble != -1 { - if let Some((updates, key)) = self.trie_updates.as_mut().zip(self.cursor.current()?) { - updates.schedule_delete(key); + if let Some((keys, key)) = self.deleted_keys.as_mut().zip(self.cursor.current()?) { + keys.insert(key); } } From d2cf129e580ef7a56c00f249b60e7cd6c00dfd9d Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:17:16 -0400 Subject: [PATCH 287/405] feat: implement write method on persistence task (#9225) --- crates/engine/tree/src/persistence.rs | 70 +++++++++++++++++++++++++-- crates/engine/tree/src/tree/mod.rs | 20 ++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index e7bfadcc1bef..cbbe50d051fe 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -4,9 +4,13 @@ use crate::tree::ExecutedBlock; use reth_db::database::Database; use reth_errors::ProviderResult; use reth_primitives::B256; -use reth_provider::ProviderFactory; +use reth_provider::{ + bundle_state::HashedStateChanges, BlockWriter, HistoryWriter, OriginalValuesKnown, + ProviderFactory, StageCheckpointWriter, StateWriter, +}; use std::sync::mpsc::{Receiver, Sender}; use tokio::sync::oneshot; +use tracing::debug; /// Writes parts of reth's in memory tree state to the database. /// @@ -34,9 +38,67 @@ impl Persistence { } /// Writes the cloned tree state to the database - fn write(&self, _blocks: Vec) -> ProviderResult<()> { - let mut _rw = self.provider.provider_rw()?; - todo!("implement this") + fn write(&self, blocks: Vec) -> ProviderResult<()> { + let provider_rw = self.provider.provider_rw()?; + + if blocks.is_empty() { + debug!(target: "tree::persistence", "Attempted to write empty block range"); + return Ok(()) + } + + let first_number = blocks.first().unwrap().block().number; + + let last = blocks.last().unwrap().block(); + let last_block_number = last.number; + + // TODO: remove all the clones and do performant / batched writes for each type of object + // instead of a loop over all blocks, + // meaning: + // * blocks + // * state + // * hashed state + // * trie updates (cannot naively extend, need helper) + // * indices (already done basically) + // Insert the blocks + for block in blocks { + // TODO: prune modes - a bit unsure that it should be at this level of abstraction and + // not another + // + // ie, an external consumer of providers (or the database task) really does not care + // about pruning, just the node. Maybe we are the biggest user, and use it enough that + // we need a helper, but I'd rather make the pruning behavior more explicit then + let prune_modes = None; + let sealed_block = + block.block().clone().try_with_senders_unchecked(block.senders().clone()).unwrap(); + provider_rw.insert_block(sealed_block, prune_modes)?; + + // Write state and changesets to the database. + // Must be written after blocks because of the receipt lookup. + let execution_outcome = block.execution_outcome().clone(); + execution_outcome.write_to_storage( + provider_rw.tx_ref(), + None, + OriginalValuesKnown::No, + )?; + + // insert hashes and intermediate merkle nodes + { + let trie_updates = block.trie_updates().clone(); + let hashed_state = block.hashed_state(); + HashedStateChanges(hashed_state.clone()).write_to_db(provider_rw.tx_ref())?; + trie_updates.flush(provider_rw.tx_ref())?; + } + + // update history indices + provider_rw.update_history_indices(first_number..=last_block_number)?; + + // Update pipeline progress + provider_rw.update_pipeline_stages(last_block_number, false)?; + } + + debug!(target: "tree::persistence", range = ?first_number..=last_block_number, "Appended blocks"); + + Ok(()) } /// Removes the blocks above the give block number from the database, returning them. diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index e4cf33923cfd..a4ccea51044e 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -49,6 +49,26 @@ impl ExecutedBlock { pub(crate) fn block(&self) -> &SealedBlock { &self.block } + + /// Returns a reference to the block's senders + pub(crate) fn senders(&self) -> &Vec
{ + &self.senders + } + + /// Returns a reference to the block's execution outcome + pub(crate) fn execution_outcome(&self) -> &ExecutionOutcome { + &self.execution_output + } + + /// Returns a reference to the hashed state result of the execution outcome + pub(crate) fn hashed_state(&self) -> &HashedPostState { + &self.hashed_state + } + + /// Returns a reference to the trie updates for the block + pub(crate) fn trie_updates(&self) -> &TrieUpdates { + &self.trie + } } /// Keeps track of the state of the tree. From 6db598da1d1f320c9e38e1c96a4acb4f5ab86751 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Mon, 1 Jul 2024 18:20:07 +0200 Subject: [PATCH 288/405] chore: extract db commands (#9217) Co-authored-by: Matthias Seitz --- Cargo.lock | 44 +++++++++++++------ bin/reth/Cargo.toml | 27 +++--------- bin/reth/src/cli/mod.rs | 3 +- .../src/commands/debug_cmd/build_block.rs | 7 +-- bin/reth/src/commands/debug_cmd/execution.rs | 2 +- .../commands/debug_cmd/in_memory_merkle.rs | 2 +- bin/reth/src/commands/debug_cmd/merkle.rs | 3 +- .../src/commands/debug_cmd/replay_engine.rs | 2 +- bin/reth/src/commands/import.rs | 8 +--- bin/reth/src/commands/import_op.rs | 10 +---- bin/reth/src/commands/import_receipts_op.rs | 2 +- bin/reth/src/commands/init_cmd.rs | 2 +- bin/reth/src/commands/init_state.rs | 2 +- bin/reth/src/commands/mod.rs | 3 -- bin/reth/src/commands/prune.rs | 3 +- .../src/commands/recover/storage_tries.rs | 2 +- bin/reth/src/commands/stage/drop.rs | 7 +-- bin/reth/src/commands/stage/dump/mod.rs | 8 +--- bin/reth/src/commands/stage/run.rs | 3 +- bin/reth/src/commands/stage/unwind.rs | 7 +-- crates/cli/commands/Cargo.toml | 37 ++++++++++++++++ .../cli/commands/src}/common.rs | 0 .../cli/commands/src}/db/checksum.rs | 2 +- .../cli/commands/src}/db/clear.rs | 0 .../cli/commands/src}/db/diff.rs | 8 ++-- .../cli/commands/src}/db/get.rs | 0 .../cli/commands/src}/db/list.rs | 0 .../cli/commands/src}/db/mod.rs | 4 +- .../cli/commands/src}/db/stats.rs | 2 +- .../cli/commands/src}/db/tui.rs | 0 crates/cli/commands/src/lib.rs | 3 ++ 31 files changed, 109 insertions(+), 94 deletions(-) rename {bin/reth/src/commands => crates/cli/commands/src}/common.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/checksum.rs (98%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/clear.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/diff.rs (99%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/get.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/list.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/mod.rs (98%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/stats.rs (99%) rename {bin/reth/src/commands => crates/cli/commands/src}/db/tui.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 0bdada0efe7b..38f323b6c32c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6279,34 +6279,26 @@ dependencies = [ name = "reth" version = "1.0.0" dependencies = [ - "ahash", "alloy-rlp", "aquamarine", "arbitrary", - "assert_matches", "backon", "clap", - "comfy-table", "confy", - "crossterm", "discv5", "eyre", "fdlimit", "futures", - "human_bytes", "itertools 0.13.0", - "jsonrpsee", "libc", "metrics-process", "proptest", "proptest-arbitrary-interop", - "rand 0.8.5", - "ratatui", - "rayon", "reth-basic-payload-builder", "reth-beacon-consensus", "reth-blockchain-tree", "reth-chainspec", + "reth-cli-commands", "reth-cli-runner", "reth-config", "reth-consensus", @@ -6315,7 +6307,6 @@ dependencies = [ "reth-db-api", "reth-db-common", "reth-discv4", - "reth-discv5", "reth-downloaders", "reth-engine-util", "reth-errors", @@ -6324,11 +6315,9 @@ dependencies = [ "reth-execution-types", "reth-exex", "reth-fs-util", - "reth-net-banlist", "reth-network", "reth-network-api", "reth-network-p2p", - "reth-nippy-jar", "reth-node-api", "reth-node-builder", "reth-node-core", @@ -6342,7 +6331,6 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-prune", - "reth-prune-types", "reth-revm", "reth-rpc", "reth-rpc-api", @@ -6587,6 +6575,36 @@ dependencies = [ [[package]] name = "reth-cli-commands" version = "1.0.0" +dependencies = [ + "ahash", + "clap", + "comfy-table", + "confy", + "crossterm", + "eyre", + "human_bytes", + "itertools 0.13.0", + "ratatui", + "reth-beacon-consensus", + "reth-chainspec", + "reth-config", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-downloaders", + "reth-evm", + "reth-fs-util", + "reth-node-core", + "reth-primitives", + "reth-provider", + "reth-stages", + "reth-static-file", + "reth-static-file-types", + "serde", + "serde_json", + "tokio", + "tracing", +] [[package]] name = "reth-cli-runner" diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 65c7c4f5911e..44edb0d9de3e 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -21,7 +21,7 @@ reth-fs-util.workspace = true reth-db = { workspace = true, features = ["mdbx"] } reth-db-api.workspace = true reth-exex.workspace = true -reth-provider = { workspace = true } +reth-provider.workspace = true reth-evm.workspace = true reth-revm.workspace = true reth-stages.workspace = true @@ -30,6 +30,7 @@ reth-errors.workspace = true reth-transaction-pool.workspace = true reth-beacon-consensus.workspace = true reth-cli-runner.workspace = true +reth-cli-commands.workspace = true reth-consensus-common.workspace = true reth-blockchain-tree.workspace = true reth-rpc-builder.workspace = true @@ -41,34 +42,29 @@ reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true -reth-net-banlist.workspace = true reth-network-api.workspace = true reth-downloaders.workspace = true reth-tracing.workspace = true reth-tasks.workspace = true -reth-ethereum-payload-builder.workspace = true reth-payload-builder.workspace = true reth-payload-primitives.workspace = true reth-payload-validator.workspace = true reth-basic-payload-builder.workspace = true -reth-discv4.workspace = true -reth-discv5.workspace = true reth-static-file.workspace = true reth-static-file-types = { workspace = true, features = ["clap"] } reth-trie = { workspace = true, features = ["metrics"] } -reth-nippy-jar.workspace = true reth-node-api.workspace = true -reth-node-ethereum.workspace = true reth-node-optimism = { workspace = true, optional = true, features = [ "optimism", ] } reth-node-core.workspace = true +reth-ethereum-payload-builder.workspace = true reth-db-common.workspace = true +reth-node-ethereum.workspace = true reth-node-builder.workspace = true reth-node-events.workspace = true reth-consensus.workspace = true reth-optimism-primitives.workspace = true -reth-prune-types.workspace = true reth-engine-util.workspace = true reth-prune.workspace = true @@ -92,15 +88,7 @@ metrics-process.workspace = true proptest.workspace = true arbitrary.workspace = true proptest-arbitrary-interop.workspace = true -rand.workspace = true -# tui -comfy-table = "7.1" -crossterm = "0.27.0" -ratatui = { version = "0.27", default-features = false, features = [ - "crossterm", -] } -human_bytes = "0.4.1" # async tokio = { workspace = true, features = [ @@ -119,8 +107,6 @@ tempfile.workspace = true backon.workspace = true similar-asserts.workspace = true itertools.workspace = true -rayon.workspace = true -ahash = "0.8" # p2p discv5.workspace = true @@ -130,8 +116,9 @@ tikv-jemallocator = { version = "0.5.0", optional = true } libc = "0.2" [dev-dependencies] -jsonrpsee.workspace = true -assert_matches = "1.5.0" +reth-discv4.workspace = true + + [features] default = ["jemalloc"] diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index 4dd567630756..2d253ea32c81 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -6,7 +6,7 @@ use crate::{ LogArgs, }, commands::{ - config_cmd, db, debug_cmd, dump_genesis, import, init_cmd, init_state, + config_cmd, debug_cmd, dump_genesis, import, init_cmd, init_state, node::{self, NoArgs}, p2p, prune, recover, stage, test_vectors, }, @@ -14,6 +14,7 @@ use crate::{ }; use clap::{value_parser, Parser, Subcommand}; use reth_chainspec::ChainSpec; +use reth_cli_commands::db; use reth_cli_runner::CliRunner; use reth_db::DatabaseEnv; use reth_node_builder::{NodeBuilder, WithLaunchContext}; diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 6c1125677815..69e0dc1cf208 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -1,9 +1,5 @@ //! Command for debugging block building. - -use crate::{ - commands::common::{AccessRights, Environment, EnvironmentArgs}, - macros::block_executor, -}; +use crate::macros::block_executor; use alloy_rlp::Decodable; use clap::Parser; use eyre::Context; @@ -14,6 +10,7 @@ use reth_beacon_consensus::EthBeaconConsensus; use reth_blockchain_tree::{ BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, TreeExternals, }; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_consensus::Consensus; use reth_db::DatabaseEnv; diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index 2172a2b4c7ba..dc8e01e54f44 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -2,13 +2,13 @@ use crate::{ args::{get_secret_key, NetworkArgs}, - commands::common::{AccessRights, Environment, EnvironmentArgs}, macros::block_executor, utils::get_single_header, }; use clap::Parser; use futures::{stream::select as stream_select, StreamExt}; use reth_beacon_consensus::EthBeaconConsensus; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_config::Config; use reth_consensus::Consensus; diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index 8d138d2c2c1b..352d7846e5bc 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -2,12 +2,12 @@ use crate::{ args::{get_secret_key, NetworkArgs}, - commands::common::{AccessRights, Environment, EnvironmentArgs}, macros::block_executor, utils::{get_single_body, get_single_header}, }; use backon::{ConstantBuilder, Retryable}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_config::Config; use reth_db::DatabaseEnv; diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index bd8f690b9a59..e1d044e663e9 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -1,14 +1,13 @@ //! Command for debugging merkle trie calculation. - use crate::{ args::{get_secret_key, NetworkArgs}, - commands::common::{AccessRights, Environment, EnvironmentArgs}, macros::block_executor, utils::get_single_header, }; use backon::{ConstantBuilder, Retryable}; use clap::Parser; use reth_beacon_consensus::EthBeaconConsensus; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_config::Config; use reth_consensus::Consensus; diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 224a0c993401..429ef625f988 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -1,6 +1,5 @@ use crate::{ args::{get_secret_key, NetworkArgs}, - commands::common::{AccessRights, Environment, EnvironmentArgs}, macros::block_executor, }; use clap::Parser; @@ -10,6 +9,7 @@ use reth_beacon_consensus::{hooks::EngineHooks, BeaconConsensusEngine, EthBeacon use reth_blockchain_tree::{ BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, TreeExternals, }; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_config::Config; use reth_consensus::Consensus; diff --git a/bin/reth/src/commands/import.rs b/bin/reth/src/commands/import.rs index 71357e083aaf..bc0f183b0e93 100644 --- a/bin/reth/src/commands/import.rs +++ b/bin/reth/src/commands/import.rs @@ -1,13 +1,9 @@ //! Command that initializes the node by importing a chain from a file. - -use crate::{ - commands::common::{AccessRights, Environment, EnvironmentArgs}, - macros::block_executor, - version::SHORT_VERSION, -}; +use crate::{macros::block_executor, version::SHORT_VERSION}; use clap::Parser; use futures::{Stream, StreamExt}; use reth_beacon_consensus::EthBeaconConsensus; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_config::Config; use reth_consensus::Consensus; use reth_db::tables; diff --git a/bin/reth/src/commands/import_op.rs b/bin/reth/src/commands/import_op.rs index f4b8716fe210..3d308ba0d826 100644 --- a/bin/reth/src/commands/import_op.rs +++ b/bin/reth/src/commands/import_op.rs @@ -1,14 +1,8 @@ //! Command that initializes the node by importing OP Mainnet chain segment below Bedrock, from a //! file. - -use crate::{ - commands::{ - common::{AccessRights, Environment, EnvironmentArgs}, - import::build_import_pipeline, - }, - version::SHORT_VERSION, -}; +use crate::{commands::import::build_import_pipeline, version::SHORT_VERSION}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_consensus::noop::NoopConsensus; use reth_db::tables; use reth_db_api::transaction::DbTx; diff --git a/bin/reth/src/commands/import_receipts_op.rs b/bin/reth/src/commands/import_receipts_op.rs index 7623c626cb02..042b7df6e7b3 100644 --- a/bin/reth/src/commands/import_receipts_op.rs +++ b/bin/reth/src/commands/import_receipts_op.rs @@ -1,8 +1,8 @@ //! Command that imports OP mainnet receipts from Bedrock datadir, exported via //! . -use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_db::tables; use reth_db_api::{database::Database, transaction::DbTx}; use reth_downloaders::{ diff --git a/bin/reth/src/commands/init_cmd.rs b/bin/reth/src/commands/init_cmd.rs index 22657f0c0255..df407c0659f0 100644 --- a/bin/reth/src/commands/init_cmd.rs +++ b/bin/reth/src/commands/init_cmd.rs @@ -1,7 +1,7 @@ //! Command that initializes the node from a genesis file. -use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_provider::BlockHashReader; use tracing::info; diff --git a/bin/reth/src/commands/init_state.rs b/bin/reth/src/commands/init_state.rs index dbf45e5816a6..4324b7f46882 100644 --- a/bin/reth/src/commands/init_state.rs +++ b/bin/reth/src/commands/init_state.rs @@ -1,7 +1,7 @@ //! Command that initializes the node from a genesis file. -use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_config::config::EtlConfig; use reth_db_api::database::Database; use reth_db_common::init::init_from_state_dump; diff --git a/bin/reth/src/commands/mod.rs b/bin/reth/src/commands/mod.rs index 0763ecc2203e..36290922a67c 100644 --- a/bin/reth/src/commands/mod.rs +++ b/bin/reth/src/commands/mod.rs @@ -1,7 +1,6 @@ //! This contains all of the `reth` commands pub mod config_cmd; -pub mod db; pub mod debug_cmd; pub mod dump_genesis; pub mod import; @@ -15,5 +14,3 @@ pub mod prune; pub mod recover; pub mod stage; pub mod test_vectors; - -pub mod common; diff --git a/bin/reth/src/commands/prune.rs b/bin/reth/src/commands/prune.rs index f3b0fcaab966..cd9cfabb26a3 100644 --- a/bin/reth/src/commands/prune.rs +++ b/bin/reth/src/commands/prune.rs @@ -1,7 +1,6 @@ //! Command that runs pruning without any limits. - -use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_prune::PrunerBuilder; use reth_static_file::StaticFileProducer; use tracing::info; diff --git a/bin/reth/src/commands/recover/storage_tries.rs b/bin/reth/src/commands/recover/storage_tries.rs index b1dbbfa88ce5..7cab05ff8527 100644 --- a/bin/reth/src/commands/recover/storage_tries.rs +++ b/bin/reth/src/commands/recover/storage_tries.rs @@ -1,5 +1,5 @@ -use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_db::tables; use reth_db_api::{ diff --git a/bin/reth/src/commands/stage/drop.rs b/bin/reth/src/commands/stage/drop.rs index ec32af330e97..88f5650d558c 100644 --- a/bin/reth/src/commands/stage/drop.rs +++ b/bin/reth/src/commands/stage/drop.rs @@ -1,11 +1,8 @@ //! Database debugging tool - -use crate::{ - args::StageEnum, - commands::common::{AccessRights, Environment, EnvironmentArgs}, -}; +use crate::args::StageEnum; use clap::Parser; use itertools::Itertools; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_db::{static_file::iter_static_files, tables, DatabaseEnv}; use reth_db_api::transaction::DbTxMut; use reth_db_common::{ diff --git a/bin/reth/src/commands/stage/dump/mod.rs b/bin/reth/src/commands/stage/dump/mod.rs index f1fbdbbdecbd..4cdf3af8d2a3 100644 --- a/bin/reth/src/commands/stage/dump/mod.rs +++ b/bin/reth/src/commands/stage/dump/mod.rs @@ -1,11 +1,7 @@ //! Database debugging tool - -use crate::{ - args::DatadirArgs, - commands::common::{AccessRights, Environment, EnvironmentArgs}, - dirs::DataDirPath, -}; +use crate::{args::DatadirArgs, dirs::DataDirPath}; use clap::Parser; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_db::{init_db, mdbx::DatabaseArguments, tables, DatabaseEnv}; use reth_db_api::{ cursor::DbCursorRO, database::Database, models::ClientVersion, table::TableImporter, diff --git a/bin/reth/src/commands/stage/run.rs b/bin/reth/src/commands/stage/run.rs index 55824bd79c69..101b89e0277a 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/bin/reth/src/commands/stage/run.rs @@ -1,15 +1,14 @@ //! Main `stage` command //! //! Stage debugging tool - use crate::{ args::{get_secret_key, NetworkArgs, StageEnum}, - commands::common::{AccessRights, Environment, EnvironmentArgs}, macros::block_executor, prometheus_exporter, }; use clap::Parser; use reth_beacon_consensus::EthBeaconConsensus; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_config::config::{HashingConfig, SenderRecoveryConfig, TransactionLookupConfig}; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; diff --git a/bin/reth/src/commands/stage/unwind.rs b/bin/reth/src/commands/stage/unwind.rs index e3cb0cc8fcd5..3f8eb59628da 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/bin/reth/src/commands/stage/unwind.rs @@ -1,7 +1,9 @@ //! Unwinding a certain block range +use crate::macros::block_executor; use clap::{Parser, Subcommand}; use reth_beacon_consensus::EthBeaconConsensus; +use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_config::Config; use reth_consensus::Consensus; use reth_db_api::database::Database; @@ -24,11 +26,6 @@ use std::{ops::RangeInclusive, sync::Arc}; use tokio::sync::watch; use tracing::info; -use crate::{ - commands::common::{AccessRights, Environment, EnvironmentArgs}, - macros::block_executor, -}; - /// `reth stage unwind` command #[derive(Debug, Parser)] pub struct Command { diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index d12abefcd8b0..bc9532b24349 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -8,3 +8,40 @@ homepage.workspace = true repository.workspace = true [lints] + +[dependencies] +reth-db = { workspace = true, features = ["mdbx"] } +reth-db-api.workspace = true +reth-provider.workspace = true +reth-primitives.workspace = true +reth-node-core.workspace = true +reth-fs-util.workspace = true +reth-db-common.workspace = true +reth-static-file-types.workspace = true +reth-beacon-consensus.workspace = true +reth-chainspec.workspace = true +reth-config.workspace = true +reth-downloaders.workspace = true +reth-evm.workspace = true +reth-stages.workspace = true +reth-static-file.workspace = true + +confy.workspace = true +tokio.workspace = true +itertools.workspace = true + +# misc +ahash = "0.8" +human_bytes = "0.4.1" +eyre.workspace = true +clap = { workspace = true, features = ["derive", "env"] } +serde.workspace = true +serde_json.workspace = true +tracing.workspace = true + +# tui +comfy-table = "7.0" +crossterm = "0.27.0" +ratatui = { version = "0.27", default-features = false, features = [ + "crossterm", +] } \ No newline at end of file diff --git a/bin/reth/src/commands/common.rs b/crates/cli/commands/src/common.rs similarity index 100% rename from bin/reth/src/commands/common.rs rename to crates/cli/commands/src/common.rs diff --git a/bin/reth/src/commands/db/checksum.rs b/crates/cli/commands/src/db/checksum.rs similarity index 98% rename from bin/reth/src/commands/db/checksum.rs rename to crates/cli/commands/src/db/checksum.rs index 9af9a2321637..abc183da4ed4 100644 --- a/bin/reth/src/commands/db/checksum.rs +++ b/crates/cli/commands/src/db/checksum.rs @@ -1,4 +1,4 @@ -use crate::commands::db::get::{maybe_json_value_parser, table_key}; +use crate::db::get::{maybe_json_value_parser, table_key}; use ahash::RandomState; use clap::Parser; use reth_db::{DatabaseEnv, RawKey, RawTable, RawValue, TableViewer, Tables}; diff --git a/bin/reth/src/commands/db/clear.rs b/crates/cli/commands/src/db/clear.rs similarity index 100% rename from bin/reth/src/commands/db/clear.rs rename to crates/cli/commands/src/db/clear.rs diff --git a/bin/reth/src/commands/db/diff.rs b/crates/cli/commands/src/db/diff.rs similarity index 99% rename from bin/reth/src/commands/db/diff.rs rename to crates/cli/commands/src/db/diff.rs index cd9e24c1d761..e025c4648c35 100644 --- a/bin/reth/src/commands/db/diff.rs +++ b/crates/cli/commands/src/db/diff.rs @@ -1,11 +1,11 @@ -use crate::{ - args::DatabaseArgs, - dirs::{DataDirPath, PlatformPath}, -}; use clap::Parser; use reth_db::{open_db_read_only, tables_to_generic, DatabaseEnv, Tables}; use reth_db_api::{cursor::DbCursorRO, database::Database, table::Table, transaction::DbTx}; use reth_db_common::DbTool; +use reth_node_core::{ + args::DatabaseArgs, + dirs::{DataDirPath, PlatformPath}, +}; use std::{ collections::HashMap, fmt::Debug, diff --git a/bin/reth/src/commands/db/get.rs b/crates/cli/commands/src/db/get.rs similarity index 100% rename from bin/reth/src/commands/db/get.rs rename to crates/cli/commands/src/db/get.rs diff --git a/bin/reth/src/commands/db/list.rs b/crates/cli/commands/src/db/list.rs similarity index 100% rename from bin/reth/src/commands/db/list.rs rename to crates/cli/commands/src/db/list.rs diff --git a/bin/reth/src/commands/db/mod.rs b/crates/cli/commands/src/db/mod.rs similarity index 98% rename from bin/reth/src/commands/db/mod.rs rename to crates/cli/commands/src/db/mod.rs index 736d825c31b2..dc247745f5ac 100644 --- a/bin/reth/src/commands/db/mod.rs +++ b/crates/cli/commands/src/db/mod.rs @@ -1,6 +1,4 @@ -//! Database debugging tool - -use crate::commands::common::{AccessRights, Environment, EnvironmentArgs}; +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::{Parser, Subcommand}; use reth_db::version::{get_db_version, DatabaseVersionError, DB_VERSION}; use reth_db_common::DbTool; diff --git a/bin/reth/src/commands/db/stats.rs b/crates/cli/commands/src/db/stats.rs similarity index 99% rename from bin/reth/src/commands/db/stats.rs rename to crates/cli/commands/src/db/stats.rs index b1a979e54918..37f7d617ba47 100644 --- a/bin/reth/src/commands/db/stats.rs +++ b/crates/cli/commands/src/db/stats.rs @@ -1,4 +1,4 @@ -use crate::commands::db::checksum::ChecksumViewer; +use crate::db::checksum::ChecksumViewer; use clap::Parser; use comfy_table::{Cell, Row, Table as ComfyTable}; use eyre::WrapErr; diff --git a/bin/reth/src/commands/db/tui.rs b/crates/cli/commands/src/db/tui.rs similarity index 100% rename from bin/reth/src/commands/db/tui.rs rename to crates/cli/commands/src/db/tui.rs diff --git a/crates/cli/commands/src/lib.rs b/crates/cli/commands/src/lib.rs index 33983bb856db..5f94de798c7f 100644 --- a/crates/cli/commands/src/lib.rs +++ b/crates/cli/commands/src/lib.rs @@ -7,3 +7,6 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub mod common; +pub mod db; From 984e89efeba8ae48365f86c09a8fecaa68898708 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:56:55 +0200 Subject: [PATCH 289/405] feat(clippy): add `iter_without_into_iter` (#9195) Co-authored-by: Matthias Seitz Co-authored-by: Roman Krasiuk --- Cargo.toml | 1 + crates/primitives-traits/src/withdrawal.rs | 17 +++++++++++++++++ crates/trie/trie/src/prefix_set/mod.rs | 8 ++++++++ 3 files changed, 26 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1308361d4a3e..936de53b63b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -206,6 +206,7 @@ zero_sized_map_values = "warn" single_char_pattern = "warn" needless_continue = "warn" enum_glob_use = "warn" +iter_without_into_iter = "warn" # These are nursery lints which have findings. Allow them for now. Some are not # quite mature enough for use in our codebase and some we don't really want. diff --git a/crates/primitives-traits/src/withdrawal.rs b/crates/primitives-traits/src/withdrawal.rs index 679c80cab6a0..49d4e5e31269 100644 --- a/crates/primitives-traits/src/withdrawal.rs +++ b/crates/primitives-traits/src/withdrawal.rs @@ -65,6 +65,23 @@ impl Withdrawals { } } +impl<'a> IntoIterator for &'a Withdrawals { + type Item = &'a Withdrawal; + type IntoIter = core::slice::Iter<'a, Withdrawal>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut Withdrawals { + type Item = &'a mut Withdrawal; + type IntoIter = core::slice::IterMut<'a, Withdrawal>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/trie/trie/src/prefix_set/mod.rs b/crates/trie/trie/src/prefix_set/mod.rs index f6a8789e0105..228e0abee3c0 100644 --- a/crates/trie/trie/src/prefix_set/mod.rs +++ b/crates/trie/trie/src/prefix_set/mod.rs @@ -177,6 +177,14 @@ impl PrefixSet { } } +impl<'a> IntoIterator for &'a PrefixSet { + type IntoIter = std::slice::Iter<'a, reth_trie_common::Nibbles>; + type Item = &'a reth_trie_common::Nibbles; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + #[cfg(test)] mod tests { use super::*; From 8d55e6bb5d8fd39e5e6e46dd8b22373516eda69b Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:23:37 -0600 Subject: [PATCH 290/405] fix: typo in book intro (#9228) --- book/intro.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/book/intro.md b/book/intro.md index f945d694e6ce..077cfed30883 100644 --- a/book/intro.md +++ b/book/intro.md @@ -5,8 +5,7 @@ _Documentation for Reth users and developers._ Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is an **Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient.** -Reth is production ready, and suitable for usage in mission-critical environments such as staking or high-uptime servi -ces. We also actively recommend professional node operators to switch to Reth in production for performance and cost reasons in use cases where high performance with great margins is required such as RPC, MEV, Indexing, Simulations, and P2P activities. +Reth is production ready, and suitable for usage in mission-critical environments such as staking or high-uptime services. We also actively recommend professional node operators to switch to Reth in production for performance and cost reasons in use cases where high performance with great margins is required such as RPC, MEV, Indexing, Simulations, and P2P activities. From 52068ccee6db987a0fd1eda606b5542551974fb4 Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 2 Jul 2024 01:26:22 +0800 Subject: [PATCH 291/405] fix(rpc/admin): missing enode/enr in admin_peers endpoint (#9043) Signed-off-by: jsvisa --- crates/net/network-api/src/lib.rs | 6 +++ crates/net/network/src/manager.rs | 48 ++++++++++++++--- crates/net/network/src/peers.rs | 22 ++++---- crates/net/network/src/session/handle.rs | 9 ++-- crates/net/network/src/session/mod.rs | 34 ++---------- crates/rpc/rpc-api/src/admin.rs | 2 +- crates/rpc/rpc/src/admin.rs | 66 ++++++++++++++---------- 7 files changed, 110 insertions(+), 77 deletions(-) diff --git a/crates/net/network-api/src/lib.rs b/crates/net/network-api/src/lib.rs index e9cce0866fde..8efaec5f0fb7 100644 --- a/crates/net/network-api/src/lib.rs +++ b/crates/net/network-api/src/lib.rs @@ -195,6 +195,10 @@ pub struct PeerInfo { pub remote_id: PeerId, /// The client's name and version pub client_version: Arc, + /// The peer's enode + pub enode: String, + /// The peer's enr + pub enr: Option, /// The peer's address we're connected to pub remote_addr: SocketAddr, /// The local address of the connection @@ -207,6 +211,8 @@ pub struct PeerInfo { pub status: Arc, /// The timestamp when the session to that peer has been established. pub session_established: Instant, + /// The peer's connection kind + pub kind: PeerKind, } /// The direction of the connection. diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index b3fa43252ec1..3434e9439468 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -42,7 +42,7 @@ use reth_eth_wire::{ DisconnectReason, EthVersion, Status, }; use reth_metrics::common::mpsc::UnboundedMeteredSender; -use reth_network_api::{EthProtocolInfo, NetworkStatus, ReputationChangeKind}; +use reth_network_api::{EthProtocolInfo, NetworkStatus, PeerInfo, ReputationChangeKind}; use reth_network_peers::{NodeRecord, PeerId}; use reth_primitives::ForkId; use reth_provider::{BlockNumReader, BlockReader}; @@ -604,17 +604,17 @@ where } } NetworkHandleMessage::GetPeerInfos(tx) => { - let _ = tx.send(self.swarm.sessions_mut().get_peer_info()); + let _ = tx.send(self.get_peer_infos()); } NetworkHandleMessage::GetPeerInfoById(peer_id, tx) => { - let _ = tx.send(self.swarm.sessions_mut().get_peer_info_by_id(peer_id)); + let _ = tx.send(self.get_peer_info_by_id(peer_id)); } NetworkHandleMessage::GetPeerInfosByIds(peer_ids, tx) => { - let _ = tx.send(self.swarm.sessions().get_peer_infos_by_ids(peer_ids)); + let _ = tx.send(self.get_peer_infos_by_ids(peer_ids)); } NetworkHandleMessage::GetPeerInfosByPeerKind(kind, tx) => { - let peers = self.swarm.state().peers().peers_by_kind(kind); - let _ = tx.send(self.swarm.sessions().get_peer_infos_by_ids(peers)); + let peer_ids = self.swarm.state().peers().peers_by_kind(kind); + let _ = tx.send(self.get_peer_infos_by_ids(peer_ids)); } NetworkHandleMessage::AddRlpxSubProtocol(proto) => self.add_rlpx_sub_protocol(proto), NetworkHandleMessage::GetTransactionsHandle(tx) => { @@ -865,6 +865,42 @@ where } } + /// Returns [`PeerInfo`] for all connected peers + fn get_peer_infos(&self) -> Vec { + self.swarm + .sessions() + .active_sessions() + .iter() + .filter_map(|(&peer_id, session)| { + self.swarm + .state() + .peers() + .peer_by_id(peer_id) + .map(|(record, kind)| session.peer_info(&record, kind)) + }) + .collect() + } + + /// Returns [`PeerInfo`] for a given peer. + /// + /// Returns `None` if there's no active session to the peer. + fn get_peer_info_by_id(&self, peer_id: PeerId) -> Option { + self.swarm.sessions().active_sessions().get(&peer_id).and_then(|session| { + self.swarm + .state() + .peers() + .peer_by_id(peer_id) + .map(|(record, kind)| session.peer_info(&record, kind)) + }) + } + + /// Returns [`PeerInfo`] for a given peers. + /// + /// Ignore the non-active peer. + fn get_peer_infos_by_ids(&self, peer_ids: impl IntoIterator) -> Vec { + peer_ids.into_iter().filter_map(|peer_id| self.get_peer_info_by_id(peer_id)).collect() + } + /// Updates the metrics for active,established connections #[inline] fn update_active_connection_metrics(&self) { diff --git a/crates/net/network/src/peers.rs b/crates/net/network/src/peers.rs index ca80faf5c1bb..41950226282c 100644 --- a/crates/net/network/src/peers.rs +++ b/crates/net/network/src/peers.rs @@ -212,15 +212,17 @@ impl PeersManager { }) } - /// Returns the [`NodeRecord`] for the given peer id - #[allow(dead_code)] - fn peer_by_id(&self, peer_id: PeerId) -> Option { + /// Returns the `NodeRecord` and `PeerKind` for the given peer id + pub(crate) fn peer_by_id(&self, peer_id: PeerId) -> Option<(NodeRecord, PeerKind)> { self.peers.get(&peer_id).map(|v| { - NodeRecord::new_with_ports( - v.addr.tcp.ip(), - v.addr.tcp.port(), - v.addr.udp.map(|addr| addr.port()), - peer_id, + ( + NodeRecord::new_with_ports( + v.addr.tcp.ip(), + v.addr.tcp.port(), + v.addr.udp.map(|addr| addr.port()), + peer_id, + ), + v.kind, ) }) } @@ -1378,7 +1380,7 @@ mod tests { _ => unreachable!(), } - let record = peers.peer_by_id(peer).unwrap(); + let (record, _) = peers.peer_by_id(peer).unwrap(); assert_eq!(record.tcp_addr(), socket_addr); assert_eq!(record.udp_addr(), socket_addr); } @@ -1405,7 +1407,7 @@ mod tests { _ => unreachable!(), } - let record = peers.peer_by_id(peer).unwrap(); + let (record, _) = peers.peer_by_id(peer).unwrap(); assert_eq!(record.tcp_addr(), tcp_addr); assert_eq!(record.udp_addr(), udp_addr); } diff --git a/crates/net/network/src/session/handle.rs b/crates/net/network/src/session/handle.rs index b28b1e27e390..4c1a5e5315ac 100644 --- a/crates/net/network/src/session/handle.rs +++ b/crates/net/network/src/session/handle.rs @@ -11,8 +11,8 @@ use reth_eth_wire::{ errors::EthStreamError, DisconnectReason, EthVersion, Status, }; -use reth_network_api::PeerInfo; -use reth_network_peers::PeerId; +use reth_network_api::{PeerInfo, PeerKind}; +use reth_network_peers::{NodeRecord, PeerId}; use std::{io, net::SocketAddr, sync::Arc, time::Instant}; use tokio::sync::{ mpsc::{self, error::SendError}, @@ -136,10 +136,12 @@ impl ActiveSessionHandle { } /// Extracts the [`PeerInfo`] from the session handle. - pub(crate) fn peer_info(&self) -> PeerInfo { + pub(crate) fn peer_info(&self, record: &NodeRecord, kind: PeerKind) -> PeerInfo { PeerInfo { remote_id: self.remote_id, direction: self.direction, + enode: record.to_string(), + enr: None, remote_addr: self.remote_addr, local_addr: self.local_addr, capabilities: self.capabilities.clone(), @@ -147,6 +149,7 @@ impl ActiveSessionHandle { eth_version: self.version, status: self.status.clone(), session_established: self.established, + kind, } } } diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index 715ed59cf635..fc6f0e6a1f1e 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -170,6 +170,11 @@ impl SessionManager { self.secret_key } + /// Returns a borrowed reference to the active sessions. + pub const fn active_sessions(&self) -> &HashMap { + &self.active_sessions + } + /// Returns the session hello message. pub fn hello_message(&self) -> HelloMessageWithProtocols { self.hello_message.clone() @@ -587,35 +592,6 @@ impl SessionManager { } } } - - /// Returns [`PeerInfo`] for all connected peers - pub(crate) fn get_peer_info(&self) -> Vec { - self.active_sessions.values().map(ActiveSessionHandle::peer_info).collect() - } - - /// Returns [`PeerInfo`] for a given peer. - /// - /// Returns `None` if there's no active session to the peer. - pub(crate) fn get_peer_info_by_id(&self, peer_id: PeerId) -> Option { - self.active_sessions.get(&peer_id).map(ActiveSessionHandle::peer_info) - } - /// Returns [`PeerInfo`] for a given peer. - /// - /// Returns `None` if there's no active session to the peer. - pub(crate) fn get_peer_infos_by_ids( - &self, - peer_ids: impl IntoIterator, - ) -> Vec { - let mut infos = Vec::new(); - for peer_id in peer_ids { - if let Some(info) = - self.active_sessions.get(&peer_id).map(ActiveSessionHandle::peer_info) - { - infos.push(info); - } - } - infos - } } /// Events produced by the [`SessionManager`] diff --git a/crates/rpc/rpc-api/src/admin.rs b/crates/rpc/rpc-api/src/admin.rs index 173cd8ef7a98..66f8918a33cb 100644 --- a/crates/rpc/rpc-api/src/admin.rs +++ b/crates/rpc/rpc-api/src/admin.rs @@ -1,6 +1,6 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_network_peers::{AnyNode, NodeRecord}; -use reth_rpc_types::{admin::NodeInfo, PeerInfo}; +use reth_rpc_types::admin::{NodeInfo, PeerInfo}; /// Admin namespace rpc interface that gives access to several non-standard RPC methods. #[cfg_attr(not(feature = "client"), rpc(server, namespace = "admin"))] diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index f294a52c1bf3..1d59baa6e274 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -6,12 +6,12 @@ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_chainspec::ChainSpec; use reth_network_api::{NetworkInfo, PeerKind, Peers}; -use reth_network_peers::{AnyNode, NodeRecord}; +use reth_network_peers::{id2pk, AnyNode, NodeRecord}; use reth_rpc_api::AdminApiServer; use reth_rpc_server_types::ToRpcResult; -use reth_rpc_types::{ - admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo}, - PeerEthProtocolInfo, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, +use reth_rpc_types::admin::{ + EthInfo, EthPeerInfo, EthProtocolInfo, NodeInfo, PeerInfo, PeerNetworkInfo, PeerProtocolInfo, + Ports, ProtocolInfo, }; /// `admin` API implementation. @@ -63,33 +63,43 @@ where Ok(true) } + /// Handler for `admin_peers` async fn peers(&self) -> RpcResult> { let peers = self.network.get_all_peers().await.to_rpc_result()?; - let peers = peers - .into_iter() - .map(|peer| PeerInfo { - id: Some(peer.remote_id.to_string()), - name: peer.client_version.to_string(), - caps: peer.capabilities.capabilities().iter().map(|cap| cap.to_string()).collect(), - network: PeerNetworkInfo { - remote_address: peer.remote_addr.to_string(), - local_address: peer - .local_addr - .unwrap_or_else(|| self.network.local_addr()) - .to_string(), - }, - protocols: PeerProtocolsInfo { - eth: Some(PeerEthProtocolInfo { - difficulty: Some(peer.status.total_difficulty), - head: peer.status.blockhash.to_string(), - version: peer.status.version as u32, - }), - pip: None, - }, - }) - .collect(); + let mut infos = Vec::with_capacity(peers.len()); - Ok(peers) + for peer in peers { + if let Ok(pk) = id2pk(peer.remote_id) { + infos.push(PeerInfo { + id: pk.to_string(), + name: peer.client_version.to_string(), + enode: peer.enode, + enr: peer.enr, + caps: peer + .capabilities + .capabilities() + .iter() + .map(|cap| cap.to_string()) + .collect(), + network: PeerNetworkInfo { + remote_address: peer.remote_addr, + local_address: peer.local_addr.unwrap_or_else(|| self.network.local_addr()), + inbound: peer.direction.is_incoming(), + trusted: peer.kind.is_trusted(), + static_node: peer.kind.is_static(), + }, + protocols: PeerProtocolInfo { + eth: Some(EthPeerInfo::Info(EthInfo { + version: peer.status.version as u64, + })), + snap: None, + other: Default::default(), + }, + }) + } + } + + Ok(infos) } /// Handler for `admin_nodeInfo` From ad8ec33dc345a2581c9084363fe9a19563ff4744 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 1 Jul 2024 10:39:00 -0700 Subject: [PATCH 292/405] chore(trie): return nibbles from `TrieCursor::current` (#9227) --- crates/trie/trie/src/trie.rs | 4 ++- .../trie/src/trie_cursor/database_cursors.rs | 10 +++--- crates/trie/trie/src/trie_cursor/in_memory.rs | 36 ++++++++++--------- crates/trie/trie/src/trie_cursor/mod.rs | 4 +-- crates/trie/trie/src/trie_cursor/noop.rs | 6 ++-- crates/trie/trie/src/updates.rs | 31 ++++++++++------ crates/trie/trie/src/walker.rs | 5 ++- 7 files changed, 54 insertions(+), 42 deletions(-) diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 4d23bfc98187..49e8d00a2b4c 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -299,7 +299,9 @@ where }; trie_updates.extend( - walker_deleted_keys.into_iter().map(|key| (key, TrieOp::Delete)), + walker_deleted_keys + .into_iter() + .map(|nibbles| (TrieKey::AccountNode(nibbles), TrieOp::Delete)), ); trie_updates.extend_with_account_updates(hash_builder_updates); diff --git a/crates/trie/trie/src/trie_cursor/database_cursors.rs b/crates/trie/trie/src/trie_cursor/database_cursors.rs index 61e43c19b12c..53a64a0b09f1 100644 --- a/crates/trie/trie/src/trie_cursor/database_cursors.rs +++ b/crates/trie/trie/src/trie_cursor/database_cursors.rs @@ -1,5 +1,5 @@ use super::{TrieCursor, TrieCursorFactory}; -use crate::{updates::TrieKey, BranchNodeCompact, Nibbles, StoredNibbles, StoredNibblesSubKey}; +use crate::{BranchNodeCompact, Nibbles, StoredNibbles, StoredNibblesSubKey}; use reth_db::{tables, DatabaseError}; use reth_db_api::{ cursor::{DbCursorRO, DbDupCursorRO}, @@ -60,8 +60,8 @@ where } /// Retrieves the current key in the cursor. - fn current(&mut self) -> Result, DatabaseError> { - Ok(self.0.current()?.map(|(k, _)| TrieKey::AccountNode(k.0))) + fn current(&mut self) -> Result, DatabaseError> { + Ok(self.0.current()?.map(|(k, _)| k.0)) } } @@ -109,8 +109,8 @@ where } /// Retrieves the current value in the storage trie cursor. - fn current(&mut self) -> Result, DatabaseError> { - Ok(self.cursor.current()?.map(|(k, v)| TrieKey::StorageNode(k, v.nibbles.0))) + fn current(&mut self) -> Result, DatabaseError> { + Ok(self.cursor.current()?.map(|(_, v)| v.nibbles.0)) } } diff --git a/crates/trie/trie/src/trie_cursor/in_memory.rs b/crates/trie/trie/src/trie_cursor/in_memory.rs index f7c521f69b6b..3efd2fa5debc 100644 --- a/crates/trie/trie/src/trie_cursor/in_memory.rs +++ b/crates/trie/trie/src/trie_cursor/in_memory.rs @@ -42,7 +42,7 @@ impl<'a, CF: TrieCursorFactory> TrieCursorFactory for InMemoryTrieCursorFactory< pub struct InMemoryAccountTrieCursor<'a, C> { cursor: C, trie_updates: &'a TrieUpdatesSorted, - last_key: Option, + last_key: Option, } impl<'a, C> InMemoryAccountTrieCursor<'a, C> { @@ -56,12 +56,12 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryAccountTrieCursor<'a, C> { &mut self, key: Nibbles, ) -> Result, DatabaseError> { - if let Some((trie_key, trie_op)) = self.trie_updates.find_account_node(&key) { - self.last_key = Some(trie_key); + if let Some((nibbles, trie_op)) = self.trie_updates.find_account_node(&key) { + self.last_key = Some(nibbles); Ok(trie_op.into_update().map(|node| (key, node))) } else { let result = self.cursor.seek_exact(key)?; - self.last_key = result.as_ref().map(|(k, _)| TrieKey::AccountNode(k.clone())); + self.last_key = result.as_ref().map(|(key, _)| key.clone()); Ok(result) } } @@ -78,20 +78,22 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryAccountTrieCursor<'a, C> { .cloned(); if let Some((trie_key, trie_op)) = trie_update_entry { - let nibbles = match &trie_key { - TrieKey::AccountNode(nibbles) => nibbles.clone(), + let nibbles = match trie_key { + TrieKey::AccountNode(nibbles) => { + self.last_key = Some(nibbles.clone()); + nibbles + } _ => panic!("Invalid trie key"), }; - self.last_key = Some(trie_key); return Ok(trie_op.into_update().map(|node| (nibbles, node))) } let result = self.cursor.seek(key)?; - self.last_key = result.as_ref().map(|(k, _)| TrieKey::AccountNode(k.clone())); + self.last_key = result.as_ref().map(|(key, _)| key.clone()); Ok(result) } - fn current(&mut self) -> Result, DatabaseError> { + fn current(&mut self) -> Result, DatabaseError> { if self.last_key.is_some() { Ok(self.last_key.clone()) } else { @@ -108,7 +110,7 @@ pub struct InMemoryStorageTrieCursor<'a, C> { trie_update_index: usize, trie_updates: &'a TrieUpdatesSorted, hashed_address: B256, - last_key: Option, + last_key: Option, } impl<'a, C> InMemoryStorageTrieCursor<'a, C> { @@ -129,8 +131,7 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryStorageTrieCursor<'a, C> { Ok(trie_op.into_update().map(|node| (key, node))) } else { let result = self.cursor.seek_exact(key)?; - self.last_key = - result.as_ref().map(|(k, _)| TrieKey::StorageNode(self.hashed_address, k.clone())); + self.last_key = result.as_ref().map(|(key, _)| key.clone()); Ok(result) } } @@ -151,20 +152,21 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryStorageTrieCursor<'a, C> { trie_update_entry.filter(|(k, _)| matches!(k, TrieKey::StorageNode(_, _))) { let nibbles = match trie_key { - TrieKey::StorageNode(_, nibbles) => nibbles.clone(), + TrieKey::StorageNode(_, nibbles) => { + self.last_key = Some(nibbles.clone()); + nibbles.clone() + } _ => panic!("this should not happen!"), }; - self.last_key = Some(trie_key.clone()); return Ok(trie_op.as_update().map(|node| (nibbles, node.clone()))) } let result = self.cursor.seek(key)?; - self.last_key = - result.as_ref().map(|(k, _)| TrieKey::StorageNode(self.hashed_address, k.clone())); + self.last_key = result.as_ref().map(|(key, _)| key.clone()); Ok(result) } - fn current(&mut self) -> Result, DatabaseError> { + fn current(&mut self) -> Result, DatabaseError> { if self.last_key.is_some() { Ok(self.last_key.clone()) } else { diff --git a/crates/trie/trie/src/trie_cursor/mod.rs b/crates/trie/trie/src/trie_cursor/mod.rs index a8e0a01cf093..e5160a5526a1 100644 --- a/crates/trie/trie/src/trie_cursor/mod.rs +++ b/crates/trie/trie/src/trie_cursor/mod.rs @@ -1,4 +1,4 @@ -use crate::{updates::TrieKey, BranchNodeCompact, Nibbles}; +use crate::{BranchNodeCompact, Nibbles}; use reth_db::DatabaseError; use reth_primitives::B256; @@ -51,5 +51,5 @@ pub trait TrieCursor: Send + Sync { -> Result, DatabaseError>; /// Get the current entry. - fn current(&mut self) -> Result, DatabaseError>; + fn current(&mut self) -> Result, DatabaseError>; } diff --git a/crates/trie/trie/src/trie_cursor/noop.rs b/crates/trie/trie/src/trie_cursor/noop.rs index c55bdb80f2c5..e49c90613d37 100644 --- a/crates/trie/trie/src/trie_cursor/noop.rs +++ b/crates/trie/trie/src/trie_cursor/noop.rs @@ -1,5 +1,5 @@ use super::{TrieCursor, TrieCursorFactory}; -use crate::{updates::TrieKey, BranchNodeCompact, Nibbles}; +use crate::{BranchNodeCompact, Nibbles}; use reth_db::DatabaseError; use reth_primitives::B256; @@ -49,7 +49,7 @@ impl TrieCursor for NoopAccountTrieCursor { } /// Retrieves the current cursor position within the account trie. - fn current(&mut self) -> Result, DatabaseError> { + fn current(&mut self) -> Result, DatabaseError> { Ok(None) } } @@ -77,7 +77,7 @@ impl TrieCursor for NoopStorageTrieCursor { } /// Retrieves the current cursor position within storage tries. - fn current(&mut self) -> Result, DatabaseError> { + fn current(&mut self) -> Result, DatabaseError> { Ok(None) } } diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index b982c945591d..181f2b82d1d5 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -133,7 +133,9 @@ impl TrieUpdates { ) { // Add updates from trie walker. let (_, deleted_keys) = walker.split(); - self.extend(deleted_keys.into_iter().map(|key| (key, TrieOp::Delete))); + self.extend( + deleted_keys.into_iter().map(|nibbles| (TrieKey::AccountNode(nibbles), TrieOp::Delete)), + ); // Add account node updates from hash builder. let (_, hash_builder_updates) = hash_builder.split(); @@ -154,7 +156,11 @@ impl TrieUpdates { ) { // Add updates from trie walker. let (_, deleted_keys) = walker.split(); - self.extend(deleted_keys.into_iter().map(|key| (key, TrieOp::Delete))); + self.extend( + deleted_keys + .into_iter() + .map(|nibbles| (TrieKey::StorageNode(hashed_address, nibbles), TrieOp::Delete)), + ); // Add storage node updates from hash builder. let (_, hash_builder_updates) = hash_builder.split(); @@ -248,11 +254,12 @@ pub struct TrieUpdatesSorted { impl TrieUpdatesSorted { /// Find the account node with the given nibbles. - pub fn find_account_node(&self, key: &Nibbles) -> Option<(TrieKey, TrieOp)> { - self.trie_operations - .iter() - .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if nibbles == key)) - .cloned() + pub fn find_account_node(&self, key: &Nibbles) -> Option<(Nibbles, TrieOp)> { + self.trie_operations.iter().find_map(|(k, op)| { + k.as_account_node_key() + .filter(|nibbles| nibbles == &key) + .map(|nibbles| (nibbles.clone(), op.clone())) + }) } /// Find the storage node with the given hashed address and key. @@ -260,9 +267,11 @@ impl TrieUpdatesSorted { &self, hashed_address: &B256, key: &Nibbles, - ) -> Option<(TrieKey, TrieOp)> { - self.trie_operations.iter().find(|(k, _)| { - matches!(k, TrieKey::StorageNode(address, nibbles) if address == hashed_address && nibbles == key) - }).cloned() + ) -> Option<(Nibbles, TrieOp)> { + self.trie_operations.iter().find_map(|(k, op)| { + k.as_storage_node_key() + .filter(|(address, nibbles)| address == &hashed_address && nibbles == &key) + .map(|(_, nibbles)| (nibbles.clone(), op.clone())) + }) } } diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index 990b7fb74d52..64a68ea17de2 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -1,7 +1,6 @@ use crate::{ prefix_set::PrefixSet, trie_cursor::{CursorSubNode, TrieCursor}, - updates::TrieKey, BranchNodeCompact, Nibbles, }; use reth_db::DatabaseError; @@ -24,7 +23,7 @@ pub struct TrieWalker { /// A `PrefixSet` representing the changes to be applied to the trie. pub changes: PrefixSet, /// The retained trie node keys that need to be deleted. - deleted_keys: Option>, + deleted_keys: Option>, } impl TrieWalker { @@ -45,7 +44,7 @@ impl TrieWalker { } /// Split the walker into stack and trie updates. - pub fn split(mut self) -> (Vec, HashSet) { + pub fn split(mut self) -> (Vec, HashSet) { let keys = self.deleted_keys.take(); (self.stack, keys.unwrap_or_default()) } From 116d7a3f1b587d05d81fc18f0f6cbdf154d99b93 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 1 Jul 2024 20:01:39 +0200 Subject: [PATCH 293/405] chore: disable discovery for --dev (#9229) --- crates/e2e-test-utils/src/lib.rs | 9 +++------ crates/node/core/src/node_config.rs | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/e2e-test-utils/src/lib.rs b/crates/e2e-test-utils/src/lib.rs index 925b09b91210..e55a9a24ba2e 100644 --- a/crates/e2e-test-utils/src/lib.rs +++ b/crates/e2e-test-utils/src/lib.rs @@ -58,15 +58,12 @@ where let mut nodes: Vec> = Vec::with_capacity(num_nodes); for idx in 0..num_nodes { - let mut node_config = NodeConfig::test() + let node_config = NodeConfig::test() .with_chain(chain_spec.clone()) .with_network(network_config.clone()) .with_unused_ports() - .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()); - - if is_dev { - node_config = node_config.dev(); - } + .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()) + .set_dev(is_dev); let span = span!(Level::INFO, "node", idx); let _enter = span.enter(); diff --git a/crates/node/core/src/node_config.rs b/crates/node/core/src/node_config.rs index 8f9ff42f9eae..1f5bea21beb8 100644 --- a/crates/node/core/src/node_config.rs +++ b/crates/node/core/src/node_config.rs @@ -155,12 +155,25 @@ impl NodeConfig { .with_unused_ports() } - /// Sets --dev mode for the node + /// Sets --dev mode for the node. + /// + /// In addition to setting the `--dev` flag, this also: + /// - disables discovery in [`NetworkArgs`]. pub const fn dev(mut self) -> Self { self.dev.dev = true; + self.network.discovery.disable_discovery = true; self } + /// Sets --dev mode for the node [`NodeConfig::dev`], if `dev` is true. + pub const fn set_dev(self, dev: bool) -> Self { + if dev { + self.dev() + } else { + self + } + } + /// Set the data directory args for the node pub fn with_datadir_args(mut self, datadir_args: DatadirArgs) -> Self { self.datadir = datadir_args; From 6eca557dbe6cd51e0956e110ae928856b3022773 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:02:39 -0400 Subject: [PATCH 294/405] feat: add pruning related persistence API (#9232) --- crates/engine/tree/src/persistence.rs | 49 ++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index cbbe50d051fe..6b1afe94d3ab 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -105,6 +105,18 @@ impl Persistence { fn remove_blocks_above(&self, _block_number: u64) -> Vec { todo!("implement this") } + + /// Prunes block data before the given block hash according to the configured prune + /// configuration. + fn prune_before(&self, _block_hash: B256) { + todo!("implement this") + } + + /// Removes static file related data from the database, depending on the current block height in + /// existing static files. + fn clean_static_file_duplicates(&self) { + todo!("implement this") + } } impl Persistence @@ -135,7 +147,6 @@ where while let Ok(action) = self.incoming.recv() { match action { PersistenceAction::RemoveBlocksAbove((new_tip_num, sender)) => { - // spawn blocking so we can poll the thread later let output = self.remove_blocks_above(new_tip_num); sender.send(output).unwrap(); } @@ -147,6 +158,14 @@ where self.write(blocks).unwrap(); sender.send(last_block_hash).unwrap(); } + PersistenceAction::PruneBefore((block_hash, sender)) => { + self.prune_before(block_hash); + sender.send(()).unwrap(); + } + PersistenceAction::CleanStaticFileDuplicates(sender) => { + self.clean_static_file_duplicates(); + sender.send(()).unwrap(); + } } } } @@ -161,6 +180,14 @@ pub enum PersistenceAction { /// Removes the blocks above the given block number from the database. RemoveBlocksAbove((u64, oneshot::Sender>)), + + /// Prune associated block data before the given hash, according to already-configured prune + /// modes. + PruneBefore((B256, oneshot::Sender<()>)), + + /// Trigger a read of static file data, and delete data depending on the highest block in each + /// static file segment. + CleanStaticFileDuplicates(oneshot::Sender<()>), } /// A handle to the persistence task @@ -198,4 +225,24 @@ impl PersistenceHandle { .expect("should be able to send"); rx.await.expect("todo: err handling") } + + /// Tells the persistence task to remove block data before the given hash, according to the + /// configured prune config. + pub async fn prune_before(&self, block_hash: B256) { + let (tx, rx) = oneshot::channel(); + self.sender + .send(PersistenceAction::PruneBefore((block_hash, tx))) + .expect("should be able to send"); + rx.await.expect("todo: err handling") + } + + /// Tells the persistence task to read static file data, and delete data depending on the + /// highest block in each static file segment. + pub async fn clean_static_file_duplicates(&self) { + let (tx, rx) = oneshot::channel(); + self.sender + .send(PersistenceAction::CleanStaticFileDuplicates(tx)) + .expect("should be able to send"); + rx.await.expect("todo: err handling") + } } From 82770b0922380584e17ecd91cd06d9d0f81a16df Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:31:09 +0200 Subject: [PATCH 295/405] chore: move `fill_block_env` to `ConfigureEvmEnv` (#9238) --- crates/evm/src/lib.rs | 50 ++++++++- crates/primitives/src/header.rs | 69 ++++++++++++ crates/primitives/src/revm/env.rs | 116 +------------------- crates/rpc/rpc-eth-api/src/helpers/state.rs | 15 ++- 4 files changed, 127 insertions(+), 123 deletions(-) diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index b57969de8e61..e5e9dbdf7122 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -16,8 +16,7 @@ use core::ops::Deref; use reth_chainspec::ChainSpec; use reth_primitives::{ - revm::env::fill_block_env, Address, Header, TransactionSigned, TransactionSignedEcRecovered, - U256, + header::block_coinbase, Address, Header, TransactionSigned, TransactionSignedEcRecovered, U256, }; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; @@ -126,8 +125,46 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { total_difficulty: U256, ); + /// Fill [`BlockEnv`] field according to the chain spec and given header + fn fill_block_env( + &self, + block_env: &mut BlockEnv, + chain_spec: &ChainSpec, + header: &Header, + after_merge: bool, + ) { + let coinbase = block_coinbase(chain_spec, header, after_merge); + Self::fill_block_env_with_coinbase(block_env, header, after_merge, coinbase); + } + + /// Fill block environment with coinbase. + fn fill_block_env_with_coinbase( + block_env: &mut BlockEnv, + header: &Header, + after_merge: bool, + coinbase: Address, + ) { + block_env.number = U256::from(header.number); + block_env.coinbase = coinbase; + block_env.timestamp = U256::from(header.timestamp); + if after_merge { + block_env.prevrandao = Some(header.mix_hash); + block_env.difficulty = U256::ZERO; + } else { + block_env.difficulty = header.difficulty; + block_env.prevrandao = None; + } + block_env.basefee = U256::from(header.base_fee_per_gas.unwrap_or_default()); + block_env.gas_limit = U256::from(header.gas_limit); + + // EIP-4844 excess blob gas of this block, introduced in Cancun + if let Some(excess_blob_gas) = header.excess_blob_gas { + block_env.set_blob_excess_gas_and_price(excess_blob_gas); + } + } + /// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and - /// [`fill_block_env`]. + /// [`ConfigureEvmEnv::fill_block_env`]. fn fill_cfg_and_block_env( cfg: &mut CfgEnvWithHandlerCfg, block_env: &mut BlockEnv, @@ -137,6 +174,11 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { ) { Self::fill_cfg_env(cfg, chain_spec, header, total_difficulty); let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE; - fill_block_env(block_env, chain_spec, header, after_merge); + Self::fill_block_env_with_coinbase( + block_env, + header, + after_merge, + block_coinbase(chain_spec, header, after_merge), + ); } } diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index ea80328e7528..b3d9095ff07b 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -1,12 +1,71 @@ //! Header types. +use crate::{recover_signer_unchecked, Address, Bytes}; use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; +use reth_chainspec::{Chain, ChainSpec}; use reth_codecs::derive_arbitrary; use serde::{Deserialize, Serialize}; pub use reth_primitives_traits::{Header, HeaderError, SealedHeader}; +/// Return the coinbase address for the given header and chain spec. +pub fn block_coinbase(chain_spec: &ChainSpec, header: &Header, after_merge: bool) -> Address { + // Clique consensus fills the EXTRA_SEAL (last 65 bytes) of the extra data with the + // signer's signature. + // + // On the genesis block, the extra data is filled with zeros, so we should not attempt to + // recover the signer on the genesis block. + // + // From EIP-225: + // + // * `EXTRA_SEAL`: Fixed number of extra-data suffix bytes reserved for signer seal. + // * 65 bytes fixed as signatures are based on the standard `secp256k1` curve. + // * Filled with zeros on genesis block. + if chain_spec.chain == Chain::goerli() && !after_merge && header.number > 0 { + recover_header_signer(header).unwrap_or_else(|err| { + panic!( + "Failed to recover goerli Clique Consensus signer from header ({}, {}) using extradata {}: {:?}", + header.number, header.hash_slow(), header.extra_data, err + ) + }) + } else { + header.beneficiary + } +} + +/// Error type for recovering Clique signer from a header. +#[derive(Debug, thiserror_no_std::Error)] +pub enum CliqueSignerRecoveryError { + /// Header extradata is too short. + #[error("Invalid extra data length")] + InvalidExtraData, + /// Recovery failed. + #[error("Invalid signature: {0}")] + InvalidSignature(#[from] secp256k1::Error), +} + +/// Recover the account from signed header per clique consensus rules. +pub fn recover_header_signer(header: &Header) -> Result { + let extra_data_len = header.extra_data.len(); + // Fixed number of extra-data suffix bytes reserved for signer signature. + // 65 bytes fixed as signatures are based on the standard secp256k1 curve. + // Filled with zeros on genesis block. + let signature_start_byte = extra_data_len - 65; + let signature: [u8; 65] = header.extra_data[signature_start_byte..] + .try_into() + .map_err(|_| CliqueSignerRecoveryError::InvalidExtraData)?; + let seal_hash = { + let mut header_to_seal = header.clone(); + header_to_seal.extra_data = Bytes::from(header.extra_data[..signature_start_byte].to_vec()); + header_to_seal.hash_slow() + }; + + // TODO: this is currently unchecked recovery, does this need to be checked w.r.t EIP-2? + recover_signer_unchecked(&signature, &seal_hash.0) + .map_err(CliqueSignerRecoveryError::InvalidSignature) +} + /// Represents the direction for a headers request depending on the `reverse` field of the request. /// > The response must contain a number of block headers, of rising number when reverse is 0, /// > falling when 1 @@ -87,6 +146,7 @@ impl From for bool { #[cfg(test)] mod tests { + use super::*; use crate::{ address, b256, bloom, bytes, hex, Address, Bytes, Header, HeadersDirection, B256, U256, }; @@ -353,4 +413,13 @@ mod tests { Header::decode(&mut data.as_slice()) .expect_err("blob_gas_used size should make this header decoding fail"); } + + #[test] + fn test_recover_genesis_goerli_signer() { + // just ensures that `block_coinbase` does not panic on the genesis block + let chain_spec = reth_chainspec::GOERLI.clone(); + let header = chain_spec.genesis_header(); + let block_coinbase = block_coinbase(&chain_spec, &header, false); + assert_eq!(block_coinbase, header.beneficiary); + } } diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index 6cc81128c2a0..5c7ae7b0d5de 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -1,9 +1,7 @@ use crate::{ - recover_signer_unchecked, - revm_primitives::{BlockEnv, Env, TxEnv}, - Address, Bytes, Header, TxKind, B256, U256, + revm_primitives::{Env, TxEnv}, + Address, Bytes, TxKind, B256, U256, }; -use reth_chainspec::{Chain, ChainSpec}; use alloy_eips::{eip4788::BEACON_ROOTS_ADDRESS, eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS}; #[cfg(feature = "optimism")] @@ -12,101 +10,6 @@ use revm_primitives::OptimismFields; #[cfg(not(feature = "std"))] use alloc::vec::Vec; -/// Fill block environment from Block. -pub fn fill_block_env( - block_env: &mut BlockEnv, - chain_spec: &ChainSpec, - header: &Header, - after_merge: bool, -) { - let coinbase = block_coinbase(chain_spec, header, after_merge); - fill_block_env_with_coinbase(block_env, header, after_merge, coinbase); -} - -/// Fill block environment with coinbase. -#[inline] -pub fn fill_block_env_with_coinbase( - block_env: &mut BlockEnv, - header: &Header, - after_merge: bool, - coinbase: Address, -) { - block_env.number = U256::from(header.number); - block_env.coinbase = coinbase; - block_env.timestamp = U256::from(header.timestamp); - if after_merge { - block_env.prevrandao = Some(header.mix_hash); - block_env.difficulty = U256::ZERO; - } else { - block_env.difficulty = header.difficulty; - block_env.prevrandao = None; - } - block_env.basefee = U256::from(header.base_fee_per_gas.unwrap_or_default()); - block_env.gas_limit = U256::from(header.gas_limit); - - // EIP-4844 excess blob gas of this block, introduced in Cancun - if let Some(excess_blob_gas) = header.excess_blob_gas { - block_env.set_blob_excess_gas_and_price(excess_blob_gas); - } -} - -/// Return the coinbase address for the given header and chain spec. -pub fn block_coinbase(chain_spec: &ChainSpec, header: &Header, after_merge: bool) -> Address { - // Clique consensus fills the EXTRA_SEAL (last 65 bytes) of the extra data with the - // signer's signature. - // - // On the genesis block, the extra data is filled with zeros, so we should not attempt to - // recover the signer on the genesis block. - // - // From EIP-225: - // - // * `EXTRA_SEAL`: Fixed number of extra-data suffix bytes reserved for signer seal. - // * 65 bytes fixed as signatures are based on the standard `secp256k1` curve. - // * Filled with zeros on genesis block. - if chain_spec.chain == Chain::goerli() && !after_merge && header.number > 0 { - recover_header_signer(header).unwrap_or_else(|err| { - panic!( - "Failed to recover goerli Clique Consensus signer from header ({}, {}) using extradata {}: {:?}", - header.number, header.hash_slow(), header.extra_data, err - ) - }) - } else { - header.beneficiary - } -} - -/// Error type for recovering Clique signer from a header. -#[derive(Debug, thiserror_no_std::Error)] -pub enum CliqueSignerRecoveryError { - /// Header extradata is too short. - #[error("Invalid extra data length")] - InvalidExtraData, - /// Recovery failed. - #[error("Invalid signature: {0}")] - InvalidSignature(#[from] secp256k1::Error), -} - -/// Recover the account from signed header per clique consensus rules. -pub fn recover_header_signer(header: &Header) -> Result { - let extra_data_len = header.extra_data.len(); - // Fixed number of extra-data suffix bytes reserved for signer signature. - // 65 bytes fixed as signatures are based on the standard secp256k1 curve. - // Filled with zeros on genesis block. - let signature_start_byte = extra_data_len - 65; - let signature: [u8; 65] = header.extra_data[signature_start_byte..] - .try_into() - .map_err(|_| CliqueSignerRecoveryError::InvalidExtraData)?; - let seal_hash = { - let mut header_to_seal = header.clone(); - header_to_seal.extra_data = Bytes::from(header.extra_data[..signature_start_byte].to_vec()); - header_to_seal.hash_slow() - }; - - // TODO: this is currently unchecked recovery, does this need to be checked w.r.t EIP-2? - recover_signer_unchecked(&signature, &seal_hash.0) - .map_err(CliqueSignerRecoveryError::InvalidSignature) -} - /// Fill transaction environment with the EIP-4788 system contract message data. /// /// This requirements for the beacon root contract call defined by @@ -195,18 +98,3 @@ fn fill_tx_env_with_system_contract_call( // disable the base fee check for this call by setting the base fee to zero env.block.basefee = U256::ZERO; } - -#[cfg(test)] -mod tests { - use super::*; - use reth_chainspec::GOERLI; - - #[test] - fn test_recover_genesis_goerli_signer() { - // just ensures that `block_coinbase` does not panic on the genesis block - let chain_spec = GOERLI.clone(); - let header = chain_spec.genesis_header(); - let block_coinbase = block_coinbase(&chain_spec, &header, false); - assert_eq!(block_coinbase, header.beneficiary); - } -} diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 80859b929b7b..34424e94fcdf 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -2,11 +2,11 @@ //! RPC methods. use futures::Future; -use reth_primitives::{ - revm::env::fill_block_env_with_coinbase, Address, BlockId, BlockNumberOrTag, Bytes, Header, - B256, U256, +use reth_evm::ConfigureEvmEnv; +use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, Header, B256, U256}; +use reth_provider::{ + BlockIdReader, ChainSpecProvider, StateProvider, StateProviderBox, StateProviderFactory, }; -use reth_provider::{BlockIdReader, StateProvider, StateProviderBox, StateProviderFactory}; use reth_rpc_eth_types::{ EthApiError, EthResult, EthStateCache, PendingBlockEnv, RpcInvalidTransactionError, }; @@ -209,7 +209,12 @@ pub trait LoadState { let (cfg, mut block_env, _) = self.evm_env_at(header.parent_hash.into()).await?; let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE; - fill_block_env_with_coinbase(&mut block_env, header, after_merge, header.beneficiary); + self.evm_config().fill_block_env( + &mut block_env, + &LoadPendingBlock::provider(self).chain_spec(), + header, + after_merge, + ); Ok((cfg, block_env)) } From 95f228155f143ffc6cd1995bdb9a74d49a196190 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:08:54 -0400 Subject: [PATCH 296/405] chore: add send_action method to persistence task (#9233) --- crates/engine/tree/src/persistence.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index 6b1afe94d3ab..989ed5f76bf5 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -148,7 +148,9 @@ where match action { PersistenceAction::RemoveBlocksAbove((new_tip_num, sender)) => { let output = self.remove_blocks_above(new_tip_num); - sender.send(output).unwrap(); + + // we ignore the error because the caller may or may not care about the result + let _ = sender.send(output); } PersistenceAction::SaveBlocks((blocks, sender)) => { if blocks.is_empty() { @@ -156,15 +158,21 @@ where } let last_block_hash = blocks.last().unwrap().block().hash(); self.write(blocks).unwrap(); - sender.send(last_block_hash).unwrap(); + + // we ignore the error because the caller may or may not care about the result + let _ = sender.send(last_block_hash); } PersistenceAction::PruneBefore((block_hash, sender)) => { self.prune_before(block_hash); - sender.send(()).unwrap(); + + // we ignore the error because the caller may or may not care about the result + let _ = sender.send(()); } PersistenceAction::CleanStaticFileDuplicates(sender) => { self.clean_static_file_duplicates(); - sender.send(()).unwrap(); + + // we ignore the error because the caller may or may not care about the result + let _ = sender.send(()); } } } @@ -203,6 +211,12 @@ impl PersistenceHandle { Self { sender } } + /// Sends a specific [`PersistenceAction`] in the contained channel. The caller is responsible + /// for creating any channels for the given action. + pub fn send_action(&self, action: PersistenceAction) { + self.sender.send(action).expect("should be able to send"); + } + /// Tells the persistence task to save a certain list of finalized blocks. The blocks are /// assumed to be ordered by block number. /// From e95c6dba9d6b2cb4a729bdf3f1f02f4577e9e0a2 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:54:49 -0400 Subject: [PATCH 297/405] chore: use format_gas and format_gas_throughput for gas logs (#9247) --- Cargo.lock | 3 + crates/exex/exex/Cargo.toml | 1 + crates/exex/exex/src/backfill.rs | 3 +- crates/node/events/Cargo.toml | 1 + crates/node/events/src/node.rs | 5 +- .../src/constants/gas_units.rs | 72 +++++++++++++++++++ crates/primitives-traits/src/constants/mod.rs | 6 +- crates/primitives-traits/src/lib.rs | 1 + crates/stages/api/src/metrics/execution.rs | 20 ------ crates/stages/api/src/metrics/listener.rs | 4 +- crates/stages/api/src/metrics/mod.rs | 2 - crates/stages/stages/Cargo.toml | 1 + crates/stages/stages/src/stages/execution.rs | 25 ++----- 13 files changed, 93 insertions(+), 51 deletions(-) delete mode 100644 crates/stages/api/src/metrics/execution.rs diff --git a/Cargo.lock b/Cargo.lock index 38f323b6c32c..54f5d3f694f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7293,6 +7293,7 @@ dependencies = [ "reth-node-core", "reth-payload-builder", "reth-primitives", + "reth-primitives-traits", "reth-provider", "reth-prune-types", "reth-revm", @@ -7755,6 +7756,7 @@ dependencies = [ "reth-network", "reth-network-api", "reth-primitives", + "reth-primitives-traits", "reth-provider", "reth-prune", "reth-stages", @@ -8409,6 +8411,7 @@ dependencies = [ "reth-network-p2p", "reth-network-peers", "reth-primitives", + "reth-primitives-traits", "reth-provider", "reth-prune-types", "reth-revm", diff --git a/crates/exex/exex/Cargo.toml b/crates/exex/exex/Cargo.toml index a911f45997a7..e2a364988bad 100644 --- a/crates/exex/exex/Cargo.toml +++ b/crates/exex/exex/Cargo.toml @@ -19,6 +19,7 @@ reth-metrics.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true reth-primitives.workspace = true +reth-primitives-traits.workspace = true reth-provider.workspace = true reth-tasks.workspace = true reth-tracing.workspace = true diff --git a/crates/exex/exex/src/backfill.rs b/crates/exex/exex/src/backfill.rs index f46c82d24101..212082081b22 100644 --- a/crates/exex/exex/src/backfill.rs +++ b/crates/exex/exex/src/backfill.rs @@ -2,10 +2,11 @@ use reth_db_api::database::Database; use reth_evm::execute::{BatchExecutor, BlockExecutionError, BlockExecutorProvider}; use reth_node_api::FullNodeComponents; use reth_primitives::{Block, BlockNumber}; +use reth_primitives_traits::format_gas_throughput; use reth_provider::{Chain, FullProvider, ProviderError, TransactionVariant}; use reth_prune_types::PruneModes; use reth_revm::database::StateProviderDatabase; -use reth_stages_api::{format_gas_throughput, ExecutionStageThresholds}; +use reth_stages_api::ExecutionStageThresholds; use reth_tracing::tracing::{debug, trace}; use std::{ marker::PhantomData, diff --git a/crates/node/events/Cargo.toml b/crates/node/events/Cargo.toml index 1b14c74de5f3..d592d25f3902 100644 --- a/crates/node/events/Cargo.toml +++ b/crates/node/events/Cargo.toml @@ -20,6 +20,7 @@ reth-prune.workspace = true reth-static-file.workspace = true reth-db-api.workspace = true reth-primitives.workspace = true +reth-primitives-traits.workspace = true # alloy alloy-rpc-types-engine.workspace = true diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index 8a25c370037b..77c8fd4684fc 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -10,6 +10,7 @@ use reth_db_api::{database::Database, database_metrics::DatabaseMetadata}; use reth_network::{NetworkEvent, NetworkHandle}; use reth_network_api::PeersInfo; use reth_primitives::{constants, BlockNumber, B256}; +use reth_primitives_traits::{format_gas, format_gas_throughput}; use reth_prune::PrunerEvent; use reth_stages::{EntitiesCheckpoint, ExecOutput, PipelineEvent, StageCheckpoint, StageId}; use reth_static_file::StaticFileProducerEvent; @@ -279,8 +280,8 @@ impl NodeState { hash=?block.hash(), peers=self.num_connected_peers(), txs=block.body.len(), - mgas=%format!("{:.3}MGas", block.header.gas_used as f64 / constants::MGAS_TO_GAS as f64), - mgas_throughput=%format!("{:.3}MGas/s", block.header.gas_used as f64 / elapsed.as_secs_f64() / constants::MGAS_TO_GAS as f64), + gas=format_gas(block.header.gas_used), + gas_throughput=format_gas_throughput(block.header.gas_used, elapsed), full=%format!("{:.1}%", block.header.gas_used as f64 * 100.0 / block.header.gas_limit as f64), base_fee=%format!("{:.2}gwei", block.header.base_fee_per_gas.unwrap_or(0) as f64 / constants::GWEI_TO_WEI as f64), blobs=block.header.blob_gas_used.unwrap_or(0) / constants::eip4844::DATA_GAS_PER_BLOB, diff --git a/crates/primitives-traits/src/constants/gas_units.rs b/crates/primitives-traits/src/constants/gas_units.rs index 9916de864a63..0495a1755ef6 100644 --- a/crates/primitives-traits/src/constants/gas_units.rs +++ b/crates/primitives-traits/src/constants/gas_units.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + /// Represents one Kilogas, or `1_000` gas. pub const KILOGAS: u64 = 1_000; @@ -6,3 +8,73 @@ pub const MEGAGAS: u64 = KILOGAS * 1_000; /// Represents one Gigagas, or `1_000_000_000` gas. pub const GIGAGAS: u64 = MEGAGAS * 1_000; + +/// Returns a formatted gas throughput log, showing either: +/// * "Kgas/s", or 1,000 gas per second +/// * "Mgas/s", or 1,000,000 gas per second +/// * "Ggas/s", or 1,000,000,000 gas per second +/// +/// Depending on the magnitude of the gas throughput. +pub fn format_gas_throughput(gas: u64, execution_duration: Duration) -> String { + let gas_per_second = gas as f64 / execution_duration.as_secs_f64(); + if gas_per_second < MEGAGAS as f64 { + format!("{:.} Kgas/second", gas_per_second / KILOGAS as f64) + } else if gas_per_second < GIGAGAS as f64 { + format!("{:.} Mgas/second", gas_per_second / MEGAGAS as f64) + } else { + format!("{:.} Ggas/second", gas_per_second / GIGAGAS as f64) + } +} + +/// Returns a formatted gas log, showing either: +/// * "Kgas", or 1,000 gas +/// * "Mgas", or 1,000,000 gas +/// * "Ggas", or 1,000,000,000 gas +/// +/// Depending on the magnitude of gas. +pub fn format_gas(gas: u64) -> String { + let gas = gas as f64; + if gas < MEGAGAS as f64 { + format!("{:.} Kgas", gas / KILOGAS as f64) + } else if gas < GIGAGAS as f64 { + format!("{:.} Mgas", gas / MEGAGAS as f64) + } else { + format!("{:.} Ggas", gas / GIGAGAS as f64) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_gas_fmt() { + let gas = 100_000; + let gas_unit = format_gas(gas); + assert_eq!(gas_unit, "100 Kgas"); + + let gas = 100_000_000; + let gas_unit = format_gas(gas); + assert_eq!(gas_unit, "100 Mgas"); + + let gas = 100_000_000_000; + let gas_unit = format_gas(gas); + assert_eq!(gas_unit, "100 Ggas"); + } + + #[test] + fn test_gas_throughput_fmt() { + let duration = Duration::from_secs(1); + let gas = 100_000; + let throughput = format_gas_throughput(gas, duration); + assert_eq!(throughput, "100 Kgas/second"); + + let gas = 100_000_000; + let throughput = format_gas_throughput(gas, duration); + assert_eq!(throughput, "100 Mgas/second"); + + let gas = 100_000_000_000; + let throughput = format_gas_throughput(gas, duration); + assert_eq!(throughput, "100 Ggas/second"); + } +} diff --git a/crates/primitives-traits/src/constants/mod.rs b/crates/primitives-traits/src/constants/mod.rs index 32d41ddcd8dd..7ed018e8c8fb 100644 --- a/crates/primitives-traits/src/constants/mod.rs +++ b/crates/primitives-traits/src/constants/mod.rs @@ -3,8 +3,9 @@ use alloy_primitives::{address, b256, Address, B256, U256}; use core::time::Duration; -/// Gas units, for example [`GIGAGAS`](gas_units::GIGAGAS). +/// Gas units, for example [`GIGAGAS`]. pub mod gas_units; +pub use gas_units::{GIGAGAS, KILOGAS, MEGAGAS}; /// The client version: `reth/v{major}.{minor}.{patch}` pub const RETH_CLIENT_VERSION: &str = concat!("reth/v", env!("CARGO_PKG_VERSION")); @@ -98,9 +99,6 @@ pub const FINNEY_TO_WEI: u128 = (GWEI_TO_WEI as u128) * 1_000_000; /// Multiplier for converting ether to wei. pub const ETH_TO_WEI: u128 = FINNEY_TO_WEI * 1000; -/// Multiplier for converting mgas to gas. -pub const MGAS_TO_GAS: u64 = 1_000_000u64; - /// The Ethereum mainnet genesis hash: /// `0x0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3` pub const MAINNET_GENESIS_HASH: B256 = diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 590e9573dc4c..b7a42d9c7b5c 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -14,6 +14,7 @@ mod alloy_compat; /// Common constants. pub mod constants; +pub use constants::gas_units::{format_gas, format_gas_throughput}; /// Minimal account pub mod account; diff --git a/crates/stages/api/src/metrics/execution.rs b/crates/stages/api/src/metrics/execution.rs deleted file mode 100644 index b54ed02f653c..000000000000 --- a/crates/stages/api/src/metrics/execution.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::time::Duration; - -use reth_primitives_traits::constants::gas_units::{GIGAGAS, KILOGAS, MEGAGAS}; - -/// Returns a formatted gas throughput log, showing either: -/// * "Kgas/s", or 1,000 gas per second -/// * "Mgas/s", or 1,000,000 gas per second -/// * "Ggas/s", or 1,000,000,000 gas per second -/// -/// Depending on the magnitude of the gas throughput. -pub fn format_gas_throughput(gas: u64, execution_duration: Duration) -> String { - let gas_per_second = gas as f64 / execution_duration.as_secs_f64(); - if gas_per_second < MEGAGAS as f64 { - format!("{:.} Kgas/second", gas_per_second / KILOGAS as f64) - } else if gas_per_second < GIGAGAS as f64 { - format!("{:.} Mgas/second", gas_per_second / MEGAGAS as f64) - } else { - format!("{:.} Ggas/second", gas_per_second / GIGAGAS as f64) - } -} diff --git a/crates/stages/api/src/metrics/listener.rs b/crates/stages/api/src/metrics/listener.rs index e703367bcd67..e37eaa3d7267 100644 --- a/crates/stages/api/src/metrics/listener.rs +++ b/crates/stages/api/src/metrics/listener.rs @@ -1,6 +1,6 @@ use crate::{metrics::SyncMetrics, StageCheckpoint, StageId}; use alloy_primitives::BlockNumber; -use reth_primitives_traits::constants::MGAS_TO_GAS; +use reth_primitives_traits::constants::MEGAGAS; use std::{ future::Future, pin::Pin, @@ -83,7 +83,7 @@ impl MetricsListener { } } MetricEvent::ExecutionStageGas { gas } => { - self.sync_metrics.execution_stage.mgas_processed_total.increment(gas / MGAS_TO_GAS) + self.sync_metrics.execution_stage.mgas_processed_total.increment(gas / MEGAGAS) } } } diff --git a/crates/stages/api/src/metrics/mod.rs b/crates/stages/api/src/metrics/mod.rs index 983247ae9c0b..bed2742c25fc 100644 --- a/crates/stages/api/src/metrics/mod.rs +++ b/crates/stages/api/src/metrics/mod.rs @@ -1,7 +1,5 @@ -mod execution; mod listener; mod sync_metrics; -pub use execution::format_gas_throughput; pub use listener::{MetricEvent, MetricEventsSender, MetricsListener}; use sync_metrics::*; diff --git a/crates/stages/stages/Cargo.toml b/crates/stages/stages/Cargo.toml index ab0dd6c689a7..75531e1ce580 100644 --- a/crates/stages/stages/Cargo.toml +++ b/crates/stages/stages/Cargo.toml @@ -24,6 +24,7 @@ reth-evm.workspace = true reth-exex.workspace = true reth-network-p2p.workspace = true reth-primitives.workspace = true +reth-primitives-traits.workspace = true reth-provider.workspace = true reth-execution-types.workspace = true reth-prune-types.workspace = true diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 26db6f50f8ae..4294132c7554 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -7,6 +7,7 @@ use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; use reth_execution_types::{Chain, ExecutionOutcome}; use reth_exex::{ExExManagerHandle, ExExNotification}; use reth_primitives::{BlockNumber, Header, StaticFileSegment}; +use reth_primitives_traits::format_gas_throughput; use reth_provider::{ providers::{StaticFileProvider, StaticFileProviderRWRefMut, StaticFileWriter}, BlockReader, DatabaseProviderRW, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, @@ -15,9 +16,9 @@ use reth_provider::{ use reth_prune_types::PruneModes; use reth_revm::database::StateProviderDatabase; use reth_stages_api::{ - format_gas_throughput, BlockErrorKind, CheckpointBlockRange, EntitiesCheckpoint, ExecInput, - ExecOutput, ExecutionCheckpoint, ExecutionStageThresholds, MetricEvent, MetricEventsSender, - Stage, StageCheckpoint, StageError, StageId, UnwindInput, UnwindOutput, + BlockErrorKind, CheckpointBlockRange, EntitiesCheckpoint, ExecInput, ExecOutput, + ExecutionCheckpoint, ExecutionStageThresholds, MetricEvent, MetricEventsSender, Stage, + StageCheckpoint, StageError, StageId, UnwindInput, UnwindOutput, }; use std::{ cmp::Ordering, @@ -640,7 +641,7 @@ mod tests { StaticFileProviderFactory, }; use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig}; - use reth_stages_api::{format_gas_throughput, StageUnitCheckpoint}; + use reth_stages_api::StageUnitCheckpoint; use std::collections::BTreeMap; fn stage() -> ExecutionStage { @@ -661,22 +662,6 @@ mod tests { ) } - #[test] - fn test_gas_throughput_fmt() { - let duration = Duration::from_secs(1); - let gas = 100_000; - let throughput = format_gas_throughput(gas, duration); - assert_eq!(throughput, "100 Kgas/second"); - - let gas = 100_000_000; - let throughput = format_gas_throughput(gas, duration); - assert_eq!(throughput, "100 Mgas/second"); - - let gas = 100_000_000_000; - let throughput = format_gas_throughput(gas, duration); - assert_eq!(throughput, "100 Ggas/second"); - } - #[test] fn execution_checkpoint_matches() { let factory = create_test_provider_factory(); From 405b73045549611c3381020cef9302b4ee3f483c Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:40:07 -0400 Subject: [PATCH 298/405] chore: remove prune_modes from BlockWriter (#9231) --- .../src/commands/debug_cmd/build_block.rs | 8 +++- .../commands/debug_cmd/in_memory_merkle.rs | 1 - bin/reth/src/commands/debug_cmd/merkle.rs | 2 +- .../src/commands/debug_cmd/replay_engine.rs | 8 +++- crates/blockchain-tree/src/blockchain_tree.rs | 38 +++++++++--------- crates/cli/commands/src/common.rs | 7 ++-- crates/consensus/beacon/src/engine/mod.rs | 1 - .../consensus/beacon/src/engine/test_utils.rs | 3 +- crates/engine/tree/src/persistence.rs | 9 +---- crates/exex/exex/src/backfill.rs | 1 - crates/node/builder/src/launch/common.rs | 19 ++++----- crates/stages/stages/src/stages/execution.rs | 35 ++++++----------- .../stages/src/stages/hashing_account.rs | 2 +- crates/stages/stages/src/stages/headers.rs | 1 - crates/stages/stages/src/stages/mod.rs | 10 ++--- .../provider/src/providers/database/mod.rs | 39 ++++++++++++------- .../src/providers/database/provider.rs | 26 +++++++------ crates/storage/provider/src/traits/block.rs | 10 +---- testing/ef-tests/src/cases/blockchain_test.rs | 2 - 19 files changed, 105 insertions(+), 117 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 69e0dc1cf208..afaf799649c9 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -29,6 +29,7 @@ use reth_provider::{ providers::BlockchainProvider, BlockHashReader, BlockReader, BlockWriter, ChainSpecProvider, ProviderFactory, StageCheckpointReader, StateProviderFactory, }; +use reth_prune::PruneModes; use reth_revm::{database::StateProviderDatabase, primitives::EnvKzgSettings}; use reth_rpc_types::engine::{BlobsBundleV1, PayloadAttributes}; use reth_stages::StageId; @@ -122,7 +123,11 @@ impl Command { // configure blockchain tree let tree_externals = TreeExternals::new(provider_factory.clone(), Arc::clone(&consensus), executor); - let tree = BlockchainTree::new(tree_externals, BlockchainTreeConfig::default(), None)?; + let tree = BlockchainTree::new( + tree_externals, + BlockchainTreeConfig::default(), + PruneModes::none(), + )?; let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree)); // fetch the best block from the database @@ -298,7 +303,6 @@ impl Command { execution_outcome, hashed_post_state, trie_updates, - None, )?; info!(target: "reth::cli", "Successfully appended built block"); } diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index 352d7846e5bc..bd97bfcf3db9 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -163,7 +163,6 @@ impl Command { .clone() .try_seal_with_senders() .map_err(|_| BlockValidationError::SenderRecoveryError)?, - None, )?; execution_outcome.write_to_storage(provider_rw.tx_ref(), None, OriginalValuesKnown::No)?; let storage_lists = provider_rw.changed_storages_with_range(block.number..=block.number)?; diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index e1d044e663e9..cfd787375fdc 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -144,7 +144,7 @@ impl Command { .map_err(|block| eyre::eyre!("Error sealing block with senders: {block:?}"))?; trace!(target: "reth::cli", block_number, "Executing block"); - provider_rw.insert_block(sealed_block.clone(), None)?; + provider_rw.insert_block(sealed_block.clone())?; td += sealed_block.difficulty; let mut executor = executor_provider.batch_executor( diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 429ef625f988..02a7205a44d2 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -85,7 +85,11 @@ impl Command { // Configure blockchain tree let tree_externals = TreeExternals::new(provider_factory.clone(), Arc::clone(&consensus), executor); - let tree = BlockchainTree::new(tree_externals, BlockchainTreeConfig::default(), None)?; + let tree = BlockchainTree::new( + tree_externals, + BlockchainTreeConfig::default(), + PruneModes::none(), + )?; let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree)); // Set up the blockchain provider @@ -144,7 +148,7 @@ impl Command { network_client, Pipeline::builder().build( provider_factory.clone(), - StaticFileProducer::new(provider_factory.clone(), PruneModes::default()), + StaticFileProducer::new(provider_factory.clone(), PruneModes::none()), ), blockchain_db.clone(), Box::new(ctx.task_executor.clone()), diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 66ed04478540..da0a147acceb 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -65,8 +65,6 @@ pub struct BlockchainTree { externals: TreeExternals, /// Tree configuration config: BlockchainTreeConfig, - /// Prune modes. - prune_modes: Option, /// Broadcast channel for canon state changes notifications. canon_state_notification_sender: CanonStateNotificationSender, /// Metrics for sync stages. @@ -115,9 +113,9 @@ where /// storage space efficiently. It's important to validate this configuration to ensure it does /// not lead to unintended data loss. pub fn new( - externals: TreeExternals, + mut externals: TreeExternals, config: BlockchainTreeConfig, - prune_modes: Option, + prune_modes: PruneModes, ) -> ProviderResult { let max_reorg_depth = config.max_reorg_depth() as usize; // The size of the broadcast is twice the maximum reorg depth, because at maximum reorg @@ -125,6 +123,9 @@ where let (canon_state_notification_sender, _receiver) = tokio::sync::broadcast::channel(max_reorg_depth * 2); + // Set the prune modes argument, on the provider + externals.provider_factory = externals.provider_factory.with_prune_modes(prune_modes); + let last_canonical_hashes = externals.fetch_latest_canonical_hashes(config.num_of_canonical_hashes() as usize)?; @@ -138,7 +139,6 @@ where config.max_unconnected_blocks(), ), config, - prune_modes, canon_state_notification_sender, sync_metrics_tx: None, metrics: Default::default(), @@ -1258,7 +1258,6 @@ where state, hashed_state, trie_updates, - self.prune_modes.as_ref(), ) .map_err(|e| CanonicalError::CanonicalCommit(e.to_string()))?; @@ -1424,7 +1423,6 @@ mod tests { provider .insert_historical_block( genesis.try_seal_with_senders().expect("invalid tx signature in genesis"), - None, ) .unwrap(); @@ -1545,7 +1543,6 @@ mod tests { SealedBlock::new(chain_spec.sealed_genesis_header(), Default::default()) .try_seal_with_senders() .unwrap(), - None, ) .unwrap(); let account = Account { balance: initial_signer_balance, ..Default::default() }; @@ -1647,7 +1644,7 @@ mod tests { let mut tree = BlockchainTree::new( TreeExternals::new(provider_factory, consensus, executor_provider), BlockchainTreeConfig::default(), - None, + PruneModes::default(), ) .expect("failed to create tree"); @@ -1727,7 +1724,8 @@ mod tests { // make tree let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config, None).expect("failed to create tree"); + let mut tree = BlockchainTree::new(externals, config, PruneModes::default()) + .expect("failed to create tree"); // genesis block 10 is already canonical tree.make_canonical(B256::ZERO).unwrap(); @@ -1803,7 +1801,8 @@ mod tests { // make tree let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config, None).expect("failed to create tree"); + let mut tree = BlockchainTree::new(externals, config, PruneModes::default()) + .expect("failed to create tree"); // genesis block 10 is already canonical tree.make_canonical(B256::ZERO).unwrap(); @@ -1888,7 +1887,8 @@ mod tests { // make tree let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config, None).expect("failed to create tree"); + let mut tree = BlockchainTree::new(externals, config, PruneModes::default()) + .expect("failed to create tree"); // genesis block 10 is already canonical tree.make_canonical(B256::ZERO).unwrap(); @@ -1986,7 +1986,8 @@ mod tests { // make tree let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config, None).expect("failed to create tree"); + let mut tree = BlockchainTree::new(externals, config, PruneModes::default()) + .expect("failed to create tree"); let mut canon_notif = tree.subscribe_canon_state(); // genesis block 10 is already canonical @@ -2379,7 +2380,8 @@ mod tests { // make tree let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config, None).expect("failed to create tree"); + let mut tree = BlockchainTree::new(externals, config, PruneModes::default()) + .expect("failed to create tree"); assert_eq!( tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(), @@ -2399,8 +2401,8 @@ mod tests { tree.make_canonical(block2.hash()).unwrap(); // restart - let mut tree = - BlockchainTree::new(cloned_externals_1, config, None).expect("failed to create tree"); + let mut tree = BlockchainTree::new(cloned_externals_1, config, PruneModes::default()) + .expect("failed to create tree"); assert_eq!(tree.block_indices().last_finalized_block(), 0); let mut block1a = block1; @@ -2416,8 +2418,8 @@ mod tests { tree.finalize_block(block1a.number).unwrap(); // restart - let tree = - BlockchainTree::new(cloned_externals_2, config, None).expect("failed to create tree"); + let tree = BlockchainTree::new(cloned_externals_2, config, PruneModes::default()) + .expect("failed to create tree"); assert_eq!(tree.block_indices().last_finalized_block(), block1a.number); } diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index 41758a9f09da..b382f7312cac 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -109,7 +109,10 @@ impl EnvironmentArgs { static_file_provider: StaticFileProvider, ) -> eyre::Result>> { let has_receipt_pruning = config.prune.as_ref().map_or(false, |a| a.has_receipts_pruning()); - let factory = ProviderFactory::new(db, self.chain.clone(), static_file_provider); + let prune_modes = + config.prune.as_ref().map(|prune| prune.segments.clone()).unwrap_or_default(); + let factory = ProviderFactory::new(db, self.chain.clone(), static_file_provider) + .with_prune_modes(prune_modes.clone()); info!(target: "reth::cli", "Verifying storage consistency."); @@ -123,8 +126,6 @@ impl EnvironmentArgs { return Ok(factory) } - let prune_modes = config.prune.clone().map(|prune| prune.segments).unwrap_or_default(); - // Highly unlikely to happen, and given its destructive nature, it's better to panic // instead. assert_ne!(unwind_target, PipelineTarget::Unwind(0), "A static file <> database inconsistency was found that would trigger an unwind to block 0"); diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index eba82c295d80..b0a0284b6de6 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -2159,7 +2159,6 @@ mod tests { provider .insert_block( b.clone().try_seal_with_senders().expect("invalid tx signature in block"), - None, ) .map(drop) }) diff --git a/crates/consensus/beacon/src/engine/test_utils.rs b/crates/consensus/beacon/src/engine/test_utils.rs index f58063d3ef58..7b5ee65ee941 100644 --- a/crates/consensus/beacon/src/engine/test_utils.rs +++ b/crates/consensus/beacon/src/engine/test_utils.rs @@ -393,7 +393,8 @@ where let externals = TreeExternals::new(provider_factory.clone(), consensus, executor_factory); let config = BlockchainTreeConfig::new(1, 2, 3, 2); let tree = Arc::new(ShareableBlockchainTree::new( - BlockchainTree::new(externals, config, None).expect("failed to create tree"), + BlockchainTree::new(externals, config, PruneModes::default()) + .expect("failed to create tree"), )); let latest = self.base_config.chain_spec.genesis_header().seal_slow(); let blockchain_provider = diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index 989ed5f76bf5..96268ae64ae3 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -61,16 +61,9 @@ impl Persistence { // * indices (already done basically) // Insert the blocks for block in blocks { - // TODO: prune modes - a bit unsure that it should be at this level of abstraction and - // not another - // - // ie, an external consumer of providers (or the database task) really does not care - // about pruning, just the node. Maybe we are the biggest user, and use it enough that - // we need a helper, but I'd rather make the pruning behavior more explicit then - let prune_modes = None; let sealed_block = block.block().clone().try_with_senders_unchecked(block.senders().clone()).unwrap(); - provider_rw.insert_block(sealed_block, prune_modes)?; + provider_rw.insert_block(sealed_block)?; // Write state and changesets to the database. // Must be written after blocks because of the receipt lookup. diff --git a/crates/exex/exex/src/backfill.rs b/crates/exex/exex/src/backfill.rs index 212082081b22..ffd87e52d44f 100644 --- a/crates/exex/exex/src/backfill.rs +++ b/crates/exex/exex/src/backfill.rs @@ -323,7 +323,6 @@ mod tests { outcome_batch, Default::default(), Default::default(), - None, )?; provider_rw.commit()?; diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 83e48927df57..804c194f4906 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -320,9 +320,9 @@ impl LaunchContextWith> { self.toml_config().prune.clone().or_else(|| self.node_config().prune_config()) } - /// Returns the configured [`PruneModes`] - pub fn prune_modes(&self) -> Option { - self.prune_config().map(|config| config.segments) + /// Returns the configured [`PruneModes`], returning the default if no config was available. + pub fn prune_modes(&self) -> PruneModes { + self.prune_config().map(|config| config.segments).unwrap_or_default() } /// Returns an initialized [`PrunerBuilder`] based on the configured [`PruneConfig`] @@ -364,6 +364,7 @@ where self.chain_spec(), StaticFileProvider::read_write(self.data_dir().static_files())?, ) + .with_prune_modes(self.prune_modes()) .with_static_files_metrics(); let has_receipt_pruning = @@ -395,14 +396,11 @@ where NoopBodiesDownloader::default(), NoopBlockExecutorProvider::default(), self.toml_config().stages.clone(), - self.prune_modes().unwrap_or_default(), + self.prune_modes(), )) .build( factory.clone(), - StaticFileProducer::new( - factory.clone(), - self.prune_modes().unwrap_or_default(), - ), + StaticFileProducer::new(factory.clone(), self.prune_modes()), ); // Unwinds to block @@ -705,10 +703,7 @@ where /// Creates a new [`StaticFileProducer`] with the attached database. pub fn static_file_producer(&self) -> StaticFileProducer { - StaticFileProducer::new( - self.provider_factory().clone(), - self.prune_modes().unwrap_or_default(), - ) + StaticFileProducer::new(self.provider_factory().clone(), self.prune_modes()) } /// Returns the current head block. diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 4294132c7554..6ba6346e27d3 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -700,12 +700,9 @@ mod tests { .try_seal_with_senders() .map_err(|_| BlockValidationError::SenderRecoveryError) .unwrap(), - None, ) .unwrap(); - provider - .insert_historical_block(block.clone().try_seal_with_senders().unwrap(), None) - .unwrap(); + provider.insert_historical_block(block.clone().try_seal_with_senders().unwrap()).unwrap(); provider .static_file_provider() .latest_writer(StaticFileSegment::Headers) @@ -745,10 +742,8 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - provider.insert_historical_block(genesis.try_seal_with_senders().unwrap(), None).unwrap(); - provider - .insert_historical_block(block.clone().try_seal_with_senders().unwrap(), None) - .unwrap(); + provider.insert_historical_block(genesis.try_seal_with_senders().unwrap()).unwrap(); + provider.insert_historical_block(block.clone().try_seal_with_senders().unwrap()).unwrap(); provider .static_file_provider() .latest_writer(StaticFileSegment::Headers) @@ -788,10 +783,8 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - provider.insert_historical_block(genesis.try_seal_with_senders().unwrap(), None).unwrap(); - provider - .insert_historical_block(block.clone().try_seal_with_senders().unwrap(), None) - .unwrap(); + provider.insert_historical_block(genesis.try_seal_with_senders().unwrap()).unwrap(); + provider.insert_historical_block(block.clone().try_seal_with_senders().unwrap()).unwrap(); provider .static_file_provider() .latest_writer(StaticFileSegment::Headers) @@ -825,10 +818,8 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - provider.insert_historical_block(genesis.try_seal_with_senders().unwrap(), None).unwrap(); - provider - .insert_historical_block(block.clone().try_seal_with_senders().unwrap(), None) - .unwrap(); + provider.insert_historical_block(genesis.try_seal_with_senders().unwrap()).unwrap(); + provider.insert_historical_block(block.clone().try_seal_with_senders().unwrap()).unwrap(); provider .static_file_provider() .latest_writer(StaticFileSegment::Headers) @@ -976,10 +967,8 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - provider.insert_historical_block(genesis.try_seal_with_senders().unwrap(), None).unwrap(); - provider - .insert_historical_block(block.clone().try_seal_with_senders().unwrap(), None) - .unwrap(); + provider.insert_historical_block(genesis.try_seal_with_senders().unwrap()).unwrap(); + provider.insert_historical_block(block.clone().try_seal_with_senders().unwrap()).unwrap(); provider .static_file_provider() .latest_writer(StaticFileSegment::Headers) @@ -1095,10 +1084,8 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - provider.insert_historical_block(genesis.try_seal_with_senders().unwrap(), None).unwrap(); - provider - .insert_historical_block(block.clone().try_seal_with_senders().unwrap(), None) - .unwrap(); + provider.insert_historical_block(genesis.try_seal_with_senders().unwrap()).unwrap(); + provider.insert_historical_block(block.clone().try_seal_with_senders().unwrap()).unwrap(); provider .static_file_provider() .latest_writer(StaticFileSegment::Headers) diff --git a/crates/stages/stages/src/stages/hashing_account.rs b/crates/stages/stages/src/stages/hashing_account.rs index fe8bc8547a58..6f59be9f4514 100644 --- a/crates/stages/stages/src/stages/hashing_account.rs +++ b/crates/stages/stages/src/stages/hashing_account.rs @@ -75,7 +75,7 @@ impl AccountHashingStage { let blocks = random_block_range(&mut rng, opts.blocks.clone(), B256::ZERO, opts.txs); for block in blocks { - provider.insert_historical_block(block.try_seal_with_senders().unwrap(), None).unwrap(); + provider.insert_historical_block(block.try_seal_with_senders().unwrap()).unwrap(); } provider .static_file_provider() diff --git a/crates/stages/stages/src/stages/headers.rs b/crates/stages/stages/src/stages/headers.rs index 3d05ea52d960..71c57cae4093 100644 --- a/crates/stages/stages/src/stages/headers.rs +++ b/crates/stages/stages/src/stages/headers.rs @@ -631,7 +631,6 @@ mod tests { ExecutionOutcome::default(), HashedPostState::default(), TrieUpdates::default(), - None, ) .unwrap(); provider.commit().unwrap(); diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index 9c96f4b30a5b..4b65523bad68 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -84,11 +84,9 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); + provider_rw.insert_historical_block(genesis.try_seal_with_senders().unwrap()).unwrap(); provider_rw - .insert_historical_block(genesis.try_seal_with_senders().unwrap(), None) - .unwrap(); - provider_rw - .insert_historical_block(block.clone().try_seal_with_senders().unwrap(), None) + .insert_historical_block(block.clone().try_seal_with_senders().unwrap()) .unwrap(); // Fill with bogus blocks to respect PruneMode distance. @@ -97,9 +95,7 @@ mod tests { for block_number in 2..=tip { let nblock = random_block(&mut rng, block_number, Some(head), Some(0), Some(0)); head = nblock.hash(); - provider_rw - .insert_historical_block(nblock.try_seal_with_senders().unwrap(), None) - .unwrap(); + provider_rw.insert_historical_block(nblock.try_seal_with_senders().unwrap()).unwrap(); } provider_rw .static_file_provider() diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 9c65103a4226..c8d97a6e987d 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -18,7 +18,7 @@ use reth_primitives::{ TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, }; -use reth_prune_types::{PruneCheckpoint, PruneSegment}; +use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_errors::provider::ProviderResult; use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg}; @@ -46,6 +46,8 @@ pub struct ProviderFactory { chain_spec: Arc, /// Static File Provider static_file_provider: StaticFileProvider, + /// Optional pruning configuration + prune_modes: PruneModes, } impl ProviderFactory { @@ -55,7 +57,7 @@ impl ProviderFactory { chain_spec: Arc, static_file_provider: StaticFileProvider, ) -> Self { - Self { db: Arc::new(db), chain_spec, static_file_provider } + Self { db: Arc::new(db), chain_spec, static_file_provider, prune_modes: PruneModes::none() } } /// Enables metrics on the static file provider. @@ -64,6 +66,12 @@ impl ProviderFactory { self } + /// Sets the pruning configuration for an existing [`ProviderFactory`]. + pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { + self.prune_modes = prune_modes; + self + } + /// Returns reference to the underlying database. pub fn db_ref(&self) -> &DB { &self.db @@ -89,6 +97,7 @@ impl ProviderFactory { db: Arc::new(init_db(path, args).map_err(RethError::msg)?), chain_spec, static_file_provider, + prune_modes: PruneModes::none(), }) } } @@ -97,12 +106,16 @@ impl ProviderFactory { /// Returns a provider with a created `DbTx` inside, which allows fetching data from the /// database using different types of providers. Example: [`HeaderProvider`] /// [`BlockHashReader`]. This may fail if the inner read database transaction fails to open. + /// + /// This sets the [`PruneModes`] to [`None`], because they should only be relevant for writing + /// data. #[track_caller] pub fn provider(&self) -> ProviderResult> { Ok(DatabaseProvider::new( self.db.tx()?, self.chain_spec.clone(), self.static_file_provider.clone(), + self.prune_modes.clone(), )) } @@ -116,6 +129,7 @@ impl ProviderFactory { self.db.tx_mut()?, self.chain_spec.clone(), self.static_file_provider.clone(), + self.prune_modes.clone(), ))) } @@ -576,6 +590,7 @@ impl Clone for ProviderFactory { db: Arc::clone(&self.db), chain_spec: self.chain_spec.clone(), static_file_provider: self.static_file_provider.clone(), + prune_modes: self.prune_modes.clone(), } } } @@ -661,7 +676,7 @@ mod tests { { let provider = factory.provider_rw().unwrap(); assert_matches!( - provider.insert_block(block.clone().try_seal_with_senders().unwrap(), None), + provider.insert_block(block.clone().try_seal_with_senders().unwrap()), Ok(_) ); assert_matches!( @@ -672,16 +687,14 @@ mod tests { } { - let provider = factory.provider_rw().unwrap(); + let prune_modes = PruneModes { + sender_recovery: Some(PruneMode::Full), + transaction_lookup: Some(PruneMode::Full), + ..PruneModes::none() + }; + let provider = factory.with_prune_modes(prune_modes).provider_rw().unwrap(); assert_matches!( - provider.insert_block( - block.clone().try_seal_with_senders().unwrap(), - Some(&PruneModes { - sender_recovery: Some(PruneMode::Full), - transaction_lookup: Some(PruneMode::Full), - ..PruneModes::none() - }) - ), + provider.insert_block(block.clone().try_seal_with_senders().unwrap(),), Ok(_) ); assert_matches!(provider.transaction_sender(0), Ok(None)); @@ -701,7 +714,7 @@ mod tests { let provider = factory.provider_rw().unwrap(); assert_matches!( - provider.insert_block(block.clone().try_seal_with_senders().unwrap(), None), + provider.insert_block(block.clone().try_seal_with_senders().unwrap()), Ok(_) ); diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 32b666c50182..61c295b549af 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -104,6 +104,8 @@ pub struct DatabaseProvider { chain_spec: Arc, /// Static File provider static_file_provider: StaticFileProvider, + /// Pruning configuration + prune_modes: PruneModes, } impl DatabaseProvider { @@ -119,8 +121,9 @@ impl DatabaseProvider { tx: TX, chain_spec: Arc, static_file_provider: StaticFileProvider, + prune_modes: PruneModes, ) -> Self { - Self { tx, chain_spec, static_file_provider } + Self { tx, chain_spec, static_file_provider, prune_modes } } } @@ -174,7 +177,6 @@ impl DatabaseProvider { pub fn insert_historical_block( &self, block: SealedBlockWithSenders, - prune_modes: Option<&PruneModes>, ) -> ProviderResult { let ttd = if block.number == 0 { block.difficulty @@ -198,7 +200,7 @@ impl DatabaseProvider { writer.append_header(block.header.as_ref().clone(), ttd, block.hash())?; - self.insert_block(block, prune_modes) + self.insert_block(block) } } @@ -256,8 +258,9 @@ impl DatabaseProvider { tx: TX, chain_spec: Arc, static_file_provider: StaticFileProvider, + prune_modes: PruneModes, ) -> Self { - Self { tx, chain_spec, static_file_provider } + Self { tx, chain_spec, static_file_provider, prune_modes } } /// Consume `DbTx` or `DbTxMut`. @@ -2621,7 +2624,6 @@ impl BlockWriter for DatabaseProvider { fn insert_block( &self, block: SealedBlockWithSenders, - prune_modes: Option<&PruneModes>, ) -> ProviderResult { let block_number = block.number; @@ -2678,8 +2680,10 @@ impl BlockWriter for DatabaseProvider { for (transaction, sender) in block.block.body.into_iter().zip(block.senders.iter()) { let hash = transaction.hash(); - if prune_modes - .and_then(|modes| modes.sender_recovery) + if self + .prune_modes + .sender_recovery + .as_ref() .filter(|prune_mode| prune_mode.is_full()) .is_none() { @@ -2703,8 +2707,9 @@ impl BlockWriter for DatabaseProvider { } transactions_elapsed += elapsed; - if prune_modes - .and_then(|modes| modes.transaction_lookup) + if self + .prune_modes + .transaction_lookup .filter(|prune_mode| prune_mode.is_full()) .is_none() { @@ -2765,7 +2770,6 @@ impl BlockWriter for DatabaseProvider { execution_outcome: ExecutionOutcome, hashed_state: HashedPostState, trie_updates: TrieUpdates, - prune_modes: Option<&PruneModes>, ) -> ProviderResult<()> { if blocks.is_empty() { debug!(target: "providers::db", "Attempted to append empty block range"); @@ -2781,7 +2785,7 @@ impl BlockWriter for DatabaseProvider { // Insert the blocks for block in blocks { - self.insert_block(block, prune_modes)?; + self.insert_block(block)?; durations_recorder.record_relative(metrics::Action::InsertBlock); } diff --git a/crates/storage/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index 7211cb691fb6..3d0cf3c0cb4d 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -1,7 +1,6 @@ use reth_db_api::models::StoredBlockBodyIndices; use reth_execution_types::{Chain, ExecutionOutcome}; use reth_primitives::{BlockNumber, SealedBlockWithSenders}; -use reth_prune_types::PruneModes; use reth_storage_api::BlockReader; use reth_storage_errors::provider::ProviderResult; use reth_trie::{updates::TrieUpdates, HashedPostState}; @@ -41,11 +40,8 @@ pub trait BlockWriter: Send + Sync { /// /// Return [StoredBlockBodyIndices] that contains indices of the first and last transactions and /// transition in the block. - fn insert_block( - &self, - block: SealedBlockWithSenders, - prune_modes: Option<&PruneModes>, - ) -> ProviderResult; + fn insert_block(&self, block: SealedBlockWithSenders) + -> ProviderResult; /// Appends a batch of sealed blocks to the blockchain, including sender information, and /// updates the post-state. @@ -57,7 +53,6 @@ pub trait BlockWriter: Send + Sync { /// /// - `blocks`: Vector of `SealedBlockWithSenders` instances to append. /// - `state`: Post-state information to update after appending. - /// - `prune_modes`: Optional pruning configuration. /// /// # Returns /// @@ -68,6 +63,5 @@ pub trait BlockWriter: Send + Sync { execution_outcome: ExecutionOutcome, hashed_state: HashedPostState, trie_updates: TrieUpdates, - prune_modes: Option<&PruneModes>, ) -> ProviderResult<()>; } diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 87c3b8df1d8f..7ecd7cb33d83 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -102,7 +102,6 @@ impl Case for BlockchainTestCase { ) .try_seal_with_senders() .unwrap(), - None, )?; case.pre.write_to_db(provider.tx_ref())?; @@ -121,7 +120,6 @@ impl Case for BlockchainTestCase { let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?; provider.insert_historical_block( decoded.clone().try_seal_with_senders().unwrap(), - None, )?; Ok::, Error>(Some(decoded)) })?; From 5827c25996ad41e7a1ee5941b6df8196874d5d8b Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 3 Jul 2024 06:30:33 -0400 Subject: [PATCH 299/405] chore: fix pruner exex height docs, add run docs (#9250) --- crates/prune/prune/src/pruner.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/prune/prune/src/pruner.rs b/crates/prune/prune/src/pruner.rs index 656aa69adeb2..68a1e6751e7b 100644 --- a/crates/prune/prune/src/pruner.rs +++ b/crates/prune/prune/src/pruner.rs @@ -85,7 +85,11 @@ impl Pruner { self.event_sender.new_listener() } - /// Run the pruner + /// Run the pruner. This will only prune data up to the highest finished `ExEx` height, if there + /// are no `ExEx`s, . + /// + /// Returns a [`PruneProgress`], indicating whether pruning is finished, or there is more data + /// to prune. pub fn run(&mut self, tip_block_number: BlockNumber) -> PrunerResult { let Some(tip_block_number) = self.adjust_tip_block_number_to_finished_exex_height(tip_block_number) @@ -306,8 +310,8 @@ impl Pruner { /// Adjusts the tip block number to the finished `ExEx` height. This is needed to not prune more /// data than `ExExs` have processed. Depending on the height: - /// - [`FinishedExExHeight::NoExExs`] returns the tip block number as is as no adjustment for - /// `ExExs` is needed. + /// - [`FinishedExExHeight::NoExExs`] returns the tip block number as no adjustment for `ExExs` + /// is needed. /// - [`FinishedExExHeight::NotReady`] returns `None` as not all `ExExs` have emitted a /// `FinishedHeight` event yet. /// - [`FinishedExExHeight::Height`] returns the finished `ExEx` height. From 71041b06a88b2a9352fd3554cf70a54cc5c53991 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Wed, 3 Jul 2024 13:03:57 +0200 Subject: [PATCH 300/405] feat: add ChainspecParser trait (#9259) Co-authored-by: Matthias Seitz --- Cargo.lock | 2 ++ crates/cli/cli/Cargo.toml | 2 ++ crates/cli/cli/src/chainspec.rs | 25 +++++++++++++++++++++++++ crates/cli/cli/src/lib.rs | 2 ++ 4 files changed, 31 insertions(+) create mode 100644 crates/cli/cli/src/chainspec.rs diff --git a/Cargo.lock b/Cargo.lock index 54f5d3f694f7..eff2b371b018 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6569,6 +6569,8 @@ name = "reth-cli" version = "1.0.0" dependencies = [ "clap", + "eyre", + "reth-chainspec", "reth-cli-runner", ] diff --git a/crates/cli/cli/Cargo.toml b/crates/cli/cli/Cargo.toml index c2aa22e70e71..83ea9da6f9bb 100644 --- a/crates/cli/cli/Cargo.toml +++ b/crates/cli/cli/Cargo.toml @@ -13,6 +13,8 @@ repository.workspace = true [dependencies] # reth reth-cli-runner.workspace = true +reth-chainspec.workspace = true +eyre.workspace = true # misc clap.workspace = true diff --git a/crates/cli/cli/src/chainspec.rs b/crates/cli/cli/src/chainspec.rs new file mode 100644 index 000000000000..4c1b4372fd0b --- /dev/null +++ b/crates/cli/cli/src/chainspec.rs @@ -0,0 +1,25 @@ +use clap::builder::TypedValueParser; +use reth_chainspec::ChainSpec; +use std::sync::Arc; + +/// Trait for parsing chain specifications. +/// +/// This trait extends [`clap::builder::TypedValueParser`] to provide a parser for chain +/// specifications. Implementers of this trait must provide a list of supported chains and a +/// function to parse a given string into a [`ChainSpec`]. +pub trait ChainSpecParser: TypedValueParser> + Default { + /// List of supported chains. + const SUPPORTED_CHAINS: &'static [&'static str]; + + /// Parses the given string into a [`ChainSpec`]. + /// + /// # Arguments + /// + /// * `s` - A string slice that holds the chain spec to be parsed. + /// + /// # Errors + /// + /// This function will return an error if the input string cannot be parsed into a valid + /// [`ChainSpec`]. + fn parse(&self, s: &str) -> eyre::Result>; +} diff --git a/crates/cli/cli/src/lib.rs b/crates/cli/cli/src/lib.rs index ccaa900edbd7..9e078e82f221 100644 --- a/crates/cli/cli/src/lib.rs +++ b/crates/cli/cli/src/lib.rs @@ -14,6 +14,8 @@ use reth_cli_runner::CliRunner; use clap::{Error, Parser}; +pub mod chainspec; + /// Reth based node cli. /// /// This trait is supposed to be implemented by the main struct of the CLI. From 8e5204c119f37d9b44d58c931c1e2bab13c735b4 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 3 Jul 2024 12:05:02 +0100 Subject: [PATCH 301/405] refactor(evm): set prune modes optionally for the batch executor (#9176) --- bin/reth/src/commands/debug_cmd/merkle.rs | 10 ++-- crates/ethereum/evm/src/execute.rs | 34 ++++++------ crates/evm/src/either.rs | 13 +++-- crates/evm/src/execute.rs | 16 ++++-- crates/evm/src/noop.rs | 4 +- crates/evm/src/test_utils.rs | 4 +- crates/exex/exex/src/backfill.rs | 54 ++++++++++---------- crates/optimism/evm/src/execute.rs | 14 ++--- crates/stages/stages/src/stages/execution.rs | 3 +- 9 files changed, 83 insertions(+), 69 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index cfd787375fdc..05befe374175 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -22,7 +22,6 @@ use reth_provider::{ BlockNumReader, BlockWriter, ChainSpecProvider, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, ProviderFactory, StateWriter, }; -use reth_prune::PruneModes; use reth_revm::database::StateProviderDatabase; use reth_stages::{ stages::{AccountHashingStage, MerkleStage, StorageHashingStage}, @@ -147,13 +146,12 @@ impl Command { provider_rw.insert_block(sealed_block.clone())?; td += sealed_block.difficulty; - let mut executor = executor_provider.batch_executor( - StateProviderDatabase::new(LatestStateProviderRef::new( + let mut executor = executor_provider.batch_executor(StateProviderDatabase::new( + LatestStateProviderRef::new( provider_rw.tx_ref(), provider_rw.static_file_provider().clone(), - )), - PruneModes::none(), - ); + ), + )); executor.execute_and_verify_one((&sealed_block.clone().unseal(), td).into())?; executor.finalize().write_to_storage( provider_rw.tx_ref(), diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 76ec292396df..084bb412b6b9 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -93,14 +93,14 @@ where self.eth_executor(db) } - fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor + fn batch_executor(&self, db: DB) -> Self::BatchExecutor where DB: Database + Display>, { let executor = self.eth_executor(db); EthBatchExecutor { executor, - batch_record: BlockBatchRecord::new(prune_modes), + batch_record: BlockBatchRecord::default(), stats: BlockExecutorStats::default(), } } @@ -451,6 +451,10 @@ where self.batch_record.set_tip(tip); } + fn set_prune_modes(&mut self, prune_modes: PruneModes) { + self.batch_record.set_prune_modes(prune_modes); + } + fn size_hint(&self) -> Option { Some(self.executor.state.bundle_state.size_hint()) } @@ -634,7 +638,7 @@ mod tests { // attempt to execute an empty block with parent beacon block root, this should not fail provider - .batch_executor(StateProviderDatabase::new(&db), PruneModes::none()) + .batch_executor(StateProviderDatabase::new(&db)) .execute_and_verify_one( ( &BlockWithSenders { @@ -684,8 +688,7 @@ mod tests { ..Header::default() }; - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); // attempt to execute an empty block with parent beacon block root, this should not fail executor @@ -728,8 +731,7 @@ mod tests { let mut header = chain_spec.genesis_header(); let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); // attempt to execute the genesis block with non-zero parent beacon block root, expect err header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); @@ -816,8 +818,7 @@ mod tests { let provider = executor_provider(chain_spec); // execute header - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); // Now execute a block with the fixed header, ensure that it does not fail executor @@ -884,8 +885,7 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); // construct the header for block one let header = Header { timestamp: 1, number: 1, ..Header::default() }; @@ -938,8 +938,7 @@ mod tests { let header = chain_spec.genesis_header(); let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); // attempt to execute genesis block, this should not fail executor @@ -996,8 +995,7 @@ mod tests { ..Header::default() }; let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); // attempt to execute the fork activation block, this should not fail executor @@ -1052,8 +1050,7 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); let header = Header { parent_hash: B256::random(), @@ -1115,8 +1112,7 @@ mod tests { let header_hash = header.hash_slow(); let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); // attempt to execute the genesis block, this should not fail executor diff --git a/crates/evm/src/either.rs b/crates/evm/src/either.rs index 2f55f1668923..f6af36d2eb63 100644 --- a/crates/evm/src/either.rs +++ b/crates/evm/src/either.rs @@ -36,13 +36,13 @@ where } } - fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor + fn batch_executor(&self, db: DB) -> Self::BatchExecutor where DB: Database + Display>, { match self { - Self::Left(a) => Either::Left(a.batch_executor(db, prune_modes)), - Self::Right(b) => Either::Right(b.batch_executor(db, prune_modes)), + Self::Left(a) => Either::Left(a.batch_executor(db)), + Self::Right(b) => Either::Right(b.batch_executor(db)), } } } @@ -116,6 +116,13 @@ where } } + fn set_prune_modes(&mut self, prune_modes: PruneModes) { + match self { + Self::Left(a) => a.set_prune_modes(prune_modes), + Self::Right(b) => b.set_prune_modes(prune_modes), + } + } + fn size_hint(&self) -> Option { match self { Self::Left(a) => a.size_hint(), diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 6d076fd45303..586fed53d997 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -85,6 +85,11 @@ pub trait BatchExecutor { /// This can be used to optimize state pruning during execution. fn set_tip(&mut self, tip: BlockNumber); + /// Set the prune modes. + /// + /// They are used to determine which parts of the state should be kept during execution. + fn set_prune_modes(&mut self, prune_modes: PruneModes); + /// The size hint of the batch's tracked state size. /// /// This is used to optimize DB commits depending on the size of the state. @@ -169,10 +174,7 @@ pub trait BlockExecutorProvider: Send + Sync + Clone + Unpin + 'static { /// /// Batch executor is used to execute multiple blocks in sequence and keep track of the state /// during historical sync which involves executing multiple blocks in sequence. - /// - /// The pruning modes are used to determine which parts of the state should be kept during - /// execution. - fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor + fn batch_executor(&self, db: DB) -> Self::BatchExecutor where DB: Database + Display>; } @@ -198,7 +200,7 @@ mod tests { TestExecutor(PhantomData) } - fn batch_executor(&self, _db: DB, _prune_modes: PruneModes) -> Self::BatchExecutor + fn batch_executor(&self, _db: DB) -> Self::BatchExecutor where DB: Database + Display>, { @@ -235,6 +237,10 @@ mod tests { todo!() } + fn set_prune_modes(&mut self, _prune_modes: PruneModes) { + todo!() + } + fn size_hint(&self) -> Option { None } diff --git a/crates/evm/src/noop.rs b/crates/evm/src/noop.rs index d393f66d566d..80a2b76de834 100644 --- a/crates/evm/src/noop.rs +++ b/crates/evm/src/noop.rs @@ -32,7 +32,7 @@ impl BlockExecutorProvider for NoopBlockExecutorProvider { Self } - fn batch_executor(&self, _: DB, _: PruneModes) -> Self::BatchExecutor + fn batch_executor(&self, _: DB) -> Self::BatchExecutor where DB: Database + Display>, { @@ -65,6 +65,8 @@ impl BatchExecutor for NoopBlockExecutorProvider { fn set_tip(&mut self, _: BlockNumber) {} + fn set_prune_modes(&mut self, _: PruneModes) {} + fn size_hint(&self) -> Option { None } diff --git a/crates/evm/src/test_utils.rs b/crates/evm/src/test_utils.rs index a4d098f0b3aa..c3aa34a56a45 100644 --- a/crates/evm/src/test_utils.rs +++ b/crates/evm/src/test_utils.rs @@ -37,7 +37,7 @@ impl BlockExecutorProvider for MockExecutorProvider { self.clone() } - fn batch_executor(&self, _: DB, _: PruneModes) -> Self::BatchExecutor + fn batch_executor(&self, _: DB) -> Self::BatchExecutor where DB: Database + Display>, { @@ -77,6 +77,8 @@ impl BatchExecutor for MockExecutorProvider { fn set_tip(&mut self, _: BlockNumber) {} + fn set_prune_modes(&mut self, _: PruneModes) {} + fn size_hint(&self) -> Option { None } diff --git a/crates/exex/exex/src/backfill.rs b/crates/exex/exex/src/backfill.rs index ffd87e52d44f..0c7208a9c229 100644 --- a/crates/exex/exex/src/backfill.rs +++ b/crates/exex/exex/src/backfill.rs @@ -25,8 +25,19 @@ pub struct BackfillJobFactory { impl BackfillJobFactory { /// Creates a new [`BackfillJobFactory`]. - pub fn new(executor: E, provider: P, prune_modes: PruneModes) -> Self { - Self { executor, provider, prune_modes, thresholds: ExecutionStageThresholds::default() } + pub fn new(executor: E, provider: P) -> Self { + Self { + executor, + provider, + prune_modes: PruneModes::none(), + thresholds: ExecutionStageThresholds::default(), + } + } + + /// Sets the prune modes + pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { + self.prune_modes = prune_modes; + self } /// Sets the thresholds @@ -54,12 +65,10 @@ impl BackfillJobFactory<(), ()> { /// Creates a new [`BackfillJobFactory`] from [`FullNodeComponents`]. pub fn new_from_components( components: Node, - prune_modes: PruneModes, ) -> BackfillJobFactory { BackfillJobFactory::<_, _>::new( components.block_executor().clone(), components.provider().clone(), - prune_modes, ) } } @@ -73,8 +82,8 @@ pub struct BackfillJob { executor: E, provider: P, prune_modes: PruneModes, - range: RangeInclusive, thresholds: ExecutionStageThresholds, + range: RangeInclusive, _db: PhantomData, } @@ -102,12 +111,10 @@ where P: FullProvider, { fn execute_range(&mut self) -> Result { - let mut executor = self.executor.batch_executor( - StateProviderDatabase::new( - self.provider.history_by_block_number(self.range.start().saturating_sub(1))?, - ), - self.prune_modes.clone(), - ); + let mut executor = self.executor.batch_executor(StateProviderDatabase::new( + self.provider.history_by_block_number(self.range.start().saturating_sub(1))?, + )); + executor.set_prune_modes(self.prune_modes.clone()); let mut fetch_block_duration = Duration::default(); let mut execution_duration = Duration::default(); @@ -205,7 +212,6 @@ mod tests { providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, BlockWriter, LatestStateProviderRef, }; - use reth_prune_types::PruneModes; use reth_revm::database::StateProviderDatabase; use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; use secp256k1::Keypair; @@ -289,24 +295,18 @@ mod tests { let provider = provider_factory.provider()?; // Execute only the first block on top of genesis state let mut outcome_single = EthExecutorProvider::ethereum(chain_spec.clone()) - .batch_executor( - StateProviderDatabase::new(LatestStateProviderRef::new( - provider.tx_ref(), - provider.static_file_provider().clone(), - )), - PruneModes::none(), - ) + .batch_executor(StateProviderDatabase::new(LatestStateProviderRef::new( + provider.tx_ref(), + provider.static_file_provider().clone(), + ))) .execute_and_verify_batch([(&block1, U256::ZERO).into()])?; outcome_single.bundle.reverts.sort(); // Execute both blocks on top of the genesis state let outcome_batch = EthExecutorProvider::ethereum(chain_spec) - .batch_executor( - StateProviderDatabase::new(LatestStateProviderRef::new( - provider.tx_ref(), - provider.static_file_provider().clone(), - )), - PruneModes::none(), - ) + .batch_executor(StateProviderDatabase::new(LatestStateProviderRef::new( + provider.tx_ref(), + provider.static_file_provider().clone(), + ))) .execute_and_verify_batch([ (&block1, U256::ZERO).into(), (&block2, U256::ZERO).into(), @@ -327,7 +327,7 @@ mod tests { provider_rw.commit()?; // Backfill the first block - let factory = BackfillJobFactory::new(executor, blockchain_db, PruneModes::none()); + let factory = BackfillJobFactory::new(executor, blockchain_db); let job = factory.backfill(1..=1); let chains = job.collect::, _>>()?; diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 1f873d234a46..de4fc071e0cf 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -79,14 +79,14 @@ where self.op_executor(db) } - fn batch_executor(&self, db: DB, prune_modes: PruneModes) -> Self::BatchExecutor + fn batch_executor(&self, db: DB) -> Self::BatchExecutor where DB: Database + std::fmt::Display>, { let executor = self.op_executor(db); OpBatchExecutor { executor, - batch_record: BlockBatchRecord::new(prune_modes), + batch_record: BlockBatchRecord::default(), stats: BlockExecutorStats::default(), } } @@ -435,6 +435,10 @@ where self.batch_record.set_tip(tip); } + fn set_prune_modes(&mut self, prune_modes: PruneModes) { + self.batch_record.set_prune_modes(prune_modes); + } + fn size_hint(&self) -> Option { Some(self.executor.state.bundle_state.size_hint()) } @@ -528,8 +532,7 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); executor.state_mut().load_cache_account(L1_BLOCK_CONTRACT).unwrap(); @@ -610,8 +613,7 @@ mod tests { ); let provider = executor_provider(chain_spec); - let mut executor = - provider.batch_executor(StateProviderDatabase::new(&db), PruneModes::none()); + let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); executor.state_mut().load_cache_account(L1_BLOCK_CONTRACT).unwrap(); diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 6ba6346e27d3..556c5f7eb5e5 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -222,8 +222,9 @@ where provider.tx_ref(), provider.static_file_provider().clone(), )); - let mut executor = self.executor_provider.batch_executor(db, prune_modes); + let mut executor = self.executor_provider.batch_executor(db); executor.set_tip(max_block); + executor.set_prune_modes(prune_modes); // Progress tracking let mut stage_progress = start_block; From b5d61d80eb499b52274ff373e38f85fd9d5600d1 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 3 Jul 2024 07:18:42 -0400 Subject: [PATCH 302/405] feat: add pruner to persistence task (#9251) Co-authored-by: Matthias Seitz Co-authored-by: Federico Gimenez --- crates/engine/tree/src/persistence.rs | 47 +++++++++++++++++---------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index 96268ae64ae3..20382343933a 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -8,7 +8,8 @@ use reth_provider::{ bundle_state::HashedStateChanges, BlockWriter, HistoryWriter, OriginalValuesKnown, ProviderFactory, StageCheckpointWriter, StateWriter, }; -use std::sync::mpsc::{Receiver, Sender}; +use reth_prune::{PruneProgress, Pruner}; +use std::sync::mpsc::{Receiver, SendError, Sender}; use tokio::sync::oneshot; use tracing::debug; @@ -29,12 +30,18 @@ pub struct Persistence { provider: ProviderFactory, /// Incoming requests to persist stuff incoming: Receiver, + /// The pruner + pruner: Pruner, } impl Persistence { /// Create a new persistence task - const fn new(provider: ProviderFactory, incoming: Receiver) -> Self { - Self { provider, incoming } + const fn new( + provider: ProviderFactory, + incoming: Receiver, + pruner: Pruner, + ) -> Self { + Self { provider, incoming, pruner } } /// Writes the cloned tree state to the database @@ -101,8 +108,9 @@ impl Persistence { /// Prunes block data before the given block hash according to the configured prune /// configuration. - fn prune_before(&self, _block_hash: B256) { - todo!("implement this") + fn prune_before(&mut self, block_num: u64) -> PruneProgress { + // TODO: doing this properly depends on pruner segment changes + self.pruner.run(block_num).expect("todo: handle errors") } /// Removes static file related data from the database, depending on the current block height in @@ -117,9 +125,9 @@ where DB: Database + 'static, { /// Create a new persistence task, spawning it, and returning a [`PersistenceHandle`]. - fn spawn_new(provider: ProviderFactory) -> PersistenceHandle { + fn spawn_new(provider: ProviderFactory, pruner: Pruner) -> PersistenceHandle { let (tx, rx) = std::sync::mpsc::channel(); - let task = Self::new(provider, rx); + let task = Self::new(provider, rx, pruner); std::thread::Builder::new() .name("Persistence Task".to_string()) .spawn(|| task.run()) @@ -135,7 +143,7 @@ where { /// This is the main loop, that will listen to persistence events and perform the requested /// database actions - fn run(self) { + fn run(mut self) { // If the receiver errors then senders have disconnected, so the loop should then end. while let Ok(action) = self.incoming.recv() { match action { @@ -155,11 +163,11 @@ where // we ignore the error because the caller may or may not care about the result let _ = sender.send(last_block_hash); } - PersistenceAction::PruneBefore((block_hash, sender)) => { - self.prune_before(block_hash); + PersistenceAction::PruneBefore((block_num, sender)) => { + let res = self.prune_before(block_num); // we ignore the error because the caller may or may not care about the result - let _ = sender.send(()); + let _ = sender.send(res); } PersistenceAction::CleanStaticFileDuplicates(sender) => { self.clean_static_file_duplicates(); @@ -182,9 +190,9 @@ pub enum PersistenceAction { /// Removes the blocks above the given block number from the database. RemoveBlocksAbove((u64, oneshot::Sender>)), - /// Prune associated block data before the given hash, according to already-configured prune - /// modes. - PruneBefore((B256, oneshot::Sender<()>)), + /// Prune associated block data before the given block number, according to already-configured + /// prune modes. + PruneBefore((u64, oneshot::Sender)), /// Trigger a read of static file data, and delete data depending on the highest block in each /// static file segment. @@ -206,8 +214,11 @@ impl PersistenceHandle { /// Sends a specific [`PersistenceAction`] in the contained channel. The caller is responsible /// for creating any channels for the given action. - pub fn send_action(&self, action: PersistenceAction) { - self.sender.send(action).expect("should be able to send"); + pub fn send_action( + &self, + action: PersistenceAction, + ) -> Result<(), SendError> { + self.sender.send(action) } /// Tells the persistence task to save a certain list of finalized blocks. The blocks are @@ -235,10 +246,10 @@ impl PersistenceHandle { /// Tells the persistence task to remove block data before the given hash, according to the /// configured prune config. - pub async fn prune_before(&self, block_hash: B256) { + pub async fn prune_before(&self, block_num: u64) -> PruneProgress { let (tx, rx) = oneshot::channel(); self.sender - .send(PersistenceAction::PruneBefore((block_hash, tx))) + .send(PersistenceAction::PruneBefore((block_num, tx))) .expect("should be able to send"); rx.await.expect("todo: err handling") } From d41aac380bfebbbfd4208dff6f8bd0ceda00e9b1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 3 Jul 2024 15:33:43 +0200 Subject: [PATCH 303/405] feat: add non feature gated noop block reader (#9261) --- Cargo.lock | 1 + crates/net/network/Cargo.toml | 5 ++- crates/net/network/src/config.rs | 7 ++-- crates/net/network/src/eth_requests.rs | 2 +- crates/net/network/src/manager.rs | 6 +-- crates/net/network/src/state.rs | 2 +- crates/net/network/src/swarm.rs | 4 +- crates/net/network/src/test_utils/testnet.rs | 5 +-- crates/storage/storage-api/src/lib.rs | 2 + crates/storage/storage-api/src/noop.rs | 44 ++++++++++++++++++++ 10 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 crates/storage/storage-api/src/noop.rs diff --git a/Cargo.lock b/Cargo.lock index eff2b371b018..20aed5f8f155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7485,6 +7485,7 @@ dependencies = [ "reth-network-types", "reth-primitives", "reth-provider", + "reth-storage-api", "reth-tasks", "reth-tokio-util", "reth-tracing", diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index d61caa7bec96..100114624e71 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -25,7 +25,8 @@ reth-eth-wire.workspace = true reth-ecies.workspace = true reth-tasks.workspace = true reth-transaction-pool.workspace = true -reth-provider.workspace = true +reth-storage-api.workspace = true +reth-provider = { workspace = true, optional = true } reth-tokio-util.workspace = true reth-consensus.workspace = true reth-network-peers.workspace = true @@ -98,7 +99,7 @@ criterion = { workspace = true, features = ["async_tokio", "html_reports"] } default = ["serde"] geth-tests = [] serde = ["dep:serde", "dep:humantime-serde", "secp256k1/serde", "enr/serde", "dep:serde_json", "reth-network-types/serde"] -test-utils = ["reth-provider/test-utils", "dep:tempfile", "reth-transaction-pool/test-utils", "reth-network-types/test-utils"] +test-utils = ["dep:reth-provider", "reth-provider?/test-utils", "dep:tempfile", "reth-transaction-pool/test-utils", "reth-network-types/test-utils"] [[bench]] name = "bench" diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 3f7887a9b8db..6d4cd188fa3c 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -14,7 +14,7 @@ use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status}; use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer}; use reth_network_types::{PeersConfig, SessionsConfig}; use reth_primitives::{ForkFilter, Head}; -use reth_provider::{BlockReader, HeaderProvider}; +use reth_storage_api::{BlockReader, HeaderProvider}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use secp256k1::SECP256K1; use std::{collections::HashSet, net::SocketAddr, sync::Arc}; @@ -443,11 +443,10 @@ impl NetworkConfigBuilder { /// Convenience function for creating a [`NetworkConfig`] with a noop provider that does /// nothing. - #[cfg(any(test, feature = "test-utils"))] pub fn build_with_noop_provider( self, - ) -> NetworkConfig { - self.build(reth_provider::test_utils::NoopProvider::default()) + ) -> NetworkConfig { + self.build(Default::default()) } /// Consumes the type and creates the actual [`NetworkConfig`] diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 3b7e9500e077..7df124375e67 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -13,7 +13,7 @@ use reth_eth_wire::{ use reth_network_p2p::error::RequestResult; use reth_network_peers::PeerId; use reth_primitives::{BlockBody, BlockHashOrNumber, Header, HeadersDirection}; -use reth_provider::{BlockReader, HeaderProvider, ReceiptProvider}; +use reth_storage_api::{BlockReader, HeaderProvider, ReceiptProvider}; use std::{ future::Future, pin::Pin, diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 3434e9439468..2fea2cbc43dc 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -45,7 +45,7 @@ use reth_metrics::common::mpsc::UnboundedMeteredSender; use reth_network_api::{EthProtocolInfo, NetworkStatus, PeerInfo, ReputationChangeKind}; use reth_network_peers::{NodeRecord, PeerId}; use reth_primitives::ForkId; -use reth_provider::{BlockNumReader, BlockReader}; +use reth_storage_api::BlockNumReader; use reth_tasks::shutdown::GracefulShutdown; use reth_tokio_util::EventSender; use secp256k1::SecretKey; @@ -926,7 +926,7 @@ where impl NetworkManager where - C: BlockReader + Unpin, + C: BlockNumReader + Unpin, { /// Drives the [`NetworkManager`] future until a [`GracefulShutdown`] signal is received. /// @@ -955,7 +955,7 @@ where impl Future for NetworkManager where - C: BlockReader + Unpin, + C: BlockNumReader + Unpin, { type Output = (); diff --git a/crates/net/network/src/state.rs b/crates/net/network/src/state.rs index 7334e483b2b4..3dce15cfca61 100644 --- a/crates/net/network/src/state.rs +++ b/crates/net/network/src/state.rs @@ -20,7 +20,7 @@ use reth_eth_wire::{ use reth_network_api::PeerKind; use reth_network_peers::PeerId; use reth_primitives::{ForkId, B256}; -use reth_provider::BlockNumReader; +use reth_storage_api::BlockNumReader; use std::{ collections::{HashMap, VecDeque}, net::{IpAddr, SocketAddr}, diff --git a/crates/net/network/src/swarm.rs b/crates/net/network/src/swarm.rs index 057cd1143726..faf39f8390fe 100644 --- a/crates/net/network/src/swarm.rs +++ b/crates/net/network/src/swarm.rs @@ -13,7 +13,7 @@ use reth_eth_wire::{ EthVersion, Status, }; use reth_network_peers::PeerId; -use reth_provider::{BlockNumReader, BlockReader}; +use reth_storage_api::BlockNumReader; use std::{ io, net::SocketAddr, @@ -287,7 +287,7 @@ where impl Stream for Swarm where - C: BlockReader + Unpin, + C: BlockNumReader + Unpin, { type Item = SwarmEvent; diff --git a/crates/net/network/src/test_utils/testnet.rs b/crates/net/network/src/test_utils/testnet.rs index c7642accd0d8..5de45b229824 100644 --- a/crates/net/network/src/test_utils/testnet.rs +++ b/crates/net/network/src/test_utils/testnet.rs @@ -16,9 +16,8 @@ use reth_chainspec::MAINNET; use reth_eth_wire::{protocol::Protocol, DisconnectReason, HelloMessageWithProtocols}; use reth_network_api::{NetworkInfo, Peers}; use reth_network_peers::PeerId; -use reth_provider::{ - test_utils::NoopProvider, BlockReader, BlockReaderIdExt, HeaderProvider, StateProviderFactory, -}; +use reth_provider::test_utils::NoopProvider; +use reth_storage_api::{BlockReader, BlockReaderIdExt, HeaderProvider, StateProviderFactory}; use reth_tasks::TokioTaskExecutor; use reth_tokio_util::EventStream; use reth_transaction_pool::{ diff --git a/crates/storage/storage-api/src/lib.rs b/crates/storage/storage-api/src/lib.rs index c5663bc2a360..440c27d37dcc 100644 --- a/crates/storage/storage-api/src/lib.rs +++ b/crates/storage/storage-api/src/lib.rs @@ -51,3 +51,5 @@ pub use trie::*; mod withdrawals; pub use withdrawals::*; + +pub mod noop; diff --git a/crates/storage/storage-api/src/noop.rs b/crates/storage/storage-api/src/noop.rs new file mode 100644 index 000000000000..a55371f3c3d9 --- /dev/null +++ b/crates/storage/storage-api/src/noop.rs @@ -0,0 +1,44 @@ +//! Various noop implementations for traits. + +use crate::{BlockHashReader, BlockNumReader}; +use reth_chainspec::ChainInfo; +use reth_primitives::{BlockNumber, B256}; +use reth_storage_errors::provider::ProviderResult; + +/// Supports various api interfaces for testing purposes. +#[derive(Debug, Clone, Default, Copy)] +#[non_exhaustive] +pub struct NoopBlockReader; + +/// Noop implementation for testing purposes +impl BlockHashReader for NoopBlockReader { + fn block_hash(&self, _number: u64) -> ProviderResult> { + Ok(None) + } + + fn canonical_hashes_range( + &self, + _start: BlockNumber, + _end: BlockNumber, + ) -> ProviderResult> { + Ok(vec![]) + } +} + +impl BlockNumReader for NoopBlockReader { + fn chain_info(&self) -> ProviderResult { + Ok(ChainInfo::default()) + } + + fn best_block_number(&self) -> ProviderResult { + Ok(0) + } + + fn last_block_number(&self) -> ProviderResult { + Ok(0) + } + + fn block_number(&self, _hash: B256) -> ProviderResult> { + Ok(None) + } +} From c5167a4784817e97fef20882e2133ca73e82ee02 Mon Sep 17 00:00:00 2001 From: Querty <98064975+Quertyy@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:05:44 +0200 Subject: [PATCH 304/405] refactor: move write_peers_to_file to NetworkManager impl (#9134) Co-authored-by: Matthias Seitz --- Cargo.lock | 2 ++ crates/net/network/Cargo.toml | 1 + crates/net/network/src/manager.rs | 19 +++++++++++++++++ crates/node/builder/Cargo.toml | 3 +++ crates/node/builder/src/builder/mod.rs | 15 ++++++++++++-- crates/node/core/src/utils.rs | 28 +------------------------- 6 files changed, 39 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20aed5f8f155..ec85c912cf91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7476,6 +7476,7 @@ dependencies = [ "reth-dns-discovery", "reth-ecies", "reth-eth-wire", + "reth-fs-util", "reth-metrics", "reth-net-banlist", "reth-network", @@ -7650,6 +7651,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tracing", ] [[package]] diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 100114624e71..efadac80cf14 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] # reth reth-chainspec.workspace = true +reth-fs-util.workspace = true reth-primitives.workspace = true reth-net-banlist.workspace = true reth-network-api.workspace = true diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 2fea2cbc43dc..9d68bac3c0bb 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -41,6 +41,7 @@ use reth_eth_wire::{ capability::{Capabilities, CapabilityMessage}, DisconnectReason, EthVersion, Status, }; +use reth_fs_util::{self as fs, FsPathError}; use reth_metrics::common::mpsc::UnboundedMeteredSender; use reth_network_api::{EthProtocolInfo, NetworkStatus, PeerInfo, ReputationChangeKind}; use reth_network_peers::{NodeRecord, PeerId}; @@ -51,6 +52,7 @@ use reth_tokio_util::EventSender; use secp256k1::SecretKey; use std::{ net::SocketAddr, + path::Path, pin::Pin, sync::{ atomic::{AtomicU64, AtomicUsize, Ordering}, @@ -336,6 +338,11 @@ where self.swarm.state().peers().iter_peers() } + /// Returns the number of peers in the peer set. + pub fn num_known_peers(&self) -> usize { + self.swarm.state().peers().num_known_peers() + } + /// Returns a new [`PeersHandle`] that can be cloned and shared. /// /// The [`PeersHandle`] can be used to interact with the network's peer set. @@ -343,6 +350,18 @@ where self.swarm.state().peers().handle() } + /// Collect the peers from the [`NetworkManager`] and write them to the given + /// `persistent_peers_file`. + pub fn write_peers_to_file(&self, persistent_peers_file: &Path) -> Result<(), FsPathError> { + let known_peers = self.all_peers().collect::>(); + let known_peers = serde_json::to_string_pretty(&known_peers).map_err(|e| { + FsPathError::WriteJson { source: e, path: persistent_peers_file.to_path_buf() } + })?; + persistent_peers_file.parent().map(fs::create_dir_all).transpose()?; + fs::write(persistent_peers_file, known_peers)?; + Ok(()) + } + /// Returns a new [`FetchClient`] that can be cloned and shared. /// /// The [`FetchClient`] is the entrypoint for sending requests to the network. diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index f83145b052e9..351dbc3d8501 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -72,5 +72,8 @@ confy.workspace = true rayon.workspace = true backon.workspace = true +# tracing +tracing.workspace = true + [dev-dependencies] tempfile.workspace = true diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 72e56b71a3ea..dbdef2291460 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -30,7 +30,6 @@ use reth_node_core::{ dirs::{ChainPath, DataDirPath, MaybePlatformPath}, node_config::NodeConfig, primitives::Head, - utils::write_peers_to_file, }; use reth_primitives::revm_primitives::EnvKzgSettings; use reth_provider::{providers::BlockchainProvider, ChainSpecProvider}; @@ -39,6 +38,7 @@ use reth_transaction_pool::{PoolConfig, TransactionPool}; use secp256k1::SecretKey; pub use states::*; use std::sync::Arc; +use tracing::{info, trace, warn}; mod states; @@ -509,7 +509,18 @@ impl BuilderContext { "p2p network task", |shutdown| { network.run_until_graceful_shutdown(shutdown, |network| { - write_peers_to_file(&network, known_peers_file) + if let Some(peers_file) = known_peers_file { + let num_known_peers = network.num_known_peers(); + trace!(target: "reth::cli", peers_file=?peers_file, num_peers=%num_known_peers, "Saving current peers"); + match network.write_peers_to_file(peers_file.as_path()) { + Ok(_) => { + info!(target: "reth::cli", peers_file=?peers_file, "Wrote network peers to file"); + } + Err(err) => { + warn!(target: "reth::cli", %err, "Failed to write network peers to file"); + } + } + } }) }, ); diff --git a/crates/node/core/src/utils.rs b/crates/node/core/src/utils.rs index 8393b50bef0a..7bf3314f503a 100644 --- a/crates/node/core/src/utils.rs +++ b/crates/node/core/src/utils.rs @@ -4,22 +4,19 @@ use eyre::Result; use reth_chainspec::ChainSpec; use reth_consensus_common::validation::validate_block_pre_execution; -use reth_fs_util as fs; -use reth_network::NetworkManager; use reth_network_p2p::{ bodies::client::BodiesClient, headers::client::{HeadersClient, HeadersRequest}, priority::Priority, }; use reth_primitives::{BlockHashOrNumber, HeadersDirection, SealedBlock, SealedHeader}; -use reth_provider::BlockReader; use reth_rpc_types::engine::{JwtError, JwtSecret}; use std::{ env::VarError, path::{Path, PathBuf}, sync::Arc, }; -use tracing::{debug, info, trace, warn}; +use tracing::{debug, info}; /// Parses a user-specified path with support for environment variables and common shorthands (e.g. /// ~ for the user's home directory). @@ -38,29 +35,6 @@ pub fn get_or_create_jwt_secret_from_path(path: &Path) -> Result(network: &NetworkManager, persistent_peers_file: Option) -where - C: BlockReader + Unpin, -{ - if let Some(file_path) = persistent_peers_file { - let known_peers = network.all_peers().collect::>(); - if let Ok(known_peers) = serde_json::to_string_pretty(&known_peers) { - trace!(target: "reth::cli", peers_file =?file_path, num_peers=%known_peers.len(), "Saving current peers"); - let parent_dir = file_path.parent().map(fs::create_dir_all).transpose(); - match parent_dir.and_then(|_| fs::write(&file_path, known_peers)) { - Ok(_) => { - info!(target: "reth::cli", peers_file=?file_path, "Wrote network peers to file"); - } - Err(err) => { - warn!(target: "reth::cli", %err, peers_file=?file_path, "Failed to write network peers to file"); - } - } - } - } -} - /// Get a single header from network pub async fn get_single_header( client: Client, From aa13539a3a48c79c1bfcd92fffaab2579daf8431 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 3 Jul 2024 16:16:27 +0200 Subject: [PATCH 305/405] chore: simplify p2p subcommand (#9265) --- bin/reth/src/commands/{p2p/mod.rs => p2p.rs} | 18 +++++------------- crates/net/network/src/config.rs | 12 +++++++++++- 2 files changed, 16 insertions(+), 14 deletions(-) rename bin/reth/src/commands/{p2p/mod.rs => p2p.rs} (90%) diff --git a/bin/reth/src/commands/p2p/mod.rs b/bin/reth/src/commands/p2p.rs similarity index 90% rename from bin/reth/src/commands/p2p/mod.rs rename to bin/reth/src/commands/p2p.rs index 161eb6cc9133..73426441c4b6 100644 --- a/bin/reth/src/commands/p2p/mod.rs +++ b/bin/reth/src/commands/p2p.rs @@ -12,12 +12,10 @@ use backon::{ConstantBuilder, Retryable}; use clap::{Parser, Subcommand}; use reth_chainspec::ChainSpec; use reth_config::Config; -use reth_db::create_db; use reth_network::NetworkConfigBuilder; use reth_network_p2p::bodies::client::BodiesClient; use reth_node_core::args::DatadirArgs; use reth_primitives::BlockHashOrNumber; -use reth_provider::{providers::StaticFileProvider, ProviderFactory}; use std::{path::PathBuf, sync::Arc}; /// `reth p2p` command @@ -75,10 +73,6 @@ pub enum Subcommands { impl Command { /// Execute `p2p` command pub async fn execute(&self) -> eyre::Result<()> { - let tempdir = tempfile::TempDir::new()?; - let noop_db = Arc::new(create_db(tempdir.into_path(), self.db.database_args())?); - - // add network name to data dir let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain); let config_path = self.config.clone().unwrap_or_else(|| data_dir.config()); @@ -101,7 +95,7 @@ impl Command { let rlpx_socket = (self.network.addr, self.network.port).into(); let boot_nodes = self.chain.bootnodes().unwrap_or_default(); - let network = NetworkConfigBuilder::new(p2p_secret_key) + let net = NetworkConfigBuilder::new(p2p_secret_key) .peer_config(config.peers_config_with_basic_nodes_from_file(None)) .external_ip_resolver(self.network.nat) .chain_spec(self.chain.clone()) @@ -110,13 +104,11 @@ impl Command { .apply(|builder| { self.network.discovery.apply_to_builder(builder, rlpx_socket, boot_nodes) }) - .build(Arc::new(ProviderFactory::new( - noop_db, - self.chain.clone(), - StaticFileProvider::read_write(data_dir.static_files())?, - ))) - .start_network() + .build_with_noop_provider() + .manager() .await?; + let network = net.handle().clone(); + tokio::task::spawn(net); let fetch_client = network.fetch_client().await?; let retries = self.retries.max(1); diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 6d4cd188fa3c..b197fc55f8f1 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -14,7 +14,7 @@ use reth_eth_wire::{HelloMessage, HelloMessageWithProtocols, Status}; use reth_network_peers::{mainnet_nodes, pk2id, sepolia_nodes, PeerId, TrustedPeer}; use reth_network_types::{PeersConfig, SessionsConfig}; use reth_primitives::{ForkFilter, Head}; -use reth_storage_api::{BlockReader, HeaderProvider}; +use reth_storage_api::{BlockNumReader, BlockReader, HeaderProvider}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use secp256k1::SECP256K1; use std::{collections::HashSet, net::SocketAddr, sync::Arc}; @@ -119,6 +119,16 @@ impl NetworkConfig { } } +impl NetworkConfig +where + C: BlockNumReader, +{ + /// Convenience method for calling [`NetworkManager::new`]. + pub async fn manager(self) -> Result, NetworkError> { + NetworkManager::new(self).await + } +} + impl NetworkConfig where C: BlockReader + HeaderProvider + Clone + Unpin + 'static, From 1998f44b1bd8173f097c01f5d48c37a646f2cff9 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 3 Jul 2024 07:38:48 -0700 Subject: [PATCH 306/405] trie: revamp trie updates (#9239) --- .../commands/debug_cmd/in_memory_merkle.rs | 9 +- crates/engine/tree/src/persistence.rs | 2 +- crates/stages/stages/benches/setup/mod.rs | 6 +- crates/stages/stages/src/stages/merkle.rs | 8 +- crates/storage/db-common/src/init.rs | 17 +- .../src/bundle_state/execution_outcome.rs | 2 +- .../src/providers/database/provider.rs | 6 +- crates/trie/parallel/benches/root.rs | 2 +- crates/trie/parallel/src/async_root.rs | 4 +- crates/trie/parallel/src/parallel_root.rs | 4 +- crates/trie/trie/src/proof.rs | 3 +- crates/trie/trie/src/trie.rs | 273 ++--------- crates/trie/trie/src/trie_cursor/in_memory.rs | 91 +--- crates/trie/trie/src/updates.rs | 438 +++++++++--------- crates/trie/trie/src/walker.rs | 20 +- 15 files changed, 320 insertions(+), 565 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index bd97bfcf3db9..7fcffffcdb0b 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -25,7 +25,7 @@ use reth_provider::{ use reth_revm::database::StateProviderDatabase; use reth_stages::StageId; use reth_tasks::TaskExecutor; -use reth_trie::{updates::TrieKey, StateRoot}; +use reth_trie::StateRoot; use std::{path::PathBuf, sync::Arc}; use tracing::*; @@ -187,15 +187,16 @@ impl Command { // Compare updates let mut in_mem_mismatched = Vec::new(); let mut incremental_mismatched = Vec::new(); - let mut in_mem_updates_iter = in_memory_updates.into_iter().peekable(); - let mut incremental_updates_iter = incremental_trie_updates.into_iter().peekable(); + let mut in_mem_updates_iter = in_memory_updates.account_nodes_ref().iter().peekable(); + let mut incremental_updates_iter = + incremental_trie_updates.account_nodes_ref().iter().peekable(); while in_mem_updates_iter.peek().is_some() || incremental_updates_iter.peek().is_some() { match (in_mem_updates_iter.next(), incremental_updates_iter.next()) { (Some(in_mem), Some(incr)) => { similar_asserts::assert_eq!(in_mem.0, incr.0, "Nibbles don't match"); if in_mem.1 != incr.1 && - matches!(in_mem.0, TrieKey::AccountNode(ref nibbles) if nibbles.len() > self.skip_node_depth.unwrap_or_default()) + in_mem.0.len() > self.skip_node_depth.unwrap_or_default() { in_mem_mismatched.push(in_mem); incremental_mismatched.push(incr); diff --git a/crates/engine/tree/src/persistence.rs b/crates/engine/tree/src/persistence.rs index 20382343933a..23b3a5827c59 100644 --- a/crates/engine/tree/src/persistence.rs +++ b/crates/engine/tree/src/persistence.rs @@ -86,7 +86,7 @@ impl Persistence { let trie_updates = block.trie_updates().clone(); let hashed_state = block.hashed_state(); HashedStateChanges(hashed_state.clone()).write_to_db(provider_rw.tx_ref())?; - trie_updates.flush(provider_rw.tx_ref())?; + trie_updates.write_to_database(provider_rw.tx_ref())?; } // update history indices diff --git a/crates/stages/stages/benches/setup/mod.rs b/crates/stages/stages/benches/setup/mod.rs index de08526bec4a..0f2dd2acf692 100644 --- a/crates/stages/stages/benches/setup/mod.rs +++ b/crates/stages/stages/benches/setup/mod.rs @@ -139,7 +139,11 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> TestStageDB { let offset = transitions.len() as u64; db.insert_changesets(transitions, None).unwrap(); - db.commit(|tx| Ok(updates.flush(tx)?)).unwrap(); + db.commit(|tx| { + updates.write_to_database(tx)?; + Ok(()) + }) + .unwrap(); let (transitions, final_state) = random_changeset_range( &mut rng, diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 5a3d31a40436..9bbd68ed3515 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -217,7 +217,7 @@ impl Stage for MerkleStage { })?; match progress { StateRootProgress::Progress(state, hashed_entries_walked, updates) => { - updates.flush(tx)?; + updates.write_to_database(tx)?; let checkpoint = MerkleCheckpoint::new( to_block, @@ -237,7 +237,7 @@ impl Stage for MerkleStage { }) } StateRootProgress::Complete(root, hashed_entries_walked, updates) => { - updates.flush(tx)?; + updates.write_to_database(tx)?; entities_checkpoint.processed += hashed_entries_walked as u64; @@ -252,7 +252,7 @@ impl Stage for MerkleStage { error!(target: "sync::stages::merkle", %e, ?current_block_number, ?to_block, "Incremental state root failed! {INVALID_STATE_ROOT_ERROR_MESSAGE}"); StageError::Fatal(Box::new(e)) })?; - updates.flush(provider.tx_ref())?; + updates.write_to_database(provider.tx_ref())?; let total_hashed_entries = (provider.count_entries::()? + provider.count_entries::()?) @@ -325,7 +325,7 @@ impl Stage for MerkleStage { validate_state_root(block_root, target.seal_slow(), input.unwind_to)?; // Validation passed, apply unwind changes to the database. - updates.flush(provider.tx_ref())?; + updates.write_to_database(provider.tx_ref())?; // TODO(alexey): update entities checkpoint } else { diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 76314f0c8b71..bf0c28379c35 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -464,19 +464,17 @@ fn compute_state_root(provider: &DatabaseProviderRW) -> eyre:: .root_with_progress()? { StateRootProgress::Progress(state, _, updates) => { - let updates_len = updates.len(); + let updated_len = updates.write_to_database(tx)?; + total_flushed_updates += updated_len; trace!(target: "reth::cli", last_account_key = %state.last_account_key, - updates_len, + updated_len, total_flushed_updates, "Flushing trie updates" ); intermediate_state = Some(*state); - updates.flush(tx)?; - - total_flushed_updates += updates_len; if total_flushed_updates % SOFT_LIMIT_COUNT_FLUSHED_UPDATES == 0 { info!(target: "reth::cli", @@ -486,15 +484,12 @@ fn compute_state_root(provider: &DatabaseProviderRW) -> eyre:: } } StateRootProgress::Complete(root, _, updates) => { - let updates_len = updates.len(); - - updates.flush(tx)?; - - total_flushed_updates += updates_len; + let updated_len = updates.write_to_database(tx)?; + total_flushed_updates += updated_len; trace!(target: "reth::cli", %root, - updates_len = updates_len, + updated_len, total_flushed_updates, "State root has been computed" ); diff --git a/crates/storage/provider/src/bundle_state/execution_outcome.rs b/crates/storage/provider/src/bundle_state/execution_outcome.rs index 4d3b92b05734..009076d0ae9d 100644 --- a/crates/storage/provider/src/bundle_state/execution_outcome.rs +++ b/crates/storage/provider/src/bundle_state/execution_outcome.rs @@ -892,7 +892,7 @@ mod tests { } let (_, updates) = StateRoot::from_tx(tx).root_with_updates().unwrap(); - updates.flush(tx).unwrap(); + updates.write_to_database(tx).unwrap(); }) .unwrap(); diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 61c295b549af..287575046bea 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -2399,7 +2399,7 @@ impl HashingWriter for DatabaseProvider { block_hash: end_block_hash, }))) } - trie_updates.flush(&self.tx)?; + trie_updates.write_to_database(&self.tx)?; } durations_recorder.record_relative(metrics::Action::InsertMerkleTree); @@ -2595,7 +2595,7 @@ impl BlockExecutionWriter for DatabaseProvider { block_hash: parent_hash, }))) } - trie_updates.flush(&self.tx)?; + trie_updates.write_to_database(&self.tx)?; } // get blocks @@ -2797,7 +2797,7 @@ impl BlockWriter for DatabaseProvider { // insert hashes and intermediate merkle nodes { HashedStateChanges(hashed_state).write_to_db(&self.tx)?; - trie_updates.flush(&self.tx)?; + trie_updates.write_to_database(&self.tx)?; } durations_recorder.record_relative(metrics::Action::InsertHashes); diff --git a/crates/trie/parallel/benches/root.rs b/crates/trie/parallel/benches/root.rs index d52702fbcc4a..288e930bae3c 100644 --- a/crates/trie/parallel/benches/root.rs +++ b/crates/trie/parallel/benches/root.rs @@ -30,7 +30,7 @@ pub fn calculate_state_root(c: &mut Criterion) { HashedStateChanges(db_state).write_to_db(provider_rw.tx_ref()).unwrap(); let (_, updates) = StateRoot::from_tx(provider_rw.tx_ref()).root_with_updates().unwrap(); - updates.flush(provider_rw.tx_ref()).unwrap(); + updates.write_to_database(provider_rw.tx_ref()).unwrap(); provider_rw.commit().unwrap(); } diff --git a/crates/trie/parallel/src/async_root.rs b/crates/trie/parallel/src/async_root.rs index e1a031a038f5..e568be81b4c5 100644 --- a/crates/trie/parallel/src/async_root.rs +++ b/crates/trie/parallel/src/async_root.rs @@ -166,7 +166,7 @@ where }; if retain_updates { - trie_updates.extend(updates.into_iter()); + trie_updates.insert_storage_updates(hashed_address, updates); } account_rlp.clear(); @@ -179,7 +179,7 @@ where let root = hash_builder.root(); - trie_updates.finalize_state_updates( + trie_updates.finalize( account_node_iter.walker, hash_builder, prefix_sets.destroyed_accounts, diff --git a/crates/trie/parallel/src/parallel_root.rs b/crates/trie/parallel/src/parallel_root.rs index e276d7e055a8..5e26b97b672b 100644 --- a/crates/trie/parallel/src/parallel_root.rs +++ b/crates/trie/parallel/src/parallel_root.rs @@ -148,7 +148,7 @@ where }; if retain_updates { - trie_updates.extend(updates.into_iter()); + trie_updates.insert_storage_updates(hashed_address, updates); } account_rlp.clear(); @@ -161,7 +161,7 @@ where let root = hash_builder.root(); - trie_updates.finalize_state_updates( + trie_updates.finalize( account_node_iter.walker, hash_builder, prefix_sets.destroyed_accounts, diff --git a/crates/trie/trie/src/proof.rs b/crates/trie/trie/src/proof.rs index 04e7952f5db7..65bce7d2865b 100644 --- a/crates/trie/trie/src/proof.rs +++ b/crates/trie/trie/src/proof.rs @@ -12,6 +12,7 @@ use reth_db_api::transaction::DbTx; use reth_execution_errors::{StateRootError, StorageRootError}; use reth_primitives::{constants::EMPTY_ROOT_HASH, keccak256, Address, B256}; use reth_trie_common::{proof::ProofRetainer, AccountProof, StorageProof, TrieAccount}; + /// A struct for generating merkle proofs. /// /// Proof generator adds the target address and slots to the prefix set, enables the proof retainer @@ -226,7 +227,7 @@ mod tests { let (root, updates) = StateRoot::from_tx(provider.tx_ref()) .root_with_updates() .map_err(Into::::into)?; - updates.flush(provider.tx_mut())?; + updates.write_to_database(provider.tx_mut())?; provider.commit()?; diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index 49e8d00a2b4c..64bcc0a7d83a 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -5,7 +5,7 @@ use crate::{ progress::{IntermediateStateRootState, StateRootProgress}, stats::TrieTracker, trie_cursor::TrieCursorFactory, - updates::{TrieKey, TrieOp, TrieUpdates}, + updates::{StorageTrieUpdates, TrieUpdates}, walker::TrieWalker, HashBuilder, Nibbles, TrieAccount, }; @@ -237,6 +237,7 @@ where let mut account_rlp = Vec::with_capacity(128); let mut hashed_entries_walked = 0; + let mut updated_storage_nodes = 0; while let Some(node) = account_node_iter.try_next()? { match node { TrieElement::Branch(node) => { @@ -273,7 +274,9 @@ where let (root, storage_slots_walked, updates) = storage_root_calculator.root_with_updates()?; hashed_entries_walked += storage_slots_walked; - trie_updates.extend(updates); + // We only walk over hashed address once, so it's safe to insert. + updated_storage_nodes += updates.len(); + trie_updates.insert_storage_updates(hashed_address, updates); root } else { storage_root_calculator.root()? @@ -285,12 +288,14 @@ where hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); // Decide if we need to return intermediate progress. - let total_updates_len = trie_updates.len() + - account_node_iter.walker.deleted_keys_len() + + let total_updates_len = updated_storage_nodes + + account_node_iter.walker.removed_keys_len() + hash_builder.updates_len(); if retain_updates && total_updates_len as u64 >= self.threshold { let (walker_stack, walker_deleted_keys) = account_node_iter.walker.split(); + trie_updates.removed_nodes.extend(walker_deleted_keys); let (hash_builder, hash_builder_updates) = hash_builder.split(); + trie_updates.account_nodes.extend(hash_builder_updates); let state = IntermediateStateRootState { hash_builder, @@ -298,13 +303,6 @@ where last_account_key: hashed_address, }; - trie_updates.extend( - walker_deleted_keys - .into_iter() - .map(|nibbles| (TrieKey::AccountNode(nibbles), TrieOp::Delete)), - ); - trie_updates.extend_with_account_updates(hash_builder_updates); - return Ok(StateRootProgress::Progress( Box::new(state), hashed_entries_walked, @@ -317,7 +315,7 @@ where let root = hash_builder.root(); - trie_updates.finalize_state_updates( + trie_updates.finalize( account_node_iter.walker, hash_builder, self.prefix_sets.destroyed_accounts, @@ -456,7 +454,7 @@ where /// # Returns /// /// The storage root and storage trie updates for a given address. - pub fn root_with_updates(self) -> Result<(B256, usize, TrieUpdates), StorageRootError> { + pub fn root_with_updates(self) -> Result<(B256, usize, StorageTrieUpdates), StorageRootError> { self.calculate(true) } @@ -479,7 +477,7 @@ where pub fn calculate( self, retain_updates: bool, - ) -> Result<(B256, usize, TrieUpdates), StorageRootError> { + ) -> Result<(B256, usize, StorageTrieUpdates), StorageRootError> { trace!(target: "trie::storage_root", hashed_address = ?self.hashed_address, "calculating storage root"); let mut hashed_storage_cursor = @@ -487,11 +485,7 @@ where // short circuit on empty storage if hashed_storage_cursor.is_storage_empty()? { - return Ok(( - EMPTY_ROOT_HASH, - 0, - TrieUpdates::from([(TrieKey::StorageTrie(self.hashed_address), TrieOp::Delete)]), - )) + return Ok((EMPTY_ROOT_HASH, 0, StorageTrieUpdates::deleted())) } let mut tracker = TrieTracker::default(); @@ -520,12 +514,8 @@ where let root = hash_builder.root(); - let mut trie_updates = TrieUpdates::default(); - trie_updates.finalize_storage_updates( - self.hashed_address, - storage_node_iter.walker, - hash_builder, - ); + let mut trie_updates = StorageTrieUpdates::default(); + trie_updates.finalize(storage_node_iter.walker, hash_builder); let stats = tracker.finish(); @@ -551,11 +541,9 @@ where mod tests { use super::*; use crate::{ - hashed_cursor::HashedPostStateCursorFactory, prefix_set::PrefixSetMut, test_utils::{state_root, state_root_prehashed, storage_root, storage_root_prehashed}, - trie_cursor::InMemoryTrieCursorFactory, - BranchNodeCompact, HashedPostState, HashedStorage, TrieMask, + BranchNodeCompact, TrieMask, }; use proptest::{prelude::ProptestConfig, proptest}; use proptest_arbitrary_interop::arb; @@ -569,7 +557,6 @@ mod tests { use reth_trie_common::triehash::KeccakHasher; use std::{ collections::{BTreeMap, HashMap}, - iter, ops::Mul, str::FromStr, sync::Arc, @@ -629,7 +616,7 @@ mod tests { let modified_root = loader.root().unwrap(); // Update the intermediate roots table so that we can run the incremental verification - trie_updates.flush(tx.tx_ref()).unwrap(); + trie_updates.write_to_database(tx.tx_ref(), hashed_address).unwrap(); // 3. Calculate the incremental root let mut storage_changes = PrefixSetMut::default(); @@ -988,14 +975,7 @@ mod tests { assert_eq!(root, computed_expected_root); // Check account trie - let mut account_updates = trie_updates - .iter() - .filter_map(|(k, v)| match (k, v) { - (TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => Some((nibbles, node)), - _ => None, - }) - .collect::>(); - account_updates.sort_unstable_by(|a, b| a.0.cmp(b.0)); + let account_updates = trie_updates.clone().into_sorted().account_nodes; assert_eq!(account_updates.len(), 2); let (nibbles1a, node1a) = account_updates.first().unwrap(); @@ -1015,16 +995,13 @@ mod tests { assert_eq!(node2a.hashes.len(), 1); // Check storage trie - let storage_updates = trie_updates - .iter() - .filter_map(|entry| match entry { - (TrieKey::StorageNode(_, nibbles), TrieOp::Update(node)) => Some((nibbles, node)), - _ => None, - }) - .collect::>(); - assert_eq!(storage_updates.len(), 1); + let mut updated_storage_trie = + trie_updates.storage_tries.iter().filter(|(_, u)| !u.storage_nodes.is_empty()); + assert_eq!(updated_storage_trie.clone().count(), 1); + let (_, storage_trie_updates) = updated_storage_trie.next().unwrap(); + assert_eq!(storage_trie_updates.storage_nodes.len(), 1); - let (nibbles3, node3) = storage_updates.first().unwrap(); + let (nibbles3, node3) = storage_trie_updates.storage_nodes.iter().next().unwrap(); assert!(nibbles3.is_empty()); assert_eq!(node3.state_mask, TrieMask::new(0b1010)); assert_eq!(node3.tree_mask, TrieMask::new(0b0000)); @@ -1058,14 +1035,7 @@ mod tests { .unwrap(); assert_eq!(root, expected_state_root); - let mut account_updates = trie_updates - .iter() - .filter_map(|entry| match entry { - (TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => Some((nibbles, node)), - _ => None, - }) - .collect::>(); - account_updates.sort_by(|a, b| a.0.cmp(b.0)); + let account_updates = trie_updates.into_sorted().account_nodes; assert_eq!(account_updates.len(), 2); let (nibbles1b, node1b) = account_updates.first().unwrap(); @@ -1112,19 +1082,11 @@ mod tests { .root_with_updates() .unwrap(); assert_eq!(root, computed_expected_root); - assert_eq!(trie_updates.len(), 7); - assert_eq!(trie_updates.iter().filter(|(_, op)| op.is_update()).count(), 2); + assert_eq!(trie_updates.account_nodes.len() + trie_updates.removed_nodes.len(), 1); - let account_updates = trie_updates - .iter() - .filter_map(|entry| match entry { - (TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => Some((nibbles, node)), - _ => None, - }) - .collect::>(); - assert_eq!(account_updates.len(), 1); + assert_eq!(trie_updates.account_nodes.len(), 1); - let (nibbles1c, node1c) = account_updates.first().unwrap(); + let (nibbles1c, node1c) = trie_updates.account_nodes.iter().next().unwrap(); assert_eq!(nibbles1c[..], [0xB]); assert_eq!(node1c.state_mask, TrieMask::new(0b1011)); @@ -1171,19 +1133,15 @@ mod tests { .root_with_updates() .unwrap(); assert_eq!(root, computed_expected_root); - assert_eq!(trie_updates.len(), 6); - assert_eq!(trie_updates.iter().filter(|(_, op)| op.is_update()).count(), 1); // no storage root update - - let account_updates = trie_updates + assert_eq!(trie_updates.account_nodes.len() + trie_updates.removed_nodes.len(), 1); + assert!(!trie_updates + .storage_tries .iter() - .filter_map(|entry| match entry { - (TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => Some((nibbles, node)), - _ => None, - }) - .collect::>(); - assert_eq!(account_updates.len(), 1); + .any(|(_, u)| !u.storage_nodes.is_empty() || !u.removed_nodes.is_empty())); // no storage root update + + assert_eq!(trie_updates.account_nodes.len(), 1); - let (nibbles1d, node1d) = account_updates.first().unwrap(); + let (nibbles1d, node1d) = trie_updates.account_nodes.iter().next().unwrap(); assert_eq!(nibbles1d[..], [0xB]); assert_eq!(node1d.state_mask, TrieMask::new(0b1011)); @@ -1207,19 +1165,7 @@ mod tests { let (got, updates) = StateRoot::from_tx(tx.tx_ref()).root_with_updates().unwrap(); assert_eq!(expected, got); - - // Check account trie - let account_updates = updates - .iter() - .filter_map(|entry| match entry { - (TrieKey::AccountNode(nibbles), TrieOp::Update(node)) => { - Some((nibbles.clone(), node.clone())) - } - _ => None, - }) - .collect::>(); - - assert_trie_updates(&account_updates); + assert_trie_updates(&updates.account_nodes); } #[test] @@ -1231,7 +1177,7 @@ mod tests { let (got, updates) = StateRoot::from_tx(tx.tx_ref()).root_with_updates().unwrap(); assert_eq!(expected, got); - updates.flush(tx.tx_ref()).unwrap(); + updates.write_to_database(tx.tx_ref()).unwrap(); // read the account updates from the db let mut accounts_trie = tx.tx_ref().cursor_read::().unwrap(); @@ -1278,7 +1224,7 @@ mod tests { state.iter().map(|(&key, &balance)| (key, (Account { balance, ..Default::default() }, std::iter::empty()))) ); assert_eq!(expected_root, state_root); - trie_updates.flush(tx.tx_ref()).unwrap(); + trie_updates.write_to_database(tx.tx_ref()).unwrap(); } } } @@ -1294,26 +1240,14 @@ mod tests { let (got, _, updates) = StorageRoot::from_tx_hashed(tx.tx_ref(), hashed_address).root_with_updates().unwrap(); assert_eq!(expected_root, got); - - // Check account trie - let storage_updates = updates - .iter() - .filter_map(|entry| match entry { - (TrieKey::StorageNode(_, nibbles), TrieOp::Update(node)) => { - Some((nibbles.clone(), node.clone())) - } - _ => None, - }) - .collect::>(); - assert_eq!(expected_updates, storage_updates); - - assert_trie_updates(&storage_updates); + assert_eq!(expected_updates, updates); + assert_trie_updates(&updates.storage_nodes); } fn extension_node_storage_trie( tx: &DatabaseProviderRW>>, hashed_address: B256, - ) -> (B256, HashMap) { + ) -> (B256, StorageTrieUpdates) { let value = U256::from(1); let mut hashed_storage = tx.tx_ref().cursor_write::().unwrap(); @@ -1336,7 +1270,8 @@ mod tests { let root = hb.root(); let (_, updates) = hb.split(); - (root, updates) + let trie_updates = StorageTrieUpdates { storage_nodes: updates, ..Default::default() }; + (root, trie_updates) } fn extension_node_trie(tx: &DatabaseProviderRW>>) -> B256 { @@ -1377,126 +1312,4 @@ mod tests { assert_eq!(node.root_hash, None); assert_eq!(node.hashes.len(), 1); } - - #[test] - fn trie_updates_across_multiple_iterations() { - let address = Address::ZERO; - let hashed_address = keccak256(address); - - let factory = create_test_provider_factory(); - - let mut hashed_storage = BTreeMap::default(); - let mut post_state = HashedPostState::default(); - - // Block #1 - // Update specific storage slots - let mut modified_storage = BTreeMap::default(); - - // 0x0f.. - let modified_key_prefix = Nibbles::from_nibbles( - [0x0, 0xf].into_iter().chain(iter::repeat(0).take(62)).collect::>(), - ); - - // 0x0faa0.. - let mut modified_entry1 = modified_key_prefix.clone(); - modified_entry1.set_at(2, 0xa); - modified_entry1.set_at(3, 0xa); - - // 0x0faaa.. - let mut modified_entry2 = modified_key_prefix.clone(); - modified_entry2.set_at(2, 0xa); - modified_entry2.set_at(3, 0xa); - modified_entry2.set_at(4, 0xa); - - // 0x0fab0.. - let mut modified_entry3 = modified_key_prefix.clone(); - modified_entry3.set_at(2, 0xa); - modified_entry3.set_at(3, 0xb); - - // 0x0fba0.. - let mut modified_entry4 = modified_key_prefix; - modified_entry4.set_at(2, 0xb); - modified_entry4.set_at(3, 0xa); - - [modified_entry1, modified_entry2, modified_entry3.clone(), modified_entry4] - .into_iter() - .for_each(|key| { - modified_storage.insert(B256::from_slice(&key.pack()), U256::from(1)); - }); - - // Update main hashed storage. - hashed_storage.extend(modified_storage.clone()); - post_state.extend(HashedPostState::default().with_storages([( - hashed_address, - HashedStorage::from_iter(false, modified_storage.clone()), - )])); - - let (storage_root, block1_updates) = compute_storage_root( - address, - factory.provider().unwrap().tx_ref(), - &post_state, - &TrieUpdates::default(), - ); - assert_eq!(storage_root, storage_root_prehashed(hashed_storage.clone())); - - // Block #2 - // Set 0x0fab0.. hashed slot to 0 - modified_storage.insert(B256::from_slice(&modified_entry3.pack()), U256::ZERO); - - // Update main hashed storage. - hashed_storage.remove(&B256::from_slice(&modified_entry3.pack())); - post_state.extend(HashedPostState::default().with_storages([( - hashed_address, - HashedStorage::from_iter(false, modified_storage.clone()), - )])); - - let (storage_root, block2_updates) = compute_storage_root( - address, - factory.provider().unwrap().tx_ref(), - &post_state, - &block1_updates, - ); - assert_eq!(storage_root, storage_root_prehashed(hashed_storage.clone())); - - // Commit trie updates - { - let mut updates = block1_updates; - updates.extend(block2_updates); - - let provider_rw = factory.provider_rw().unwrap(); - let mut hashed_storage_cursor = - provider_rw.tx_ref().cursor_dup_write::().unwrap(); - for (hashed_slot, value) in &hashed_storage { - hashed_storage_cursor - .upsert(hashed_address, StorageEntry { key: *hashed_slot, value: *value }) - .unwrap(); - } - updates.flush(provider_rw.tx_ref()).unwrap(); - provider_rw.commit().unwrap(); - } - - // Recompute storage root for block #3 - let storage_root = - StorageRoot::from_tx(factory.provider().unwrap().tx_ref(), address).root().unwrap(); - assert_eq!(storage_root, storage_root_prehashed(hashed_storage.clone())); - } - - fn compute_storage_root( - address: Address, - tx: &TX, - post_state: &HashedPostState, - update: &TrieUpdates, - ) -> (B256, TrieUpdates) { - let mut prefix_sets = post_state.construct_prefix_sets(); - let (root, _, updates) = StorageRoot::from_tx(tx, address) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - tx, - &post_state.clone().into_sorted(), - )) - .with_trie_cursor_factory(InMemoryTrieCursorFactory::new(tx, &update.sorted())) - .with_prefix_set(prefix_sets.storage_prefix_sets.remove(&keccak256(address)).unwrap()) - .root_with_updates() - .unwrap(); - (root, updates) - } } diff --git a/crates/trie/trie/src/trie_cursor/in_memory.rs b/crates/trie/trie/src/trie_cursor/in_memory.rs index 3efd2fa5debc..83ef6e5d6c8a 100644 --- a/crates/trie/trie/src/trie_cursor/in_memory.rs +++ b/crates/trie/trie/src/trie_cursor/in_memory.rs @@ -1,5 +1,5 @@ use super::{TrieCursor, TrieCursorFactory}; -use crate::updates::{TrieKey, TrieUpdatesSorted}; +use crate::updates::TrieUpdatesSorted; use reth_db::DatabaseError; use reth_primitives::B256; use reth_trie_common::{BranchNodeCompact, Nibbles}; @@ -39,6 +39,7 @@ impl<'a, CF: TrieCursorFactory> TrieCursorFactory for InMemoryTrieCursorFactory< /// The cursor to iterate over account trie updates and corresponding database entries. /// It will always give precedence to the data from the trie updates. #[derive(Debug)] +#[allow(dead_code)] pub struct InMemoryAccountTrieCursor<'a, C> { cursor: C, trie_updates: &'a TrieUpdatesSorted, @@ -54,57 +55,27 @@ impl<'a, C> InMemoryAccountTrieCursor<'a, C> { impl<'a, C: TrieCursor> TrieCursor for InMemoryAccountTrieCursor<'a, C> { fn seek_exact( &mut self, - key: Nibbles, + _key: Nibbles, ) -> Result, DatabaseError> { - if let Some((nibbles, trie_op)) = self.trie_updates.find_account_node(&key) { - self.last_key = Some(nibbles); - Ok(trie_op.into_update().map(|node| (key, node))) - } else { - let result = self.cursor.seek_exact(key)?; - self.last_key = result.as_ref().map(|(key, _)| key.clone()); - Ok(result) - } + unimplemented!() } fn seek( &mut self, - key: Nibbles, + _key: Nibbles, ) -> Result, DatabaseError> { - let trie_update_entry = self - .trie_updates - .trie_operations - .iter() - .find(|(k, _)| matches!(k, TrieKey::AccountNode(nibbles) if nibbles <= &key)) - .cloned(); - - if let Some((trie_key, trie_op)) = trie_update_entry { - let nibbles = match trie_key { - TrieKey::AccountNode(nibbles) => { - self.last_key = Some(nibbles.clone()); - nibbles - } - _ => panic!("Invalid trie key"), - }; - return Ok(trie_op.into_update().map(|node| (nibbles, node))) - } - - let result = self.cursor.seek(key)?; - self.last_key = result.as_ref().map(|(key, _)| key.clone()); - Ok(result) + unimplemented!() } fn current(&mut self) -> Result, DatabaseError> { - if self.last_key.is_some() { - Ok(self.last_key.clone()) - } else { - self.cursor.current() - } + unimplemented!() } } /// The cursor to iterate over storage trie updates and corresponding database entries. /// It will always give precedence to the data from the trie updates. #[derive(Debug)] +#[allow(dead_code)] pub struct InMemoryStorageTrieCursor<'a, C> { cursor: C, trie_update_index: usize, @@ -122,55 +93,19 @@ impl<'a, C> InMemoryStorageTrieCursor<'a, C> { impl<'a, C: TrieCursor> TrieCursor for InMemoryStorageTrieCursor<'a, C> { fn seek_exact( &mut self, - key: Nibbles, + _key: Nibbles, ) -> Result, DatabaseError> { - if let Some((trie_key, trie_op)) = - self.trie_updates.find_storage_node(&self.hashed_address, &key) - { - self.last_key = Some(trie_key); - Ok(trie_op.into_update().map(|node| (key, node))) - } else { - let result = self.cursor.seek_exact(key)?; - self.last_key = result.as_ref().map(|(key, _)| key.clone()); - Ok(result) - } + unimplemented!() } fn seek( &mut self, - key: Nibbles, + _key: Nibbles, ) -> Result, DatabaseError> { - let mut trie_update_entry = self.trie_updates.trie_operations.get(self.trie_update_index); - while trie_update_entry - .filter(|(k, _)| matches!(k, TrieKey::StorageNode(address, nibbles) if address == &self.hashed_address && nibbles < &key)).is_some() - { - self.trie_update_index += 1; - trie_update_entry = self.trie_updates.trie_operations.get(self.trie_update_index); - } - - if let Some((trie_key, trie_op)) = - trie_update_entry.filter(|(k, _)| matches!(k, TrieKey::StorageNode(_, _))) - { - let nibbles = match trie_key { - TrieKey::StorageNode(_, nibbles) => { - self.last_key = Some(nibbles.clone()); - nibbles.clone() - } - _ => panic!("this should not happen!"), - }; - return Ok(trie_op.as_update().map(|node| (nibbles, node.clone()))) - } - - let result = self.cursor.seek(key)?; - self.last_key = result.as_ref().map(|(key, _)| key.clone()); - Ok(result) + unimplemented!() } fn current(&mut self) -> Result, DatabaseError> { - if self.last_key.is_some() { - Ok(self.last_key.clone()) - } else { - self.cursor.current() - } + unimplemented!() } } diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 181f2b82d1d5..98a5922a14f7 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -2,276 +2,282 @@ use crate::{ walker::TrieWalker, BranchNodeCompact, HashBuilder, Nibbles, StorageTrieEntry, StoredBranchNode, StoredNibbles, StoredNibblesSubKey, }; -use derive_more::Deref; use reth_db::tables; use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, transaction::{DbTx, DbTxMut}, }; use reth_primitives::B256; -use std::collections::{hash_map::IntoIter, HashMap, HashSet}; +use std::collections::{HashMap, HashSet}; -/// The key of a trie node. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// The aggregation of trie updates. +#[derive(PartialEq, Eq, Clone, Default, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TrieKey { - /// A node in the account trie. - AccountNode(Nibbles), - /// A node in the storage trie. - StorageNode(B256, Nibbles), - /// Storage trie of an account. - StorageTrie(B256), +pub struct TrieUpdates { + pub(crate) account_nodes: HashMap, + pub(crate) removed_nodes: HashSet, + pub(crate) storage_tries: HashMap, } -impl TrieKey { - /// Returns reference to account node key if the key is for [`Self::AccountNode`]. - pub const fn as_account_node_key(&self) -> Option<&Nibbles> { - if let Self::AccountNode(nibbles) = &self { - Some(nibbles) - } else { - None - } +impl TrieUpdates { + /// Returns `true` if the updates are empty. + pub fn is_empty(&self) -> bool { + self.account_nodes.is_empty() && + self.removed_nodes.is_empty() && + self.storage_tries.is_empty() } - /// Returns reference to storage node key if the key is for [`Self::StorageNode`]. - pub const fn as_storage_node_key(&self) -> Option<(&B256, &Nibbles)> { - if let Self::StorageNode(key, subkey) = &self { - Some((key, subkey)) - } else { - None - } + /// Returns reference to updated account nodes. + pub const fn account_nodes_ref(&self) -> &HashMap { + &self.account_nodes } - /// Returns reference to storage trie key if the key is for [`Self::StorageTrie`]. - pub const fn as_storage_trie_key(&self) -> Option<&B256> { - if let Self::StorageTrie(key) = &self { - Some(key) - } else { - None - } + /// Insert storage updates for a given hashed address. + pub fn insert_storage_updates( + &mut self, + hashed_address: B256, + storage_updates: StorageTrieUpdates, + ) { + let existing = self.storage_tries.insert(hashed_address, storage_updates); + debug_assert!(existing.is_none()); } -} -/// The operation to perform on the trie. -#[derive(PartialEq, Eq, Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TrieOp { - /// Delete the node entry. - Delete, - /// Update the node entry with the provided value. - Update(BranchNodeCompact), -} + /// Finalize state trie updates. + pub fn finalize( + &mut self, + walker: TrieWalker, + hash_builder: HashBuilder, + destroyed_accounts: HashSet, + ) { + // Retrieve deleted keys from trie walker. + let (_, removed_node_keys) = walker.split(); + self.removed_nodes.extend(removed_node_keys); -impl TrieOp { - /// Returns `true` if the operation is an update. - pub const fn is_update(&self) -> bool { - matches!(self, Self::Update(..)) - } + // Retrieve updated nodes from hash builder. + let (_, updated_nodes) = hash_builder.split(); + self.account_nodes.extend(updated_nodes); - /// Returns reference to updated branch node if operation is [`Self::Update`]. - pub const fn as_update(&self) -> Option<&BranchNodeCompact> { - if let Self::Update(node) = &self { - Some(node) - } else { - None + // Add deleted storage tries for destroyed accounts. + for destroyed in destroyed_accounts { + self.storage_tries.entry(destroyed).or_default().set_deleted(true); } } - /// Returns owned updated branch node if operation is [`Self::Update`]. - pub fn into_update(self) -> Option { - if let Self::Update(node) = self { - Some(node) - } else { - None + /// Converts trie updates into [`TrieUpdatesSorted`]. + pub fn into_sorted(self) -> TrieUpdatesSorted { + let mut account_nodes = Vec::from_iter(self.account_nodes); + account_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + let mut storage_tries = Vec::from_iter( + self.storage_tries + .into_iter() + .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted())), + ); + storage_tries.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + TrieUpdatesSorted { removed_nodes: self.removed_nodes, account_nodes, storage_tries } + } + + /// Flush updates all aggregated updates to the database. + /// + /// # Returns + /// + /// The number of storage trie entries updated in the database. + pub fn write_to_database(self, tx: &TX) -> Result + where + TX: DbTx + DbTxMut, + { + if self.is_empty() { + return Ok(0) } + + // Track the number of inserted entries. + let mut num_entries = 0; + + // Merge updated and removed nodes. Updated nodes must take precedence. + let mut account_updates = self + .removed_nodes + .into_iter() + .filter_map(|n| (!self.account_nodes.contains_key(&n)).then_some((n, None))) + .collect::>(); + account_updates + .extend(self.account_nodes.into_iter().map(|(nibbles, node)| (nibbles, Some(node)))); + // Sort trie node updates. + account_updates.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + + let mut account_trie_cursor = tx.cursor_write::()?; + for (key, updated_node) in account_updates { + let nibbles = StoredNibbles(key); + match updated_node { + Some(node) => { + if !nibbles.0.is_empty() { + num_entries += 1; + account_trie_cursor.upsert(nibbles, StoredBranchNode(node))?; + } + } + None => { + num_entries += 1; + if account_trie_cursor.seek_exact(nibbles)?.is_some() { + account_trie_cursor.delete_current()?; + } + } + } + } + + let mut storage_tries = Vec::from_iter(self.storage_tries); + storage_tries.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + let mut storage_trie_cursor = tx.cursor_dup_write::()?; + for (hashed_address, storage_trie_updates) in storage_tries { + let updated_storage_entries = + storage_trie_updates.write_with_cursor(&mut storage_trie_cursor, hashed_address)?; + num_entries += updated_storage_entries; + } + + Ok(num_entries) } } -/// The aggregation of trie updates. -#[derive(Debug, Default, Clone, PartialEq, Eq, Deref)] +/// Trie updates for storage trie of a single account. +#[derive(PartialEq, Eq, Clone, Default, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TrieUpdates { - trie_operations: HashMap, +pub struct StorageTrieUpdates { + /// Flag indicating whether the trie was deleted. + pub(crate) is_deleted: bool, + /// Collection of updated storage trie nodes. + pub(crate) storage_nodes: HashMap, + /// Collection of removed storage trie nodes. + pub(crate) removed_nodes: HashSet, } -impl From<[(TrieKey, TrieOp); N]> for TrieUpdates { - fn from(value: [(TrieKey, TrieOp); N]) -> Self { - Self { trie_operations: HashMap::from(value) } +impl StorageTrieUpdates { + /// Returns empty storage trie updates with `deleted` set to `true`. + pub fn deleted() -> Self { + Self { + is_deleted: true, + storage_nodes: HashMap::default(), + removed_nodes: HashSet::default(), + } } -} - -impl IntoIterator for TrieUpdates { - type Item = (TrieKey, TrieOp); - type IntoIter = IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.trie_operations.into_iter() + /// Returns the length of updated nodes. + pub fn len(&self) -> usize { + (self.is_deleted as usize) + self.storage_nodes.len() + self.removed_nodes.len() } -} -impl TrieUpdates { - /// Extend the updates with trie updates. - pub fn extend(&mut self, updates: impl IntoIterator) { - self.trie_operations.extend(updates); + /// Returns `true` if storage updates are empty. + pub fn is_empty(&self) -> bool { + !self.is_deleted && self.storage_nodes.is_empty() && self.removed_nodes.is_empty() } - /// Extend the updates with account trie updates. - pub fn extend_with_account_updates(&mut self, updates: HashMap) { - self.extend( - updates - .into_iter() - .map(|(nibbles, node)| (TrieKey::AccountNode(nibbles), TrieOp::Update(node))), - ); + /// Sets `deleted` flag on the storage trie. + pub fn set_deleted(&mut self, deleted: bool) { + self.is_deleted = deleted; } - /// Finalize state trie updates. - pub fn finalize_state_updates( - &mut self, - walker: TrieWalker, - hash_builder: HashBuilder, - destroyed_accounts: HashSet, - ) { - // Add updates from trie walker. - let (_, deleted_keys) = walker.split(); - self.extend( - deleted_keys.into_iter().map(|nibbles| (TrieKey::AccountNode(nibbles), TrieOp::Delete)), - ); + /// Finalize storage trie updates for by taking updates from walker and hash builder. + pub fn finalize(&mut self, walker: TrieWalker, hash_builder: HashBuilder) { + // Retrieve deleted keys from trie walker. + let (_, removed_keys) = walker.split(); + self.removed_nodes.extend(removed_keys); - // Add account node updates from hash builder. - let (_, hash_builder_updates) = hash_builder.split(); - self.extend_with_account_updates(hash_builder_updates); + // Retrieve updated nodes from hash builder. + let (_, updated_nodes) = hash_builder.split(); + self.storage_nodes.extend(updated_nodes); + } - // Add deleted storage tries for destroyed accounts. - self.extend( - destroyed_accounts.into_iter().map(|key| (TrieKey::StorageTrie(key), TrieOp::Delete)), - ); + /// Convert storage trie updates into [`StorageTrieUpdatesSorted`]. + pub fn into_sorted(self) -> StorageTrieUpdatesSorted { + let mut storage_nodes = Vec::from_iter(self.storage_nodes); + storage_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + StorageTrieUpdatesSorted { + is_deleted: self.is_deleted, + removed_nodes: self.removed_nodes, + storage_nodes, + } } - /// Finalize storage trie updates for a given address. - pub fn finalize_storage_updates( - &mut self, + /// Initializes a storage trie cursor and writes updates to database. + pub fn write_to_database( + self, + tx: &TX, hashed_address: B256, - walker: TrieWalker, - hash_builder: HashBuilder, - ) { - // Add updates from trie walker. - let (_, deleted_keys) = walker.split(); - self.extend( - deleted_keys - .into_iter() - .map(|nibbles| (TrieKey::StorageNode(hashed_address, nibbles), TrieOp::Delete)), - ); + ) -> Result + where + TX: DbTx + DbTxMut, + { + if self.is_empty() { + return Ok(0) + } - // Add storage node updates from hash builder. - let (_, hash_builder_updates) = hash_builder.split(); - self.extend(hash_builder_updates.into_iter().map(|(nibbles, node)| { - (TrieKey::StorageNode(hashed_address, nibbles), TrieOp::Update(node)) - })); + let mut cursor = tx.cursor_dup_write::()?; + self.write_with_cursor(&mut cursor, hashed_address) } - /// Flush updates all aggregated updates to the database. - pub fn flush(self, tx: &(impl DbTx + DbTxMut)) -> Result<(), reth_db::DatabaseError> { - if self.trie_operations.is_empty() { - return Ok(()) + /// Writes updates to database. + /// + /// # Returns + /// + /// The number of storage trie entries updated in the database. + fn write_with_cursor( + self, + cursor: &mut C, + hashed_address: B256, + ) -> Result + where + C: DbCursorRO + + DbCursorRW + + DbDupCursorRO + + DbDupCursorRW, + { + // The storage trie for this account has to be deleted. + if self.is_deleted && cursor.seek_exact(hashed_address)?.is_some() { + cursor.delete_current_duplicates()?; } - let mut account_trie_cursor = tx.cursor_write::()?; - let mut storage_trie_cursor = tx.cursor_dup_write::()?; + // Merge updated and removed nodes. Updated nodes must take precedence. + let mut storage_updates = self + .removed_nodes + .into_iter() + .filter_map(|n| (!self.storage_nodes.contains_key(&n)).then_some((n, None))) + .collect::>(); + storage_updates + .extend(self.storage_nodes.into_iter().map(|(nibbles, node)| (nibbles, Some(node)))); + // Sort trie node updates. + storage_updates.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - let mut trie_operations = Vec::from_iter(self.trie_operations); - trie_operations.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - for (key, operation) in trie_operations { - match key { - TrieKey::AccountNode(nibbles) => { - let nibbles = StoredNibbles(nibbles); - match operation { - TrieOp::Delete => { - if account_trie_cursor.seek_exact(nibbles)?.is_some() { - account_trie_cursor.delete_current()?; - } - } - TrieOp::Update(node) => { - if !nibbles.0.is_empty() { - account_trie_cursor.upsert(nibbles, StoredBranchNode(node))?; - } - } - } - } - TrieKey::StorageTrie(hashed_address) => match operation { - TrieOp::Delete => { - if storage_trie_cursor.seek_exact(hashed_address)?.is_some() { - storage_trie_cursor.delete_current_duplicates()?; - } - } - TrieOp::Update(..) => unreachable!("Cannot update full storage trie."), - }, - TrieKey::StorageNode(hashed_address, nibbles) => { - if !nibbles.is_empty() { - let nibbles = StoredNibblesSubKey(nibbles); - // Delete the old entry if it exists. - if storage_trie_cursor - .seek_by_key_subkey(hashed_address, nibbles.clone())? - .filter(|e| e.nibbles == nibbles) - .is_some() - { - storage_trie_cursor.delete_current()?; - } + let mut num_entries = 0; + for (nibbles, maybe_updated) in storage_updates.into_iter().filter(|(n, _)| !n.is_empty()) { + num_entries += 1; + let nibbles = StoredNibblesSubKey(nibbles); + // Delete the old entry if it exists. + if cursor + .seek_by_key_subkey(hashed_address, nibbles.clone())? + .filter(|e| e.nibbles == nibbles) + .is_some() + { + cursor.delete_current()?; + } - // The operation is an update, insert new entry. - if let TrieOp::Update(node) = operation { - storage_trie_cursor - .upsert(hashed_address, StorageTrieEntry { nibbles, node })?; - } - } - } - }; + // There is an updated version of this node, insert new entry. + if let Some(node) = maybe_updated { + cursor.upsert(hashed_address, StorageTrieEntry { nibbles, node })?; + } } - Ok(()) - } - - /// creates [`TrieUpdatesSorted`] by sorting the `trie_operations`. - pub fn sorted(&self) -> TrieUpdatesSorted { - let mut trie_operations = Vec::from_iter(self.trie_operations.clone()); - trie_operations.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - TrieUpdatesSorted { trie_operations } - } - - /// converts trie updates into [`TrieUpdatesSorted`]. - pub fn into_sorted(self) -> TrieUpdatesSorted { - let mut trie_operations = Vec::from_iter(self.trie_operations); - trie_operations.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - TrieUpdatesSorted { trie_operations } + Ok(num_entries) } } -/// The aggregation of trie updates. -#[derive(Debug, Default, Clone, PartialEq, Eq, Deref)] +/// Sorted trie updates used for lookups and insertions. +#[derive(PartialEq, Eq, Clone, Default, Debug)] pub struct TrieUpdatesSorted { - /// Sorted collection of trie operations. - pub(crate) trie_operations: Vec<(TrieKey, TrieOp)>, + pub(crate) account_nodes: Vec<(Nibbles, BranchNodeCompact)>, + pub(crate) removed_nodes: HashSet, + pub(crate) storage_tries: Vec<(B256, StorageTrieUpdatesSorted)>, } -impl TrieUpdatesSorted { - /// Find the account node with the given nibbles. - pub fn find_account_node(&self, key: &Nibbles) -> Option<(Nibbles, TrieOp)> { - self.trie_operations.iter().find_map(|(k, op)| { - k.as_account_node_key() - .filter(|nibbles| nibbles == &key) - .map(|nibbles| (nibbles.clone(), op.clone())) - }) - } - - /// Find the storage node with the given hashed address and key. - pub fn find_storage_node( - &self, - hashed_address: &B256, - key: &Nibbles, - ) -> Option<(Nibbles, TrieOp)> { - self.trie_operations.iter().find_map(|(k, op)| { - k.as_storage_node_key() - .filter(|(address, nibbles)| address == &hashed_address && nibbles == &key) - .map(|(_, nibbles)| (nibbles.clone(), op.clone())) - }) - } +/// Sorted trie updates used for lookups and insertions. +#[derive(PartialEq, Eq, Clone, Default, Debug)] +pub struct StorageTrieUpdatesSorted { + pub(crate) is_deleted: bool, + pub(crate) storage_nodes: Vec<(Nibbles, BranchNodeCompact)>, + pub(crate) removed_nodes: HashSet, } diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index 64a68ea17de2..5f151e8b9a26 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -22,15 +22,15 @@ pub struct TrieWalker { pub can_skip_current_node: bool, /// A `PrefixSet` representing the changes to be applied to the trie. pub changes: PrefixSet, - /// The retained trie node keys that need to be deleted. - deleted_keys: Option>, + /// The retained trie node keys that need to be removed. + removed_keys: Option>, } impl TrieWalker { /// Constructs a new `TrieWalker` from existing stack and a cursor. pub fn from_stack(cursor: C, stack: Vec, changes: PrefixSet) -> Self { let mut this = - Self { cursor, changes, stack, can_skip_current_node: false, deleted_keys: None }; + Self { cursor, changes, stack, can_skip_current_node: false, removed_keys: None }; this.update_skip_node(); this } @@ -38,14 +38,14 @@ impl TrieWalker { /// Sets the flag whether the trie updates should be stored. pub fn with_deletions_retained(mut self, retained: bool) -> Self { if retained { - self.deleted_keys = Some(HashSet::default()); + self.removed_keys = Some(HashSet::default()); } self } /// Split the walker into stack and trie updates. pub fn split(mut self) -> (Vec, HashSet) { - let keys = self.deleted_keys.take(); + let keys = self.removed_keys.take(); (self.stack, keys.unwrap_or_default()) } @@ -58,9 +58,9 @@ impl TrieWalker { println!("====================== END STACK ======================\n"); } - /// The current length of the deleted keys. - pub fn deleted_keys_len(&self) -> usize { - self.deleted_keys.as_ref().map_or(0, |u| u.len()) + /// The current length of the removed keys. + pub fn removed_keys_len(&self) -> usize { + self.removed_keys.as_ref().map_or(0, |u| u.len()) } /// Returns the current key in the trie. @@ -112,7 +112,7 @@ impl TrieWalker { changes, stack: vec![CursorSubNode::default()], can_skip_current_node: false, - deleted_keys: None, + removed_keys: None, }; // Set up the root node of the trie in the stack, if it exists. @@ -188,7 +188,7 @@ impl TrieWalker { // Delete the current node if it's included in the prefix set or it doesn't contain the root // hash. if !self.can_skip_current_node || nibble != -1 { - if let Some((keys, key)) = self.deleted_keys.as_mut().zip(self.cursor.current()?) { + if let Some((keys, key)) = self.removed_keys.as_mut().zip(self.cursor.current()?) { keys.insert(key); } } From 08fc298e5565b546eddc8fd968d0bf0790cf69a4 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Wed, 3 Jul 2024 16:39:31 +0200 Subject: [PATCH 307/405] feat: moved optimism commands to create and remove from bin (#9242) Co-authored-by: Matthias Seitz --- Cargo.lock | 30 ++++++ bin/reth/Cargo.toml | 4 + bin/reth/src/cli/mod.rs | 4 +- bin/reth/src/commands/mod.rs | 2 - crates/optimism/cli/Cargo.toml | 46 +++++++++ .../cli/src/commands/build_pipeline.rs | 96 +++++++++++++++++++ .../optimism/cli/src/commands/import.rs | 4 +- .../cli/src/commands/import_receipts.rs | 0 crates/optimism/cli/src/commands/mod.rs | 4 + crates/optimism/cli/src/lib.rs | 8 +- 10 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 crates/optimism/cli/src/commands/build_pipeline.rs rename bin/reth/src/commands/import_op.rs => crates/optimism/cli/src/commands/import.rs (98%) rename bin/reth/src/commands/import_receipts_op.rs => crates/optimism/cli/src/commands/import_receipts.rs (100%) create mode 100644 crates/optimism/cli/src/commands/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ec85c912cf91..99cfc38d7804 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6324,6 +6324,7 @@ dependencies = [ "reth-node-ethereum", "reth-node-events", "reth-node-optimism", + "reth-optimism-cli", "reth-optimism-primitives", "reth-payload-builder", "reth-payload-primitives", @@ -6340,6 +6341,7 @@ dependencies = [ "reth-rpc-types", "reth-rpc-types-compat", "reth-stages", + "reth-stages-api", "reth-static-file", "reth-static-file-types", "reth-tasks", @@ -7821,6 +7823,34 @@ dependencies = [ [[package]] name = "reth-optimism-cli" version = "1.0.0" +dependencies = [ + "alloy-primitives", + "clap", + "eyre", + "futures-util", + "reth-cli-commands", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-downloaders", + "reth-errors", + "reth-evm-optimism", + "reth-execution-types", + "reth-network-p2p", + "reth-node-core", + "reth-node-events", + "reth-optimism-primitives", + "reth-primitives", + "reth-provider", + "reth-prune", + "reth-stages", + "reth-stages-types", + "reth-static-file", + "reth-static-file-types", + "tokio", + "tracing", +] [[package]] name = "reth-optimism-consensus" diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 44edb0d9de3e..6ea5c346a5e8 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -67,6 +67,8 @@ reth-consensus.workspace = true reth-optimism-primitives.workspace = true reth-engine-util.workspace = true reth-prune.workspace = true +reth-stages-api.workspace = true +reth-optimism-cli = { workspace = true, optional = true } # crypto alloy-rlp.workspace = true @@ -135,6 +137,8 @@ min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] optimism = [ + "dep:reth-optimism-cli", + "reth-optimism-cli?/optimism", "reth-primitives/optimism", "reth-rpc/optimism", "reth-provider/optimism", diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index 2d253ea32c81..a7e8a759a0f7 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -197,11 +197,11 @@ pub enum Commands { /// This syncs RLP encoded OP blocks below Bedrock from a file, without executing. #[cfg(feature = "optimism")] #[command(name = "import-op")] - ImportOp(crate::commands::import_op::ImportOpCommand), + ImportOp(reth_optimism_cli::ImportOpCommand), /// This imports RLP encoded receipts from a file. #[cfg(feature = "optimism")] #[command(name = "import-receipts-op")] - ImportReceiptsOp(crate::commands::import_receipts_op::ImportReceiptsOpCommand), + ImportReceiptsOp(reth_optimism_cli::ImportReceiptsOpCommand), /// Dumps genesis block JSON configuration to stdout. DumpGenesis(dump_genesis::DumpGenesisCommand), /// Database debugging utilities diff --git a/bin/reth/src/commands/mod.rs b/bin/reth/src/commands/mod.rs index 36290922a67c..a8f5ff3fdf8a 100644 --- a/bin/reth/src/commands/mod.rs +++ b/bin/reth/src/commands/mod.rs @@ -4,8 +4,6 @@ pub mod config_cmd; pub mod debug_cmd; pub mod dump_genesis; pub mod import; -pub mod import_op; -pub mod import_receipts_op; pub mod init_cmd; pub mod init_state; pub mod node; diff --git a/crates/optimism/cli/Cargo.toml b/crates/optimism/cli/Cargo.toml index 05201b658f76..b5cd12c33893 100644 --- a/crates/optimism/cli/Cargo.toml +++ b/crates/optimism/cli/Cargo.toml @@ -8,3 +8,49 @@ homepage.workspace = true repository.workspace = true [lints] +workspace = true + +[dependencies] +reth-static-file-types = { workspace = true, features = ["clap"] } +clap = { workspace = true, features = ["derive", "env"] } +reth-cli-commands.workspace = true +reth-consensus.workspace = true +reth-db = { workspace = true, features = ["mdbx"] } +reth-db-api.workspace = true +reth-downloaders.workspace = true +reth-optimism-primitives.workspace = true +reth-provider.workspace = true +reth-prune.workspace = true +reth-stages.workspace = true +reth-static-file.workspace = true +reth-execution-types.workspace = true +reth-node-core.workspace = true +reth-primitives.workspace = true + + +reth-stages-types.workspace = true +reth-node-events.workspace = true +reth-network-p2p.workspace = true +reth-errors.workspace = true + +reth-config.workspace = true +alloy-primitives.workspace = true +futures-util.workspace = true +reth-evm-optimism.workspace = true + + + +tokio = { workspace = true, features = [ + "sync", + "macros", + "time", + "rt-multi-thread", +] } +tracing.workspace = true +eyre.workspace = true + +[features] + optimism = [ + "reth-primitives/optimism", + "reth-evm-optimism/optimism", + ] \ No newline at end of file diff --git a/crates/optimism/cli/src/commands/build_pipeline.rs b/crates/optimism/cli/src/commands/build_pipeline.rs new file mode 100644 index 000000000000..29761d0f7e49 --- /dev/null +++ b/crates/optimism/cli/src/commands/build_pipeline.rs @@ -0,0 +1,96 @@ +use alloy_primitives::B256; +use futures_util::{Stream, StreamExt}; +use reth_config::Config; +use reth_consensus::Consensus; +use reth_db_api::database::Database; +use reth_downloaders::{ + bodies::bodies::BodiesDownloaderBuilder, file_client::FileClient, + headers::reverse_headers::ReverseHeadersDownloaderBuilder, +}; +use reth_errors::ProviderError; +use reth_evm_optimism::OpExecutorProvider; +use reth_network_p2p::{ + bodies::downloader::BodyDownloader, + headers::downloader::{HeaderDownloader, SyncTarget}, +}; +use reth_node_events::node::NodeEvent; +use reth_provider::{BlockNumReader, ChainSpecProvider, HeaderProvider, ProviderFactory}; +use reth_prune::PruneModes; +use reth_stages::{sets::DefaultStages, Pipeline, StageSet}; +use reth_stages_types::StageId; +use reth_static_file::StaticFileProducer; +use std::sync::Arc; +use tokio::sync::watch; + +/// Builds import pipeline. +/// +/// If configured to execute, all stages will run. Otherwise, only stages that don't require state +/// will run. +pub async fn build_import_pipeline( + config: &Config, + provider_factory: ProviderFactory, + consensus: &Arc, + file_client: Arc, + static_file_producer: StaticFileProducer, + disable_exec: bool, +) -> eyre::Result<(Pipeline, impl Stream)> +where + DB: Database + Clone + Unpin + 'static, + C: Consensus + 'static, +{ + if !file_client.has_canonical_blocks() { + eyre::bail!("unable to import non canonical blocks"); + } + + // Retrieve latest header found in the database. + let last_block_number = provider_factory.last_block_number()?; + let local_head = provider_factory + .sealed_header(last_block_number)? + .ok_or(ProviderError::HeaderNotFound(last_block_number.into()))?; + + let mut header_downloader = ReverseHeadersDownloaderBuilder::new(config.stages.headers) + .build(file_client.clone(), consensus.clone()) + .into_task(); + // TODO: The pipeline should correctly configure the downloader on its own. + // Find the possibility to remove unnecessary pre-configuration. + header_downloader.update_local_head(local_head); + header_downloader.update_sync_target(SyncTarget::Tip(file_client.tip().unwrap())); + + let mut body_downloader = BodiesDownloaderBuilder::new(config.stages.bodies) + .build(file_client.clone(), consensus.clone(), provider_factory.clone()) + .into_task(); + // TODO: The pipeline should correctly configure the downloader on its own. + // Find the possibility to remove unnecessary pre-configuration. + body_downloader + .set_download_range(file_client.min_block().unwrap()..=file_client.max_block().unwrap()) + .expect("failed to set download range"); + + let (tip_tx, tip_rx) = watch::channel(B256::ZERO); + let executor = OpExecutorProvider::optimism(provider_factory.chain_spec()); + + let max_block = file_client.max_block().unwrap_or(0); + + let pipeline = Pipeline::builder() + .with_tip_sender(tip_tx) + // we want to sync all blocks the file client provides or 0 if empty + .with_max_block(max_block) + .add_stages( + DefaultStages::new( + provider_factory.clone(), + tip_rx, + consensus.clone(), + header_downloader, + body_downloader, + executor, + config.stages.clone(), + PruneModes::default(), + ) + .builder() + .disable_all_if(&StageId::STATE_REQUIRED, || disable_exec), + ) + .build(provider_factory, static_file_producer); + + let events = pipeline.events().map(Into::into); + + Ok((pipeline, events)) +} diff --git a/bin/reth/src/commands/import_op.rs b/crates/optimism/cli/src/commands/import.rs similarity index 98% rename from bin/reth/src/commands/import_op.rs rename to crates/optimism/cli/src/commands/import.rs index 3d308ba0d826..d28ec658a0f2 100644 --- a/bin/reth/src/commands/import_op.rs +++ b/crates/optimism/cli/src/commands/import.rs @@ -1,6 +1,5 @@ //! Command that initializes the node by importing OP Mainnet chain segment below Bedrock, from a //! file. -use crate::{commands::import::build_import_pipeline, version::SHORT_VERSION}; use clap::Parser; use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_consensus::noop::NoopConsensus; @@ -9,6 +8,7 @@ use reth_db_api::transaction::DbTx; use reth_downloaders::file_client::{ ChunkedFileReader, FileClient, DEFAULT_BYTE_LEN_CHUNK_CHAIN_FILE, }; +use reth_node_core::version::SHORT_VERSION; use reth_optimism_primitives::bedrock_import::is_dup_tx; use reth_provider::StageCheckpointReader; use reth_prune::PruneModes; @@ -17,6 +17,8 @@ use reth_static_file::StaticFileProducer; use std::{path::PathBuf, sync::Arc}; use tracing::{debug, error, info}; +use crate::commands::build_pipeline::build_import_pipeline; + /// Syncs RLP encoded blocks from a file. #[derive(Debug, Parser)] pub struct ImportOpCommand { diff --git a/bin/reth/src/commands/import_receipts_op.rs b/crates/optimism/cli/src/commands/import_receipts.rs similarity index 100% rename from bin/reth/src/commands/import_receipts_op.rs rename to crates/optimism/cli/src/commands/import_receipts.rs diff --git a/crates/optimism/cli/src/commands/mod.rs b/crates/optimism/cli/src/commands/mod.rs new file mode 100644 index 000000000000..373e7802cd4a --- /dev/null +++ b/crates/optimism/cli/src/commands/mod.rs @@ -0,0 +1,4 @@ +/// Helper function to build an import pipeline. +pub mod build_pipeline; +pub mod import; +pub mod import_receipts; diff --git a/crates/optimism/cli/src/lib.rs b/crates/optimism/cli/src/lib.rs index a5133bea139c..67d0ccd6176c 100644 --- a/crates/optimism/cli/src/lib.rs +++ b/crates/optimism/cli/src/lib.rs @@ -5,5 +5,11 @@ html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "optimism")] + +/// Optimism CLI commands. +pub mod commands; +pub use commands::{import::ImportOpCommand, import_receipts::ImportReceiptsOpCommand}; From 7a647f4f1e08a031d4dde3aecb49960401dfb659 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:54:28 +0200 Subject: [PATCH 308/405] chore: move `pre_block_beacon_root_contract_call` to evm crates (#9244) --- Cargo.lock | 1 + crates/ethereum/evm/src/execute.rs | 7 +- crates/ethereum/evm/src/lib.rs | 41 +++++- crates/ethereum/payload/src/lib.rs | 29 ++-- crates/evm/Cargo.toml | 2 +- crates/evm/src/lib.rs | 13 +- crates/evm/src/system_calls.rs | 125 ++++++++++++++++++ crates/optimism/evm/src/execute.rs | 5 +- crates/optimism/evm/src/lib.rs | 44 ++++++ crates/optimism/payload/src/builder.rs | 26 +++- crates/payload/basic/src/lib.rs | 46 +------ crates/primitives/src/revm/env.rs | 23 +--- crates/revm/src/state_change.rs | 73 +--------- .../rpc-eth-api/src/helpers/pending_block.rs | 13 +- crates/rpc/rpc-eth-types/src/pending_block.rs | 43 +----- examples/custom-evm/src/main.rs | 9 ++ examples/stateful-precompile/src/main.rs | 9 ++ 17 files changed, 302 insertions(+), 207 deletions(-) create mode 100644 crates/evm/src/system_calls.rs diff --git a/Cargo.lock b/Cargo.lock index 99cfc38d7804..00d261324996 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7197,6 +7197,7 @@ dependencies = [ name = "reth-evm" version = "1.0.0" dependencies = [ + "alloy-eips", "auto_impl", "futures-util", "parking_lot 0.12.3", diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 084bb412b6b9..b4da027f88f1 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -11,6 +11,7 @@ use reth_evm::{ BatchExecutor, BlockExecutionError, BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, BlockValidationError, Executor, ProviderError, }, + system_calls::apply_beacon_root_contract_call, ConfigureEvm, }; use reth_execution_types::ExecutionOutcome; @@ -22,8 +23,8 @@ use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, db::states::bundle_state::BundleRetention, state_change::{ - apply_beacon_root_contract_call, apply_blockhashes_update, - apply_withdrawal_requests_contract_call, post_block_balance_increments, + apply_blockhashes_update, apply_withdrawal_requests_contract_call, + post_block_balance_increments, }, Evm, State, }; @@ -147,7 +148,7 @@ where DB::Error: Into + std::fmt::Display, { // apply pre execution changes - apply_beacon_root_contract_call( + apply_beacon_root_contract_call::( &self.chain_spec, block.timestamp, block.number, diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 86386ceb5af0..1c0b6b83bf2f 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -16,7 +16,7 @@ use reth_chainspec::{ChainSpec, Head}; use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_primitives::{transaction::FillTxEnv, Address, Header, TransactionSigned, U256}; use reth_revm::{Database, EvmBuilder}; -use revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}; +use revm_primitives::{AnalysisKind, Bytes, CfgEnvWithHandlerCfg, Env, TxEnv, TxKind}; mod config; pub use config::{revm_spec, revm_spec_by_timestamp_after_merge}; @@ -61,6 +61,45 @@ impl ConfigureEvmEnv for EthEvmConfig { fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { transaction.fill_tx_env(tx_env, sender); } + + fn fill_tx_env_system_contract_call( + env: &mut Env, + caller: Address, + contract: Address, + data: Bytes, + ) { + #[allow(clippy::needless_update)] // side-effect of optimism fields + let tx = TxEnv { + caller, + transact_to: TxKind::Call(contract), + // Explicitly set nonce to None so revm does not do any nonce checks + nonce: None, + gas_limit: 30_000_000, + value: U256::ZERO, + data, + // Setting the gas price to zero enforces that no value is transferred as part of the + // call, and that the call will not count against the block's gas limit + gas_price: U256::ZERO, + // The chain ID check is not relevant here and is disabled if set to None + chain_id: None, + // Setting the gas priority fee to None ensures the effective gas price is derived from + // the `gas_price` field, which we need to be zero + gas_priority_fee: None, + access_list: Vec::new(), + // blob fields can be None for this tx + blob_hashes: Vec::new(), + max_fee_per_blob_gas: None, + // TODO remove this once this crate is no longer built with optimism + ..Default::default() + }; + env.tx = tx; + + // ensure the block gas limit is >= the tx + env.block.gas_limit = U256::from(env.tx.gas_limit); + + // disable the base fee check for this call by setting the base fee to zero + env.block.basefee = U256::ZERO; + } } impl ConfigureEvm for EthEvmConfig { diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 84d43b33c149..dabaedeeb3b1 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -11,11 +11,10 @@ use reth_basic_payload_builder::{ commit_withdrawals, is_better_payload, post_block_withdrawal_requests_contract_call, - pre_block_beacon_root_contract_call, BuildArguments, BuildOutcome, PayloadBuilder, - PayloadConfig, WithdrawalsOutcome, + BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig, WithdrawalsOutcome, }; use reth_errors::RethError; -use reth_evm::ConfigureEvm; +use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm}; use reth_evm_ethereum::{eip6110::parse_deposits_from_receipts, EthEvmConfig}; use reth_execution_types::ExecutionOutcome; use reth_payload_builder::{ @@ -114,11 +113,13 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, + self.evm_config.clone(), &chain_spec, - block_number, &initialized_cfg, &initialized_block_env, - &attributes, + block_number, + attributes.timestamp, + attributes.parent_beacon_block_root, ) .map_err(|err| { warn!(target: "payload_builder", @@ -126,7 +127,7 @@ where %err, "failed to apply beacon root contract call for empty payload" ); - err + PayloadBuilderError::Internal(err.into()) })?; // apply eip-2935 blockhashes update @@ -288,12 +289,22 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, + evm_config.clone(), &chain_spec, - block_number, &initialized_cfg, &initialized_block_env, - &attributes, - )?; + block_number, + attributes.timestamp, + attributes.parent_beacon_block_root, + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to apply beacon root contract call for empty payload" + ); + PayloadBuilderError::Internal(err.into()) + })?; // apply eip-2935 blockhashes update apply_blockhashes_update( diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index 23f7e1b25768..ab338371984b 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -21,7 +21,7 @@ reth-storage-errors.workspace = true reth-execution-types.workspace = true revm.workspace = true - +alloy-eips.workspace = true auto_impl.workspace = true futures-util.workspace = true parking_lot = { workspace = true, optional = true } diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index e5e9dbdf7122..325eb7e29607 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -19,12 +19,15 @@ use reth_primitives::{ header::block_coinbase, Address, Header, TransactionSigned, TransactionSignedEcRecovered, U256, }; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; -use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; +use revm_primitives::{ + BlockEnv, Bytes, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg, SpecId, TxEnv, +}; pub mod either; pub mod execute; pub mod noop; pub mod provider; +pub mod system_calls; #[cfg(any(test, feature = "test-utils"))] /// test helpers for mocking executor @@ -117,6 +120,14 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { /// Fill transaction environment from a [`TransactionSigned`] and the given sender address. fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address); + /// Fill transaction environment with a system contract call. + fn fill_tx_env_system_contract_call( + env: &mut Env, + caller: Address, + contract: Address, + data: Bytes, + ); + /// Fill [`CfgEnvWithHandlerCfg`] fields according to the chain spec and given header fn fill_cfg_env( cfg_env: &mut CfgEnvWithHandlerCfg, diff --git a/crates/evm/src/system_calls.rs b/crates/evm/src/system_calls.rs new file mode 100644 index 000000000000..2e247ba38202 --- /dev/null +++ b/crates/evm/src/system_calls.rs @@ -0,0 +1,125 @@ +//! System contract call functions. + +use alloy_eips::eip4788::BEACON_ROOTS_ADDRESS; +use reth_chainspec::{ChainSpec, EthereumHardforks}; +use reth_execution_errors::{BlockExecutionError, BlockValidationError}; +use revm::{interpreter::Host, Database, DatabaseCommit, Evm}; +use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, B256}; + +use crate::ConfigureEvm; + +/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call. +/// +/// This constructs a new [Evm] with the given DB, and environment +/// ([`CfgEnvWithHandlerCfg`] and [`BlockEnv`]) to execute the pre block contract call. +/// +/// This uses [`apply_beacon_root_contract_call`] to ultimately apply the beacon root contract state +/// change. +#[allow(clippy::too_many_arguments)] +pub fn pre_block_beacon_root_contract_call( + db: &mut DB, + _emv_config: EvmConfig, + chain_spec: &ChainSpec, + initialized_cfg: &CfgEnvWithHandlerCfg, + initialized_block_env: &BlockEnv, + block_number: u64, + block_timestamp: u64, + parent_beacon_block_root: Option, +) -> Result<(), BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: std::fmt::Display, + EvmConfig: ConfigureEvm, +{ + // apply pre-block EIP-4788 contract call + let mut evm_pre_block = Evm::builder() + .with_db(db) + .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + Default::default(), + )) + .build(); + + // initialize a block from the env, because the pre block call needs the block itself + apply_beacon_root_contract_call::( + chain_spec, + block_timestamp, + block_number, + parent_beacon_block_root, + &mut evm_pre_block, + ) +} + +/// Applies the pre-block call to the [EIP-4788] beacon block root contract, using the given block, +/// [`ChainSpec`], EVM. +/// +/// If Cancun is not activated or the block is the genesis block, then this is a no-op, and no +/// state changes are made. +/// +/// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788 +#[inline] +pub fn apply_beacon_root_contract_call( + chain_spec: &ChainSpec, + block_timestamp: u64, + block_number: u64, + parent_beacon_block_root: Option, + evm: &mut Evm<'_, EXT, DB>, +) -> Result<(), BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: core::fmt::Display, + EvmConfig: ConfigureEvm, +{ + if !chain_spec.is_cancun_active_at_timestamp(block_timestamp) { + return Ok(()) + } + + let parent_beacon_block_root = + parent_beacon_block_root.ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?; + + // if the block number is zero (genesis block) then the parent beacon block root must + // be 0x0 and no system transaction may occur as per EIP-4788 + if block_number == 0 { + if parent_beacon_block_root != B256::ZERO { + return Err(BlockValidationError::CancunGenesisParentBeaconBlockRootNotZero { + parent_beacon_block_root, + } + .into()) + } + return Ok(()) + } + + // get previous env + let previous_env = Box::new(evm.context.env().clone()); + + // modify env for pre block call + EvmConfig::fill_tx_env_system_contract_call( + &mut evm.context.evm.env, + alloy_eips::eip4788::SYSTEM_ADDRESS, + BEACON_ROOTS_ADDRESS, + parent_beacon_block_root.0.into(), + ); + + let mut state = match evm.transact() { + Ok(res) => res.state, + Err(e) => { + evm.context.evm.env = previous_env; + return Err(BlockValidationError::BeaconRootContractCall { + parent_beacon_block_root: Box::new(parent_beacon_block_root), + message: e.to_string(), + } + .into()) + } + }; + + state.remove(&alloy_eips::eip4788::SYSTEM_ADDRESS); + state.remove(&evm.block().coinbase); + + evm.context.evm.db.commit(state); + + // re-set the previous env + evm.context.evm.env = previous_env; + + Ok(()) +} diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index de4fc071e0cf..2c797c78626e 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -7,6 +7,7 @@ use reth_evm::{ BatchExecutor, BlockExecutionError, BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, BlockValidationError, Executor, ProviderError, }, + system_calls::apply_beacon_root_contract_call, ConfigureEvm, }; use reth_execution_types::ExecutionOutcome; @@ -16,7 +17,7 @@ use reth_prune_types::PruneModes; use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, db::states::bundle_state::BundleRetention, - state_change::{apply_beacon_root_contract_call, post_block_balance_increments}, + state_change::post_block_balance_increments, Evm, State, }; use revm_primitives::{ @@ -121,7 +122,7 @@ where DB: Database + std::fmt::Display>, { // apply pre execution changes - apply_beacon_root_contract_call( + apply_beacon_root_contract_call::( &self.chain_spec, block.timestamp, block.number, diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 2d1b0afb07f7..61f6838ddbd8 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -27,6 +27,7 @@ pub use l1::*; mod error; pub use error::OptimismBlockExecutionError; +use revm_primitives::{Bytes, Env, OptimismFields, TxKind}; /// Optimism-related EVM configuration. #[derive(Debug, Default, Clone, Copy)] @@ -38,6 +39,49 @@ impl ConfigureEvmEnv for OptimismEvmConfig { transaction.fill_tx_env(tx_env, sender); } + fn fill_tx_env_system_contract_call( + env: &mut Env, + caller: Address, + contract: Address, + data: Bytes, + ) { + env.tx = TxEnv { + caller, + transact_to: TxKind::Call(contract), + // Explicitly set nonce to None so revm does not do any nonce checks + nonce: None, + gas_limit: 30_000_000, + value: U256::ZERO, + data, + // Setting the gas price to zero enforces that no value is transferred as part of the + // call, and that the call will not count against the block's gas limit + gas_price: U256::ZERO, + // The chain ID check is not relevant here and is disabled if set to None + chain_id: None, + // Setting the gas priority fee to None ensures the effective gas price is derived from + // the `gas_price` field, which we need to be zero + gas_priority_fee: None, + access_list: Vec::new(), + // blob fields can be None for this tx + blob_hashes: Vec::new(), + max_fee_per_blob_gas: None, + optimism: OptimismFields { + source_hash: None, + mint: None, + is_system_transaction: Some(false), + // The L1 fee is not charged for the EIP-4788 transaction, submit zero bytes for the + // enveloped tx size. + enveloped_tx: Some(Bytes::default()), + }, + }; + + // ensure the block gas limit is >= the tx + env.block.gas_limit = U256::from(env.tx.gas_limit); + + // disable the base fee check for this call by setting the base fee to zero + env.block.basefee = U256::ZERO; + } + fn fill_cfg_env( cfg_env: &mut CfgEnvWithHandlerCfg, chain_spec: &ChainSpec, diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 9bb8d3cdcdf5..4df6c67d246d 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -6,7 +6,7 @@ use crate::{ }; use reth_basic_payload_builder::*; use reth_chainspec::{ChainSpec, EthereumHardforks, OptimismHardfork}; -use reth_evm::ConfigureEvm; +use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm}; use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; use reth_primitives::{ @@ -125,11 +125,13 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, + self.evm_config.clone(), &chain_spec, - block_number, &initialized_cfg, &initialized_block_env, - &attributes, + block_number, + attributes.payload_attributes.timestamp, + attributes.payload_attributes.parent_beacon_block_root, ) .map_err(|err| { warn!(target: "payload_builder", @@ -137,7 +139,7 @@ where %err, "failed to apply beacon root contract call for empty payload" ); - err + PayloadBuilderError::Internal(err.into()) })?; let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals( @@ -286,12 +288,22 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, + evm_config.clone(), &chain_spec, - block_number, &initialized_cfg, &initialized_block_env, - &attributes, - )?; + block_number, + attributes.payload_attributes.timestamp, + attributes.payload_attributes.parent_beacon_block_root, + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to apply beacon root contract call for empty payload" + ); + PayloadBuilderError::Internal(err.into()) + })?; // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism // blocks will always have at least a single transaction in them (the L1 info transaction), diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index d677ce842ffd..d7c58d3bdc92 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -25,8 +25,7 @@ use reth_provider::{ BlockReaderIdExt, BlockSource, CanonStateNotification, ProviderError, StateProviderFactory, }; use reth_revm::state_change::{ - apply_beacon_root_contract_call, apply_withdrawal_requests_contract_call, - post_block_withdrawals_balance_increments, + apply_withdrawal_requests_contract_call, post_block_withdrawals_balance_increments, }; use reth_tasks::TaskSpawner; use reth_transaction_pool::TransactionPool; @@ -923,49 +922,6 @@ pub fn commit_withdrawals>( }) } -/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call. -/// -/// This constructs a new [Evm] with the given DB, and environment -/// ([`CfgEnvWithHandlerCfg`] and [`BlockEnv`]) to execute the pre block contract call. -/// -/// The parent beacon block root used for the call is gathered from the given -/// [`PayloadBuilderAttributes`]. -/// -/// This uses [`apply_beacon_root_contract_call`] to ultimately apply the beacon root contract state -/// change. -pub fn pre_block_beacon_root_contract_call( - db: &mut DB, - chain_spec: &ChainSpec, - block_number: u64, - initialized_cfg: &CfgEnvWithHandlerCfg, - initialized_block_env: &BlockEnv, - attributes: &Attributes, -) -> Result<(), PayloadBuilderError> -where - DB::Error: std::fmt::Display, - Attributes: PayloadBuilderAttributes, -{ - // apply pre-block EIP-4788 contract call - let mut evm_pre_block = Evm::builder() - .with_db(db) - .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - Default::default(), - )) - .build(); - - // initialize a block from the env, because the pre block call needs the block itself - apply_beacon_root_contract_call( - chain_spec, - attributes.timestamp(), - block_number, - attributes.parent_beacon_block_root(), - &mut evm_pre_block, - ) - .map_err(|err| PayloadBuilderError::Internal(err.into())) -} - /// Apply the [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) post block contract call. /// /// This constructs a new [Evm] with the given DB, and environment diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index 5c7ae7b0d5de..81fee7d8c2f6 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -1,34 +1,15 @@ use crate::{ revm_primitives::{Env, TxEnv}, - Address, Bytes, TxKind, B256, U256, + Address, Bytes, TxKind, U256, }; -use alloy_eips::{eip4788::BEACON_ROOTS_ADDRESS, eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS}; +use alloy_eips::eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS; #[cfg(feature = "optimism")] use revm_primitives::OptimismFields; #[cfg(not(feature = "std"))] use alloc::vec::Vec; -/// Fill transaction environment with the EIP-4788 system contract message data. -/// -/// This requirements for the beacon root contract call defined by -/// [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) are: -/// -/// At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. -/// before processing any transactions), call [`BEACON_ROOTS_ADDRESS`] as -/// [`SYSTEM_ADDRESS`](alloy_eips::eip4788::SYSTEM_ADDRESS) with the 32-byte input of -/// `header.parent_beacon_block_root`. This will trigger the `set()` routine of the beacon roots -/// contract. -pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_block_root: B256) { - fill_tx_env_with_system_contract_call( - env, - alloy_eips::eip4788::SYSTEM_ADDRESS, - BEACON_ROOTS_ADDRESS, - parent_beacon_block_root.0.into(), - ); -} - /// Fill transaction environment with the EIP-7002 withdrawal requests contract message data. // /// This requirement for the withdrawal requests contract call defined by diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index 5fad6ae42563..01098182fc4f 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -7,11 +7,8 @@ use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus_common::calc; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ - revm::env::{ - fill_tx_env_with_beacon_root_contract_call, - fill_tx_env_with_withdrawal_requests_contract_call, - }, - Address, Block, Request, Withdrawal, Withdrawals, B256, U256, + revm::env::fill_tx_env_with_withdrawal_requests_contract_call, Address, Block, Request, + Withdrawal, Withdrawals, B256, U256, }; use reth_storage_errors::provider::ProviderError; use revm::{ @@ -139,72 +136,6 @@ fn eip2935_block_hash_slot>>( Ok((slot, EvmStorageSlot::new_changed(current_hash, block_hash.into()))) } -/// Applies the pre-block call to the [EIP-4788] beacon block root contract, using the given block, -/// [`ChainSpec`], EVM. -/// -/// If Cancun is not activated or the block is the genesis block, then this is a no-op, and no -/// state changes are made. -/// -/// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788 -#[inline] -pub fn apply_beacon_root_contract_call( - chain_spec: &ChainSpec, - block_timestamp: u64, - block_number: u64, - parent_beacon_block_root: Option, - evm: &mut Evm<'_, EXT, DB>, -) -> Result<(), BlockExecutionError> -where - DB::Error: core::fmt::Display, -{ - if !chain_spec.is_cancun_active_at_timestamp(block_timestamp) { - return Ok(()) - } - - let parent_beacon_block_root = - parent_beacon_block_root.ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?; - - // if the block number is zero (genesis block) then the parent beacon block root must - // be 0x0 and no system transaction may occur as per EIP-4788 - if block_number == 0 { - if parent_beacon_block_root != B256::ZERO { - return Err(BlockValidationError::CancunGenesisParentBeaconBlockRootNotZero { - parent_beacon_block_root, - } - .into()) - } - return Ok(()) - } - - // get previous env - let previous_env = Box::new(evm.context.env().clone()); - - // modify env for pre block call - fill_tx_env_with_beacon_root_contract_call(&mut evm.context.evm.env, parent_beacon_block_root); - - let mut state = match evm.transact() { - Ok(res) => res.state, - Err(e) => { - evm.context.evm.env = previous_env; - return Err(BlockValidationError::BeaconRootContractCall { - parent_beacon_block_root: Box::new(parent_beacon_block_root), - message: e.to_string(), - } - .into()) - } - }; - - state.remove(&alloy_eips::eip4788::SYSTEM_ADDRESS); - state.remove(&evm.block().coinbase); - - evm.context.evm.db.commit(state); - - // re-set the previous env - evm.context.evm.env = previous_env; - - Ok(()) -} - /// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the /// given timestamp. /// diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index 8f3e38817695..b421d7b10b51 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -5,7 +5,7 @@ use std::time::{Duration, Instant}; use futures::Future; use reth_chainspec::EthereumHardforks; -use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; +use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm, ConfigureEvmEnv}; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH}, @@ -25,8 +25,8 @@ use reth_revm::{ database::StateProviderDatabase, state_change::post_block_withdrawals_balance_increments, }; use reth_rpc_eth_types::{ - pending_block::{pre_block_beacon_root_contract_call, pre_block_blockhashes_update}, - EthApiError, EthResult, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin, + pending_block::pre_block_blockhashes_update, EthApiError, EthResult, PendingBlock, + PendingBlockEnv, PendingBlockEnvOrigin, }; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State}; @@ -235,12 +235,15 @@ pub trait LoadPendingBlock { // parent beacon block root pre_block_beacon_root_contract_call( &mut db, + self.evm_config().clone(), chain_spec.as_ref(), - block_number, &cfg, &block_env, + block_number, + block_env.timestamp.to::(), origin.header().parent_beacon_block_root, - )?; + ) + .map_err(|err| EthApiError::Internal(err.into()))?; origin.header().parent_beacon_block_root } else { None diff --git a/crates/rpc/rpc-eth-types/src/pending_block.rs b/crates/rpc/rpc-eth-types/src/pending_block.rs index 7e27a9d6b692..64dd2aeb59b4 100644 --- a/crates/rpc/rpc-eth-types/src/pending_block.rs +++ b/crates/rpc/rpc-eth-types/src/pending_block.rs @@ -8,10 +8,10 @@ use derive_more::Constructor; use reth_chainspec::ChainSpec; use reth_primitives::{BlockId, BlockNumberOrTag, SealedBlockWithSenders, SealedHeader, B256}; use reth_provider::ProviderError; -use reth_revm::state_change::{apply_beacon_root_contract_call, apply_blockhashes_update}; +use reth_revm::state_change::apply_blockhashes_update; use revm_primitives::{ db::{Database, DatabaseCommit}, - BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, + BlockEnv, CfgEnvWithHandlerCfg, }; use super::{EthApiError, EthResult}; @@ -27,45 +27,6 @@ pub struct PendingBlockEnv { pub origin: PendingBlockEnvOrigin, } -/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call. -/// -/// This constructs a new [Evm](revm::Evm) with the given DB, and environment -/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] to execute the pre block contract call. -/// -/// This uses [`apply_beacon_root_contract_call`] to ultimately apply the beacon root contract state -/// change. -pub fn pre_block_beacon_root_contract_call( - db: &mut DB, - chain_spec: &ChainSpec, - block_number: u64, - initialized_cfg: &CfgEnvWithHandlerCfg, - initialized_block_env: &BlockEnv, - parent_beacon_block_root: Option, -) -> EthResult<()> -where - DB::Error: fmt::Display, -{ - // apply pre-block EIP-4788 contract call - let mut evm_pre_block = revm::Evm::builder() - .with_db(db) - .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - Default::default(), - )) - .build(); - - // initialize a block from the env, because the pre block call needs the block itself - apply_beacon_root_contract_call( - chain_spec, - initialized_block_env.timestamp.to::(), - block_number, - parent_beacon_block_root, - &mut evm_pre_block, - ) - .map_err(|err| EthApiError::Internal(err.into())) -} - /// Apply the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) pre block state transitions. /// /// This constructs a new [Evm](revm::Evm) with the given DB, and environment diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 388438857500..a9d8058b9193 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -93,6 +93,15 @@ impl ConfigureEvmEnv for MyEvmConfig { fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { EthEvmConfig::default().fill_tx_env(tx_env, transaction, sender) } + + fn fill_tx_env_system_contract_call( + env: &mut Env, + caller: Address, + contract: Address, + data: Bytes, + ) { + EthEvmConfig::fill_tx_env_system_contract_call(env, caller, contract, data) + } } impl ConfigureEvm for MyEvmConfig { diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index adca439d764a..538adfaafe75 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -145,6 +145,15 @@ impl ConfigureEvmEnv for MyEvmConfig { ) { EthEvmConfig::fill_cfg_env(cfg_env, chain_spec, header, total_difficulty) } + + fn fill_tx_env_system_contract_call( + env: &mut Env, + caller: Address, + contract: Address, + data: Bytes, + ) { + EthEvmConfig::fill_tx_env_system_contract_call(env, caller, contract, data) + } } impl ConfigureEvm for MyEvmConfig { From 3440b2f2b57c3f721a25daec4706b62ae7410855 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Wed, 3 Jul 2024 16:56:56 +0200 Subject: [PATCH 309/405] feat: add ethereum engine chain orchestrator (#9241) --- Cargo.lock | 18 +++ Cargo.toml | 1 + crates/engine/tree/Cargo.toml | 16 ++- crates/engine/tree/src/backfill.rs | 58 +-------- crates/engine/tree/src/engine.rs | 19 ++- crates/engine/tree/src/lib.rs | 8 +- crates/engine/tree/src/test_utils.rs | 60 ++++++++- crates/ethereum/engine/Cargo.toml | 31 +++++ crates/ethereum/engine/src/lib.rs | 12 ++ crates/ethereum/engine/src/orchestrator.rs | 138 +++++++++++++++++++++ 10 files changed, 297 insertions(+), 64 deletions(-) create mode 100644 crates/ethereum/engine/Cargo.toml create mode 100644 crates/ethereum/engine/src/lib.rs create mode 100644 crates/ethereum/engine/src/orchestrator.rs diff --git a/Cargo.lock b/Cargo.lock index 00d261324996..277cbf904170 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7128,6 +7128,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-ethereum-engine" +version = "1.0.0" +dependencies = [ + "futures", + "pin-project", + "reth-beacon-consensus", + "reth-chainspec", + "reth-db-api", + "reth-engine-tree", + "reth-ethereum-engine-primitives", + "reth-network-p2p", + "reth-stages-api", + "reth-tasks", + "tokio", + "tokio-stream", +] + [[package]] name = "reth-ethereum-engine-primitives" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 936de53b63b4..417fe3ef8293 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "crates/errors/", "crates/ethereum-forks/", "crates/ethereum/consensus/", + "crates/ethereum/engine/", "crates/ethereum/engine-primitives/", "crates/ethereum/evm", "crates/ethereum/node", diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index bcc8ae34bddd..e2a1c462d6c8 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -30,6 +30,7 @@ reth-payload-validator.workspace = true reth-primitives.workspace = true reth-provider.workspace = true reth-prune.workspace = true +reth-prune-types.workspace = true reth-revm.workspace = true reth-rpc-types.workspace = true reth-stages-api.workspace = true @@ -54,11 +55,24 @@ aquamarine.workspace = true parking_lot.workspace = true tracing.workspace = true +# optional deps for test-utils +reth-stages = { workspace = true, optional = true } +reth-tracing = { workspace = true, optional = true } + [dev-dependencies] # reth +reth-db = { workspace = true, features = ["test-utils"] } reth-network-p2p = { workspace = true, features = ["test-utils"] } reth-prune-types.workspace = true reth-stages = { workspace = true, features = ["test-utils"] } reth-tracing.workspace = true -assert_matches.workspace = true \ No newline at end of file +assert_matches.workspace = true + +[features] +test-utils = [ + "reth-db/test-utils", + "reth-network-p2p/test-utils", + "reth-stages/test-utils", + "reth-tracing" +] diff --git a/crates/engine/tree/src/backfill.rs b/crates/engine/tree/src/backfill.rs index 3060ddd1d4e7..24153bed24c0 100644 --- a/crates/engine/tree/src/backfill.rs +++ b/crates/engine/tree/src/backfill.rs @@ -206,23 +206,17 @@ impl PipelineState { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::insert_headers_into_client; + use crate::test_utils::{insert_headers_into_client, TestPipelineBuilder}; use assert_matches::assert_matches; use futures::poll; - use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET}; + use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_db::{mdbx::DatabaseEnv, test_utils::TempDatabase}; use reth_network_p2p::test_utils::TestFullBlockClient; use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, BlockNumber, Header, B256}; - use reth_provider::{ - test_utils::create_test_provider_factory_with_chain_spec, ExecutionOutcome, - }; - use reth_prune_types::PruneModes; - use reth_stages::{test_utils::TestStages, ExecOutput, StageError}; + use reth_stages::ExecOutput; use reth_stages_api::StageCheckpoint; - use reth_static_file::StaticFileProducer; use reth_tasks::TokioTaskExecutor; use std::{collections::VecDeque, future::poll_fn, sync::Arc}; - use tokio::sync::watch; struct TestHarness { pipeline_sync: PipelineSync>>, @@ -263,52 +257,6 @@ mod tests { } } - struct TestPipelineBuilder { - pipeline_exec_outputs: VecDeque>, - executor_results: Vec, - } - - impl TestPipelineBuilder { - /// Create a new [`TestPipelineBuilder`]. - const fn new() -> Self { - Self { pipeline_exec_outputs: VecDeque::new(), executor_results: Vec::new() } - } - - /// Set the pipeline execution outputs to use for the test consensus engine. - fn with_pipeline_exec_outputs( - mut self, - pipeline_exec_outputs: VecDeque>, - ) -> Self { - self.pipeline_exec_outputs = pipeline_exec_outputs; - self - } - - /// Set the executor results to use for the test consensus engine. - #[allow(dead_code)] - fn with_executor_results(mut self, executor_results: Vec) -> Self { - self.executor_results = executor_results; - self - } - - /// Builds the pipeline. - fn build(self, chain_spec: Arc) -> Pipeline>> { - reth_tracing::init_test_tracing(); - - // Setup pipeline - let (tip_tx, _tip_rx) = watch::channel(B256::default()); - let pipeline = Pipeline::builder() - .add_stages(TestStages::new(self.pipeline_exec_outputs, Default::default())) - .with_tip_sender(tip_tx); - - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); - - let static_file_producer = - StaticFileProducer::new(provider_factory.clone(), PruneModes::default()); - - pipeline.build(provider_factory, static_file_producer) - } - } - #[tokio::test] async fn pipeline_started_and_finished() { const TOTAL_BLOCKS: usize = 10; diff --git a/crates/engine/tree/src/engine.rs b/crates/engine/tree/src/engine.rs index 2010c3768a42..25d4fabf7832 100644 --- a/crates/engine/tree/src/engine.rs +++ b/crates/engine/tree/src/engine.rs @@ -10,9 +10,10 @@ use reth_engine_primitives::EngineTypes; use reth_primitives::{SealedBlockWithSenders, B256}; use std::{ collections::HashSet, + sync::mpsc::Sender, task::{Context, Poll}, }; -use tokio::sync::mpsc; +use tokio::sync::mpsc::UnboundedReceiver; /// Advances the chain based on incoming requests. /// @@ -146,13 +147,23 @@ pub trait EngineRequestHandler: Send + Sync { #[derive(Debug)] pub struct EngineApiRequestHandler { /// channel to send messages to the tree to execute the payload. - to_tree: std::sync::mpsc::Sender>>, + to_tree: Sender>>, /// channel to receive messages from the tree. - from_tree: mpsc::UnboundedReceiver, + from_tree: UnboundedReceiver, // TODO add db controller } -impl EngineApiRequestHandler where T: EngineTypes {} +impl EngineApiRequestHandler +where + T: EngineTypes, +{ + pub const fn new( + to_tree: Sender>>, + from_tree: UnboundedReceiver, + ) -> Self { + Self { to_tree, from_tree } + } +} impl EngineRequestHandler for EngineApiRequestHandler where diff --git a/crates/engine/tree/src/lib.rs b/crates/engine/tree/src/lib.rs index 52ef90e4b637..8f40119b2cc0 100644 --- a/crates/engine/tree/src/lib.rs +++ b/crates/engine/tree/src/lib.rs @@ -1,4 +1,8 @@ //! This crate includes the core components for advancing a reth chain. +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", @@ -27,5 +31,5 @@ pub mod persistence; /// Support for interacting with the blockchain tree. pub mod tree; -#[cfg(test)] -mod test_utils; +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; diff --git a/crates/engine/tree/src/test_utils.rs b/crates/engine/tree/src/test_utils.rs index eed483e29932..0a5fbd5ad560 100644 --- a/crates/engine/tree/src/test_utils.rs +++ b/crates/engine/tree/src/test_utils.rs @@ -1,6 +1,62 @@ +use reth_chainspec::ChainSpec; +use reth_db::{mdbx::DatabaseEnv, test_utils::TempDatabase}; use reth_network_p2p::test_utils::TestFullBlockClient; -use reth_primitives::{BlockBody, SealedHeader}; -use std::ops::Range; +use reth_primitives::{BlockBody, SealedHeader, B256}; +use reth_provider::{test_utils::create_test_provider_factory_with_chain_spec, ExecutionOutcome}; +use reth_prune_types::PruneModes; +use reth_stages::{test_utils::TestStages, ExecOutput, StageError}; +use reth_stages_api::Pipeline; +use reth_static_file::StaticFileProducer; +use std::{collections::VecDeque, ops::Range, sync::Arc}; +use tokio::sync::watch; + +/// Test pipeline builder. +#[derive(Default)] +pub struct TestPipelineBuilder { + pipeline_exec_outputs: VecDeque>, + executor_results: Vec, +} + +impl TestPipelineBuilder { + /// Create a new [`TestPipelineBuilder`]. + pub const fn new() -> Self { + Self { pipeline_exec_outputs: VecDeque::new(), executor_results: Vec::new() } + } + + /// Set the pipeline execution outputs to use for the test consensus engine. + pub fn with_pipeline_exec_outputs( + mut self, + pipeline_exec_outputs: VecDeque>, + ) -> Self { + self.pipeline_exec_outputs = pipeline_exec_outputs; + self + } + + /// Set the executor results to use for the test consensus engine. + #[allow(dead_code)] + pub fn with_executor_results(mut self, executor_results: Vec) -> Self { + self.executor_results = executor_results; + self + } + + /// Builds the pipeline. + pub fn build(self, chain_spec: Arc) -> Pipeline>> { + reth_tracing::init_test_tracing(); + + // Setup pipeline + let (tip_tx, _tip_rx) = watch::channel(B256::default()); + let pipeline = Pipeline::builder() + .add_stages(TestStages::new(self.pipeline_exec_outputs, Default::default())) + .with_tip_sender(tip_tx); + + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); + + let static_file_producer = + StaticFileProducer::new(provider_factory.clone(), PruneModes::default()); + + pipeline.build(provider_factory, static_file_producer) + } +} pub(crate) fn insert_headers_into_client( client: &TestFullBlockClient, diff --git a/crates/ethereum/engine/Cargo.toml b/crates/ethereum/engine/Cargo.toml new file mode 100644 index 000000000000..05fbc4386cde --- /dev/null +++ b/crates/ethereum/engine/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "reth-ethereum-engine" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-beacon-consensus.workspace = true +reth-chainspec.workspace = true +reth-db-api.workspace = true +reth-engine-tree.workspace = true +reth-ethereum-engine-primitives.workspace = true +reth-network-p2p.workspace = true +reth-stages-api.workspace = true +reth-tasks.workspace = true + +# async +futures.workspace = true +pin-project.workspace = true +tokio = { workspace = true, features = ["sync"] } +tokio-stream.workspace = true + +[dev-dependencies] +reth-engine-tree = { workspace = true, features = ["test-utils"] } diff --git a/crates/ethereum/engine/src/lib.rs b/crates/ethereum/engine/src/lib.rs new file mode 100644 index 000000000000..e623dd733052 --- /dev/null +++ b/crates/ethereum/engine/src/lib.rs @@ -0,0 +1,12 @@ +//! Ethereum engine implementation. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +/// Ethereum engine orchestrator. +pub mod orchestrator; diff --git a/crates/ethereum/engine/src/orchestrator.rs b/crates/ethereum/engine/src/orchestrator.rs new file mode 100644 index 000000000000..0abf352eeab7 --- /dev/null +++ b/crates/ethereum/engine/src/orchestrator.rs @@ -0,0 +1,138 @@ +use futures::{ready, StreamExt}; +use pin_project::pin_project; +use reth_beacon_consensus::{BeaconEngineMessage, EthBeaconConsensus}; +use reth_chainspec::ChainSpec; +use reth_db_api::database::Database; +use reth_engine_tree::{ + backfill::PipelineSync, + chain::ChainOrchestrator, + download::BasicBlockDownloader, + engine::{EngineApiEvent, EngineApiRequestHandler, EngineHandler, FromEngine}, +}; +use reth_ethereum_engine_primitives::EthEngineTypes; +use reth_network_p2p::{bodies::client::BodiesClient, headers::client::HeadersClient}; +use reth_stages_api::Pipeline; +use reth_tasks::TaskSpawner; +use std::{ + future::Future, + pin::Pin, + sync::{mpsc::Sender, Arc}, + task::{Context, Poll}, +}; +use tokio::sync::mpsc::UnboundedReceiver; +use tokio_stream::wrappers::UnboundedReceiverStream; + +/// Alias for Ethereum chain orchestrator. +type EthServiceType = ChainOrchestrator< + EngineHandler< + EngineApiRequestHandler, + UnboundedReceiverStream>, + BasicBlockDownloader, + >, + PipelineSync, +>; + +/// The type that drives the Ethereum chain forward and communicates progress. +#[pin_project] +#[allow(missing_debug_implementations)] +pub struct EthService +where + DB: Database + 'static, + Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, +{ + orchestrator: EthServiceType, +} + +impl EthService +where + DB: Database + 'static, + Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, +{ + /// Constructor for `EthService`. + pub fn new( + chain_spec: Arc, + client: Client, + to_tree: Sender>>, + from_tree: UnboundedReceiver, + incoming_requests: UnboundedReceiverStream>, + pipeline: Pipeline, + pipeline_task_spawner: Box, + ) -> Self { + let consensus = Arc::new(EthBeaconConsensus::new(chain_spec)); + let downloader = BasicBlockDownloader::new(client, consensus); + + let engine_handler = EngineApiRequestHandler::new(to_tree, from_tree); + let handler = EngineHandler::new(engine_handler, downloader, incoming_requests); + + let backfill_sync = PipelineSync::new(pipeline, pipeline_task_spawner); + + Self { orchestrator: ChainOrchestrator::new(handler, backfill_sync) } + } +} + +impl Future for EthService +where + DB: Database + 'static, + Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, +{ + type Output = Result<(), EthServiceError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Call poll on the inner orchestrator. + let mut orchestrator = self.project().orchestrator; + loop { + match ready!(StreamExt::poll_next_unpin(&mut orchestrator, cx)) { + Some(_event) => continue, + None => return Poll::Ready(Ok(())), + } + } + } +} + +/// Potential error returned by `EthService`. +#[derive(Debug)] +pub struct EthServiceError {} + +#[cfg(test)] +mod tests { + use super::*; + use reth_chainspec::{ChainSpecBuilder, MAINNET}; + use reth_engine_tree::test_utils::TestPipelineBuilder; + use reth_ethereum_engine_primitives::EthEngineTypes; + use reth_network_p2p::test_utils::TestFullBlockClient; + use reth_tasks::TokioTaskExecutor; + use std::sync::{mpsc::channel, Arc}; + use tokio::sync::mpsc::unbounded_channel; + + #[test] + fn eth_chain_orchestrator_build() { + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(MAINNET.genesis.clone()) + .paris_activated() + .build(), + ); + + let client = TestFullBlockClient::default(); + + let (_tx, rx) = unbounded_channel::>(); + let incoming_requests = UnboundedReceiverStream::new(rx); + + let pipeline = TestPipelineBuilder::new().build(chain_spec.clone()); + let pipeline_task_spawner = Box::::default(); + + let (to_tree_tx, _to_tree_rx) = channel(); + let (_from_tree_tx, from_tree_rx) = unbounded_channel(); + + let _eth_chain_orchestrator = EthService::new( + chain_spec, + client, + to_tree_tx, + from_tree_rx, + incoming_requests, + pipeline, + pipeline_task_spawner, + ); + } +} From 78d4f8c4bd53d32c62e452c77a509b15ced1fb8a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 3 Jul 2024 17:08:07 +0200 Subject: [PATCH 310/405] feat: add empty ethereum cli crate (#9268) --- Cargo.lock | 4 ++++ Cargo.toml | 2 ++ crates/ethereum/cli/Cargo.toml | 10 ++++++++++ crates/ethereum/cli/src/lib.rs | 9 +++++++++ 4 files changed, 25 insertions(+) create mode 100644 crates/ethereum/cli/Cargo.toml create mode 100644 crates/ethereum/cli/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 277cbf904170..ee2c0356686f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7117,6 +7117,10 @@ dependencies = [ "thiserror", ] +[[package]] +name = "reth-ethereum-cli" +version = "1.0.0" + [[package]] name = "reth-ethereum-consensus" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 417fe3ef8293..ceb9c9532083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "crates/engine/util/", "crates/errors/", "crates/ethereum-forks/", + "crates/ethereum/cli/", "crates/ethereum/consensus/", "crates/ethereum/engine/", "crates/ethereum/engine-primitives/", @@ -295,6 +296,7 @@ reth-engine-util = { path = "crates/engine/util" } reth-errors = { path = "crates/errors" } reth-eth-wire = { path = "crates/net/eth-wire" } reth-eth-wire-types = { path = "crates/net/eth-wire-types" } +reth-ethereum-cli = { path = "crates/ethereum/cli" } reth-ethereum-consensus = { path = "crates/ethereum/consensus" } reth-ethereum-engine-primitives = { path = "crates/ethereum/engine-primitives" } reth-ethereum-forks = { path = "crates/ethereum-forks" } diff --git a/crates/ethereum/cli/Cargo.toml b/crates/ethereum/cli/Cargo.toml new file mode 100644 index 000000000000..18b5f9a47066 --- /dev/null +++ b/crates/ethereum/cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "reth-ethereum-cli" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] diff --git a/crates/ethereum/cli/src/lib.rs b/crates/ethereum/cli/src/lib.rs new file mode 100644 index 000000000000..c55b2ab389d0 --- /dev/null +++ b/crates/ethereum/cli/src/lib.rs @@ -0,0 +1,9 @@ +//! Reth CLI implementation. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] From 84c5c3376e5e209458f08977421b72b465e21367 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:09:30 +0200 Subject: [PATCH 311/405] test: add unit tests for `save_receipts` (#9255) --- crates/revm/src/batch.rs | 186 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/crates/revm/src/batch.rs b/crates/revm/src/batch.rs index f2903a4f47cf..02ffba017bdf 100644 --- a/crates/revm/src/batch.rs +++ b/crates/revm/src/batch.rs @@ -210,3 +210,189 @@ impl BlockExecutorStats { ); } } + +#[cfg(test)] +mod tests { + use super::*; + use reth_primitives::{Address, Log, Receipt}; + use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig}; + use std::collections::BTreeMap; + + #[test] + fn test_save_receipts_empty() { + let mut recorder = BlockBatchRecord::default(); + // Create an empty vector of receipts + let receipts = vec![]; + + // Verify that saving receipts completes without error + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that the saved receipts are equal to a nested empty vector + assert_eq!(*recorder.receipts(), vec![vec![]].into()); + } + + #[test] + fn test_save_receipts_non_empty_no_pruning() { + let mut recorder = BlockBatchRecord::default(); + let receipts = vec![Receipt::default()]; + + // Verify that saving receipts completes without error + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that there is one block of receipts + assert_eq!(recorder.receipts().len(), 1); + // Verify that the first block contains one receipt + assert_eq!(recorder.receipts()[0].len(), 1); + // Verify that the saved receipt is the default receipt + assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); + } + + #[test] + fn test_save_receipts_with_pruning_no_prunable_receipts() { + let mut recorder = BlockBatchRecord::default(); + + // Set the first block number + recorder.set_first_block(1); + // Set the tip (highest known block) + recorder.set_tip(130); + + // Create a vector of receipts with a default receipt + let receipts = vec![Receipt::default()]; + + // Verify that saving receipts completes without error + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that there is one block of receipts + assert_eq!(recorder.receipts().len(), 1); + // Verify that the first block contains one receipt + assert_eq!(recorder.receipts()[0].len(), 1); + // Verify that the saved receipt is the default receipt + assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); + } + + #[test] + fn test_save_receipts_with_pruning_no_tip() { + // Create a PruneModes with receipts set to PruneMode::Full + let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() }; + + let mut recorder = BlockBatchRecord::new(prune_modes); + + // Set the first block number + recorder.set_first_block(1); + // Create a vector of receipts with a default receipt + let receipts = vec![Receipt::default()]; + + // Verify that saving receipts completes without error + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that there is one block of receipts + assert_eq!(recorder.receipts().len(), 1); + // Verify that the first block contains one receipt + assert_eq!(recorder.receipts()[0].len(), 1); + // Verify that the saved receipt is the default receipt + assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); + } + + #[test] + fn test_save_receipts_with_pruning_no_block_number() { + // Create a PruneModes with receipts set to PruneMode::Full + let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() }; + + // Create a BlockBatchRecord with the prune_modes + let mut recorder = BlockBatchRecord::new(prune_modes); + + // Set the tip (highest known block) + recorder.set_tip(130); + + // Create a vector of receipts with a default receipt + let receipts = vec![Receipt::default()]; + + // Verify that saving receipts completes without error + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that there is one block of receipts + assert_eq!(recorder.receipts().len(), 1); + // Verify that the first block contains one receipt + assert_eq!(recorder.receipts()[0].len(), 1); + // Verify that the saved receipt is the default receipt + assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); + } + + // Test saving receipts with pruning configuration and receipts should be pruned + #[test] + fn test_save_receipts_with_pruning_should_prune() { + // Create a PruneModes with receipts set to PruneMode::Full + let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() }; + + // Create a BlockBatchRecord with the prune_modes + let mut recorder = BlockBatchRecord::new(prune_modes); + + // Set the first block number + recorder.set_first_block(1); + // Set the tip (highest known block) + recorder.set_tip(130); + + // Create a vector of receipts with a default receipt + let receipts = vec![Receipt::default()]; + + // Verify that saving receipts completes without error + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that there is one block of receipts + assert_eq!(recorder.receipts().len(), 1); + // Verify that the receipts are pruned (empty) + assert!(recorder.receipts()[0].is_empty()); + } + + // Test saving receipts with address filter pruning + #[test] + fn test_save_receipts_with_address_filter_pruning() { + // Create a PruneModes with receipts_log_filter configuration + let prune_modes = PruneModes { + receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([ + (Address::with_last_byte(1), PruneMode::Before(1300001)), + (Address::with_last_byte(2), PruneMode::Before(1300002)), + (Address::with_last_byte(3), PruneMode::Distance(1300003)), + ])), + ..Default::default() + }; + + // Create a BlockBatchRecord with the prune_modes + let mut recorder = BlockBatchRecord::new(prune_modes); + + // Set the first block number + recorder.set_first_block(1); + // Set the tip (highest known block) + recorder.set_tip(1300000); + + // With a receipt that should be pruned (address 4 not in the log filter) + let mut receipt = Receipt::default(); + receipt.logs.push(Log { address: Address::with_last_byte(4), ..Default::default() }); + let receipts = vec![receipt.clone()]; + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that the receipts are pruned (empty) + assert_eq!(recorder.receipts().len(), 1); + assert_eq!(recorder.receipts()[0], vec![None]); + + // With a receipt that should not be pruned (address 1 in the log filter) + let mut receipt1 = Receipt::default(); + receipt1.logs.push(Log { address: Address::with_last_byte(1), ..Default::default() }); + let receipts = vec![receipt1.clone()]; + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that the second block of receipts contains the receipt + assert_eq!(recorder.receipts().len(), 2); + assert_eq!(recorder.receipts()[1][0], Some(receipt1)); + + // With a receipt that should not be pruned (address 2 in the log filter) + let mut receipt2 = Receipt::default(); + receipt2.logs.push(Log { address: Address::with_last_byte(2), ..Default::default() }); + let receipts = vec![receipt2.clone()]; + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that the third block of receipts contains the receipt + assert_eq!(recorder.receipts().len(), 3); + assert_eq!(recorder.receipts()[2][0], Some(receipt2)); + + // With a receipt that should not be pruned (address 3 in the log filter) + let mut receipt3 = Receipt::default(); + receipt3.logs.push(Log { address: Address::with_last_byte(3), ..Default::default() }); + let receipts = vec![receipt3.clone()]; + assert!(recorder.save_receipts(receipts).is_ok()); + // Verify that the fourth block of receipts contains the receipt + assert_eq!(recorder.receipts().len(), 4); + assert_eq!(recorder.receipts()[3][0], Some(receipt3)); + } +} From 335b93425e57c08455f21f8ccae4395dca61909c Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 3 Jul 2024 17:30:29 +0200 Subject: [PATCH 312/405] chore(rpc): `EthApi` builder (#9041) Co-authored-by: Matthias Seitz --- Cargo.lock | 2 + crates/ethereum/node/tests/e2e/dev.rs | 2 +- crates/node/builder/src/launch/mod.rs | 2 +- crates/node/builder/src/rpc.rs | 29 +- crates/rpc/rpc-builder/Cargo.toml | 1 + crates/rpc/rpc-builder/src/auth.rs | 5 +- crates/rpc/rpc-builder/src/config.rs | 8 +- crates/rpc/rpc-builder/src/eth.rs | 439 +++++++------ crates/rpc/rpc-builder/src/lib.rs | 592 +++++++++--------- crates/rpc/rpc-builder/tests/it/startup.rs | 21 +- crates/rpc/rpc-builder/tests/it/utils.rs | 21 +- crates/rpc/rpc-eth-api/src/core.rs | 18 +- crates/rpc/rpc-eth-api/src/helpers/mod.rs | 20 +- .../rpc-eth-api/src/helpers/transaction.rs | 20 +- crates/rpc/rpc-eth-api/src/lib.rs | 2 +- crates/rpc/rpc-eth-types/src/cache/config.rs | 2 +- crates/rpc/rpc-eth-types/src/fee_history.rs | 2 +- crates/rpc/rpc-eth-types/src/gas_oracle.rs | 2 +- crates/rpc/rpc-eth-types/src/lib.rs | 4 +- crates/rpc/rpc/Cargo.toml | 1 + crates/rpc/rpc/src/eth/core.rs | 12 +- crates/storage/db-api/src/lib.rs | 2 + .../storage/provider/src/test_utils/noop.rs | 37 +- crates/storage/provider/src/traits/full.rs | 32 +- crates/storage/provider/src/traits/mod.rs | 2 +- examples/custom-dev-node/src/main.rs | 2 +- examples/custom-inspector/src/main.rs | 4 +- examples/rpc-db/src/main.rs | 7 +- examples/txpool-tracing/src/main.rs | 2 +- 29 files changed, 736 insertions(+), 557 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee2c0356686f..bdae392a39f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8151,6 +8151,7 @@ dependencies = [ "alloy-rlp", "assert_matches", "async-trait", + "derive_more", "futures", "http 1.1.0", "http-body", @@ -8252,6 +8253,7 @@ dependencies = [ "reth-rpc", "reth-rpc-api", "reth-rpc-engine-api", + "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-rpc-layer", "reth-rpc-server-types", diff --git a/crates/ethereum/node/tests/e2e/dev.rs b/crates/ethereum/node/tests/e2e/dev.rs index 2b20d781f7c5..0e289cfd3b75 100644 --- a/crates/ethereum/node/tests/e2e/dev.rs +++ b/crates/ethereum/node/tests/e2e/dev.rs @@ -19,7 +19,7 @@ async fn can_run_dev_node() -> eyre::Result<()> { Ok(()) } -async fn assert_chain_advances(mut node: EthNode) { +async fn assert_chain_advances(node: EthNode) { let mut notifications = node.inner.provider.canonical_state_stream(); // submit tx through rpc diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 99dc04310d82..2efde0c5a606 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -308,7 +308,7 @@ where let jwt_secret = ctx.auth_jwt_secret()?; // Start RPC servers - let (rpc_server_handles, mut rpc_registry) = crate::rpc::launch_rpc_servers( + let (rpc_server_handles, rpc_registry) = crate::rpc::launch_rpc_servers( ctx.node_adapter().clone(), engine_api, ctx.node_config(), diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 9a7a86b91b5a..7f6cb5e898cc 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1,22 +1,24 @@ //! Builder support for rpc components. +use std::{ + fmt, + ops::{Deref, DerefMut}, +}; + use futures::TryFutureExt; use reth_network::NetworkHandle; use reth_node_api::FullNodeComponents; use reth_node_core::{node_config::NodeConfig, rpc::api::EngineApiServer}; use reth_payload_builder::PayloadBuilderHandle; +use reth_rpc::eth::EthApi; use reth_rpc_builder::{ auth::{AuthRpcModule, AuthServerHandle}, config::RethRpcServerConfig, - RethModuleRegistry, RpcModuleBuilder, RpcServerHandle, TransportRpcModules, + EthApiBuild, RpcModuleBuilder, RpcRegistryInner, RpcServerHandle, TransportRpcModules, }; use reth_rpc_layer::JwtSecret; use reth_tasks::TaskExecutor; use reth_tracing::tracing::{debug, info}; -use std::{ - fmt, - ops::{Deref, DerefMut}, -}; /// Contains the handles to the spawned RPC servers. /// @@ -145,27 +147,28 @@ impl ExtendRpcModules for () { } } -/// Helper wrapper type to encapsulate the [`RethModuleRegistry`] over components trait. +/// Helper wrapper type to encapsulate the [`RpcRegistryInner`] over components trait. #[derive(Debug)] +#[allow(clippy::type_complexity)] pub struct RpcRegistry { - pub(crate) registry: RethModuleRegistry< + pub(crate) registry: RpcRegistryInner< Node::Provider, Node::Pool, NetworkHandle, TaskExecutor, Node::Provider, - Node::Evm, + EthApi, >, } impl Deref for RpcRegistry { - type Target = RethModuleRegistry< + type Target = RpcRegistryInner< Node::Provider, Node::Pool, NetworkHandle, TaskExecutor, Node::Provider, - Node::Evm, + EthApi, >; fn deref(&self) -> &Self::Target { @@ -185,7 +188,7 @@ impl Clone for RpcRegistry { } } -/// Helper container to encapsulate [`RethModuleRegistry`], [`TransportRpcModules`] and +/// Helper container to encapsulate [`RpcRegistryInner`], [`TransportRpcModules`] and /// [`AuthRpcModule`]. /// /// This can be used to access installed modules, or create commonly used handlers like @@ -202,7 +205,7 @@ pub struct RpcContext<'a, Node: FullNodeComponents> { /// A Helper type the holds instances of the configured modules. /// - /// This provides easy access to rpc handlers, such as [`RethModuleRegistry::eth_api`]. + /// This provides easy access to rpc handlers, such as [`RpcRegistryInner::eth_api`]. pub registry: &'a mut RpcRegistry, /// Holds installed modules per transport type. /// @@ -272,7 +275,7 @@ where .with_events(node.provider().clone()) .with_executor(node.task_executor().clone()) .with_evm_config(node.evm_config().clone()) - .build_with_auth_server(module_config, engine_api); + .build_with_auth_server(module_config, engine_api, EthApiBuild::build); let mut registry = RpcRegistry { registry }; let ctx = RpcContext { diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index 7b7c89c0fcff..d97b23b5125b 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -19,6 +19,7 @@ reth-node-core.workspace = true reth-provider.workspace = true reth-rpc.workspace = true reth-rpc-api.workspace = true +reth-rpc-eth-api.workspace = true reth-rpc-layer.workspace = true reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 952362e0cfda..be904f6efc80 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -1,5 +1,3 @@ -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use crate::error::{RpcError, ServerKind}; use http::header::AUTHORIZATION; use jsonrpsee::{ @@ -9,13 +7,14 @@ use jsonrpsee::{ Methods, }; use reth_engine_primitives::EngineTypes; -use reth_rpc_api::*; +use reth_rpc_api::servers::*; use reth_rpc_eth_types::EthSubscriptionIdProvider; use reth_rpc_layer::{ secret_to_bearer_header, AuthClientLayer, AuthClientService, AuthLayer, JwtAuthValidator, JwtSecret, }; use reth_rpc_server_types::constants; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use tower::layer::util::Identity; pub use jsonrpsee::server::ServerBuilder; diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index fbace8ec7803..e2becedbabae 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -214,13 +214,11 @@ impl RethRpcServerConfig for RpcServerArgs { #[cfg(test)] mod tests { - use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; - use clap::{Args, Parser}; use reth_node_core::args::RpcServerArgs; - use reth_rpc_server_types::{ - constants, constants::gas_oracle::RPC_DEFAULT_GAS_CAP, RethRpcModule, RpcModuleSelection, - }; + use reth_rpc_eth_types::RPC_DEFAULT_GAS_CAP; + use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection}; + use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use crate::config::RethRpcServerConfig; diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 7d177ed4302b..43e20b1eaeb8 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -1,232 +1,136 @@ -use std::sync::Arc; +use std::{fmt::Debug, time::Duration}; use reth_evm::ConfigureEvm; -use reth_network_api::{NetworkInfo, Peers}; +use reth_network_api::NetworkInfo; use reth_provider::{ - AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, - EvmEnvProvider, StateProviderFactory, + BlockReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, EvmEnvProvider, + FullRpcProvider, StateProviderFactory, }; -use reth_rpc::eth::{EthApi, EthFilter, EthFilterConfig, EthPubSub, RawTransactionForwarder}; +use reth_rpc::{eth::EthFilterConfig, EthApi, EthFilter, EthPubSub}; use reth_rpc_eth_types::{ cache::cache_new_blocks_task, fee_history::fee_history_cache_new_blocks_task, EthStateCache, EthStateCacheConfig, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, - GasPriceOracleConfig, + GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP, }; use reth_rpc_server_types::constants::{ - default_max_tracing_requests, gas_oracle::RPC_DEFAULT_GAS_CAP, DEFAULT_MAX_BLOCKS_PER_FILTER, - DEFAULT_MAX_LOGS_PER_RESPONSE, + default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, }; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; use serde::{Deserialize, Serialize}; -use crate::RpcModuleConfig; +/// Default value for stale filter ttl +const DEFAULT_STALE_FILTER_TTL: Duration = Duration::from_secs(5 * 60); + +/// Alias for function that builds the core `eth` namespace API. +pub type EthApiBuilder = + Box) -> EthApi>; -/// All handlers for the `eth` namespace +/// Handlers for core, filter and pubsub `eth` namespace APIs. #[derive(Debug, Clone)] -pub struct EthHandlers { +pub struct EthHandlers { /// Main `eth_` request handler - pub api: EthApi, + pub api: EthApi, /// The async caching layer used by the eth handlers pub cache: EthStateCache, /// Polling based filter handler available on all transports pub filter: EthFilter, /// Handler for subscriptions only available for transports that support it (ws, ipc) pub pubsub: EthPubSub, - /// The configured tracing call pool - pub blocking_task_pool: BlockingTaskPool, } -/// Configuration for `EthHandlersBuilder` -#[derive(Clone, Debug)] -pub(crate) struct EthHandlersConfig { - /// The provider for blockchain data, responsible for reading blocks, accounts, state, etc. - pub(crate) provider: Provider, - /// The transaction pool for managing pending transactions. - pub(crate) pool: Pool, - /// The network information, handling peer connections and network state. - pub(crate) network: Network, - /// The task executor for spawning asynchronous tasks. - pub(crate) executor: Tasks, - /// The event subscriptions for canonical state changes. - pub(crate) events: Events, - /// The EVM configuration for Ethereum Virtual Machine settings. - pub(crate) evm_config: EvmConfig, - /// An optional forwarder for raw transactions. - pub(crate) eth_raw_transaction_forwarder: Option>, +impl EthHandlers { + /// Returns a new [`EthHandlers`] builder. + #[allow(clippy::too_many_arguments)] + pub fn builder( + provider: Provider, + pool: Pool, + network: Network, + evm_config: EvmConfig, + config: EthConfig, + executor: Tasks, + events: Events, + eth_api_builder: EthApiB, + ) -> EthHandlersBuilder + where + EthApiB: FnOnce(&EthApiBuilderCtx) -> EthApi + + 'static, + { + EthHandlersBuilder { + provider, + pool, + network, + evm_config, + config, + executor, + events, + eth_api_builder: Box::new(eth_api_builder), + } + } } -/// Represents the builder for the `EthHandlers` struct, used to configure and create instances of -/// `EthHandlers`. -#[derive(Debug, Clone)] -pub(crate) struct EthHandlersBuilder { - eth_handlers_config: EthHandlersConfig, - /// Configuration for the RPC module - rpc_config: RpcModuleConfig, +/// Builds [`EthHandlers`] for core, filter, and pubsub `eth_` apis. +#[allow(missing_debug_implementations)] +pub struct EthHandlersBuilder { + provider: Provider, + pool: Pool, + network: Network, + evm_config: EvmConfig, + config: EthConfig, + executor: Tasks, + events: Events, + eth_api_builder: EthApiBuilder, } -impl - EthHandlersBuilder +impl + EthHandlersBuilder where - Provider: BlockReaderIdExt - + AccountReader - + StateProviderFactory - + EvmEnvProvider - + ChainSpecProvider - + ChangeSetReader - + Clone - + Unpin - + 'static, - Pool: TransactionPool + Clone + 'static, - Network: NetworkInfo + Peers + Clone + 'static, + Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, + Pool: Send + Sync + Clone + 'static, + EvmConfig: ConfigureEvm, + Network: Clone, Tasks: TaskSpawner + Clone + 'static, - Events: CanonStateSubscriptions + Clone + 'static, - EvmConfig: ConfigureEvm + 'static, + Events: CanonStateSubscriptions + Clone, { - /// Creates a new `EthHandlersBuilder` with the provided components. - pub(crate) const fn new( - eth_handlers_config: EthHandlersConfig, - rpc_config: RpcModuleConfig, - ) -> Self { - Self { eth_handlers_config, rpc_config } - } - - /// Builds and returns an `EthHandlers` instance. - pub(crate) fn build(self) -> EthHandlers { - // Initialize the cache - let cache = self.init_cache(); - - // Initialize the fee history cache - let fee_history_cache = self.init_fee_history_cache(&cache); - - // Spawn background tasks for cache - self.spawn_cache_tasks(&cache, &fee_history_cache); - - // Initialize the gas oracle - let gas_oracle = self.init_gas_oracle(&cache); - - // Initialize the blocking task pool - let blocking_task_pool = self.init_blocking_task_pool(); - - // Initialize the Eth API - let api = self.init_api(&cache, gas_oracle, &fee_history_cache, &blocking_task_pool); - - // Initialize the filter - let filter = self.init_filter(&cache); - - // Initialize the pubsub - let pubsub = self.init_pubsub(); - - EthHandlers { api, cache, filter, pubsub, blocking_task_pool } - } - - /// Initializes the `EthStateCache`. - fn init_cache(&self) -> EthStateCache { - EthStateCache::spawn_with( - self.eth_handlers_config.provider.clone(), - self.rpc_config.eth.cache.clone(), - self.eth_handlers_config.executor.clone(), - self.eth_handlers_config.evm_config.clone(), - ) - } - - /// Initializes the `FeeHistoryCache`. - fn init_fee_history_cache(&self, cache: &EthStateCache) -> FeeHistoryCache { - FeeHistoryCache::new(cache.clone(), self.rpc_config.eth.fee_history_cache.clone()) - } - - /// Spawns background tasks for updating caches. - fn spawn_cache_tasks(&self, cache: &EthStateCache, fee_history_cache: &FeeHistoryCache) { - // Get the stream of new canonical blocks - let new_canonical_blocks = self.eth_handlers_config.events.canonical_state_stream(); - - // Clone the cache for the task - let cache_clone = cache.clone(); + /// Returns a new instance with handlers for `eth` namespace. + pub fn build(self) -> EthHandlers { + let Self { provider, pool, network, evm_config, config, executor, events, eth_api_builder } = + self; + + let cache = EthStateCache::spawn_with( + provider.clone(), + config.cache, + executor.clone(), + evm_config.clone(), + ); - // Spawn a critical task to update the cache with new blocks - self.eth_handlers_config.executor.spawn_critical( + let new_canonical_blocks = events.canonical_state_stream(); + let c = cache.clone(); + executor.spawn_critical( "cache canonical blocks task", Box::pin(async move { - cache_new_blocks_task(cache_clone, new_canonical_blocks).await; + cache_new_blocks_task(c, new_canonical_blocks).await; }), ); - // Get another stream of new canonical blocks - let new_canonical_blocks = self.eth_handlers_config.events.canonical_state_stream(); - - // Clone the fee history cache for the task - let fhc_clone = fee_history_cache.clone(); + let ctx = EthApiBuilderCtx { + provider, + pool, + network, + evm_config, + config, + executor, + events, + cache, + }; - // Clone the provider for the task - let provider_clone = self.eth_handlers_config.provider.clone(); - - // Spawn a critical task to update the fee history cache with new blocks - self.eth_handlers_config.executor.spawn_critical( - "cache canonical blocks for fee history task", - Box::pin(async move { - fee_history_cache_new_blocks_task(fhc_clone, new_canonical_blocks, provider_clone) - .await; - }), - ); - } + let api = eth_api_builder(&ctx); - /// Initializes the `GasPriceOracle`. - fn init_gas_oracle(&self, cache: &EthStateCache) -> GasPriceOracle { - GasPriceOracle::new( - self.eth_handlers_config.provider.clone(), - self.rpc_config.eth.gas_oracle.clone(), - cache.clone(), - ) - } + let filter = EthFilterApiBuilder::build(&ctx); - /// Initializes the `BlockingTaskPool`. - fn init_blocking_task_pool(&self) -> BlockingTaskPool { - BlockingTaskPool::build().expect("failed to build tracing pool") - } + let pubsub = EthPubSubApiBuilder::build(&ctx); - /// Initializes the `EthApi`. - fn init_api( - &self, - cache: &EthStateCache, - gas_oracle: GasPriceOracle, - fee_history_cache: &FeeHistoryCache, - blocking_task_pool: &BlockingTaskPool, - ) -> EthApi { - EthApi::with_spawner( - self.eth_handlers_config.provider.clone(), - self.eth_handlers_config.pool.clone(), - self.eth_handlers_config.network.clone(), - cache.clone(), - gas_oracle, - self.rpc_config.eth.rpc_gas_cap, - Box::new(self.eth_handlers_config.executor.clone()), - blocking_task_pool.clone(), - fee_history_cache.clone(), - self.eth_handlers_config.evm_config.clone(), - self.eth_handlers_config.eth_raw_transaction_forwarder.clone(), - ) - } - - /// Initializes the `EthFilter`. - fn init_filter(&self, cache: &EthStateCache) -> EthFilter { - EthFilter::new( - self.eth_handlers_config.provider.clone(), - self.eth_handlers_config.pool.clone(), - cache.clone(), - self.rpc_config.eth.filter_config(), - Box::new(self.eth_handlers_config.executor.clone()), - ) - } - - /// Initializes the `EthPubSub`. - fn init_pubsub(&self) -> EthPubSub { - EthPubSub::with_spawner( - self.eth_handlers_config.provider.clone(), - self.eth_handlers_config.pool.clone(), - self.eth_handlers_config.events.clone(), - self.eth_handlers_config.network.clone(), - Box::new(self.eth_handlers_config.executor.clone()), - ) + EthHandlers { api, cache: ctx.cache, filter, pubsub } } } @@ -249,7 +153,7 @@ pub struct EthConfig { pub rpc_gas_cap: u64, /// /// Sets TTL for stale filters - pub stale_filter_ttl: std::time::Duration, + pub stale_filter_ttl: Duration, /// Settings for the fee history cache pub fee_history_cache: FeeHistoryCacheConfig, } @@ -264,9 +168,6 @@ impl EthConfig { } } -/// Default value for stale filter ttl -const DEFAULT_STALE_FILTER_TTL: std::time::Duration = std::time::Duration::from_secs(5 * 60); - impl Default for EthConfig { fn default() -> Self { Self { @@ -275,7 +176,7 @@ impl Default for EthConfig { max_tracing_requests: default_max_tracing_requests(), max_blocks_per_filter: DEFAULT_MAX_BLOCKS_PER_FILTER, max_logs_per_response: DEFAULT_MAX_LOGS_PER_RESPONSE, - rpc_gas_cap: RPC_DEFAULT_GAS_CAP, + rpc_gas_cap: RPC_DEFAULT_GAS_CAP.into(), stale_filter_ttl: DEFAULT_STALE_FILTER_TTL, fee_history_cache: FeeHistoryCacheConfig::default(), } @@ -319,3 +220,157 @@ impl EthConfig { self } } + +/// Context for building the `eth` namespace API. +#[derive(Debug, Clone)] +pub struct EthApiBuilderCtx { + /// Database handle. + pub provider: Provider, + /// Mempool handle. + pub pool: Pool, + /// Network handle. + pub network: Network, + /// EVM configuration. + pub evm_config: EvmConfig, + /// RPC config for `eth` namespace. + pub config: EthConfig, + /// Runtime handle. + pub executor: Tasks, + /// Events handle. + pub events: Events, + /// RPC cache handle. + pub cache: EthStateCache, +} + +/// Ethereum layer one `eth` RPC server builder. +#[derive(Default, Debug, Clone, Copy)] +pub struct EthApiBuild; + +impl EthApiBuild { + /// Builds the [`EthApiServer`](reth_rpc_eth_api::EthApiServer), for given context. + pub fn build( + ctx: &EthApiBuilderCtx, + ) -> EthApi + where + Provider: FullRpcProvider, + Pool: TransactionPool, + Network: NetworkInfo + Clone, + Tasks: TaskSpawner + Clone + 'static, + Events: CanonStateSubscriptions, + EvmConfig: ConfigureEvm, + { + let gas_oracle = GasPriceOracleBuilder::build(ctx); + let fee_history_cache = FeeHistoryCacheBuilder::build(ctx); + + EthApi::with_spawner( + ctx.provider.clone(), + ctx.pool.clone(), + ctx.network.clone(), + ctx.cache.clone(), + gas_oracle, + ctx.config.rpc_gas_cap, + Box::new(ctx.executor.clone()), + BlockingTaskPool::build().expect("failed to build blocking task pool"), + fee_history_cache, + ctx.evm_config.clone(), + None, + ) + } +} + +/// Builds the `eth_` namespace API [`EthFilterApiServer`](reth_rpc_eth_api::EthFilterApiServer). +#[derive(Debug)] +pub struct EthFilterApiBuilder; + +impl EthFilterApiBuilder { + /// Builds the [`EthFilterApiServer`](reth_rpc_eth_api::EthFilterApiServer), for given context. + pub fn build( + ctx: &EthApiBuilderCtx, + ) -> EthFilter + where + Provider: Send + Sync + Clone + 'static, + Pool: Send + Sync + Clone + 'static, + Tasks: TaskSpawner + Clone + 'static, + { + EthFilter::new( + ctx.provider.clone(), + ctx.pool.clone(), + ctx.cache.clone(), + ctx.config.filter_config(), + Box::new(ctx.executor.clone()), + ) + } +} + +/// Builds the `eth_` namespace API [`EthPubSubApiServer`](reth_rpc_eth_api::EthFilterApiServer). +#[derive(Debug)] +pub struct EthPubSubApiBuilder; + +impl EthPubSubApiBuilder { + /// Builds the [`EthPubSubApiServer`](reth_rpc_eth_api::EthPubSubApiServer), for given context. + pub fn build( + ctx: &EthApiBuilderCtx, + ) -> EthPubSub + where + Provider: Clone, + Pool: Clone, + Events: Clone, + Network: Clone, + Tasks: TaskSpawner + Clone + 'static, + { + EthPubSub::with_spawner( + ctx.provider.clone(), + ctx.pool.clone(), + ctx.events.clone(), + ctx.network.clone(), + Box::new(ctx.executor.clone()), + ) + } +} + +/// Builds `eth_` core api component [`GasPriceOracle`], for given context. +#[derive(Debug)] +pub struct GasPriceOracleBuilder; + +impl GasPriceOracleBuilder { + /// Builds a [`GasPriceOracle`], for given context. + pub fn build( + ctx: &EthApiBuilderCtx, + ) -> GasPriceOracle + where + Provider: BlockReaderIdExt + Clone, + { + GasPriceOracle::new(ctx.provider.clone(), ctx.config.gas_oracle, ctx.cache.clone()) + } +} + +/// Builds `eth_` core api component [`FeeHistoryCache`], for given context. +#[derive(Debug)] +pub struct FeeHistoryCacheBuilder; + +impl FeeHistoryCacheBuilder { + /// Builds a [`FeeHistoryCache`], for given context. + pub fn build( + ctx: &EthApiBuilderCtx, + ) -> FeeHistoryCache + where + Provider: ChainSpecProvider + BlockReaderIdExt + Clone + 'static, + Tasks: TaskSpawner, + Events: CanonStateSubscriptions, + { + let fee_history_cache = + FeeHistoryCache::new(ctx.cache.clone(), ctx.config.fee_history_cache); + + let new_canonical_blocks = ctx.events.canonical_state_stream(); + let fhc = fee_history_cache.clone(); + let provider = ctx.provider.clone(); + ctx.executor.spawn_critical( + "cache canonical blocks for fee history task", + Box::pin(async move { + fee_history_cache_new_blocks_task(fhc, new_canonical_blocks, provider).await; + }), + ); + + fee_history_cache + } +} diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 2800e83d1154..7e5faf8bbb17 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -19,13 +19,12 @@ //! ``` //! use reth_evm::ConfigureEvm; //! use reth_network_api::{NetworkInfo, Peers}; -//! use reth_provider::{ -//! AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, -//! ChangeSetReader, EvmEnvProvider, StateProviderFactory, -//! }; +//! use reth_provider::{AccountReader, CanonStateSubscriptions, ChangeSetReader, FullRpcProvider}; //! use reth_rpc_builder::{ -//! RethRpcModule, RpcModuleBuilder, RpcServerConfig, ServerBuilder, TransportRpcModuleConfig, +//! EthApiBuild, RethRpcModule, RpcModuleBuilder, RpcServerConfig, ServerBuilder, +//! TransportRpcModuleConfig, //! }; +//! //! use reth_tasks::TokioTaskExecutor; //! use reth_transaction_pool::TransactionPool; //! pub async fn launch( @@ -35,16 +34,8 @@ //! events: Events, //! evm_config: EvmConfig, //! ) where -//! Provider: AccountReader -//! + BlockReaderIdExt -//! + ChainSpecProvider -//! + ChangeSetReader -//! + StateProviderFactory -//! + EvmEnvProvider -//! + Clone -//! + Unpin -//! + 'static, -//! Pool: TransactionPool + Clone + 'static, +//! Provider: FullRpcProvider + AccountReader + ChangeSetReader, +//! Pool: TransactionPool + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static, //! EvmConfig: ConfigureEvm, @@ -64,7 +55,7 @@ //! events, //! evm_config, //! ) -//! .build(transports); +//! .build(transports, EthApiBuild::build); //! let handle = RpcServerConfig::default() //! .with_http(ServerBuilder::default()) //! .start(transport_modules) @@ -80,13 +71,10 @@ //! use reth_engine_primitives::EngineTypes; //! use reth_evm::ConfigureEvm; //! use reth_network_api::{NetworkInfo, Peers}; -//! use reth_provider::{ -//! AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, -//! ChangeSetReader, EvmEnvProvider, StateProviderFactory, -//! }; +//! use reth_provider::{AccountReader, CanonStateSubscriptions, ChangeSetReader, FullRpcProvider}; //! use reth_rpc_api::EngineApiServer; //! use reth_rpc_builder::{ -//! auth::AuthServerConfig, RethRpcModule, RpcModuleBuilder, RpcServerConfig, +//! auth::AuthServerConfig, EthApiBuild, RethRpcModule, RpcModuleBuilder, RpcServerConfig, //! TransportRpcModuleConfig, //! }; //! use reth_rpc_layer::JwtSecret; @@ -101,16 +89,8 @@ //! engine_api: EngineApi, //! evm_config: EvmConfig, //! ) where -//! Provider: AccountReader -//! + BlockReaderIdExt -//! + ChainSpecProvider -//! + ChangeSetReader -//! + StateProviderFactory -//! + EvmEnvProvider -//! + Clone -//! + Unpin -//! + 'static, -//! Pool: TransactionPool + Clone + 'static, +//! Provider: FullRpcProvider + AccountReader + ChangeSetReader, +//! Pool: TransactionPool + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static, //! EngineApi: EngineApiServer, @@ -135,7 +115,7 @@ //! //! // configure the server modules //! let (modules, auth_module, _registry) = -//! builder.build_with_auth_server(transports, engine_api); +//! builder.build_with_auth_server(transports, engine_api, EthApiBuild::build); //! //! // start the servers //! let auth_config = AuthServerConfig::builder(JwtSecret::random()).build(); @@ -155,13 +135,14 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use crate::{ - auth::AuthRpcModule, - cors::CorsDomainError, - error::WsHttpSamePortError, - eth::{EthHandlersBuilder, EthHandlersConfig}, - metrics::RpcRequestMetrics, +use std::{ + collections::HashMap, + fmt, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, + sync::Arc, + time::{Duration, SystemTime, UNIX_EPOCH}, }; + use error::{ConflictingModules, RpcError, ServerKind}; use http::{header::AUTHORIZATION, HeaderMap}; use jsonrpsee::{ @@ -174,30 +155,33 @@ use reth_evm::ConfigureEvm; use reth_ipc::server::IpcServer; use reth_network_api::{noop::NoopNetwork, NetworkInfo, Peers}; use reth_provider::{ - AccountReader, BlockReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, - ChangeSetReader, EvmEnvProvider, StateProviderFactory, + AccountReader, BlockReader, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, + EvmEnvProvider, FullRpcProvider, StateProviderFactory, }; use reth_rpc::{ - eth::{EthApi, EthBundle, RawTransactionForwarder}, - AdminApi, DebugApi, EngineEthApi, NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi, - Web3Api, + AdminApi, DebugApi, EngineEthApi, EthBundle, NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, + TxPoolApi, Web3Api, +}; +use reth_rpc_api::servers::*; +use reth_rpc_eth_api::{ + helpers::{ + Call, EthApiSpec, EthTransactions, LoadPendingBlock, TraceExt, UpdateRawTxForwarder, + }, + EthApiServer, FullEthApiServer, RawTransactionForwarder, }; -use reth_rpc_api::*; use reth_rpc_eth_types::{EthStateCache, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret}; use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, TransactionPool}; use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - fmt, - net::{Ipv4Addr, SocketAddr, SocketAddrV4}, - sync::Arc, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; use tower_http::cors::CorsLayer; use tracing::{instrument, trace}; +use crate::{ + auth::AuthRpcModule, cors::CorsDomainError, error::WsHttpSamePortError, + metrics::RpcRequestMetrics, +}; + // re-export for convenience pub use jsonrpsee::server::ServerBuilder; pub use reth_ipc::server::{ @@ -219,15 +203,18 @@ mod cors; pub mod error; /// Eth utils -mod eth; -pub use eth::{EthConfig, EthHandlers}; +pub mod eth; +pub use eth::{ + EthApiBuild, EthApiBuilderCtx, EthConfig, EthHandlers, FeeHistoryCacheBuilder, + GasPriceOracleBuilder, +}; // Rpc server metrics mod metrics; /// Convenience function for starting a server in one step. #[allow(clippy::too_many_arguments)] -pub async fn launch( +pub async fn launch( provider: Provider, pool: Pool, network: Network, @@ -236,27 +223,23 @@ pub async fn launch( executor: Tasks, events: Events, evm_config: EvmConfig, + eth: EthApiB, ) -> Result where - Provider: BlockReaderIdExt - + AccountReader - + StateProviderFactory - + EvmEnvProvider - + ChainSpecProvider - + ChangeSetReader - + Clone - + Unpin - + 'static, - Pool: TransactionPool + Clone + 'static, + Provider: FullRpcProvider + AccountReader + ChangeSetReader, + Pool: TransactionPool + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static, EvmConfig: ConfigureEvm, + EthApiB: FnOnce(&EthApiBuilderCtx) -> EthApi + + 'static, + EthApi: FullEthApiServer, { let module_config = module_config.into(); let server_config = server_config.into(); RpcModuleBuilder::new(provider, pool, network, executor, events, evm_config) - .build(module_config) + .build(module_config, eth) .start_server(server_config) .await } @@ -324,8 +307,8 @@ impl /// Configure a [`NoopTransactionPool`] instance. /// /// Caution: This will configure a pool API that does absolutely nothing. - /// This is only intended for allow easier setup of namespaces that depend on the [`EthApi`] - /// which requires a [`TransactionPool`] implementation. + /// This is only intended for allow easier setup of namespaces that depend on the + /// [`EthApi`](reth_rpc::eth::EthApi) which requires a [`TransactionPool`] implementation. pub fn with_noop_pool( self, ) -> RpcModuleBuilder { @@ -355,8 +338,8 @@ impl /// Configure a [`NoopNetwork`] instance. /// /// Caution: This will configure a network API that does absolutely nothing. - /// This is only intended for allow easier setup of namespaces that depend on the [`EthApi`] - /// which requires a [`NetworkInfo`] implementation. + /// This is only intended for allow easier setup of namespaces that depend on the + /// [`EthApi`](reth_rpc::eth::EthApi) which requires a [`NetworkInfo`] implementation. pub fn with_noop_network( self, ) -> RpcModuleBuilder { @@ -429,16 +412,8 @@ impl impl RpcModuleBuilder where - Provider: BlockReaderIdExt - + AccountReader - + StateProviderFactory - + EvmEnvProvider - + ChainSpecProvider - + ChangeSetReader - + Clone - + Unpin - + 'static, - Pool: TransactionPool + Clone + 'static, + Provider: FullRpcProvider + AccountReader + ChangeSetReader, + Pool: TransactionPool + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static, @@ -450,25 +425,31 @@ where /// This behaves exactly as [`RpcModuleBuilder::build`] for the [`TransportRpcModules`], but /// also configures the auth (engine api) server, which exposes a subset of the `eth_` /// namespace. - pub fn build_with_auth_server( + #[allow(clippy::type_complexity)] + pub fn build_with_auth_server( self, module_config: TransportRpcModuleConfig, engine: EngineApi, + eth: EthApiB, ) -> ( TransportRpcModules, AuthRpcModule, - RethModuleRegistry, + RpcRegistryInner, ) where EngineT: EngineTypes + 'static, EngineApi: EngineApiServer, + EthApiB: FnOnce(&EthApiBuilderCtx) -> EthApi + + 'static, + EthApi: FullEthApiServer, { let Self { provider, pool, network, executor, events, evm_config } = self; let config = module_config.config.clone().unwrap_or_default(); - let mut registry = - RethModuleRegistry::new(provider, pool, network, executor, events, config, evm_config); + let mut registry = RpcRegistryInner::new( + provider, pool, network, executor, events, config, evm_config, eth, + ); let modules = registry.create_transport_rpc_modules(module_config); @@ -477,7 +458,7 @@ where (modules, auth_module, registry) } - /// Converts the builder into a [`RethModuleRegistry`] which can be used to create all + /// Converts the builder into a [`RpcRegistryInner`] which can be used to create all /// components. /// /// This is useful for getting access to API handlers directly: @@ -488,7 +469,7 @@ where /// use reth_evm::ConfigureEvm; /// use reth_network_api::noop::NoopNetwork; /// use reth_provider::test_utils::{NoopProvider, TestCanonStateSubscriptions}; - /// use reth_rpc_builder::RpcModuleBuilder; + /// use reth_rpc_builder::{EthApiBuild, RpcModuleBuilder}; /// use reth_tasks::TokioTaskExecutor; /// use reth_transaction_pool::noop::NoopTransactionPool; /// @@ -500,24 +481,38 @@ where /// .with_executor(TokioTaskExecutor::default()) /// .with_events(TestCanonStateSubscriptions::default()) /// .with_evm_config(evm) - /// .into_registry(Default::default()); + /// .into_registry(Default::default(), EthApiBuild::build); /// /// let eth_api = registry.eth_api(); /// } /// ``` - pub fn into_registry( + pub fn into_registry( self, config: RpcModuleConfig, - ) -> RethModuleRegistry { + eth: EthApiB, + ) -> RpcRegistryInner + where + EthApiB: FnOnce(&EthApiBuilderCtx) -> EthApi + + 'static, + { let Self { provider, pool, network, executor, events, evm_config } = self; - RethModuleRegistry::new(provider, pool, network, executor, events, config, evm_config) + RpcRegistryInner::new(provider, pool, network, executor, events, config, evm_config, eth) } /// Configures all [`RpcModule`]s specific to the given [`TransportRpcModuleConfig`] which can /// be used to start the transport server(s). /// /// See also [`RpcServer::start`] - pub fn build(self, module_config: TransportRpcModuleConfig) -> TransportRpcModules<()> { + pub fn build( + self, + module_config: TransportRpcModuleConfig, + eth: EthApiB, + ) -> TransportRpcModules<()> + where + EthApiB: FnOnce(&EthApiBuilderCtx) -> EthApi + + 'static, + EthApi: FullEthApiServer, + { let mut modules = TransportRpcModules::default(); let Self { provider, pool, network, executor, events, evm_config } = self; @@ -525,7 +520,7 @@ where if !module_config.is_empty() { let TransportRpcModuleConfig { http, ws, ipc, config } = module_config.clone(); - let mut registry = RethModuleRegistry::new( + let mut registry = RpcRegistryInner::new( provider, pool, network, @@ -533,6 +528,7 @@ where events, config.unwrap_or_default(), evm_config, + eth, ); modules.config = module_config; @@ -621,34 +617,34 @@ impl RpcModuleConfigBuilder { /// A Helper type the holds instances of the configured modules. #[derive(Debug, Clone)] -pub struct RethModuleRegistry { +pub struct RpcRegistryInner { provider: Provider, pool: Pool, network: Network, executor: Tasks, events: Events, - /// Defines how to configure the EVM before execution. - evm_config: EvmConfig, - /// Additional settings for handlers. - config: RpcModuleConfig, - /// Holds a clone of all the eth namespace handlers - eth: Option>, + /// Holds a all `eth_` namespace handlers + eth: EthHandlers, /// to put trace calls behind semaphore blocking_pool_guard: BlockingTaskGuard, /// Contains the [Methods] of a module modules: HashMap, - /// Optional forwarder for `eth_sendRawTransaction` - // TODO(mattsse): find a more ergonomic way to configure eth/rpc customizations - eth_raw_transaction_forwarder: Option>, } -// === impl RethModuleRegistry === +// === impl RpcRegistryInner === -impl - RethModuleRegistry +impl + RpcRegistryInner +where + Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, + Pool: Send + Sync + Clone + 'static, + Network: Clone, + Events: CanonStateSubscriptions + Clone, + Tasks: TaskSpawner + Clone + 'static, { /// Creates a new, empty instance. - pub fn new( + #[allow(clippy::too_many_arguments)] + pub fn new( provider: Provider, pool: Pool, network: Network, @@ -656,34 +652,59 @@ impl events: Events, config: RpcModuleConfig, evm_config: EvmConfig, - ) -> Self { + eth_api_builder: EthApiB, + ) -> Self + where + EvmConfig: ConfigureEvm, + EthApiB: FnOnce(&EthApiBuilderCtx) -> EthApi + + 'static, + { + let blocking_pool_guard = BlockingTaskGuard::new(config.eth.max_tracing_requests); + + let eth = EthHandlers::builder( + provider.clone(), + pool.clone(), + network.clone(), + evm_config, + config.eth, + executor.clone(), + events.clone(), + eth_api_builder, + ) + .build(); + Self { provider, pool, network, - evm_config, - eth: None, + eth, executor, modules: Default::default(), - blocking_pool_guard: BlockingTaskGuard::new(config.eth.max_tracing_requests), - config, + blocking_pool_guard, events, - eth_raw_transaction_forwarder: None, } } +} - /// Sets a forwarder for `eth_sendRawTransaction` +impl + RpcRegistryInner +{ + /// Returns a reference to the installed [`EthApi`](reth_rpc::eth::EthApi). + pub const fn eth_api(&self) -> &EthApi { + &self.eth.api + } + + /// Returns a reference to the installed [`EthHandlers`]. + pub const fn eth_handlers(&self) -> &EthHandlers { + &self.eth + } + + /// Returns the [`EthStateCache`] frontend /// - /// Note: this might be removed in the future in favor of a more generic approach. - pub fn set_eth_raw_transaction_forwarder( - &mut self, - forwarder: Arc, - ) { - if let Some(eth) = self.eth.as_ref() { - // in case the eth api has been created before the forwarder was set: - eth.api.set_eth_raw_transaction_forwarder(forwarder.clone()); - } - self.eth_raw_transaction_forwarder = Some(forwarder); + /// This will spawn exactly one [`EthStateCache`] service if this is the first time the cache is + /// requested. + pub const fn eth_cache(&self) -> &EthStateCache { + &self.eth.cache } /// Returns a reference to the pool @@ -721,13 +742,30 @@ impl } } -impl - RethModuleRegistry +impl + RpcRegistryInner where - Network: NetworkInfo + Peers + Clone + 'static, + EthApi: UpdateRawTxForwarder, +{ + /// Sets a forwarder for `eth_sendRawTransaction` + /// + /// Note: this might be removed in the future in favor of a more generic approach. + pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { + // in case the eth api has been created before the forwarder was set: + self.eth.api.set_eth_raw_transaction_forwarder(forwarder.clone()); + } +} + +impl + RpcRegistryInner +where + Network: NetworkInfo + Clone + 'static, { /// Instantiates `AdminApi` - pub fn admin_api(&self) -> AdminApi { + pub fn admin_api(&self) -> AdminApi + where + Network: Peers, + { AdminApi::new(self.network.clone(), self.provider.chain_spec()) } @@ -737,7 +775,10 @@ where } /// Register Admin Namespace - pub fn register_admin(&mut self) -> &mut Self { + pub fn register_admin(&mut self) -> &mut Self + where + Network: Peers, + { let adminapi = self.admin_api(); self.modules.insert(RethRpcModule::Admin, adminapi.into_rpc().into()); self @@ -751,31 +792,24 @@ where } } -impl - RethModuleRegistry +impl + RpcRegistryInner where - Provider: BlockReaderIdExt - + AccountReader - + StateProviderFactory - + EvmEnvProvider - + ChainSpecProvider - + ChangeSetReader - + Clone - + Unpin - + 'static, - Pool: TransactionPool + Clone + 'static, + Provider: FullRpcProvider + AccountReader + ChangeSetReader, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, - Events: CanonStateSubscriptions + Clone + 'static, - EvmConfig: ConfigureEvm, + EthApi: Clone, { /// Register Eth Namespace /// /// # Panics /// /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn register_eth(&mut self) -> &mut Self { - let eth_api = self.eth_api(); + pub fn register_eth(&mut self) -> &mut Self + where + EthApi: EthApiServer, + { + let eth_api = self.eth_api().clone(); self.modules.insert(RethRpcModule::Eth, eth_api.into_rpc().into()); self } @@ -785,7 +819,10 @@ where /// # Panics /// /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn register_ots(&mut self) -> &mut Self { + pub fn register_ots(&mut self) -> &mut Self + where + EthApi: EthApiServer + TraceExt, + { let otterscan_api = self.otterscan_api(); self.modules.insert(RethRpcModule::Ots, otterscan_api.into_rpc().into()); self @@ -796,7 +833,10 @@ where /// # Panics /// /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn register_debug(&mut self) -> &mut Self { + pub fn register_debug(&mut self) -> &mut Self + where + EthApi: EthApiSpec + EthTransactions + TraceExt, + { let debug_api = self.debug_api(); self.modules.insert(RethRpcModule::Debug, debug_api.into_rpc().into()); self @@ -807,34 +847,15 @@ where /// # Panics /// /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn register_trace(&mut self) -> &mut Self { + pub fn register_trace(&mut self) -> &mut Self + where + EthApi: TraceExt, + { let trace_api = self.trace_api(); self.modules.insert(RethRpcModule::Trace, trace_api.into_rpc().into()); self } - /// Configures the auth module that includes the - /// * `engine_` namespace - /// * `api_` namespace - /// - /// Note: This does _not_ register the `engine_` in this registry. - pub fn create_auth_module(&mut self, engine_api: EngineApi) -> AuthRpcModule - where - EngineT: EngineTypes + 'static, - EngineApi: EngineApiServer, - { - let eth_handlers = self.eth_handlers(); - let mut module = RpcModule::new(()); - - module.merge(engine_api.into_rpc()).expect("No conflicting methods"); - - // also merge a subset of `eth_` handlers - let engine_eth = EngineEthApi::new(eth_handlers.api.clone(), eth_handlers.filter); - module.merge(engine_eth.into_rpc()).expect("No conflicting methods"); - - AuthRpcModule { inner: module } - } - /// Register Net Namespace /// /// See also [`Self::eth_api`] @@ -842,7 +863,10 @@ where /// # Panics /// /// If called outside of the tokio runtime. - pub fn register_net(&mut self) -> &mut Self { + pub fn register_net(&mut self) -> &mut Self + where + EthApi: EthApiSpec + 'static, + { let netapi = self.net_api(); self.modules.insert(RethRpcModule::Net, netapi.into_rpc().into()); self @@ -861,6 +885,113 @@ where self } + /// Instantiates `TraceApi` + /// + /// # Panics + /// + /// If called outside of the tokio runtime. See also [`Self::eth_api`] + pub fn trace_api(&self) -> TraceApi + where + EthApi: TraceExt, + { + TraceApi::new( + self.provider.clone(), + self.eth_api().clone(), + self.blocking_pool_guard.clone(), + ) + } + + /// Instantiates [`EthBundle`] Api + /// + /// # Panics + /// + /// If called outside of the tokio runtime. See also [`Self::eth_api`] + pub fn bundle_api(&self) -> EthBundle + where + EthApi: EthTransactions + LoadPendingBlock + Call, + { + let eth_api = self.eth_api().clone(); + EthBundle::new(eth_api, self.blocking_pool_guard.clone()) + } + + /// Instantiates `OtterscanApi` + /// + /// # Panics + /// + /// If called outside of the tokio runtime. See also [`Self::eth_api`] + pub fn otterscan_api(&self) -> OtterscanApi + where + EthApi: EthApiServer, + { + let eth_api = self.eth_api().clone(); + OtterscanApi::new(eth_api) + } + + /// Instantiates `DebugApi` + /// + /// # Panics + /// + /// If called outside of the tokio runtime. See also [`Self::eth_api`] + pub fn debug_api(&self) -> DebugApi + where + EthApi: EthApiSpec + EthTransactions + TraceExt, + { + let eth_api = self.eth_api().clone(); + DebugApi::new(self.provider.clone(), eth_api, self.blocking_pool_guard.clone()) + } + + /// Instantiates `NetApi` + /// + /// # Panics + /// + /// If called outside of the tokio runtime. See also [`Self::eth_api`] + pub fn net_api(&self) -> NetApi + where + EthApi: EthApiSpec + 'static, + { + let eth_api = self.eth_api().clone(); + NetApi::new(self.network.clone(), eth_api) + } + + /// Instantiates `RethApi` + pub fn reth_api(&self) -> RethApi { + RethApi::new(self.provider.clone(), Box::new(self.executor.clone())) + } +} + +impl + RpcRegistryInner +where + Provider: FullRpcProvider + AccountReader + ChangeSetReader, + Pool: TransactionPool + 'static, + Network: NetworkInfo + Peers + Clone + 'static, + Tasks: TaskSpawner + Clone + 'static, + Events: CanonStateSubscriptions + Clone + 'static, + EthApi: FullEthApiServer, +{ + /// Configures the auth module that includes the + /// * `engine_` namespace + /// * `api_` namespace + /// + /// Note: This does _not_ register the `engine_` in this registry. + pub fn create_auth_module(&self, engine_api: EngineApi) -> AuthRpcModule + where + EngineT: EngineTypes + 'static, + EngineApi: EngineApiServer, + { + let mut module = RpcModule::new(()); + + module.merge(engine_api.into_rpc()).expect("No conflicting methods"); + + // also merge a subset of `eth_` handlers + let eth_handlers = self.eth_handlers(); + let engine_eth = EngineEthApi::new(eth_handlers.api.clone(), eth_handlers.filter.clone()); + + module.merge(engine_eth.into_rpc()).expect("No conflicting methods"); + + AuthRpcModule { inner: module } + } + /// Helper function to create a [`RpcModule`] if it's not `None` fn maybe_module(&mut self, config: Option<&RpcModuleSelection>) -> Option> { config.map(|config| self.module_for(config)) @@ -908,13 +1039,8 @@ where &mut self, namespaces: impl Iterator, ) -> Vec { - let EthHandlers { - api: eth_api, - filter: eth_filter, - pubsub: eth_pubsub, - cache: _, - blocking_task_pool: _, - } = self.with_eth(|eth| eth.clone()); + let EthHandlers { api: eth_api, filter: eth_filter, pubsub: eth_pubsub, .. } = + self.eth_handlers().clone(); // Create a copy, so we can list out all the methods for rpc_ api let namespaces: Vec<_> = namespaces.collect(); @@ -983,120 +1109,6 @@ where }) .collect::>() } - - /// Returns the [`EthStateCache`] frontend - /// - /// This will spawn exactly one [`EthStateCache`] service if this is the first time the cache is - /// requested. - pub fn eth_cache(&mut self) -> EthStateCache { - self.with_eth(|handlers| handlers.cache.clone()) - } - - /// Creates the [`EthHandlers`] type the first time this is called. - /// - /// This will spawn the required service tasks for [`EthApi`] for: - /// - [`EthStateCache`] - /// - [`FeeHistoryCache`](reth_rpc_eth_types::FeeHistoryCache) - fn with_eth(&mut self, f: F) -> R - where - F: FnOnce(&EthHandlers) -> R, - { - f(match &self.eth { - Some(eth) => eth, - None => self.eth.insert(self.init_eth()), - }) - } - - fn init_eth(&self) -> EthHandlers { - EthHandlersBuilder::new( - EthHandlersConfig { - provider: self.provider.clone(), - pool: self.pool.clone(), - network: self.network.clone(), - executor: self.executor.clone(), - events: self.events.clone(), - evm_config: self.evm_config.clone(), - eth_raw_transaction_forwarder: self.eth_raw_transaction_forwarder.clone(), - }, - self.config.clone(), - ) - .build() - } - - /// Returns the configured [`EthHandlers`] or creates it if it does not exist yet - /// - /// # Panics - /// - /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn eth_handlers(&mut self) -> EthHandlers { - self.with_eth(|handlers| handlers.clone()) - } - - /// Returns the configured [`EthApi`] or creates it if it does not exist yet - /// - /// Caution: This will spawn the necessary tasks required by the [`EthApi`]: [`EthStateCache`]. - /// - /// # Panics - /// - /// If called outside of the tokio runtime. - pub fn eth_api(&mut self) -> EthApi { - self.with_eth(|handlers| handlers.api.clone()) - } - - /// Instantiates `TraceApi` - /// - /// # Panics - /// - /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn trace_api(&mut self) -> TraceApi> { - let eth = self.eth_handlers(); - TraceApi::new(self.provider.clone(), eth.api, self.blocking_pool_guard.clone()) - } - - /// Instantiates [`EthBundle`] Api - /// - /// # Panics - /// - /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn bundle_api(&mut self) -> EthBundle> { - let eth_api = self.eth_api(); - EthBundle::new(eth_api, self.blocking_pool_guard.clone()) - } - - /// Instantiates `OtterscanApi` - /// - /// # Panics - /// - /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn otterscan_api(&mut self) -> OtterscanApi> { - let eth_api = self.eth_api(); - OtterscanApi::new(eth_api) - } - - /// Instantiates `DebugApi` - /// - /// # Panics - /// - /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn debug_api(&mut self) -> DebugApi> { - let eth_api = self.eth_api(); - DebugApi::new(self.provider.clone(), eth_api, self.blocking_pool_guard.clone()) - } - - /// Instantiates `NetApi` - /// - /// # Panics - /// - /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn net_api(&mut self) -> NetApi> { - let eth_api = self.eth_api(); - NetApi::new(self.network.clone(), eth_api) - } - - /// Instantiates `RethApi` - pub fn reth_api(&self) -> RethApi { - RethApi::new(self.provider.clone(), Box::new(self.executor.clone())) - } } /// A builder type for configuring and launching the servers that will handle RPC requests. diff --git a/crates/rpc/rpc-builder/tests/it/startup.rs b/crates/rpc/rpc-builder/tests/it/startup.rs index 91800166f1d9..4c873f2b38c9 100644 --- a/crates/rpc/rpc-builder/tests/it/startup.rs +++ b/crates/rpc/rpc-builder/tests/it/startup.rs @@ -1,14 +1,16 @@ //! Startup tests -use crate::utils::{ - launch_http, launch_http_ws_same_port, launch_ws, test_address, test_rpc_builder, -}; +use std::io; + use reth_rpc_builder::{ error::{RpcError, ServerKind, WsHttpSamePortError}, - RpcServerConfig, TransportRpcModuleConfig, + EthApiBuild, RpcServerConfig, TransportRpcModuleConfig, }; use reth_rpc_server_types::RethRpcModule; -use std::io; + +use crate::utils::{ + launch_http, launch_http_ws_same_port, launch_ws, test_address, test_rpc_builder, +}; fn is_addr_in_use_kind(err: &RpcError, kind: ServerKind) -> bool { match err { @@ -24,7 +26,8 @@ async fn test_http_addr_in_use() { let handle = launch_http(vec![RethRpcModule::Admin]).await; let addr = handle.http_local_addr().unwrap(); let builder = test_rpc_builder(); - let server = builder.build(TransportRpcModuleConfig::set_http(vec![RethRpcModule::Admin])); + let server = builder + .build(TransportRpcModuleConfig::set_http(vec![RethRpcModule::Admin]), EthApiBuild::build); let result = server .start_server(RpcServerConfig::http(Default::default()).with_http_address(addr)) .await; @@ -37,7 +40,8 @@ async fn test_ws_addr_in_use() { let handle = launch_ws(vec![RethRpcModule::Admin]).await; let addr = handle.ws_local_addr().unwrap(); let builder = test_rpc_builder(); - let server = builder.build(TransportRpcModuleConfig::set_ws(vec![RethRpcModule::Admin])); + let server = builder + .build(TransportRpcModuleConfig::set_ws(vec![RethRpcModule::Admin]), EthApiBuild::build); let result = server.start_server(RpcServerConfig::ws(Default::default()).with_ws_address(addr)).await; let err = result.unwrap_err(); @@ -58,6 +62,7 @@ async fn test_launch_same_port_different_modules() { let server = builder.build( TransportRpcModuleConfig::set_ws(vec![RethRpcModule::Admin]) .with_http(vec![RethRpcModule::Eth]), + EthApiBuild::build, ); let addr = test_address(); let res = server @@ -81,6 +86,7 @@ async fn test_launch_same_port_same_cors() { let server = builder.build( TransportRpcModuleConfig::set_ws(vec![RethRpcModule::Eth]) .with_http(vec![RethRpcModule::Eth]), + EthApiBuild::build, ); let addr = test_address(); let res = server @@ -102,6 +108,7 @@ async fn test_launch_same_port_different_cors() { let server = builder.build( TransportRpcModuleConfig::set_ws(vec![RethRpcModule::Eth]) .with_http(vec![RethRpcModule::Eth]), + EthApiBuild::build, ); let addr = test_address(); let res = server diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index d751b2d331a2..85c9dbeac3f2 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -1,3 +1,5 @@ +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use reth_beacon_consensus::BeaconConsensusEngineHandle; use reth_chainspec::MAINNET; use reth_ethereum_engine_primitives::EthEngineTypes; @@ -7,7 +9,7 @@ use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_provider::test_utils::{NoopProvider, TestCanonStateSubscriptions}; use reth_rpc_builder::{ auth::{AuthRpcModule, AuthServerConfig, AuthServerHandle}, - RpcModuleBuilder, RpcServerConfig, RpcServerHandle, TransportRpcModuleConfig, + EthApiBuild, RpcModuleBuilder, RpcServerConfig, RpcServerHandle, TransportRpcModuleConfig, }; use reth_rpc_engine_api::EngineApi; use reth_rpc_layer::JwtSecret; @@ -15,7 +17,6 @@ use reth_rpc_server_types::RpcModuleSelection; use reth_rpc_types::engine::{ClientCode, ClientVersionV1}; use reth_tasks::TokioTaskExecutor; use reth_transaction_pool::test_utils::{TestPool, TestPoolBuilder}; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use tokio::sync::mpsc::unbounded_channel; /// Localhost with port 0 so a free port is used. @@ -51,7 +52,7 @@ pub async fn launch_auth(secret: JwtSecret) -> AuthServerHandle { /// Launches a new server with http only with the given modules pub async fn launch_http(modules: impl Into) -> RpcServerHandle { let builder = test_rpc_builder(); - let server = builder.build(TransportRpcModuleConfig::set_http(modules)); + let server = builder.build(TransportRpcModuleConfig::set_http(modules), EthApiBuild::build); server .start_server(RpcServerConfig::http(Default::default()).with_http_address(test_address())) .await @@ -61,7 +62,7 @@ pub async fn launch_http(modules: impl Into) -> RpcServerHan /// Launches a new server with ws only with the given modules pub async fn launch_ws(modules: impl Into) -> RpcServerHandle { let builder = test_rpc_builder(); - let server = builder.build(TransportRpcModuleConfig::set_ws(modules)); + let server = builder.build(TransportRpcModuleConfig::set_ws(modules), EthApiBuild::build); server .start_server(RpcServerConfig::ws(Default::default()).with_ws_address(test_address())) .await @@ -72,8 +73,10 @@ pub async fn launch_ws(modules: impl Into) -> RpcServerHandl pub async fn launch_http_ws(modules: impl Into) -> RpcServerHandle { let builder = test_rpc_builder(); let modules = modules.into(); - let server = - builder.build(TransportRpcModuleConfig::set_ws(modules.clone()).with_http(modules)); + let server = builder.build( + TransportRpcModuleConfig::set_ws(modules.clone()).with_http(modules), + EthApiBuild::build, + ); server .start_server( RpcServerConfig::ws(Default::default()) @@ -89,8 +92,10 @@ pub async fn launch_http_ws(modules: impl Into) -> RpcServer pub async fn launch_http_ws_same_port(modules: impl Into) -> RpcServerHandle { let builder = test_rpc_builder(); let modules = modules.into(); - let server = - builder.build(TransportRpcModuleConfig::set_ws(modules.clone()).with_http(modules)); + let server = builder.build( + TransportRpcModuleConfig::set_ws(modules.clone()).with_http(modules), + EthApiBuild::build, + ); let addr = test_address(); server .start_server( diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 834682670cae..1f5ced83d2f6 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -16,9 +16,16 @@ use reth_rpc_types::{ use tracing::trace; use crate::helpers::{ - EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadReceipt, Trace, + transaction::UpdateRawTxForwarder, EthApiSpec, EthBlocks, EthCall, EthFees, EthState, + EthTransactions, FullEthApi, }; +/// Helper trait, unifies functionality that must be supported to implement all RPC methods for +/// server. +pub trait FullEthApiServer: EthApiServer + FullEthApi + UpdateRawTxForwarder + Clone {} + +impl FullEthApiServer for T where T: EthApiServer + FullEthApi + UpdateRawTxForwarder + Clone {} + /// Eth rpc interface: #[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] @@ -324,14 +331,7 @@ pub trait EthApi { #[async_trait::async_trait] impl EthApiServer for T where - Self: EthApiSpec - + EthTransactions - + EthBlocks - + EthState - + EthCall - + EthFees - + Trace - + LoadReceipt, + Self: FullEthApi, { /// Handler for: `eth_protocolVersion` async fn protocol_version(&self) -> RpcResult { diff --git a/crates/rpc/rpc-eth-api/src/helpers/mod.rs b/crates/rpc/rpc-eth-api/src/helpers/mod.rs index 321e9b03ec5d..d7d31320ce0b 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/mod.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/mod.rs @@ -36,7 +36,7 @@ pub use signer::EthSigner; pub use spec::EthApiSpec; pub use state::{EthState, LoadState}; pub use trace::Trace; -pub use transaction::{EthTransactions, LoadTransaction}; +pub use transaction::{EthTransactions, LoadTransaction, UpdateRawTxForwarder}; /// Extension trait that bundles traits needed for tracing transactions. pub trait TraceExt: @@ -45,3 +45,21 @@ pub trait TraceExt: } impl TraceExt for T where T: LoadTransaction + LoadBlock + LoadPendingBlock + Trace + Call {} + +/// Helper trait to unify all `eth` rpc server building block traits, for simplicity. +pub trait FullEthApi: + EthApiSpec + EthTransactions + EthBlocks + EthState + EthCall + EthFees + Trace + LoadReceipt +{ +} + +impl FullEthApi for T where + T: EthApiSpec + + EthTransactions + + EthBlocks + + EthState + + EthCall + + EthFees + + Trace + + LoadReceipt +{ +} diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index acf0156c6d9f..0402200cefbf 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -1,7 +1,7 @@ //! Database access for `eth_` transaction RPC methods. Loads transaction and receipt data w.r.t. //! network. -use std::{fmt, sync::Arc}; +use std::{fmt, ops::Deref, sync::Arc}; use alloy_dyn_abi::TypedData; use futures::Future; @@ -648,3 +648,21 @@ pub trait RawTransactionForwarder: fmt::Debug + Send + Sync + 'static { /// Forwards raw transaction bytes for `eth_sendRawTransaction` async fn forward_raw_transaction(&self, raw: &[u8]) -> EthResult<()>; } + +/// Configure server's forwarder for `eth_sendRawTransaction`, at runtime. +pub trait UpdateRawTxForwarder { + /// Sets a forwarder for `eth_sendRawTransaction` + /// + /// Note: this might be removed in the future in favor of a more generic approach. + fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc); +} + +impl UpdateRawTxForwarder for T +where + T: Deref>, + K: UpdateRawTxForwarder, +{ + fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { + self.deref().deref().set_eth_raw_transaction_forwarder(forwarder); + } +} diff --git a/crates/rpc/rpc-eth-api/src/lib.rs b/crates/rpc/rpc-eth-api/src/lib.rs index 922c6ed77fad..1aed94d5cc6e 100644 --- a/crates/rpc/rpc-eth-api/src/lib.rs +++ b/crates/rpc/rpc-eth-api/src/lib.rs @@ -19,7 +19,7 @@ pub mod helpers; pub mod pubsub; pub use bundle::{EthBundleApiServer, EthCallBundleApiServer}; -pub use core::EthApiServer; +pub use core::{EthApiServer, FullEthApiServer}; pub use filter::EthFilterApiServer; pub use pubsub::EthPubSubApiServer; diff --git a/crates/rpc/rpc-eth-types/src/cache/config.rs b/crates/rpc/rpc-eth-types/src/cache/config.rs index c2d379652a47..64999bd6bf3e 100644 --- a/crates/rpc/rpc-eth-types/src/cache/config.rs +++ b/crates/rpc/rpc-eth-types/src/cache/config.rs @@ -8,7 +8,7 @@ use reth_rpc_server_types::constants::cache::{ }; /// Settings for the [`EthStateCache`](super::EthStateCache). -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EthStateCacheConfig { /// Max number of blocks in cache. diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 6f84511aa6eb..fef2dc9eab21 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -168,7 +168,7 @@ impl FeeHistoryCache { } /// Settings for the [`FeeHistoryCache`]. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FeeHistoryCacheConfig { /// Max number of blocks in cache. diff --git a/crates/rpc/rpc-eth-types/src/gas_oracle.rs b/crates/rpc/rpc-eth-types/src/gas_oracle.rs index 4993c5d78a6e..92226748cead 100644 --- a/crates/rpc/rpc-eth-types/src/gas_oracle.rs +++ b/crates/rpc/rpc-eth-types/src/gas_oracle.rs @@ -24,7 +24,7 @@ use super::{EthApiError, EthResult, EthStateCache, RpcInvalidTransactionError}; pub const RPC_DEFAULT_GAS_CAP: GasCap = GasCap(constants::gas_oracle::RPC_DEFAULT_GAS_CAP); /// Settings for the [`GasPriceOracle`] -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GasPriceOracleConfig { /// The number of populated blocks to produce the gas price estimate diff --git a/crates/rpc/rpc-eth-types/src/lib.rs b/crates/rpc/rpc-eth-types/src/lib.rs index be4e1619ce42..fb9901dd071c 100644 --- a/crates/rpc/rpc-eth-types/src/lib.rs +++ b/crates/rpc/rpc-eth-types/src/lib.rs @@ -26,7 +26,9 @@ pub use cache::{ }; pub use error::{EthApiError, EthResult, RevertError, RpcInvalidTransactionError, SignError}; pub use fee_history::{FeeHistoryCache, FeeHistoryCacheConfig, FeeHistoryEntry}; -pub use gas_oracle::{GasCap, GasPriceOracle, GasPriceOracleConfig, GasPriceOracleResult}; +pub use gas_oracle::{ + GasCap, GasPriceOracle, GasPriceOracleConfig, GasPriceOracleResult, RPC_DEFAULT_GAS_CAP, +}; pub use id_provider::EthSubscriptionIdProvider; pub use logs_utils::EthFilterError; pub use pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 118a9a82190d..df6787199ce3 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -71,6 +71,7 @@ futures.workspace = true rand.workspace = true serde.workspace = true thiserror.workspace = true +derive_more.workspace = true [dev-dependencies] reth-evm-ethereum.workspace = true diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index f2eb81851f9b..e876faca8af6 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -3,10 +3,11 @@ use std::sync::Arc; +use derive_more::Deref; use reth_primitives::{BlockNumberOrTag, U256}; use reth_provider::{BlockReaderIdExt, ChainSpecProvider}; use reth_rpc_eth_api::{ - helpers::{EthSigner, SpawnBlocking}, + helpers::{transaction::UpdateRawTxForwarder, EthSigner, SpawnBlocking}, RawTransactionForwarder, }; use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock}; @@ -24,6 +25,7 @@ use crate::eth::DevSigner; /// separately in submodules. The rpc handler implementation can then delegate to the main impls. /// This way [`EthApi`] is not limited to [`jsonrpsee`] and can be used standalone or in other /// network handlers (for example ipc). +#[derive(Deref)] pub struct EthApi { /// All nested fields bundled together. pub(super) inner: Arc>, @@ -302,6 +304,14 @@ impl EthApiInner UpdateRawTxForwarder + for EthApiInner +{ + fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { + self.raw_transaction_forwarder.write().replace(forwarder); + } +} + #[cfg(test)] mod tests { use jsonrpsee_types::error::INVALID_PARAMS_CODE; diff --git a/crates/storage/db-api/src/lib.rs b/crates/storage/db-api/src/lib.rs index dc7ab0eb4a65..cd25b3c65fa0 100644 --- a/crates/storage/db-api/src/lib.rs +++ b/crates/storage/db-api/src/lib.rs @@ -79,3 +79,5 @@ pub mod models; mod scale; mod utils; + +pub use database::Database; diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index d52da187f7dd..5ffdfedc55a1 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -1,11 +1,8 @@ -use crate::{ - traits::{BlockSource, ReceiptProvider}, - AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, - ChainSpecProvider, ChangeSetReader, EvmEnvProvider, HeaderProvider, PruneCheckpointReader, - ReceiptProviderIdExt, RequestsProvider, StageCheckpointReader, StateProvider, StateProviderBox, - StateProviderFactory, StateRootProvider, TransactionVariant, TransactionsProvider, - WithdrawalsProvider, +use std::{ + ops::{RangeBounds, RangeInclusive}, + sync::Arc, }; + use reth_chainspec::{ChainInfo, ChainSpec, MAINNET}; use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices}; use reth_evm::ConfigureEvmEnv; @@ -23,9 +20,17 @@ use revm::{ db::BundleState, primitives::{BlockEnv, CfgEnvWithHandlerCfg}, }; -use std::{ - ops::{RangeBounds, RangeInclusive}, - sync::Arc, +use tokio::sync::broadcast; + +use crate::{ + providers::StaticFileProvider, + traits::{BlockSource, ReceiptProvider}, + AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, + CanonStateNotifications, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, + EvmEnvProvider, HeaderProvider, PruneCheckpointReader, ReceiptProviderIdExt, RequestsProvider, + StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, + StateRootProvider, StaticFileProviderFactory, TransactionVariant, TransactionsProvider, + WithdrawalsProvider, }; /// Supports various api interfaces for testing purposes. @@ -465,3 +470,15 @@ impl PruneCheckpointReader for NoopProvider { Ok(None) } } + +impl StaticFileProviderFactory for NoopProvider { + fn static_file_provider(&self) -> StaticFileProvider { + StaticFileProvider::default() + } +} + +impl CanonStateSubscriptions for NoopProvider { + fn subscribe_to_canonical_state(&self) -> CanonStateNotifications { + broadcast::channel(1).1 + } +} diff --git a/crates/storage/provider/src/traits/full.rs b/crates/storage/provider/src/traits/full.rs index 6b35d6b25135..c53150560d3a 100644 --- a/crates/storage/provider/src/traits/full.rs +++ b/crates/storage/provider/src/traits/full.rs @@ -2,8 +2,8 @@ use crate::{ AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, - DatabaseProviderFactory, EvmEnvProvider, StageCheckpointReader, StateProviderFactory, - StaticFileProviderFactory, + DatabaseProviderFactory, EvmEnvProvider, HeaderProvider, StageCheckpointReader, + StateProviderFactory, StaticFileProviderFactory, TransactionsProvider, }; use reth_db_api::database::Database; @@ -41,3 +41,31 @@ impl FullProvider for T where + 'static { } + +/// Helper trait to unify all provider traits required to support `eth` RPC server behaviour, for +/// simplicity. +pub trait FullRpcProvider: + StateProviderFactory + + EvmEnvProvider + + ChainSpecProvider + + BlockReaderIdExt + + HeaderProvider + + TransactionsProvider + + Clone + + Unpin + + 'static +{ +} + +impl FullRpcProvider for T where + T: StateProviderFactory + + EvmEnvProvider + + ChainSpecProvider + + BlockReaderIdExt + + HeaderProvider + + TransactionsProvider + + Clone + + Unpin + + 'static +{ +} diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index bf69eda03b0b..16071edfff12 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -43,7 +43,7 @@ mod stats; pub use stats::StatsReader; mod full; -pub use full::FullProvider; +pub use full::{FullProvider, FullRpcProvider}; mod tree_viewer; pub use tree_viewer::TreeViewer; diff --git a/examples/custom-dev-node/src/main.rs b/examples/custom-dev-node/src/main.rs index 24e7b229f54b..176e4c503827 100644 --- a/examples/custom-dev-node/src/main.rs +++ b/examples/custom-dev-node/src/main.rs @@ -27,7 +27,7 @@ async fn main() -> eyre::Result<()> { .with_rpc(RpcServerArgs::default().with_http()) .with_chain(custom_chain()); - let NodeHandle { mut node, node_exit_future: _ } = NodeBuilder::new(node_config) + let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config) .testing_node(tasks.executor()) .node(EthereumNode::default()) .launch() diff --git a/examples/custom-inspector/src/main.rs b/examples/custom-inspector/src/main.rs index fd1d82b59338..b6721eded67c 100644 --- a/examples/custom-inspector/src/main.rs +++ b/examples/custom-inspector/src/main.rs @@ -31,14 +31,14 @@ fn main() { Cli::::parse() .run(|builder, args| async move { // launch the node - let NodeHandle { mut node, node_exit_future } = + let NodeHandle { node, node_exit_future } = builder.node(EthereumNode::default()).launch().await?; // create a new subscription to pending transactions let mut pending_transactions = node.pool.new_pending_pool_transactions_listener(); // get an instance of the `trace_` API handler - let eth_api = node.rpc_registry.eth_api(); + let eth_api = node.rpc_registry.eth_api().clone(); println!("Spawning trace task!"); diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index 90447790561f..6d18640f7f96 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -12,6 +12,8 @@ //! cast rpc myrpcExt_customMethod //! ``` +use std::{path::Path, sync::Arc}; + use reth::{ providers::{ providers::{BlockchainProvider, StaticFileProvider}, @@ -25,7 +27,7 @@ use reth_db_api::models::ClientVersion; // Bringing up the RPC use reth::rpc::builder::{ - RethRpcModule, RpcModuleBuilder, RpcServerConfig, TransportRpcModuleConfig, + EthApiBuild, RethRpcModule, RpcModuleBuilder, RpcServerConfig, TransportRpcModuleConfig, }; // Configuring the network parts, ideally also wouldn't need to think about this. use myrpc_ext::{MyRpcExt, MyRpcExtApiServer}; @@ -34,7 +36,6 @@ use reth::{ tasks::TokioTaskExecutor, }; use reth_node_ethereum::EthEvmConfig; -use std::{path::Path, sync::Arc}; // Custom rpc extension pub mod myrpc_ext; @@ -71,7 +72,7 @@ async fn main() -> eyre::Result<()> { // Pick which namespaces to expose. let config = TransportRpcModuleConfig::default().with_http([RethRpcModule::Eth]); - let mut server = rpc_builder.build(config); + let mut server = rpc_builder.build(config, EthApiBuild::build); // Add a custom rpc namespace let custom_rpc = MyRpcExt { provider }; diff --git a/examples/txpool-tracing/src/main.rs b/examples/txpool-tracing/src/main.rs index 85a5b795aad7..c9a14dee18a7 100644 --- a/examples/txpool-tracing/src/main.rs +++ b/examples/txpool-tracing/src/main.rs @@ -28,7 +28,7 @@ fn main() { Cli::::parse() .run(|builder, args| async move { // launch the node - let NodeHandle { mut node, node_exit_future } = + let NodeHandle { node, node_exit_future } = builder.node(EthereumNode::default()).launch().await?; // create a new subscription to pending transactions From 7401f46790f46c87cb1040f2292e38b88e14a07e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 3 Jul 2024 18:25:39 +0200 Subject: [PATCH 313/405] feat: add resolve blocking for TrustedNode (#9258) --- crates/net/peers/src/trusted_peer.rs | 70 +++++++++++++++++----------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/crates/net/peers/src/trusted_peer.rs b/crates/net/peers/src/trusted_peer.rs index 27096bcab3d8..b52c98232f5c 100644 --- a/crates/net/peers/src/trusted_peer.rs +++ b/crates/net/peers/src/trusted_peer.rs @@ -45,24 +45,41 @@ impl TrustedPeer { Self { host, tcp_port: port, udp_port: port, id } } + const fn to_node_record(&self, ip: IpAddr) -> NodeRecord { + NodeRecord { address: ip, id: self.id, tcp_port: self.tcp_port, udp_port: self.udp_port } + } + + /// Tries to resolve directly to a [`NodeRecord`] if the host is an IP address. + fn try_node_record(&self) -> Result { + match &self.host { + Host::Ipv4(ip) => Ok(self.to_node_record((*ip).into())), + Host::Ipv6(ip) => Ok(self.to_node_record((*ip).into())), + Host::Domain(domain) => Err(domain), + } + } + + /// Resolves the host in a [`TrustedPeer`] to an IP address, returning a [`NodeRecord`]. + /// + /// This use [`ToSocketAddr`](std::net::ToSocketAddrs) to resolve the host to an IP address. + pub fn resolve_blocking(&self) -> Result { + let domain = match self.try_node_record() { + Ok(record) => return Ok(record), + Err(domain) => domain, + }; + // Resolve the domain to an IP address + let mut ips = std::net::ToSocketAddrs::to_socket_addrs(&(domain, 0))?; + let ip = ips + .next() + .ok_or_else(|| Error::new(std::io::ErrorKind::AddrNotAvailable, "No IP found"))?; + + Ok(self.to_node_record(ip.ip())) + } + /// Resolves the host in a [`TrustedPeer`] to an IP address, returning a [`NodeRecord`]. pub async fn resolve(&self) -> Result { - let domain = match self.host.to_owned() { - Host::Ipv4(ip) => { - let id = self.id; - let tcp_port = self.tcp_port; - let udp_port = self.udp_port; - - return Ok(NodeRecord { address: ip.into(), id, tcp_port, udp_port }) - } - Host::Ipv6(ip) => { - let id = self.id; - let tcp_port = self.tcp_port; - let udp_port = self.udp_port; - - return Ok(NodeRecord { address: ip.into(), id, tcp_port, udp_port }) - } - Host::Domain(domain) => domain, + let domain = match self.try_node_record() { + Ok(record) => return Ok(record), + Err(domain) => domain, }; // Resolve the domain to an IP address @@ -70,12 +87,8 @@ impl TrustedPeer { let ip = ips .next() .ok_or_else(|| Error::new(std::io::ErrorKind::AddrNotAvailable, "No IP found"))?; - Ok(NodeRecord { - address: ip.ip(), - id: self.id, - tcp_port: self.tcp_port, - udp_port: self.udp_port, - }) + + Ok(self.to_node_record(ip.ip())) } } @@ -285,15 +298,16 @@ mod tests { TrustedPeer::new(url::Host::Domain(domain.to_owned()), 30300, PeerId::random()); // Resolve domain and validate - let rec = rec.resolve().await.unwrap(); - match rec.address { - std::net::IpAddr::V4(addr) => { + let ensure = |rec: NodeRecord| match rec.address { + IpAddr::V4(addr) => { assert_eq!(addr, std::net::Ipv4Addr::new(127, 0, 0, 1)) } - std::net::IpAddr::V6(addr) => { - assert_eq!(addr, std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)) + IpAddr::V6(addr) => { + assert_eq!(addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)) } - } + }; + ensure(rec.resolve().await.unwrap()); + ensure(rec.resolve_blocking().unwrap()); } } } From 4f3f5067cebb1ceb292131f833c948559d11327d Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:48:28 +0200 Subject: [PATCH 314/405] test(transaction-pool): add unit tests for `BestTransactionsWithFees` `next` (#9274) --- crates/transaction-pool/src/pool/best.rs | 179 +++++++++++++++++++++-- 1 file changed, 168 insertions(+), 11 deletions(-) diff --git a/crates/transaction-pool/src/pool/best.rs b/crates/transaction-pool/src/pool/best.rs index a595b9f4d6ed..23f2652c30ca 100644 --- a/crates/transaction-pool/src/pool/best.rs +++ b/crates/transaction-pool/src/pool/best.rs @@ -49,18 +49,16 @@ impl Iterator for BestTransactionsWithFees { // find the next transaction that satisfies the base fee loop { let best = self.best.next()?; - if best.transaction.max_fee_per_gas() < self.base_fee as u128 { - // tx violates base fee, mark it as invalid and continue - crate::traits::BestTransactions::mark_invalid(self, &best); + // If both the base fee and blob fee (if applicable for EIP-4844) are satisfied, return + // the transaction + if best.transaction.max_fee_per_gas() >= self.base_fee as u128 && + best.transaction + .max_fee_per_blob_gas() + .map_or(true, |fee| fee >= self.base_fee_per_blob_gas as u128) + { + return Some(best); } else { - // tx is EIP4844 and violates blob fee, mark it as invalid and continue - if best.transaction.max_fee_per_blob_gas().is_some_and(|max_fee_per_blob_gas| { - max_fee_per_blob_gas < self.base_fee_per_blob_gas as u128 - }) { - crate::traits::BestTransactions::mark_invalid(self, &best); - continue - }; - return Some(best) + crate::traits::BestTransactions::mark_invalid(self, &best); } } } @@ -321,4 +319,163 @@ mod tests { // iterator is empty assert!(best.next().is_none()); } + + #[test] + fn test_best_with_fees_iter_base_fee_satisfied() { + let mut pool = PendingPool::new(MockOrdering::default()); + let mut f = MockTransactionFactory::default(); + + let num_tx = 5; + let base_fee: u64 = 10; + let base_fee_per_blob_gas: u64 = 15; + + // Insert transactions with a max_fee_per_gas greater than or equal to the base fee + // Without blob fee + for nonce in 0..num_tx { + let tx = MockTransaction::eip1559() + .rng_hash() + .with_nonce(nonce) + .with_max_fee(base_fee as u128 + 5); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx), 0); + } + + let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas); + + for nonce in 0..num_tx { + let tx = best.next().expect("Transaction should be returned"); + assert_eq!(tx.nonce(), nonce); + assert!(tx.transaction.max_fee_per_gas() >= base_fee as u128); + } + } + + #[test] + fn test_best_with_fees_iter_base_fee_violated() { + let mut pool = PendingPool::new(MockOrdering::default()); + let mut f = MockTransactionFactory::default(); + + let num_tx = 5; + let base_fee: u64 = 20; + let base_fee_per_blob_gas: u64 = 15; + + // Insert transactions with a max_fee_per_gas less than the base fee + for nonce in 0..num_tx { + let tx = MockTransaction::eip1559() + .rng_hash() + .with_nonce(nonce) + .with_max_fee(base_fee as u128 - 5); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx), 0); + } + + let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas); + + // No transaction should be returned since all violate the base fee + assert!(best.next().is_none()); + } + + #[test] + fn test_best_with_fees_iter_blob_fee_satisfied() { + let mut pool = PendingPool::new(MockOrdering::default()); + let mut f = MockTransactionFactory::default(); + + let num_tx = 5; + let base_fee: u64 = 10; + let base_fee_per_blob_gas: u64 = 20; + + // Insert transactions with a max_fee_per_blob_gas greater than or equal to the base fee per + // blob gas + for nonce in 0..num_tx { + let tx = MockTransaction::eip4844() + .rng_hash() + .with_nonce(nonce) + .with_max_fee(base_fee as u128 + 5) + .with_blob_fee(base_fee_per_blob_gas as u128 + 5); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx), 0); + } + + let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas); + + // All transactions should be returned in order since they satisfy both base fee and blob + // fee + for nonce in 0..num_tx { + let tx = best.next().expect("Transaction should be returned"); + assert_eq!(tx.nonce(), nonce); + assert!(tx.transaction.max_fee_per_gas() >= base_fee as u128); + assert!( + tx.transaction.max_fee_per_blob_gas().unwrap() >= base_fee_per_blob_gas as u128 + ); + } + + // No more transactions should be returned + assert!(best.next().is_none()); + } + + #[test] + fn test_best_with_fees_iter_blob_fee_violated() { + let mut pool = PendingPool::new(MockOrdering::default()); + let mut f = MockTransactionFactory::default(); + + let num_tx = 5; + let base_fee: u64 = 10; + let base_fee_per_blob_gas: u64 = 20; + + // Insert transactions with a max_fee_per_blob_gas less than the base fee per blob gas + for nonce in 0..num_tx { + let tx = MockTransaction::eip4844() + .rng_hash() + .with_nonce(nonce) + .with_max_fee(base_fee as u128 + 5) + .with_blob_fee(base_fee_per_blob_gas as u128 - 5); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx), 0); + } + + let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas); + + // No transaction should be returned since all violate the blob fee + assert!(best.next().is_none()); + } + + #[test] + fn test_best_with_fees_iter_mixed_fees() { + let mut pool = PendingPool::new(MockOrdering::default()); + let mut f = MockTransactionFactory::default(); + + let base_fee: u64 = 10; + let base_fee_per_blob_gas: u64 = 20; + + // Insert transactions with varying max_fee_per_gas and max_fee_per_blob_gas + let tx1 = + MockTransaction::eip1559().rng_hash().with_nonce(0).with_max_fee(base_fee as u128 + 5); + let tx2 = MockTransaction::eip4844() + .rng_hash() + .with_nonce(1) + .with_max_fee(base_fee as u128 + 5) + .with_blob_fee(base_fee_per_blob_gas as u128 + 5); + let tx3 = MockTransaction::eip4844() + .rng_hash() + .with_nonce(2) + .with_max_fee(base_fee as u128 + 5) + .with_blob_fee(base_fee_per_blob_gas as u128 - 5); + let tx4 = + MockTransaction::eip1559().rng_hash().with_nonce(3).with_max_fee(base_fee as u128 - 5); + + pool.add_transaction(Arc::new(f.validated(tx1.clone())), 0); + pool.add_transaction(Arc::new(f.validated(tx2.clone())), 0); + pool.add_transaction(Arc::new(f.validated(tx3)), 0); + pool.add_transaction(Arc::new(f.validated(tx4)), 0); + + let mut best = pool.best_with_basefee_and_blobfee(base_fee, base_fee_per_blob_gas); + + let expected_order = vec![tx1, tx2]; + for expected_tx in expected_order { + let tx = best.next().expect("Transaction should be returned"); + assert_eq!(tx.transaction, expected_tx); + } + + // No more transactions should be returned + assert!(best.next().is_none()); + } } From ba370918c86fbd2f41b732e1c7dca62a913076b2 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 3 Jul 2024 21:17:33 +0200 Subject: [PATCH 315/405] clippy: rm some `type_complexity` (#9276) Co-authored-by: Matthias Seitz --- crates/consensus/beacon/src/engine/mod.rs | 19 +++++++++++++------ crates/etl/src/lib.rs | 11 +++++++++-- crates/storage/db/benches/hash_keys.rs | 1 - crates/storage/db/src/static_file/cursor.rs | 10 ++++++---- examples/stateful-precompile/src/main.rs | 9 +++++++-- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index b0a0284b6de6..9673f6205db2 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -88,6 +88,18 @@ const MAX_INVALID_HEADERS: u32 = 512u32; /// If the distance exceeds this threshold, the pipeline will be used for sync. pub const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS; +/// Represents a pending forkchoice update. +/// +/// This type encapsulates the necessary components for a pending forkchoice update +/// in the context of a beacon consensus engine. +/// +/// It consists of: +/// - The current fork choice state. +/// - Optional payload attributes specific to the engine type. +/// - Sender for the result of an oneshot channel, conveying the outcome of the fork choice update. +type PendingForkchoiceUpdate = + (ForkchoiceState, Option, oneshot::Sender>); + /// The beacon consensus engine is the driver that switches between historical and live sync. /// /// The beacon consensus engine is itself driven by messages from the Consensus Layer, which are @@ -189,12 +201,7 @@ where /// It is recorded if we cannot process the forkchoice update because /// a hook with database read-write access is active. /// This is a temporary solution to always process missed FCUs. - #[allow(clippy::type_complexity)] - pending_forkchoice_update: Option<( - ForkchoiceState, - Option, - oneshot::Sender>, - )>, + pending_forkchoice_update: Option>, /// Tracks the header of invalid payloads that were rejected by the engine because they're /// invalid. invalid_headers: InvalidHeaderCache, diff --git a/crates/etl/src/lib.rs b/crates/etl/src/lib.rs index 137a96fff1c4..0b1bd129ca0a 100644 --- a/crates/etl/src/lib.rs +++ b/crates/etl/src/lib.rs @@ -164,6 +164,14 @@ where } } +/// Type alias for the items stored in the heap of [`EtlIter`]. +/// +/// Each item in the heap is a tuple containing: +/// - A `Reverse` tuple of a key-value pair (`Vec, Vec`), used to maintain the heap in +/// ascending order of keys. +/// - An index (`usize`) representing the source file from which the key-value pair was read. +type HeapItem = (Reverse<(Vec, Vec)>, usize); + /// `EtlIter` is an iterator for traversing through sorted key-value pairs in a collection of ETL /// files. These files are created using the [`Collector`] and contain data where keys are encoded /// and values are compressed. @@ -174,8 +182,7 @@ where #[derive(Debug)] pub struct EtlIter<'a> { /// Heap managing the next items to be iterated. - #[allow(clippy::type_complexity)] - heap: BinaryHeap<(Reverse<(Vec, Vec)>, usize)>, + heap: BinaryHeap, /// Reference to the vector of ETL files being iterated over. files: &'a mut Vec, } diff --git a/crates/storage/db/benches/hash_keys.rs b/crates/storage/db/benches/hash_keys.rs index d37146fd1e28..1807e6f4a6ec 100644 --- a/crates/storage/db/benches/hash_keys.rs +++ b/crates/storage/db/benches/hash_keys.rs @@ -130,7 +130,6 @@ where /// Generates two batches. The first is to be inserted into the database before running the /// benchmark. The second is to be benchmarked with. -#[allow(clippy::type_complexity)] fn generate_batches(size: usize) -> (Vec>, Vec>) where T: Table, diff --git a/crates/storage/db/src/static_file/cursor.rs b/crates/storage/db/src/static_file/cursor.rs index 9a93ca224429..4a052c6abf3b 100644 --- a/crates/storage/db/src/static_file/cursor.rs +++ b/crates/storage/db/src/static_file/cursor.rs @@ -10,6 +10,9 @@ use std::sync::Arc; #[derive(Debug, Deref, DerefMut)] pub struct StaticFileCursor<'a>(NippyJarCursor<'a, SegmentHeader>); +/// Type alias for column results with optional values. +type ColumnResult = ProviderResult>; + impl<'a> StaticFileCursor<'a> { /// Returns a new [`StaticFileCursor`]. pub fn new(jar: &'a NippyJar, reader: Arc) -> ProviderResult { @@ -56,7 +59,7 @@ impl<'a> StaticFileCursor<'a> { pub fn get_one( &mut self, key_or_num: KeyOrNumber<'_>, - ) -> ProviderResult> { + ) -> ColumnResult { let row = self.get(key_or_num, M::MASK)?; match row { @@ -69,7 +72,7 @@ impl<'a> StaticFileCursor<'a> { pub fn get_two( &mut self, key_or_num: KeyOrNumber<'_>, - ) -> ProviderResult> { + ) -> ColumnResult<(M::FIRST, M::SECOND)> { let row = self.get(key_or_num, M::MASK)?; match row { @@ -79,11 +82,10 @@ impl<'a> StaticFileCursor<'a> { } /// Gets three column values from a row. - #[allow(clippy::type_complexity)] pub fn get_three( &mut self, key_or_num: KeyOrNumber<'_>, - ) -> ProviderResult> { + ) -> ColumnResult<(M::FIRST, M::SECOND, M::THIRD)> { let row = self.get(key_or_num, M::MASK)?; match row { diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index 538adfaafe75..038a18c4b5a6 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -30,6 +30,12 @@ use reth_tracing::{RethTracer, Tracer}; use schnellru::{ByLength, LruMap}; use std::{collections::HashMap, sync::Arc}; +/// Type alias for the LRU cache used within the [`PrecompileCache`]. +type PrecompileLRUCache = LruMap<(Bytes, u64), PrecompileResult>; + +/// Type alias for the thread-safe `Arc>` wrapper around [`PrecompileCache`]. +type CachedPrecompileResult = Arc>; + /// A cache for precompile inputs / outputs. /// /// This assumes that the precompile is a standard precompile, as in `StandardPrecompileFn`, meaning @@ -40,8 +46,7 @@ use std::{collections::HashMap, sync::Arc}; #[derive(Debug, Default)] pub struct PrecompileCache { /// Caches for each precompile input / output. - #[allow(clippy::type_complexity)] - cache: HashMap<(Address, SpecId), Arc>>>, + cache: HashMap<(Address, SpecId), CachedPrecompileResult>, } /// Custom EVM configuration From 2f3104b4cb60e3929465c756b2bd3737ff6b886b Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 3 Jul 2024 21:43:21 +0200 Subject: [PATCH 316/405] test: rm useless unit tests for `calc_next_block_base_fee` (#9280) --- crates/chainspec/src/constants/mod.rs | 37 -------- crates/chainspec/src/constants/optimism.rs | 102 --------------------- 2 files changed, 139 deletions(-) diff --git a/crates/chainspec/src/constants/mod.rs b/crates/chainspec/src/constants/mod.rs index 9af4f946b92d..cde927189c8b 100644 --- a/crates/chainspec/src/constants/mod.rs +++ b/crates/chainspec/src/constants/mod.rs @@ -10,40 +10,3 @@ pub(crate) const MAINNET_DEPOSIT_CONTRACT: DepositContract = DepositContract::ne #[cfg(feature = "optimism")] pub(crate) mod optimism; - -#[cfg(test)] -mod tests { - use alloy_eips::calc_next_block_base_fee; - - #[test] - fn calculate_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1125000000, 1083333333, 1053571428, 1179939062, 1116028649, 918084097, 1063811730, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - crate::BaseFeeParams::ethereum(), - ) as u64 - ); - } - } -} diff --git a/crates/chainspec/src/constants/optimism.rs b/crates/chainspec/src/constants/optimism.rs index d4a1de6d0ea4..1c32df6f37ed 100644 --- a/crates/chainspec/src/constants/optimism.rs +++ b/crates/chainspec/src/constants/optimism.rs @@ -44,105 +44,3 @@ pub(crate) const OP_CANYON_BASE_FEE_PARAMS: BaseFeeParams = BaseFeeParams { max_change_denominator: OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, elasticity_multiplier: OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, }; - -#[cfg(test)] -mod tests { - use super::*; - use alloy_eips::calc_next_block_base_fee; - - #[test] - fn calculate_optimism_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - OP_BASE_FEE_PARAMS, - ) as u64 - ); - } - } - - #[test] - fn calculate_optimism_sepolia_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - OP_SEPOLIA_BASE_FEE_PARAMS, - ) as u64 - ); - } - } - - #[test] - fn calculate_base_sepolia_base_fee_success() { - let base_fee = [ - 1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0, - 1, 2, - ]; - let gas_used = [ - 10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000, - 10000000, - ]; - let gas_limit = [ - 10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000, - 18000000, 18000000, - ]; - let next_base_fee = [ - 1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1, - 2, 3, - ]; - - for i in 0..base_fee.len() { - assert_eq!( - next_base_fee[i], - calc_next_block_base_fee( - gas_used[i] as u128, - gas_limit[i] as u128, - base_fee[i] as u128, - BASE_SEPOLIA_BASE_FEE_PARAMS, - ) as u64 - ); - } - } -} From f3fd7e73ccb6a91fe1a92bda9b96a53ca0193607 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 3 Jul 2024 21:48:41 +0200 Subject: [PATCH 317/405] fix: always evaluate build_profile_name at compile time (#9278) --- .../node/core/src/metrics/version_metrics.rs | 4 +-- crates/node/core/src/version.rs | 30 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/node/core/src/metrics/version_metrics.rs b/crates/node/core/src/metrics/version_metrics.rs index cea907fc3ca0..03769d990f35 100644 --- a/crates/node/core/src/metrics/version_metrics.rs +++ b/crates/node/core/src/metrics/version_metrics.rs @@ -1,6 +1,6 @@ //! This exposes reth's version information over prometheus. -use crate::version::{build_profile_name, VERGEN_GIT_SHA}; +use crate::version::{BUILD_PROFILE_NAME, VERGEN_GIT_SHA}; use metrics::gauge; /// Contains version information for the application. @@ -28,7 +28,7 @@ impl Default for VersionInfo { cargo_features: env!("VERGEN_CARGO_FEATURES"), git_sha: VERGEN_GIT_SHA, target_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"), - build_profile: build_profile_name(), + build_profile: BUILD_PROFILE_NAME, } } } diff --git a/crates/node/core/src/version.rs b/crates/node/core/src/version.rs index 5151b861d586..adc922787189 100644 --- a/crates/node/core/src/version.rs +++ b/crates/node/core/src/version.rs @@ -70,9 +70,23 @@ pub const LONG_VERSION: &str = const_format::concatcp!( env!("VERGEN_CARGO_FEATURES"), "\n", "Build Profile: ", - build_profile_name() + BUILD_PROFILE_NAME ); +pub(crate) const BUILD_PROFILE_NAME: &str = { + // Derived from https://stackoverflow.com/questions/73595435/how-to-get-profile-from-cargo-toml-in-build-rs-or-at-runtime + // We split on the path separator of the *host* machine, which may be different from + // `std::path::MAIN_SEPARATOR_STR`. + const OUT_DIR: &str = env!("OUT_DIR"); + let unix_parts = const_format::str_split!(OUT_DIR, '/'); + if unix_parts.len() >= 4 { + unix_parts[unix_parts.len() - 4] + } else { + let win_parts = const_format::str_split!(OUT_DIR, '\\'); + win_parts[win_parts.len() - 4] + } +}; + /// The version information for reth formatted for P2P (devp2p). /// /// - The latest version from Cargo.toml @@ -116,20 +130,6 @@ pub fn default_client_version() -> ClientVersion { } } -pub(crate) const fn build_profile_name() -> &'static str { - // Derived from https://stackoverflow.com/questions/73595435/how-to-get-profile-from-cargo-toml-in-build-rs-or-at-runtime - // We split on the path separator of the *host* machine, which may be different from - // `std::path::MAIN_SEPARATOR_STR`. - const OUT_DIR: &str = env!("OUT_DIR"); - let unix_parts = const_format::str_split!(OUT_DIR, '/'); - if unix_parts.len() >= 4 { - unix_parts[unix_parts.len() - 4] - } else { - let win_parts = const_format::str_split!(OUT_DIR, '\\'); - win_parts[win_parts.len() - 4] - } -} - #[cfg(test)] mod tests { use super::*; From a7caf0d28486c2b2d45e94ec0d68b422620075b9 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 3 Jul 2024 13:43:26 -0700 Subject: [PATCH 318/405] feat(rpc): enable historical proofs (#9273) --- book/cli/reth/node.md | 5 +++ crates/node/core/src/args/rpc_server.rs | 11 ++++++ crates/rpc/rpc-builder/src/config.rs | 1 + crates/rpc/rpc-builder/src/eth.rs | 13 ++++++- crates/rpc/rpc-eth-api/src/core.rs | 10 +----- crates/rpc/rpc-eth-api/src/helpers/state.rs | 26 +++++++------- crates/rpc/rpc-eth-types/src/error.rs | 4 +++ crates/rpc/rpc-server-types/src/constants.rs | 6 ++++ crates/rpc/rpc/src/eth/core.rs | 35 +++++++++++++------ crates/rpc/rpc/src/eth/helpers/state.rs | 11 ++++-- crates/rpc/rpc/src/eth/helpers/transaction.rs | 2 ++ 11 files changed, 87 insertions(+), 37 deletions(-) diff --git a/book/cli/reth/node.md b/book/cli/reth/node.md index 575fe18cc8f9..c27d7251c492 100644 --- a/book/cli/reth/node.md +++ b/book/cli/reth/node.md @@ -313,6 +313,11 @@ RPC: [default: 50000000] + --rpc.eth-proof-window + The maximum proof window for historical proof generation. This value allows for generating historical proofs up to configured number of blocks from current tip (up to `tip - window`) + + [default: 0] + RPC State Cache: --rpc-cache.max-blocks Max number of blocks in cache diff --git a/crates/node/core/src/args/rpc_server.rs b/crates/node/core/src/args/rpc_server.rs index bad1e24213b9..9b562329b463 100644 --- a/crates/node/core/src/args/rpc_server.rs +++ b/crates/node/core/src/args/rpc_server.rs @@ -156,6 +156,16 @@ pub struct RpcServerArgs { )] pub rpc_gas_cap: u64, + /// The maximum proof window for historical proof generation. + /// This value allows for generating historical proofs up to + /// configured number of blocks from current tip (up to `tip - window`). + #[arg( + long = "rpc.eth-proof-window", + default_value_t = constants::DEFAULT_ETH_PROOF_WINDOW, + value_parser = RangedU64ValueParser::::new().range(..=constants::MAX_ETH_PROOF_WINDOW) + )] + pub rpc_eth_proof_window: u64, + /// State cache configuration. #[command(flatten)] pub rpc_state_cache: RpcStateCacheArgs, @@ -286,6 +296,7 @@ impl Default for RpcServerArgs { rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(), rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(), rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP, + rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW, gas_price_oracle: GasPriceOracleArgs::default(), rpc_state_cache: RpcStateCacheArgs::default(), } diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index e2becedbabae..837b80a99b00 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -91,6 +91,7 @@ impl RethRpcServerConfig for RpcServerArgs { .max_tracing_requests(self.rpc_max_tracing_requests) .max_blocks_per_filter(self.rpc_max_blocks_per_filter.unwrap_or_max()) .max_logs_per_response(self.rpc_max_logs_per_response.unwrap_or_max() as usize) + .eth_proof_window(self.rpc_eth_proof_window) .rpc_gas_cap(self.rpc_gas_cap) .state_cache(self.state_cache_config()) .gpo_config(self.gas_price_oracle_config()) diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 43e20b1eaeb8..8e897b7710a2 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -13,7 +13,8 @@ use reth_rpc_eth_types::{ GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP, }; use reth_rpc_server_types::constants::{ - default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, + default_max_tracing_requests, DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKS_PER_FILTER, + DEFAULT_MAX_LOGS_PER_RESPONSE, }; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; @@ -141,6 +142,8 @@ pub struct EthConfig { pub cache: EthStateCacheConfig, /// Settings for the gas price oracle pub gas_oracle: GasPriceOracleConfig, + /// The maximum number of blocks into the past for generating state proofs. + pub eth_proof_window: u64, /// The maximum number of tracing calls that can be executed in concurrently. pub max_tracing_requests: usize, /// Maximum number of blocks that could be scanned per filter request in `eth_getLogs` calls. @@ -173,6 +176,7 @@ impl Default for EthConfig { Self { cache: EthStateCacheConfig::default(), gas_oracle: GasPriceOracleConfig::default(), + eth_proof_window: DEFAULT_ETH_PROOF_WINDOW, max_tracing_requests: default_max_tracing_requests(), max_blocks_per_filter: DEFAULT_MAX_BLOCKS_PER_FILTER, max_logs_per_response: DEFAULT_MAX_LOGS_PER_RESPONSE, @@ -219,6 +223,12 @@ impl EthConfig { self.rpc_gas_cap = rpc_gas_cap; self } + + /// Configures the maximum proof window for historical proof generation. + pub const fn eth_proof_window(mut self, window: u64) -> Self { + self.eth_proof_window = window; + self + } } /// Context for building the `eth` namespace API. @@ -269,6 +279,7 @@ impl EthApiBuild { ctx.cache.clone(), gas_oracle, ctx.config.rpc_gas_cap, + ctx.config.eth_proof_window, Box::new(ctx.executor.clone()), BlockingTaskPool::build().expect("failed to build blocking task pool"), fee_history_cache, diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 1f5ced83d2f6..cf11c6d3196b 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -4,7 +4,6 @@ use alloy_dyn_abi::TypedData; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; -use reth_rpc_eth_types::EthApiError; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use reth_rpc_types::{ serde_helpers::JsonStorageKey, @@ -715,13 +714,6 @@ where block_number: Option, ) -> RpcResult { trace!(target: "rpc::eth", ?address, ?keys, ?block_number, "Serving eth_getProof"); - let res = EthState::get_proof(self, address, keys, block_number)?.await; - - Ok(res.map_err(|e| match e { - EthApiError::InvalidBlockRange => { - internal_rpc_err("eth_getProof is unimplemented for historical blocks") - } - _ => e.into(), - })?) + Ok(EthState::get_proof(self, address, keys, block_number)?.await?) } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 34424e94fcdf..1f07f35ae327 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -3,7 +3,7 @@ use futures::Future; use reth_evm::ConfigureEvmEnv; -use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, Header, B256, U256}; +use reth_primitives::{Address, BlockId, Bytes, Header, B256, U256}; use reth_provider::{ BlockIdReader, ChainSpecProvider, StateProvider, StateProviderBox, StateProviderFactory, }; @@ -19,6 +19,9 @@ use super::{EthApiSpec, LoadPendingBlock, SpawnBlocking}; /// Helper methods for `eth_` methods relating to state (accounts). pub trait EthState: LoadState + SpawnBlocking { + /// Returns the maximum number of blocks into the past for generating state proofs. + fn max_proof_window(&self) -> u64; + /// Returns the number of transactions sent from an address at the given block identifier. /// /// If this is [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this will @@ -90,19 +93,14 @@ pub trait EthState: LoadState + SpawnBlocking { let chain_info = self.chain_info()?; let block_id = block_id.unwrap_or_default(); - // if we are trying to create a proof for the latest block, but have a BlockId as input - // that is not BlockNumberOrTag::Latest, then we need to figure out whether or not the - // BlockId corresponds to the latest block - let is_latest_block = match block_id { - BlockId::Number(BlockNumberOrTag::Number(num)) => num == chain_info.best_number, - BlockId::Hash(hash) => hash == chain_info.best_hash.into(), - BlockId::Number(BlockNumberOrTag::Latest) => true, - _ => false, - }; - - // TODO: remove when HistoricalStateProviderRef::proof is implemented - if !is_latest_block { - return Err(EthApiError::InvalidBlockRange) + // Check whether the distance to the block exceeds the maximum configured window. + let block_number = self + .provider() + .block_number_for_id(block_id)? + .ok_or(EthApiError::UnknownBlockNumber)?; + let max_window = self.max_proof_window(); + if chain_info.best_number.saturating_sub(block_number) > max_window { + return Err(EthApiError::ExceedsMaxProofWindow) } Ok(self.spawn_tracing(move |this| { diff --git a/crates/rpc/rpc-eth-types/src/error.rs b/crates/rpc/rpc-eth-types/src/error.rs index 4ddbf9a38dfe..7cb302a53bb2 100644 --- a/crates/rpc/rpc-eth-types/src/error.rs +++ b/crates/rpc/rpc-eth-types/src/error.rs @@ -54,6 +54,9 @@ pub enum EthApiError { /// When an invalid block range is provided #[error("invalid block range")] InvalidBlockRange, + /// Thrown when the target block for proof computation exceeds the maximum configured window. + #[error("distance to target block exceeds maximum proof window")] + ExceedsMaxProofWindow, /// An internal error where prevrandao is not set in the evm's environment #[error("prevrandao not in the EVM's environment after merge")] PrevrandaoNotSet, @@ -143,6 +146,7 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { EthApiError::InvalidTransactionSignature | EthApiError::EmptyRawTransactionData | EthApiError::InvalidBlockRange | + EthApiError::ExceedsMaxProofWindow | EthApiError::ConflictingFeeFieldsInRequest | EthApiError::Signing(_) | EthApiError::BothStateAndStateDiffInOverride(_) | diff --git a/crates/rpc/rpc-server-types/src/constants.rs b/crates/rpc/rpc-server-types/src/constants.rs index 807d96a91256..f1af5fb26f75 100644 --- a/crates/rpc/rpc-server-types/src/constants.rs +++ b/crates/rpc/rpc-server-types/src/constants.rs @@ -42,6 +42,12 @@ pub const DEFAULT_ENGINE_API_IPC_ENDPOINT: &str = r"\\.\pipe\reth_engine_api.ipc #[cfg(not(windows))] pub const DEFAULT_ENGINE_API_IPC_ENDPOINT: &str = "/tmp/reth_engine_api.ipc"; +/// The default eth historical proof window. +pub const DEFAULT_ETH_PROOF_WINDOW: u64 = 0; + +/// Maximum eth historical proof window. Equivalent to roughly one month of data. +pub const MAX_ETH_PROOF_WINDOW: u64 = 216_000; + /// GPO specific constants pub mod gas_oracle { use alloy_primitives::U256; diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index e876faca8af6..083352554624 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use derive_more::Deref; use reth_primitives::{BlockNumberOrTag, U256}; -use reth_provider::{BlockReaderIdExt, ChainSpecProvider}; +use reth_provider::BlockReaderIdExt; use reth_rpc_eth_api::{ helpers::{transaction::UpdateRawTxForwarder, EthSigner, SpawnBlocking}, RawTransactionForwarder, @@ -31,18 +31,9 @@ pub struct EthApi { pub(super) inner: Arc>, } -impl EthApi { - /// Sets a forwarder for `eth_sendRawTransaction` - /// - /// Note: this might be removed in the future in favor of a more generic approach. - pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { - self.inner.raw_transaction_forwarder.write().replace(forwarder); - } -} - impl EthApi where - Provider: BlockReaderIdExt + ChainSpecProvider, + Provider: BlockReaderIdExt, { /// Creates a new, shareable instance using the default tokio task spawner. #[allow(clippy::too_many_arguments)] @@ -53,6 +44,7 @@ where eth_cache: EthStateCache, gas_oracle: GasPriceOracle, gas_cap: impl Into, + eth_proof_window: u64, blocking_task_pool: BlockingTaskPool, fee_history_cache: FeeHistoryCache, evm_config: EvmConfig, @@ -65,6 +57,7 @@ where eth_cache, gas_oracle, gas_cap.into().into(), + eth_proof_window, Box::::default(), blocking_task_pool, fee_history_cache, @@ -82,6 +75,7 @@ where eth_cache: EthStateCache, gas_oracle: GasPriceOracle, gas_cap: u64, + eth_proof_window: u64, task_spawner: Box, blocking_task_pool: BlockingTaskPool, fee_history_cache: FeeHistoryCache, @@ -104,6 +98,7 @@ where eth_cache, gas_oracle, gas_cap, + eth_proof_window, starting_block: U256::from(latest_block), task_spawner, pending_block: Default::default(), @@ -115,6 +110,15 @@ where Self { inner: Arc::new(inner) } } +} + +impl EthApi { + /// Sets a forwarder for `eth_sendRawTransaction` + /// + /// Note: this might be removed in the future in favor of a more generic approach. + pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { + self.inner.raw_transaction_forwarder.write().replace(forwarder); + } /// Returns the state cache frontend pub fn cache(&self) -> &EthStateCache { @@ -131,6 +135,11 @@ where self.inner.gas_cap } + /// The maximum number of blocks into the past for generating state proofs. + pub fn eth_proof_window(&self) -> u64 { + self.inner.eth_proof_window + } + /// Returns the inner `Provider` pub fn provider(&self) -> &Provider { &self.inner.provider @@ -208,6 +217,8 @@ pub struct EthApiInner { gas_oracle: GasPriceOracle, /// Maximum gas limit for `eth_call` and call tracing RPC methods. gas_cap: u64, + /// The maximum number of blocks into the past for generating state proofs. + eth_proof_window: u64, /// The block number at which the node started starting_block: U256, /// The type that can spawn tasks which would otherwise block. @@ -330,6 +341,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; + use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; use reth_rpc_types::FeeHistory; use reth_tasks::pool::BlockingTaskPool; use reth_testing_utils::{generators, generators::Rng}; @@ -361,6 +373,7 @@ mod tests { cache.clone(), GasPriceOracle::new(provider, Default::default(), cache), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), fee_history_cache, evm_config, diff --git a/crates/rpc/rpc/src/eth/helpers/state.rs b/crates/rpc/rpc/src/eth/helpers/state.rs index dd28c6465665..369bd6ba7529 100644 --- a/crates/rpc/rpc/src/eth/helpers/state.rs +++ b/crates/rpc/rpc/src/eth/helpers/state.rs @@ -8,9 +8,13 @@ use reth_rpc_eth_types::EthStateCache; use crate::EthApi; -impl EthState for EthApi where - Self: LoadState + SpawnBlocking +impl EthState for EthApi +where + Self: LoadState + SpawnBlocking, { + fn max_proof_window(&self) -> u64 { + self.eth_proof_window() + } } impl LoadState for EthApi @@ -47,6 +51,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; + use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::test_utils::testing_pool; @@ -66,6 +71,7 @@ mod tests { cache.clone(), GasPriceOracle::new(NoopProvider::default(), Default::default(), cache.clone()), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), evm_config, @@ -91,6 +97,7 @@ mod tests { cache.clone(), GasPriceOracle::new(mock_provider, Default::default(), cache.clone()), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), evm_config, diff --git a/crates/rpc/rpc/src/eth/helpers/transaction.rs b/crates/rpc/rpc/src/eth/helpers/transaction.rs index 9d86be1b2e24..13e3dbd5c0aa 100644 --- a/crates/rpc/rpc/src/eth/helpers/transaction.rs +++ b/crates/rpc/rpc/src/eth/helpers/transaction.rs @@ -68,6 +68,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; + use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::{test_utils::testing_pool, TransactionPool}; @@ -91,6 +92,7 @@ mod tests { cache.clone(), GasPriceOracle::new(noop_provider, Default::default(), cache.clone()), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), fee_history_cache, evm_config, From 4db0edd72f3ad650f156e43682fff0a36aac62ca Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:00:23 +0200 Subject: [PATCH 319/405] ci(hive): build `reth` externally (#9281) --- .github/assets/hive/Dockerfile | 8 ++++++++ .github/workflows/hive.yml | 14 ++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 .github/assets/hive/Dockerfile diff --git a/.github/assets/hive/Dockerfile b/.github/assets/hive/Dockerfile new file mode 100644 index 000000000000..9f75ba6f1cf2 --- /dev/null +++ b/.github/assets/hive/Dockerfile @@ -0,0 +1,8 @@ +FROM ubuntu + +COPY dist/reth /usr/local/bin + +COPY LICENSE-* ./ + +EXPOSE 30303 30303/udp 9001 8545 8546 +ENTRYPOINT ["/usr/local/bin/reth"] \ No newline at end of file diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index 3340393d0873..421e4b2f505d 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -23,17 +23,23 @@ jobs: group: Reth steps: - uses: actions/checkout@v4 - - run: mkdir artifacts + - run: mkdir artifacts + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Build reth + run: | + cargo build --features asm-keccak --profile hivetests --bin reth --locked + mkdir dist && cp ./target/hivetests/reth ./dist/reth - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and export reth image uses: docker/build-push-action@v6 with: context: . + file: .github/assets/hive/Dockerfile tags: ghcr.io/paradigmxyz/reth:latest - build-args: | - BUILD_PROFILE=hivetests - FEATURES=asm-keccak outputs: type=docker,dest=./artifacts/reth_image.tar cache-from: type=gha cache-to: type=gha,mode=max From 4dc832ab00b33a659c0ad60e7a35eb11e214ef8a Mon Sep 17 00:00:00 2001 From: clabby Date: Wed, 3 Jul 2024 17:05:38 -0400 Subject: [PATCH 320/405] chore: Expose `TrieUpdates` inner fields (#9277) --- crates/trie/trie/src/updates.rs | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 98a5922a14f7..86119a673c9d 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -32,6 +32,16 @@ impl TrieUpdates { &self.account_nodes } + /// Returns a reference to removed account nodes. + pub const fn removed_nodes_ref(&self) -> &HashSet { + &self.removed_nodes + } + + /// Returns a reference to updated storage tries. + pub const fn storage_tries_ref(&self) -> &HashMap { + &self.storage_tries + } + /// Insert storage updates for a given hashed address. pub fn insert_storage_updates( &mut self, @@ -162,6 +172,21 @@ impl StorageTrieUpdates { (self.is_deleted as usize) + self.storage_nodes.len() + self.removed_nodes.len() } + /// Returns `true` if the trie was deleted. + pub const fn is_deleted(&self) -> bool { + self.is_deleted + } + + /// Returns reference to updated storage nodes. + pub const fn storage_nodes_ref(&self) -> &HashMap { + &self.storage_nodes + } + + /// Returns reference to removed storage nodes. + pub const fn removed_nodes_ref(&self) -> &HashSet { + &self.removed_nodes + } + /// Returns `true` if storage updates are empty. pub fn is_empty(&self) -> bool { !self.is_deleted && self.storage_nodes.is_empty() && self.removed_nodes.is_empty() @@ -274,6 +299,23 @@ pub struct TrieUpdatesSorted { pub(crate) storage_tries: Vec<(B256, StorageTrieUpdatesSorted)>, } +impl TrieUpdatesSorted { + /// Returns reference to updated account nodes. + pub fn account_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] { + &self.account_nodes + } + + /// Returns reference to removed account nodes. + pub const fn removed_nodes_ref(&self) -> &HashSet { + &self.removed_nodes + } + + /// Returns reference to updated storage tries. + pub fn storage_tries_ref(&self) -> &[(B256, StorageTrieUpdatesSorted)] { + &self.storage_tries + } +} + /// Sorted trie updates used for lookups and insertions. #[derive(PartialEq, Eq, Clone, Default, Debug)] pub struct StorageTrieUpdatesSorted { @@ -281,3 +323,20 @@ pub struct StorageTrieUpdatesSorted { pub(crate) storage_nodes: Vec<(Nibbles, BranchNodeCompact)>, pub(crate) removed_nodes: HashSet, } + +impl StorageTrieUpdatesSorted { + /// Returns `true` if the trie was deleted. + pub const fn is_deleted(&self) -> bool { + self.is_deleted + } + + /// Returns reference to updated storage nodes. + pub fn storage_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] { + &self.storage_nodes + } + + /// Returns reference to removed storage nodes. + pub const fn removed_nodes_ref(&self) -> &HashSet { + &self.removed_nodes + } +} From 5ccff2a6fa3caec6ebe4f96b2cc982731ff41f57 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:45:11 -0400 Subject: [PATCH 321/405] chore: use direct link to threshold docs (#9284) --- crates/stages/stages/src/stages/execution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 556c5f7eb5e5..a2ee50a606d6 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -107,7 +107,7 @@ impl ExecutionStage { /// Create an execution stage with the provided executor. /// - /// The commit threshold will be set to `10_000`. + /// The commit threshold will be set to [`MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD`]. pub fn new_with_executor(executor_provider: E) -> Self { Self::new( executor_provider, From f74b8ce72ce2105f50d8ade3a925a959d7353f57 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 4 Jul 2024 08:39:14 +0200 Subject: [PATCH 322/405] chore(rpc): rm dup getters `EthApi` (#9283) --- crates/rpc/rpc/src/eth/core.rs | 61 +++++++--------------------------- 1 file changed, 12 insertions(+), 49 deletions(-) diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index 083352554624..c82e306252b3 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -112,55 +112,6 @@ where } } -impl EthApi { - /// Sets a forwarder for `eth_sendRawTransaction` - /// - /// Note: this might be removed in the future in favor of a more generic approach. - pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { - self.inner.raw_transaction_forwarder.write().replace(forwarder); - } - - /// Returns the state cache frontend - pub fn cache(&self) -> &EthStateCache { - &self.inner.eth_cache - } - - /// Returns the gas oracle frontend - pub fn gas_oracle(&self) -> &GasPriceOracle { - &self.inner.gas_oracle - } - - /// Returns the configured gas limit cap for `eth_call` and tracing related calls - pub fn gas_cap(&self) -> u64 { - self.inner.gas_cap - } - - /// The maximum number of blocks into the past for generating state proofs. - pub fn eth_proof_window(&self) -> u64 { - self.inner.eth_proof_window - } - - /// Returns the inner `Provider` - pub fn provider(&self) -> &Provider { - &self.inner.provider - } - - /// Returns the inner `Network` - pub fn network(&self) -> &Network { - &self.inner.network - } - - /// Returns the inner `Pool` - pub fn pool(&self) -> &Pool { - &self.inner.pool - } - - /// Returns fee history cache - pub fn fee_history_cache(&self) -> &FeeHistoryCache { - &self.inner.fee_history_cache - } -} - impl std::fmt::Debug for EthApi { @@ -313,6 +264,18 @@ impl EthApiInner U256 { self.starting_block } + + /// Returns the inner `Network` + #[inline] + pub const fn network(&self) -> &Network { + &self.network + } + + /// The maximum number of blocks into the past for generating state proofs. + #[inline] + pub const fn eth_proof_window(&self) -> u64 { + self.eth_proof_window + } } impl UpdateRawTxForwarder From 9f55a6a70287338790cb0475080eaa74783a612e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 4 Jul 2024 08:40:39 +0200 Subject: [PATCH 323/405] chore: move `withdrawal_requests_contract_call` to `reth-evm` (#9272) --- Cargo.lock | 1 - crates/ethereum/evm/src/execute.rs | 10 +- crates/ethereum/payload/src/lib.rs | 50 +++++----- crates/evm/src/system_calls.rs | 145 ++++++++++++++++++++++++++++- crates/payload/basic/src/lib.rs | 40 +------- crates/primitives/src/lib.rs | 2 - crates/primitives/src/revm/env.rs | 81 ---------------- crates/primitives/src/revm/mod.rs | 8 -- crates/revm/Cargo.toml | 1 - crates/revm/src/state_change.rs | 105 +-------------------- 10 files changed, 182 insertions(+), 261 deletions(-) delete mode 100644 crates/primitives/src/revm/env.rs delete mode 100644 crates/primitives/src/revm/mod.rs diff --git a/Cargo.lock b/Cargo.lock index bdae392a39f6..335361424c8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8128,7 +8128,6 @@ name = "reth-revm" version = "1.0.0" dependencies = [ "alloy-eips", - "alloy-rlp", "reth-chainspec", "reth-consensus-common", "reth-execution-errors", diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index b4da027f88f1..00267712f189 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -11,7 +11,7 @@ use reth_evm::{ BatchExecutor, BlockExecutionError, BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, BlockValidationError, Executor, ProviderError, }, - system_calls::apply_beacon_root_contract_call, + system_calls::{apply_beacon_root_contract_call, apply_withdrawal_requests_contract_call}, ConfigureEvm, }; use reth_execution_types::ExecutionOutcome; @@ -22,10 +22,7 @@ use reth_prune_types::PruneModes; use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, db::states::bundle_state::BundleRetention, - state_change::{ - apply_blockhashes_update, apply_withdrawal_requests_contract_call, - post_block_balance_increments, - }, + state_change::{apply_blockhashes_update, post_block_balance_increments}, Evm, State, }; use revm_primitives::{ @@ -222,7 +219,8 @@ where crate::eip6110::parse_deposits_from_receipts(&self.chain_spec, &receipts)?; // Collect all EIP-7685 requests - let withdrawal_requests = apply_withdrawal_requests_contract_call(&mut evm)?; + let withdrawal_requests = + apply_withdrawal_requests_contract_call::(&mut evm)?; [deposit_requests, withdrawal_requests].concat() } else { diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index dabaedeeb3b1..ed1d6cdfddda 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -10,11 +10,16 @@ #![allow(clippy::useless_let_if_seq)] use reth_basic_payload_builder::{ - commit_withdrawals, is_better_payload, post_block_withdrawal_requests_contract_call, - BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig, WithdrawalsOutcome, + commit_withdrawals, is_better_payload, BuildArguments, BuildOutcome, PayloadBuilder, + PayloadConfig, WithdrawalsOutcome, }; use reth_errors::RethError; -use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm}; +use reth_evm::{ + system_calls::{ + post_block_withdrawal_requests_contract_call, pre_block_beacon_root_contract_call, + }, + ConfigureEvm, +}; use reth_evm_ethereum::{eip6110::parse_deposits_from_receipts, EthEvmConfig}; use reth_execution_types::ExecutionOutcome; use reth_payload_builder::{ @@ -190,22 +195,24 @@ where } // Calculate the requests and the requests root. - let (requests, requests_root) = - if chain_spec.is_prague_active_at_timestamp(attributes.timestamp) { - // We do not calculate the EIP-6110 deposit requests because there are no - // transactions in an empty payload. - let withdrawal_requests = post_block_withdrawal_requests_contract_call( - &mut db, - &initialized_cfg, - &initialized_block_env, - )?; - - let requests = withdrawal_requests; - let requests_root = calculate_requests_root(&requests); - (Some(requests.into()), Some(requests_root)) - } else { - (None, None) - }; + let (requests, requests_root) = if chain_spec + .is_prague_active_at_timestamp(attributes.timestamp) + { + // We do not calculate the EIP-6110 deposit requests because there are no + // transactions in an empty payload. + let withdrawal_requests = post_block_withdrawal_requests_contract_call::( + &mut db, + &initialized_cfg, + &initialized_block_env, + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; + + let requests = withdrawal_requests; + let requests_root = calculate_requests_root(&requests); + (Some(requests.into()), Some(requests_root)) + } else { + (None, None) + }; let header = Header { parent_hash: parent_block.hash(), @@ -436,11 +443,12 @@ where { let deposit_requests = parse_deposits_from_receipts(&chain_spec, receipts.iter().flatten()) .map_err(|err| PayloadBuilderError::Internal(RethError::Execution(err.into())))?; - let withdrawal_requests = post_block_withdrawal_requests_contract_call( + let withdrawal_requests = post_block_withdrawal_requests_contract_call::( &mut db, &initialized_cfg, &initialized_block_env, - )?; + ) + .map_err(|err| PayloadBuilderError::Internal(err.into()))?; let requests = [deposit_requests, withdrawal_requests].concat(); let requests_root = calculate_requests_root(&requests); diff --git a/crates/evm/src/system_calls.rs b/crates/evm/src/system_calls.rs index 2e247ba38202..e9a5518a8568 100644 --- a/crates/evm/src/system_calls.rs +++ b/crates/evm/src/system_calls.rs @@ -1,12 +1,18 @@ //! System contract call functions. -use alloy_eips::eip4788::BEACON_ROOTS_ADDRESS; +use crate::ConfigureEvm; +use alloy_eips::{ + eip4788::BEACON_ROOTS_ADDRESS, + eip7002::{WithdrawalRequest, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS}, +}; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; +use reth_primitives::{Buf, Request}; use revm::{interpreter::Host, Database, DatabaseCommit, Evm}; -use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, B256}; - -use crate::ConfigureEvm; +use revm_primitives::{ + Address, BlockEnv, Bytes, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, FixedBytes, + ResultAndState, B256, +}; /// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call. /// @@ -123,3 +129,134 @@ where Ok(()) } + +/// Apply the [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) post block contract call. +/// +/// This constructs a new [Evm] with the given DB, and environment +/// ([`CfgEnvWithHandlerCfg`] and [`BlockEnv`]) to execute the post block contract call. +/// +/// This uses [`apply_withdrawal_requests_contract_call`] to ultimately calculate the +/// [requests](Request). +pub fn post_block_withdrawal_requests_contract_call( + db: &mut DB, + initialized_cfg: &CfgEnvWithHandlerCfg, + initialized_block_env: &BlockEnv, +) -> Result, BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: std::fmt::Display, + EvmConfig: ConfigureEvm, +{ + // apply post-block EIP-7002 contract call + let mut evm_post_block = Evm::builder() + .with_db(db) + .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + Default::default(), + )) + .build(); + + // initialize a block from the env, because the post block call needs the block itself + apply_withdrawal_requests_contract_call::(&mut evm_post_block) +} + +/// Applies the post-block call to the EIP-7002 withdrawal requests contract. +/// +/// If Prague is not active at the given timestamp, then this is a no-op, and an empty vector is +/// returned. Otherwise, the withdrawal requests are returned. +#[inline] +pub fn apply_withdrawal_requests_contract_call( + evm: &mut Evm<'_, EXT, DB>, +) -> Result, BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: core::fmt::Display, + EvmConfig: ConfigureEvm, +{ + // get previous env + let previous_env = Box::new(evm.context.env().clone()); + + // Fill transaction environment with the EIP-7002 withdrawal requests contract message data. + // + // This requirement for the withdrawal requests contract call defined by + // [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) is: + // + // At the end of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. + // after processing all transactions and after performing the block body withdrawal requests + // validations), call the contract as `SYSTEM_ADDRESS`. + EvmConfig::fill_tx_env_system_contract_call( + &mut evm.context.evm.env, + alloy_eips::eip7002::SYSTEM_ADDRESS, + WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + Bytes::new(), + ); + + let ResultAndState { result, mut state } = match evm.transact() { + Ok(res) => res, + Err(e) => { + evm.context.evm.env = previous_env; + return Err(BlockValidationError::WithdrawalRequestsContractCall { + message: format!("execution failed: {e}"), + } + .into()) + } + }; + + // cleanup the state + state.remove(&alloy_eips::eip7002::SYSTEM_ADDRESS); + state.remove(&evm.block().coinbase); + evm.context.evm.db.commit(state); + + // re-set the previous env + evm.context.evm.env = previous_env; + + let mut data = match result { + ExecutionResult::Success { output, .. } => Ok(output.into_data()), + ExecutionResult::Revert { output, .. } => { + Err(BlockValidationError::WithdrawalRequestsContractCall { + message: format!("execution reverted: {output}"), + }) + } + ExecutionResult::Halt { reason, .. } => { + Err(BlockValidationError::WithdrawalRequestsContractCall { + message: format!("execution halted: {reason:?}"), + }) + } + }?; + + // Withdrawals are encoded as a series of withdrawal requests, each with the following + // format: + // + // +------+--------+--------+ + // | addr | pubkey | amount | + // +------+--------+--------+ + // 20 48 8 + + const WITHDRAWAL_REQUEST_SIZE: usize = 20 + 48 + 8; + let mut withdrawal_requests = Vec::with_capacity(data.len() / WITHDRAWAL_REQUEST_SIZE); + while data.has_remaining() { + if data.remaining() < WITHDRAWAL_REQUEST_SIZE { + return Err(BlockValidationError::WithdrawalRequestsContractCall { + message: "invalid withdrawal request length".to_string(), + } + .into()) + } + + let mut source_address = Address::ZERO; + data.copy_to_slice(source_address.as_mut_slice()); + + let mut validator_pubkey = FixedBytes::<48>::ZERO; + data.copy_to_slice(validator_pubkey.as_mut_slice()); + + let amount = data.get_u64(); + + withdrawal_requests.push(Request::WithdrawalRequest(WithdrawalRequest { + source_address, + validator_pubkey, + amount, + })); + } + + Ok(withdrawal_requests) +} diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index d7c58d3bdc92..d5e3766bdde8 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -19,19 +19,17 @@ use reth_payload_builder::{ use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ constants::{EMPTY_WITHDRAWALS, RETH_CLIENT_VERSION, SLOT_DURATION}, - proofs, BlockNumberOrTag, Bytes, Request, SealedBlock, Withdrawals, B256, U256, + proofs, BlockNumberOrTag, Bytes, SealedBlock, Withdrawals, B256, U256, }; use reth_provider::{ BlockReaderIdExt, BlockSource, CanonStateNotification, ProviderError, StateProviderFactory, }; -use reth_revm::state_change::{ - apply_withdrawal_requests_contract_call, post_block_withdrawals_balance_increments, -}; +use reth_revm::state_change::post_block_withdrawals_balance_increments; use reth_tasks::TaskSpawner; use reth_transaction_pool::TransactionPool; use revm::{ - primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg}, - Database, DatabaseCommit, Evm, State, + primitives::{BlockEnv, CfgEnvWithHandlerCfg}, + Database, State, }; use std::{ fmt, @@ -922,36 +920,6 @@ pub fn commit_withdrawals>( }) } -/// Apply the [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) post block contract call. -/// -/// This constructs a new [Evm] with the given DB, and environment -/// ([`CfgEnvWithHandlerCfg`] and [`BlockEnv`]) to execute the post block contract call. -/// -/// This uses [`apply_withdrawal_requests_contract_call`] to ultimately calculate the -/// [requests](Request). -pub fn post_block_withdrawal_requests_contract_call( - db: &mut DB, - initialized_cfg: &CfgEnvWithHandlerCfg, - initialized_block_env: &BlockEnv, -) -> Result, PayloadBuilderError> -where - DB::Error: std::fmt::Display, -{ - // apply post-block EIP-7002 contract call - let mut evm_post_block = Evm::builder() - .with_db(db) - .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - Default::default(), - )) - .build(); - - // initialize a block from the env, because the post block call needs the block itself - apply_withdrawal_requests_contract_call(&mut evm_post_block) - .map_err(|err| PayloadBuilderError::Internal(err.into())) -} - /// Checks if the new payload is better than the current best. /// /// This compares the total fees of the blocks, higher is better. diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 4d6b7b3ed6ad..5d3def5c3e58 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -32,8 +32,6 @@ pub mod genesis; pub mod header; pub mod proofs; mod receipt; -/// Helpers for working with revm -pub mod revm; pub use reth_static_file_types as static_file; pub mod transaction; #[cfg(any(test, feature = "arbitrary"))] diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs deleted file mode 100644 index 81fee7d8c2f6..000000000000 --- a/crates/primitives/src/revm/env.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::{ - revm_primitives::{Env, TxEnv}, - Address, Bytes, TxKind, U256, -}; - -use alloy_eips::eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS; -#[cfg(feature = "optimism")] -use revm_primitives::OptimismFields; - -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - -/// Fill transaction environment with the EIP-7002 withdrawal requests contract message data. -// -/// This requirement for the withdrawal requests contract call defined by -/// [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) is: -// -/// At the end of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. -/// after processing all transactions and after performing the block body withdrawal requests -/// validations), call the contract as `SYSTEM_ADDRESS`. -pub fn fill_tx_env_with_withdrawal_requests_contract_call(env: &mut Env) { - fill_tx_env_with_system_contract_call( - env, - alloy_eips::eip7002::SYSTEM_ADDRESS, - WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - Bytes::new(), - ); -} - -/// Fill transaction environment with the system caller and the system contract address and message -/// data. -/// -/// This is a system operation and therefore: -/// * the call must execute to completion -/// * the call does not count against the block’s gas limit -/// * the call does not follow the EIP-1559 burn semantics - no value should be transferred as part -/// of the call -/// * if no code exists at the provided address, the call will fail silently -fn fill_tx_env_with_system_contract_call( - env: &mut Env, - caller: Address, - contract: Address, - data: Bytes, -) { - env.tx = TxEnv { - caller, - transact_to: TxKind::Call(contract), - // Explicitly set nonce to None so revm does not do any nonce checks - nonce: None, - gas_limit: 30_000_000, - value: U256::ZERO, - data, - // Setting the gas price to zero enforces that no value is transferred as part of the call, - // and that the call will not count against the block's gas limit - gas_price: U256::ZERO, - // The chain ID check is not relevant here and is disabled if set to None - chain_id: None, - // Setting the gas priority fee to None ensures the effective gas price is derived from the - // `gas_price` field, which we need to be zero - gas_priority_fee: None, - access_list: Vec::new(), - // blob fields can be None for this tx - blob_hashes: Vec::new(), - max_fee_per_blob_gas: None, - #[cfg(feature = "optimism")] - optimism: OptimismFields { - source_hash: None, - mint: None, - is_system_transaction: Some(false), - // The L1 fee is not charged for the EIP-4788 transaction, submit zero bytes for the - // enveloped tx size. - enveloped_tx: Some(Bytes::default()), - }, - }; - - // ensure the block gas limit is >= the tx - env.block.gas_limit = U256::from(env.tx.gas_limit); - - // disable the base fee check for this call by setting the base fee to zero - env.block.basefee = U256::ZERO; -} diff --git a/crates/primitives/src/revm/mod.rs b/crates/primitives/src/revm/mod.rs deleted file mode 100644 index 40fc719c950b..000000000000 --- a/crates/primitives/src/revm/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Helpers for working with revm. - -/// The `env` module provides utility methods for filling revm transaction and block environments. -/// -/// It includes functions to fill transaction and block environments with relevant data, prepare -/// the block and transaction environments for system contract calls, and recover the signer from -/// Clique-formatted extra data in ethereum headers. -pub mod env; diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 4d7a7f684de0..bbb60b293edd 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -27,7 +27,6 @@ revm.workspace = true # alloy alloy-eips.workspace = true -alloy-rlp.workspace = true # common tracing.workspace = true diff --git a/crates/revm/src/state_change.rs b/crates/revm/src/state_change.rs index 01098182fc4f..519f9704d702 100644 --- a/crates/revm/src/state_change.rs +++ b/crates/revm/src/state_change.rs @@ -1,23 +1,12 @@ -use alloy_eips::{ - eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE}, - eip7002::WithdrawalRequest, -}; -use alloy_rlp::Buf; +use alloy_eips::eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE}; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_consensus_common::calc; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; -use reth_primitives::{ - revm::env::fill_tx_env_with_withdrawal_requests_contract_call, Address, Block, Request, - Withdrawal, Withdrawals, B256, U256, -}; +use reth_primitives::{Address, Block, Withdrawal, Withdrawals, B256, U256}; use reth_storage_errors::provider::ProviderError; use revm::{ - interpreter::Host, - primitives::{ - Account, AccountInfo, Bytecode, EvmStorageSlot, ExecutionResult, FixedBytes, - ResultAndState, BLOCKHASH_SERVE_WINDOW, - }, - Database, DatabaseCommit, Evm, + primitives::{Account, AccountInfo, Bytecode, EvmStorageSlot, BLOCKHASH_SERVE_WINDOW}, + Database, DatabaseCommit, }; // reuse revm's hashbrown implementation for no-std @@ -179,89 +168,3 @@ pub fn insert_post_block_withdrawals_balance_increments( } } } - -/// Applies the post-block call to the EIP-7002 withdrawal requests contract. -/// -/// If Prague is not active at the given timestamp, then this is a no-op, and an empty vector is -/// returned. Otherwise, the withdrawal requests are returned. -#[inline] -pub fn apply_withdrawal_requests_contract_call( - evm: &mut Evm<'_, EXT, DB>, -) -> Result, BlockExecutionError> -where - DB::Error: core::fmt::Display, -{ - // get previous env - let previous_env = Box::new(evm.context.env().clone()); - - // modify env for pre block call - fill_tx_env_with_withdrawal_requests_contract_call(&mut evm.context.evm.env); - - let ResultAndState { result, mut state } = match evm.transact() { - Ok(res) => res, - Err(e) => { - evm.context.evm.env = previous_env; - return Err(BlockValidationError::WithdrawalRequestsContractCall { - message: format!("execution failed: {e}"), - } - .into()) - } - }; - - // cleanup the state - state.remove(&alloy_eips::eip7002::SYSTEM_ADDRESS); - state.remove(&evm.block().coinbase); - evm.context.evm.db.commit(state); - - // re-set the previous env - evm.context.evm.env = previous_env; - - let mut data = match result { - ExecutionResult::Success { output, .. } => Ok(output.into_data()), - ExecutionResult::Revert { output, .. } => { - Err(BlockValidationError::WithdrawalRequestsContractCall { - message: format!("execution reverted: {output}"), - }) - } - ExecutionResult::Halt { reason, .. } => { - Err(BlockValidationError::WithdrawalRequestsContractCall { - message: format!("execution halted: {reason:?}"), - }) - } - }?; - - // Withdrawals are encoded as a series of withdrawal requests, each with the following - // format: - // - // +------+--------+--------+ - // | addr | pubkey | amount | - // +------+--------+--------+ - // 20 48 8 - - const WITHDRAWAL_REQUEST_SIZE: usize = 20 + 48 + 8; - let mut withdrawal_requests = Vec::with_capacity(data.len() / WITHDRAWAL_REQUEST_SIZE); - while data.has_remaining() { - if data.remaining() < WITHDRAWAL_REQUEST_SIZE { - return Err(BlockValidationError::WithdrawalRequestsContractCall { - message: "invalid withdrawal request length".to_string(), - } - .into()) - } - - let mut source_address = Address::ZERO; - data.copy_to_slice(source_address.as_mut_slice()); - - let mut validator_pubkey = FixedBytes::<48>::ZERO; - data.copy_to_slice(validator_pubkey.as_mut_slice()); - - let amount = data.get_u64(); - - withdrawal_requests.push(Request::WithdrawalRequest(WithdrawalRequest { - source_address, - validator_pubkey, - amount, - })); - } - - Ok(withdrawal_requests) -} From f0c97cab74a9647c699cdbbcf240c8f25e06fae4 Mon Sep 17 00:00:00 2001 From: yutianwu Date: Thu, 4 Jul 2024 16:16:20 +0800 Subject: [PATCH 324/405] fix(cli): don't init datadir if it doesn't exist in db command (#9264) Co-authored-by: Matthias Seitz --- crates/cli/commands/src/db/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cli/commands/src/db/mod.rs b/crates/cli/commands/src/db/mod.rs index dc247745f5ac..de1f1cc3826f 100644 --- a/crates/cli/commands/src/db/mod.rs +++ b/crates/cli/commands/src/db/mod.rs @@ -67,6 +67,16 @@ impl Command { let db_path = data_dir.db(); let static_files_path = data_dir.static_files(); + // ensure the provided datadir exist + eyre::ensure!( + data_dir.data_dir().is_dir(), + "Datadir does not exist: {:?}", + data_dir.data_dir() + ); + + // ensure the provided database exist + eyre::ensure!(db_path.is_dir(), "Database does not exist: {:?}", db_path); + match self.command { // TODO: We'll need to add this on the DB trait. Subcommands::Stats(command) => { From edbbc9636eca770102f6ff33779130deb7b38360 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Thu, 4 Jul 2024 10:25:22 +0200 Subject: [PATCH 325/405] chore: rename eth engine module orchestrator -> service (#9288) --- crates/ethereum/engine/src/lib.rs | 4 ++-- crates/ethereum/engine/src/{orchestrator.rs => service.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename crates/ethereum/engine/src/{orchestrator.rs => service.rs} (100%) diff --git a/crates/ethereum/engine/src/lib.rs b/crates/ethereum/engine/src/lib.rs index e623dd733052..8cb60de5925b 100644 --- a/crates/ethereum/engine/src/lib.rs +++ b/crates/ethereum/engine/src/lib.rs @@ -8,5 +8,5 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -/// Ethereum engine orchestrator. -pub mod orchestrator; +/// Ethereum engine service. +pub mod service; diff --git a/crates/ethereum/engine/src/orchestrator.rs b/crates/ethereum/engine/src/service.rs similarity index 100% rename from crates/ethereum/engine/src/orchestrator.rs rename to crates/ethereum/engine/src/service.rs From 0373c5875ac038e4ea71de3f5777f4052db76911 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 4 Jul 2024 02:08:24 -0700 Subject: [PATCH 326/405] chore(trie): revamp inner in-memory trie cursor representation (#9287) --- crates/trie/trie/src/trie_cursor/in_memory.rs | 56 ++++++++++++++++--- crates/trie/trie/src/updates.rs | 15 +++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/crates/trie/trie/src/trie_cursor/in_memory.rs b/crates/trie/trie/src/trie_cursor/in_memory.rs index 83ef6e5d6c8a..983974da38db 100644 --- a/crates/trie/trie/src/trie_cursor/in_memory.rs +++ b/crates/trie/trie/src/trie_cursor/in_memory.rs @@ -1,13 +1,19 @@ use super::{TrieCursor, TrieCursorFactory}; -use crate::updates::TrieUpdatesSorted; +use crate::{ + forward_cursor::ForwardInMemoryCursor, + updates::{StorageTrieUpdatesSorted, TrieUpdatesSorted}, +}; use reth_db::DatabaseError; use reth_primitives::B256; use reth_trie_common::{BranchNodeCompact, Nibbles}; +use std::collections::HashSet; /// The trie cursor factory for the trie updates. #[derive(Debug, Clone)] pub struct InMemoryTrieCursorFactory<'a, CF> { + /// Underlying trie cursor factory. cursor_factory: CF, + /// Reference to sorted trie updates. trie_updates: &'a TrieUpdatesSorted, } @@ -32,7 +38,11 @@ impl<'a, CF: TrieCursorFactory> TrieCursorFactory for InMemoryTrieCursorFactory< hashed_address: B256, ) -> Result { let cursor = self.cursor_factory.storage_trie_cursor(hashed_address)?; - Ok(InMemoryStorageTrieCursor::new(cursor, hashed_address, self.trie_updates)) + Ok(InMemoryStorageTrieCursor::new( + hashed_address, + cursor, + self.trie_updates.storage_tries.get(&hashed_address), + )) } } @@ -41,14 +51,25 @@ impl<'a, CF: TrieCursorFactory> TrieCursorFactory for InMemoryTrieCursorFactory< #[derive(Debug)] #[allow(dead_code)] pub struct InMemoryAccountTrieCursor<'a, C> { + /// The database cursor. cursor: C, - trie_updates: &'a TrieUpdatesSorted, + /// Forward-only in-memory cursor over storage trie nodes. + in_memory_cursor: ForwardInMemoryCursor<'a, Nibbles, BranchNodeCompact>, + /// Collection of removed trie nodes. + removed_nodes: &'a HashSet, + /// Last key returned by the cursor. last_key: Option, } impl<'a, C> InMemoryAccountTrieCursor<'a, C> { const fn new(cursor: C, trie_updates: &'a TrieUpdatesSorted) -> Self { - Self { cursor, trie_updates, last_key: None } + let in_memory_cursor = ForwardInMemoryCursor::new(&trie_updates.account_nodes); + Self { + cursor, + in_memory_cursor, + removed_nodes: &trie_updates.removed_nodes, + last_key: None, + } } } @@ -77,16 +98,33 @@ impl<'a, C: TrieCursor> TrieCursor for InMemoryAccountTrieCursor<'a, C> { #[derive(Debug)] #[allow(dead_code)] pub struct InMemoryStorageTrieCursor<'a, C> { - cursor: C, - trie_update_index: usize, - trie_updates: &'a TrieUpdatesSorted, + /// The hashed address of the account that trie belongs to. hashed_address: B256, + /// The database cursor. + cursor: C, + /// Forward-only in-memory cursor over storage trie nodes. + in_memory_cursor: Option>, + /// Reference to the set of removed storage node keys. + removed_nodes: Option<&'a HashSet>, + /// The flag indicating whether the storage trie was cleared. + storage_trie_cleared: bool, + /// Last key returned by the cursor. last_key: Option, } impl<'a, C> InMemoryStorageTrieCursor<'a, C> { - const fn new(cursor: C, hashed_address: B256, trie_updates: &'a TrieUpdatesSorted) -> Self { - Self { cursor, trie_updates, trie_update_index: 0, hashed_address, last_key: None } + fn new(hashed_address: B256, cursor: C, updates: Option<&'a StorageTrieUpdatesSorted>) -> Self { + let in_memory_cursor = updates.map(|u| ForwardInMemoryCursor::new(&u.storage_nodes)); + let removed_nodes = updates.map(|u| &u.removed_nodes); + let storage_trie_cleared = updates.map_or(false, |u| u.is_deleted); + Self { + hashed_address, + cursor, + in_memory_cursor, + removed_nodes, + storage_trie_cleared, + last_key: None, + } } } diff --git a/crates/trie/trie/src/updates.rs b/crates/trie/trie/src/updates.rs index 86119a673c9d..eba5d1963d78 100644 --- a/crates/trie/trie/src/updates.rs +++ b/crates/trie/trie/src/updates.rs @@ -77,12 +77,11 @@ impl TrieUpdates { pub fn into_sorted(self) -> TrieUpdatesSorted { let mut account_nodes = Vec::from_iter(self.account_nodes); account_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - let mut storage_tries = Vec::from_iter( - self.storage_tries - .into_iter() - .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted())), - ); - storage_tries.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + let storage_tries = self + .storage_tries + .into_iter() + .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted())) + .collect(); TrieUpdatesSorted { removed_nodes: self.removed_nodes, account_nodes, storage_tries } } @@ -296,7 +295,7 @@ impl StorageTrieUpdates { pub struct TrieUpdatesSorted { pub(crate) account_nodes: Vec<(Nibbles, BranchNodeCompact)>, pub(crate) removed_nodes: HashSet, - pub(crate) storage_tries: Vec<(B256, StorageTrieUpdatesSorted)>, + pub(crate) storage_tries: HashMap, } impl TrieUpdatesSorted { @@ -311,7 +310,7 @@ impl TrieUpdatesSorted { } /// Returns reference to updated storage tries. - pub fn storage_tries_ref(&self) -> &[(B256, StorageTrieUpdatesSorted)] { + pub const fn storage_tries_ref(&self) -> &HashMap { &self.storage_tries } } From e7803f3e1ce0a170901ad049a63d00cf756ba2a4 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 4 Jul 2024 11:32:34 +0200 Subject: [PATCH 327/405] perf: resolve trusted nodes concurrently (#9291) --- crates/node/builder/src/launch/common.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 804c194f4906..d15778666d28 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -209,15 +209,16 @@ impl LaunchContextWith { info!(target: "reth::cli", "Adding trusted nodes"); // resolve trusted peers if they use a domain instead of dns - for peer in &self.attachment.config.network.trusted_peers { + let resolved = futures::future::try_join_all( + self.attachment.config.network.trusted_peers.iter().map(|peer| async move { let backoff = ConstantBuilder::default() .with_max_times(self.attachment.config.network.dns_retries); - let resolved = (move || { peer.resolve() }) - .retry(&backoff) - .notify(|err, _| warn!(target: "reth::cli", "Error resolving peer domain: {err}. Retrying...")) - .await?; - self.attachment.toml_config.peers.trusted_nodes.insert(resolved); - } + (move || { peer.resolve() }) + .retry(&backoff) + .notify(|err, _| warn!(target: "reth::cli", "Error resolving peer domain: {err}. Retrying...")) + .await + })).await?; + self.attachment.toml_config.peers.trusted_nodes.extend(resolved); } Ok(self) } From a81c1e44a987fc326ba9aa0a3a4c97f3c532cb42 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 4 Jul 2024 11:35:35 +0200 Subject: [PATCH 328/405] perf: spawn eth proof on IO pool (#9293) --- crates/rpc/rpc-eth-api/src/helpers/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 1f07f35ae327..50678a877678 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -103,7 +103,7 @@ pub trait EthState: LoadState + SpawnBlocking { return Err(EthApiError::ExceedsMaxProofWindow) } - Ok(self.spawn_tracing(move |this| { + Ok(self.spawn_blocking_io(move |this| { let state = this.state_at_block_id(block_id)?; let storage_keys = keys.iter().map(|key| key.0).collect::>(); let proof = state.proof(address, &storage_keys)?; From 38f2d00c8a25db4b46586ba00d8bbf27e6cff326 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 4 Jul 2024 11:38:57 +0200 Subject: [PATCH 329/405] feat: add empty optimism rpc crate (#9295) --- Cargo.lock | 4 ++++ Cargo.toml | 2 ++ crates/optimism/rpc/Cargo.toml | 10 ++++++++++ crates/optimism/rpc/src/lib.rs | 9 +++++++++ 4 files changed, 25 insertions(+) create mode 100644 crates/optimism/rpc/Cargo.toml create mode 100644 crates/optimism/rpc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 335361424c8e..1253e52d4e9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7914,6 +7914,10 @@ dependencies = [ name = "reth-optimism-primitives" version = "1.0.0" +[[package]] +name = "reth-optimism-rpc" +version = "1.0.0" + [[package]] name = "reth-payload-builder" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index ceb9c9532083..7a8627a7a153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ members = [ "crates/optimism/node/", "crates/optimism/payload/", "crates/optimism/primitives/", + "crates/optimism/rpc/", "crates/payload/basic/", "crates/payload/builder/", "crates/payload/primitives/", @@ -334,6 +335,7 @@ reth-optimism-cli = { path = "crates/optimism/cli" } reth-optimism-consensus = { path = "crates/optimism/consensus" } reth-optimism-payload-builder = { path = "crates/optimism/payload" } reth-optimism-primitives = { path = "crates/optimism/primitives" } +reth-optimism-rpc = { path = "crates/optimism/rpc" } reth-payload-builder = { path = "crates/payload/builder" } reth-payload-primitives = { path = "crates/payload/primitives" } reth-payload-validator = { path = "crates/payload/validator" } diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml new file mode 100644 index 000000000000..f6645519e65a --- /dev/null +++ b/crates/optimism/rpc/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "reth-optimism-rpc" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] diff --git a/crates/optimism/rpc/src/lib.rs b/crates/optimism/rpc/src/lib.rs new file mode 100644 index 000000000000..f263793af102 --- /dev/null +++ b/crates/optimism/rpc/src/lib.rs @@ -0,0 +1,9 @@ +//! OP-Reth RPC support. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] From 27ed81317f14a7347e05cbaadf817ec95aebfde8 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 4 Jul 2024 12:15:21 +0200 Subject: [PATCH 330/405] ci: re-enable hive tests (#9240) --- .github/{scripts => assets}/check_no_std.sh | 0 .github/assets/hive/expected_failures.yaml | 128 ++++++++++++++++++++ .github/assets/hive/parse.py | 43 +++++++ .github/{scripts => assets}/install_geth.sh | 0 .github/{scripts => assets}/label_pr.js | 0 .github/workflows/hive.yml | 45 +++---- .github/workflows/integration.yml | 2 +- .github/workflows/label-pr.yml | 2 +- .github/workflows/lint.yml | 2 +- 9 files changed, 193 insertions(+), 29 deletions(-) rename .github/{scripts => assets}/check_no_std.sh (100%) create mode 100644 .github/assets/hive/expected_failures.yaml create mode 100644 .github/assets/hive/parse.py rename .github/{scripts => assets}/install_geth.sh (100%) rename .github/{scripts => assets}/label_pr.js (100%) diff --git a/.github/scripts/check_no_std.sh b/.github/assets/check_no_std.sh similarity index 100% rename from .github/scripts/check_no_std.sh rename to .github/assets/check_no_std.sh diff --git a/.github/assets/hive/expected_failures.yaml b/.github/assets/hive/expected_failures.yaml new file mode 100644 index 000000000000..831cb966fb64 --- /dev/null +++ b/.github/assets/hive/expected_failures.yaml @@ -0,0 +1,128 @@ +# https://github.com/paradigmxyz/reth/issues/7015 +# https://github.com/paradigmxyz/reth/issues/6332 +rpc-compat: + - debug_getRawBlock/get-invalid-number (reth) + - debug_getRawHeader/get-invalid-number (reth) + - debug_getRawReceipts/get-invalid-number (reth) + - debug_getRawTransaction/get-invalid-hash (reth) + + - eth_call/call-callenv (reth) + - eth_createAccessList/create-al-contract-eip1559 (reth) + - eth_createAccessList/create-al-contract (reth) + - eth_feeHistory/fee-history (reth) + - eth_getStorageAt/get-storage-invalid-key-too-large (reth) + - eth_getStorageAt/get-storage-invalid-key (reth) + - eth_getTransactionReceipt/get-access-list (reth) + - eth_getTransactionReceipt/get-blob-tx (reth) + - eth_getTransactionReceipt/get-dynamic-fee (reth) + +# https://github.com/paradigmxyz/reth/issues/8732 +engine-withdrawals: + - Withdrawals Fork On Genesis (Paris) (reth) + - Withdrawals Fork on Block 1 (Paris) (reth) + - Withdrawals Fork on Block 2 (Paris) (reth) + - Withdrawals Fork on Block 3 (Paris) (reth) + - Withdraw to a single account (Paris) (reth) + - Withdraw to two accounts (Paris) (reth) + - Withdraw many accounts (Paris) (reth) + - Withdraw zero amount (Paris) (reth) + - Empty Withdrawals (Paris) (reth) + - Corrupted Block Hash Payload (INVALID) (Paris) (reth) + - Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload (Paris) (reth) + - Withdrawals Fork on Block 1 - 8 Block Re-Org, Sync (Paris) (reth) + - Withdrawals Fork on Block 8 - 10 Block Re-Org NewPayload (Paris) (reth) + - Withdrawals Fork on Block 8 - 10 Block Re-Org Sync (Paris) (reth) + - Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org (Paris) (reth) + - Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org Sync (Paris) (reth) + - Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org (Paris) (reth) + - Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org Sync (Paris) (reth) + + # https://github.com/paradigmxyz/reth/issues/8304#issuecomment-2208515839 + - Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account - No Transactions (Paris) (reth) + - Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account (Paris) (reth) + - Sync after 2 blocks - Withdrawals on Genesis - Single Withdrawal Account (Paris) (reth) + - Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts - No Transactions (Paris) (reth) + - Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts (Paris) (reth) + - Sync after 128 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts (Paris) (reth) + +# https://github.com/paradigmxyz/reth/issues/8305 +# https://github.com/paradigmxyz/reth/issues/6217 +engine-api: + - Inconsistent Head in ForkchoiceState (Paris) (reth) + - Invalid NewPayload, StateRoot, Syncing=True, EmptyTxs=True, DynFeeTxs=False (Paris) (reth) + - Invalid NewPayload, StateRoot, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Paris) (reth) + - Invalid NewPayload, PrevRandao, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=True, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=True, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasLimit, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Timestamp, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Paris) (reth) + + # Hive issue + # https://github.com/ethereum/hive/issues/1135 + - Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Nonce, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Nonce, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Gas, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Gas, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction GasPrice, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction GasPrice, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Value, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Value, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, ReceiptsRoot, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, ReceiptsRoot, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasLimit, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasUsed, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasUsed, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Timestamp, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Incomplete Transactions, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Incomplete Transactions, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth) + +# https://github.com/paradigmxyz/reth/issues/8305 +# https://github.com/paradigmxyz/reth/issues/6217 +# https://github.com/paradigmxyz/reth/issues/8306 +# https://github.com/paradigmxyz/reth/issues/7144 +engine-cancun: + - Blob Transaction Ordering, Multiple Clients (Cancun) (reth) + - Inconsistent Head in ForkchoiceState (Cancun) (reth) + - Invalid NewPayload, StateRoot, Syncing=True, EmptyTxs=True, DynFeeTxs=False (Cancun) (reth) + - Invalid NewPayload, StateRoot, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Cancun) (reth) + - Invalid NewPayload, PrevRandao, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=True, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=True, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, StateRoot, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasLimit, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Timestamp, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Cancun) (reth) + - Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth) + - Invalid NewPayload, ParentBeaconBlockRoot, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Cancun) (reth) + - Invalid NewPayload, BlobGasUsed, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Cancun) (reth) + - Invalid NewPayload, Blob Count on BlobGasUsed, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Cancun) (reth) + - Invalid NewPayload, ExcessBlobGas, Syncing=True, EmptyTxs=False, DynFeeTxs=False (Cancun) (reth) + + # Hive issue + # https://github.com/ethereum/hive/issues/1135 + - Invalid Missing Ancestor Syncing ReOrg, ReceiptsRoot, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, ReceiptsRoot, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasLimit, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasUsed, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, GasUsed, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Timestamp, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Incomplete Transactions, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Incomplete Transactions, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Nonce, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Nonce, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Gas, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Gas, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction GasPrice, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction GasPrice, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Value, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Cancun) (reth) + - Invalid Missing Ancestor Syncing ReOrg, Transaction Value, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Cancun) (reth) + +# https://github.com/paradigmxyz/reth/issues/8579 +sync: + - sync reth -> reth \ No newline at end of file diff --git a/.github/assets/hive/parse.py b/.github/assets/hive/parse.py new file mode 100644 index 000000000000..ee75fdf55317 --- /dev/null +++ b/.github/assets/hive/parse.py @@ -0,0 +1,43 @@ +import json +import yaml +import sys +import argparse + +# Argument parser setup +parser = argparse.ArgumentParser(description="Check for unexpected test results based on an exclusion list.") +parser.add_argument("report_json", help="Path to the hive report JSON file.") +parser.add_argument("--exclusion", required=True, help="Path to the exclusion YAML file.") +args = parser.parse_args() + +# Load hive JSON +with open(args.report_json, 'r') as file: + report = json.load(file) + +# Load exclusion YAML +with open(args.exclusion, 'r') as file: + exclusion_data = yaml.safe_load(file) + exclusions = exclusion_data.get(report['name'], []) + +# Collect unexpected failures and passes +unexpected_failures = [] +unexpected_passes = [] + +for test in report['testCases'].values(): + test_name = test['name'] + test_pass = test['summaryResult']['pass'] + if test_name in exclusions: + if test_pass: + unexpected_passes.append(test_name) + else: + if not test_pass: + unexpected_failures.append(test_name) + +# Check if there are any unexpected failures or passes and exit with error +if unexpected_failures or unexpected_passes: + if unexpected_failures: + print("Unexpected Failures:", unexpected_failures) + if unexpected_passes: + print("Unexpected Passes:", unexpected_passes) + sys.exit(1) + +print("Success.") \ No newline at end of file diff --git a/.github/scripts/install_geth.sh b/.github/assets/install_geth.sh similarity index 100% rename from .github/scripts/install_geth.sh rename to .github/assets/install_geth.sh diff --git a/.github/scripts/label_pr.js b/.github/assets/label_pr.js similarity index 100% rename from .github/scripts/label_pr.js rename to .github/assets/label_pr.js diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index 421e4b2f505d..d18ffd65ff91 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -72,12 +72,9 @@ jobs: strategy: fail-fast: false matrix: - # TODO: enable etherem/sync once resolved: - # https://github.com/paradigmxyz/reth/issues/8579 # ethereum/rpc to be deprecated: # https://github.com/ethereum/hive/pull/1117 - # sim: [smoke/genesis, smoke/network, ethereum/sync] - sim: [smoke/genesis, smoke/network] + sim: [smoke/genesis, smoke/network, ethereum/sync] include: - sim: devp2p limit: discv4 @@ -104,26 +101,16 @@ jobs: - TestBlobViolations - sim: ethereum/engine limit: engine-exchange-capabilities - # TODO: enable engine-withdrawals once resolved: - # https://github.com/paradigmxyz/reth/issues/8732 - # - sim: ethereum/engine - # limit: engine-withdrawals + - sim: ethereum/engine + limit: engine-withdrawals - sim: ethereum/engine limit: engine-auth - sim: ethereum/engine limit: engine-transition - # TODO: enable engine-api once resolved: - # https://github.com/paradigmxyz/reth/issues/8305 - # https://github.com/paradigmxyz/reth/issues/6217 - # - sim: ethereum/engine - # limit: engine-api - # TODO: enable cancun once resolved: - # https://github.com/paradigmxyz/reth/issues/8305 - # https://github.com/paradigmxyz/reth/issues/6217 - # https://github.com/paradigmxyz/reth/issues/8306 - # https://github.com/paradigmxyz/reth/issues/7144 - # - sim: ethereum/engine - # limit: cancun + - sim: ethereum/engine + limit: engine-api + - sim: ethereum/engine + limit: cancun # eth_ rpc methods - sim: ethereum/rpc-compat include: @@ -144,12 +131,9 @@ jobs: - eth_getTransactionReceipt - eth_sendRawTransaction - eth_syncing - # TODO: enable debug_ rpc-compat once resolved: - # https://github.com/paradigmxyz/reth/issues/7015 - # https://github.com/paradigmxyz/reth/issues/6332 # debug_ rpc methods - # - sim: ethereum/rpc-compat - # include: [debug_] + - sim: ethereum/rpc-compat + include: [debug_] # Pyspec cancun jobs - sim: pyspec include: [cancun/eip4844] @@ -190,6 +174,10 @@ jobs: permissions: issues: write steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Download artifacts uses: actions/download-artifact@v4 with: @@ -215,7 +203,11 @@ jobs: - name: Run ${{ matrix.sim }} simulator run: | cd hivetests - hive --sim "${{ matrix.sim }}$" --sim.limit "${{matrix.limit}}/${{join(matrix.include, '|')}}" --client reth + hive --sim "${{ matrix.sim }}$" --sim.limit "${{matrix.limit}}/${{join(matrix.include, '|')}}" --sim.parallelism 2 --client reth || true + + - name: Parse hive output + run: | + find hivetests/workspace/logs -type f -name "*.json" ! -name "hive.json" | xargs -I {} python .github/assets/hive/parse.py {} --exclusion .github/assets/hive/expected_failures.yaml - name: Create github issue if sim failed env: @@ -239,6 +231,7 @@ jobs: -f title='Hive Test Failure: ${{ matrix.sim }}' \ -f body="!!!!!!! This is an automated issue created by the hive test failure !!!!!!!

The hive test for ${{ matrix.sim }} failed. Please investigate and fix the issue.

[Link to the failed run](https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }})" \ -f "labels[]=C-hivetest" + - name: Print simulator output if: ${{ failure() }} run: | diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index b4b494a90256..103a87706bca 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Install Geth - run: .github/scripts/install_geth.sh + run: .github/assets/install_geth.sh - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml index 857d354a8fb8..07727173531b 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -19,5 +19,5 @@ jobs: uses: actions/github-script@v7 with: script: | - const label_pr = require('./.github/scripts/label_pr.js') + const label_pr = require('./.github/assets/label_pr.js') await label_pr({github, context}) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c07cee38830b..d0329aefc89f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -58,7 +58,7 @@ jobs: with: cache-on-failure: true - name: Run no_std checks - run: .github/scripts/check_no_std.sh + run: .github/assets/check_no_std.sh crate-checks: runs-on: ubuntu-latest From afe86895ff6b0d4a77ca1976d282b4a936472500 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 4 Jul 2024 12:16:39 +0200 Subject: [PATCH 331/405] feat: feature gate tokio::net lookup (#9289) --- crates/net/network/Cargo.toml | 2 +- crates/net/peers/Cargo.toml | 4 +++- crates/net/peers/src/lib.rs | 5 +++++ crates/net/peers/src/trusted_peer.rs | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index efadac80cf14..61b887f042d5 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -30,7 +30,7 @@ reth-storage-api.workspace = true reth-provider = { workspace = true, optional = true } reth-tokio-util.workspace = true reth-consensus.workspace = true -reth-network-peers.workspace = true +reth-network-peers = { workspace = true, features = ["net"] } reth-network-types.workspace = true # ethereum diff --git a/crates/net/peers/Cargo.toml b/crates/net/peers/Cargo.toml index 6b107432632e..5ac24edea759 100644 --- a/crates/net/peers/Cargo.toml +++ b/crates/net/peers/Cargo.toml @@ -25,13 +25,15 @@ secp256k1 = { workspace = true, optional = true } serde_with.workspace = true thiserror.workspace = true url.workspace = true -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, optional = true } [dev-dependencies] alloy-primitives = { workspace = true, features = ["rand"] } rand.workspace = true secp256k1 = { workspace = true, features = ["rand"] } serde_json.workspace = true +tokio = { workspace = true, features = ["net", "macros", "rt"] } [features] secp256k1 = ["dep:secp256k1", "enr/secp256k1"] +net = ["dep:tokio", "tokio?/net"] diff --git a/crates/net/peers/src/lib.rs b/crates/net/peers/src/lib.rs index b58f499abf3a..e80331f90468 100644 --- a/crates/net/peers/src/lib.rs +++ b/crates/net/peers/src/lib.rs @@ -39,6 +39,11 @@ //! - [`TrustedPeer`]: A [`NodeRecord`] with an optional domain name, which can be resolved to a //! [`NodeRecord`]. Useful for adding trusted peers at startup, whose IP address may not be //! static. +//! +//! +//! ## Feature Flags +//! +//! - `net`: Support for address lookups. #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", diff --git a/crates/net/peers/src/trusted_peer.rs b/crates/net/peers/src/trusted_peer.rs index b52c98232f5c..aa7e0a015336 100644 --- a/crates/net/peers/src/trusted_peer.rs +++ b/crates/net/peers/src/trusted_peer.rs @@ -76,6 +76,7 @@ impl TrustedPeer { } /// Resolves the host in a [`TrustedPeer`] to an IP address, returning a [`NodeRecord`]. + #[cfg(any(test, feature = "net"))] pub async fn resolve(&self) -> Result { let domain = match self.try_node_record() { Ok(record) => return Ok(record), From af280b98f7d94e66a72e763c50698877d1ec2dce Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 4 Jul 2024 13:10:27 +0200 Subject: [PATCH 332/405] chore: remove unused async (#9299) --- bin/reth-bench/src/authenticated_transport.rs | 4 ++-- bin/reth/src/commands/import.rs | 5 ++--- bin/reth/src/commands/stage/dump/execution.rs | 9 ++++----- .../commands/stage/dump/hashing_account.rs | 5 ++--- .../commands/stage/dump/hashing_storage.rs | 5 ++--- bin/reth/src/commands/stage/dump/merkle.rs | 9 ++++----- bin/reth/src/commands/stage/unwind.rs | 4 ++-- crates/consensus/beacon/src/engine/handle.rs | 2 +- crates/node/builder/src/launch/common.rs | 8 ++++---- crates/node/builder/src/launch/mod.rs | 10 ++++------ crates/node/builder/src/setup.rs | 7 +++---- crates/rpc/rpc-builder/src/lib.rs | 4 ++-- crates/rpc/rpc-engine-api/src/engine_api.rs | 20 +++++++++---------- crates/rpc/rpc/src/eth/pubsub.rs | 6 +++--- 14 files changed, 45 insertions(+), 53 deletions(-) diff --git a/bin/reth-bench/src/authenticated_transport.rs b/bin/reth-bench/src/authenticated_transport.rs index e92b581bc5fa..c946d244de9e 100644 --- a/bin/reth-bench/src/authenticated_transport.rs +++ b/bin/reth-bench/src/authenticated_transport.rs @@ -39,7 +39,7 @@ impl InnerTransport { jwt: JwtSecret, ) -> Result<(Self, Claims), AuthenticatedTransportError> { match url.scheme() { - "http" | "https" => Self::connect_http(url, jwt).await, + "http" | "https" => Self::connect_http(url, jwt), "ws" | "wss" => Self::connect_ws(url, jwt).await, "file" => Ok((Self::connect_ipc(url).await?, Claims::default())), _ => Err(AuthenticatedTransportError::BadScheme(url.scheme().to_string())), @@ -48,7 +48,7 @@ impl InnerTransport { /// Connects to an HTTP [`alloy_transport_http::Http`] transport. Returns an [`InnerTransport`] /// and the [Claims] generated from the jwt. - async fn connect_http( + fn connect_http( url: Url, jwt: JwtSecret, ) -> Result<(Self, Claims), AuthenticatedTransportError> { diff --git a/bin/reth/src/commands/import.rs b/bin/reth/src/commands/import.rs index bc0f183b0e93..f4810f05148b 100644 --- a/bin/reth/src/commands/import.rs +++ b/bin/reth/src/commands/import.rs @@ -96,8 +96,7 @@ impl ImportCommand { Arc::new(file_client), StaticFileProducer::new(provider_factory.clone(), PruneModes::default()), self.no_state, - ) - .await?; + )?; // override the tip pipeline.set_tip(tip); @@ -153,7 +152,7 @@ impl ImportCommand { /// /// If configured to execute, all stages will run. Otherwise, only stages that don't require state /// will run. -pub async fn build_import_pipeline( +pub fn build_import_pipeline( config: &Config, provider_factory: ProviderFactory, consensus: &Arc, diff --git a/bin/reth/src/commands/stage/dump/execution.rs b/bin/reth/src/commands/stage/dump/execution.rs index 67b6d5a659c4..05df9f4b90dc 100644 --- a/bin/reth/src/commands/stage/dump/execution.rs +++ b/bin/reth/src/commands/stage/dump/execution.rs @@ -21,7 +21,7 @@ pub(crate) async fn dump_execution_stage( import_tables_with_range(&output_db, db_tool, from, to)?; - unwind_and_copy(db_tool, from, tip_block_number, &output_db).await?; + unwind_and_copy(db_tool, from, tip_block_number, &output_db)?; if should_run { dry_run( @@ -32,8 +32,7 @@ pub(crate) async fn dump_execution_stage( ), to, from, - ) - .await?; + )?; } Ok(()) @@ -120,7 +119,7 @@ fn import_tables_with_range( /// Dry-run an unwind to FROM block, so we can get the `PlainStorageState` and /// `PlainAccountState` safely. There might be some state dependency from an address /// which hasn't been changed in the given range. -async fn unwind_and_copy( +fn unwind_and_copy( db_tool: &DbTool, from: u64, tip_block_number: u64, @@ -151,7 +150,7 @@ async fn unwind_and_copy( } /// Try to re-execute the stage without committing -async fn dry_run( +fn dry_run( output_provider_factory: ProviderFactory, to: u64, from: u64, diff --git a/bin/reth/src/commands/stage/dump/hashing_account.rs b/bin/reth/src/commands/stage/dump/hashing_account.rs index 899b521fdc57..025899231de8 100644 --- a/bin/reth/src/commands/stage/dump/hashing_account.rs +++ b/bin/reth/src/commands/stage/dump/hashing_account.rs @@ -38,8 +38,7 @@ pub(crate) async fn dump_hashing_account_stage( ), to, from, - ) - .await?; + )?; } Ok(()) @@ -71,7 +70,7 @@ fn unwind_and_copy( } /// Try to re-execute the stage straight away -async fn dry_run( +fn dry_run( output_provider_factory: ProviderFactory, to: u64, from: u64, diff --git a/bin/reth/src/commands/stage/dump/hashing_storage.rs b/bin/reth/src/commands/stage/dump/hashing_storage.rs index f05ac390dc8e..ad6298887403 100644 --- a/bin/reth/src/commands/stage/dump/hashing_storage.rs +++ b/bin/reth/src/commands/stage/dump/hashing_storage.rs @@ -28,8 +28,7 @@ pub(crate) async fn dump_hashing_storage_stage( ), to, from, - ) - .await?; + )?; } Ok(()) @@ -66,7 +65,7 @@ fn unwind_and_copy( } /// Try to re-execute the stage straight away -async fn dry_run( +fn dry_run( output_provider_factory: ProviderFactory, to: u64, from: u64, diff --git a/bin/reth/src/commands/stage/dump/merkle.rs b/bin/reth/src/commands/stage/dump/merkle.rs index 4e2541b60ea2..f004a4a1a3e9 100644 --- a/bin/reth/src/commands/stage/dump/merkle.rs +++ b/bin/reth/src/commands/stage/dump/merkle.rs @@ -44,7 +44,7 @@ pub(crate) async fn dump_merkle_stage( ) })??; - unwind_and_copy(db_tool, (from, to), tip_block_number, &output_db).await?; + unwind_and_copy(db_tool, (from, to), tip_block_number, &output_db)?; if should_run { dry_run( @@ -55,15 +55,14 @@ pub(crate) async fn dump_merkle_stage( ), to, from, - ) - .await?; + )?; } Ok(()) } /// Dry-run an unwind to FROM block and copy the necessary table data to the new database. -async fn unwind_and_copy( +fn unwind_and_copy( db_tool: &DbTool, range: (u64, u64), tip_block_number: u64, @@ -143,7 +142,7 @@ async fn unwind_and_copy( } /// Try to re-execute the stage straight away -async fn dry_run( +fn dry_run( output_provider_factory: ProviderFactory, to: u64, from: u64, diff --git a/bin/reth/src/commands/stage/unwind.rs b/bin/reth/src/commands/stage/unwind.rs index 3f8eb59628da..e5c4fde963b8 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/bin/reth/src/commands/stage/unwind.rs @@ -78,7 +78,7 @@ impl Command { } // This will build an offline-only pipeline if the `offline` flag is enabled - let mut pipeline = self.build_pipeline(config, provider_factory.clone()).await?; + let mut pipeline = self.build_pipeline(config, provider_factory)?; // Move all applicable data from database to static files. pipeline.move_to_static_files()?; @@ -108,7 +108,7 @@ impl Command { Ok(()) } - async fn build_pipeline( + fn build_pipeline( self, config: Config, provider_factory: ProviderFactory>, diff --git a/crates/consensus/beacon/src/engine/handle.rs b/crates/consensus/beacon/src/engine/handle.rs index 0f0d9e1da80b..0cffc67b3ff1 100644 --- a/crates/consensus/beacon/src/engine/handle.rs +++ b/crates/consensus/beacon/src/engine/handle.rs @@ -87,7 +87,7 @@ where /// Sends a transition configuration exchange message to the beacon consensus engine. /// /// See also - pub async fn transition_configuration_exchanged(&self) { + pub fn transition_configuration_exchanged(&self) { let _ = self.to_engine.send(BeaconEngineMessage::TransitionConfigurationExchanged); } diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index d15778666d28..f57b3f010e61 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -71,11 +71,11 @@ impl LaunchContext { /// `config`. /// /// Attaches both the `NodeConfig` and the loaded `reth.toml` config to the launch context. - pub async fn with_loaded_toml_config( + pub fn with_loaded_toml_config( self, config: NodeConfig, ) -> eyre::Result> { - let toml_config = self.load_toml_config(&config).await?; + let toml_config = self.load_toml_config(&config)?; Ok(self.with(WithConfigs { config, toml_config })) } @@ -83,7 +83,7 @@ impl LaunchContext { /// `config`. /// /// This is async because the trusted peers may have to be resolved. - pub async fn load_toml_config(&self, config: &NodeConfig) -> eyre::Result { + pub fn load_toml_config(&self, config: &NodeConfig) -> eyre::Result { let config_path = config.config.clone().unwrap_or_else(|| self.data_dir.config()); let mut toml_config = confy::load_path::(&config_path) @@ -518,7 +518,7 @@ where } /// Creates a `BlockchainProvider` and attaches it to the launch context. - pub async fn with_blockchain_db( + pub fn with_blockchain_db( self, ) -> eyre::Result>>> where diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 2efde0c5a606..9af5a765c31d 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -104,7 +104,7 @@ where let ctx = ctx .with_configured_globals() // load the toml config - .with_loaded_toml_config(config).await? + .with_loaded_toml_config(config)? // add resolved peers .with_resolved_peers().await? // attach the database @@ -127,7 +127,7 @@ where .with_metrics() // passing FullNodeTypes as type parameter here so that we can build // later the components. - .with_blockchain_db::().await? + .with_blockchain_db::()? .with_components(components_builder, on_component_initialized).await?; // spawn exexs @@ -201,8 +201,7 @@ where static_file_producer, ctx.components().block_executor().clone(), pipeline_exex_handle, - ) - .await?; + )?; let pipeline_events = pipeline.events(); task.set_pipeline_events(pipeline_events); @@ -223,8 +222,7 @@ where static_file_producer, ctx.components().block_executor().clone(), pipeline_exex_handle, - ) - .await?; + )?; (pipeline, Either::Right(network_client.clone())) }; diff --git a/crates/node/builder/src/setup.rs b/crates/node/builder/src/setup.rs index cf4d090dcbe7..294d7a8f68a0 100644 --- a/crates/node/builder/src/setup.rs +++ b/crates/node/builder/src/setup.rs @@ -24,7 +24,7 @@ use tokio::sync::watch; /// Constructs a [Pipeline] that's wired to the network #[allow(clippy::too_many_arguments)] -pub async fn build_networked_pipeline( +pub fn build_networked_pipeline( config: &StageConfig, client: Client, consensus: Arc, @@ -63,15 +63,14 @@ where static_file_producer, executor, exex_manager_handle, - ) - .await?; + )?; Ok(pipeline) } /// Builds the [Pipeline] with the given [`ProviderFactory`] and downloaders. #[allow(clippy::too_many_arguments)] -pub async fn build_pipeline( +pub fn build_pipeline( provider_factory: ProviderFactory, stage_config: &StageConfig, header_downloader: H, diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 7e5faf8bbb17..ee57e79e9aa7 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1699,7 +1699,7 @@ enum WsHttpServers { impl WsHttpServers { /// Starts the servers and returns the handles (http, ws) - async fn start( + fn start( self, http_module: Option>, ws_module: Option>, @@ -1796,7 +1796,7 @@ impl RpcServer { jwt_secret: None, }; - let (http, ws) = ws_http.server.start(http, ws, &config).await?; + let (http, ws) = ws_http.server.start(http, ws, &config)?; handle.http = http; handle.ws = ws; diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 11bde1c744f8..b0f53667a695 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -83,7 +83,7 @@ where } /// Fetches the client version. - async fn get_client_version_v1( + fn get_client_version_v1( &self, _client: ClientVersionV1, ) -> EngineApiResult> { @@ -444,7 +444,7 @@ where /// Called to verify network configuration parameters and ensure that Consensus and Execution /// layers are using the latest configuration. - pub async fn exchange_transition_configuration( + pub fn exchange_transition_configuration( &self, config: TransitionConfiguration, ) -> EngineApiResult { @@ -469,7 +469,7 @@ where }) } - self.inner.beacon_consensus.transition_configuration_exchanged().await; + self.inner.beacon_consensus.transition_configuration_exchanged(); // Short circuit if communicated block hash is zero if terminal_block_hash.is_zero() { @@ -801,7 +801,7 @@ where ) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_exchangeTransitionConfigurationV1"); let start = Instant::now(); - let res = Self::exchange_transition_configuration(self, config).await; + let res = Self::exchange_transition_configuration(self, config); self.inner.metrics.latency.exchange_transition_configuration.record(start.elapsed()); Ok(res?) } @@ -814,7 +814,7 @@ where client: ClientVersionV1, ) -> RpcResult> { trace!(target: "rpc::engine", "Serving engine_getClientVersionV1"); - let res = Self::get_client_version_v1(self, client).await; + let res = Self::get_client_version_v1(self, client); Ok(res?) } @@ -889,7 +889,7 @@ mod tests { commit: "defa64b2".to_string(), }; let (_, api) = setup_engine_api(); - let res = api.get_client_version_v1(client.clone()).await; + let res = api.get_client_version_v1(client.clone()); assert_eq!(res.unwrap(), vec![client]); } @@ -1045,7 +1045,7 @@ mod tests { ..Default::default() }; - let res = api.exchange_transition_configuration(transition_config).await; + let res = api.exchange_transition_configuration(transition_config); assert_matches!( res, @@ -1077,7 +1077,7 @@ mod tests { }; // Unknown block number - let res = api.exchange_transition_configuration(transition_config).await; + let res = api.exchange_transition_configuration(transition_config); assert_matches!( res, @@ -1091,7 +1091,7 @@ mod tests { execution_terminal_block.clone().unseal(), ); - let res = api.exchange_transition_configuration(transition_config).await; + let res = api.exchange_transition_configuration(transition_config); assert_matches!( res, @@ -1120,7 +1120,7 @@ mod tests { handle.provider.add_block(terminal_block.hash(), terminal_block.unseal()); - let config = api.exchange_transition_configuration(transition_config).await.unwrap(); + let config = api.exchange_transition_configuration(transition_config).unwrap(); assert_eq!(config, transition_config); } } diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs index 3585be9f8561..426923dc4458 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -162,7 +162,7 @@ where BroadcastStream::new(pubsub.chain_events.subscribe_to_canonical_state()); // get current sync status let mut initial_sync_status = pubsub.network.is_syncing(); - let current_sub_res = pubsub.sync_status(initial_sync_status).await; + let current_sub_res = pubsub.sync_status(initial_sync_status); // send the current status immediately let msg = SubscriptionMessage::from_json(¤t_sub_res) @@ -179,7 +179,7 @@ where initial_sync_status = current_syncing; // send a new message now that the status changed - let sync_status = pubsub.sync_status(current_syncing).await; + let sync_status = pubsub.sync_status(current_syncing); let msg = SubscriptionMessage::from_json(&sync_status) .map_err(SubscriptionSerializeError::new)?; if accepted_sink.send(msg).await.is_err() { @@ -270,7 +270,7 @@ where Provider: BlockReader + 'static, { /// Returns the current sync status for the `syncing` subscription - async fn sync_status(&self, is_syncing: bool) -> EthSubscriptionResult { + fn sync_status(&self, is_syncing: bool) -> EthSubscriptionResult { if is_syncing { let current_block = self.provider.chain_info().map(|info| info.best_number).unwrap_or_default(); From 610110bb67e1f49c00ce0e3bc8d96d76a2df8f25 Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 4 Jul 2024 20:13:29 +0800 Subject: [PATCH 333/405] feat(rpc/ots): add rpc erigon_getHeaderByNumber (#9300) Signed-off-by: jsvisa --- crates/rpc/rpc-api/src/otterscan.rs | 12 +++++++++++- crates/rpc/rpc-builder/tests/it/http.rs | 2 ++ crates/rpc/rpc/src/otterscan.rs | 7 ++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-api/src/otterscan.rs b/crates/rpc/rpc-api/src/otterscan.rs index 2156765bb206..ebf3c9132707 100644 --- a/crates/rpc/rpc-api/src/otterscan.rs +++ b/crates/rpc/rpc-api/src/otterscan.rs @@ -5,13 +5,23 @@ use reth_rpc_types::{ BlockDetails, ContractCreator, InternalOperation, OtsBlockTransactions, TraceEntry, TransactionsWithReceipts, }, - Transaction, + Header, Transaction, }; /// Otterscan rpc interface. #[cfg_attr(not(feature = "client"), rpc(server, namespace = "ots"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "ots"))] pub trait Otterscan { + /// Get the block header by block number, required by otterscan. + /// Otterscan currently requires this endpoint, used as: + /// + /// 1. check if the node is Erigon or not + /// 2. get block header instead of the full block + /// + /// Ref: + #[method(name = "getHeaderByNumber", aliases = ["erigon_getHeaderByNumber"])] + async fn get_header_by_number(&self, block_number: u64) -> RpcResult>; + /// Check if a certain address contains a deployed code. #[method(name = "hasCode")] async fn has_code(&self, address: Address, block_number: Option) -> RpcResult; diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index f778dc22a9ab..0e90eca3e874 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -301,6 +301,8 @@ where let nonce = 1; let block_hash = B256::default(); + OtterscanClient::get_header_by_number(client, 1).await.unwrap(); + OtterscanClient::has_code(client, address, None).await.unwrap(); OtterscanClient::get_api_level(client).await.unwrap(); diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index 4088d71ca0fa..b3ec8baa6070 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -10,7 +10,7 @@ use reth_rpc_types::{ BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions, OtsReceipt, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, }, - BlockTransactions, Transaction, + BlockTransactions, Header, Transaction, }; use revm_inspectors::transfer::{TransferInspector, TransferKind}; use revm_primitives::ExecutionResult; @@ -35,6 +35,11 @@ impl OtterscanServer for OtterscanApi where Eth: EthApiServer + TraceExt + 'static, { + /// Handler for `{ots,erigon}_getHeaderByNumber` + async fn get_header_by_number(&self, block_number: u64) -> RpcResult> { + self.eth.header_by_number(BlockNumberOrTag::Number(block_number)).await + } + /// Handler for `ots_hasCode` async fn has_code(&self, address: Address, block_number: Option) -> RpcResult { self.eth.get_code(address, block_number).await.map(|code| !code.is_empty()) From f759ca603685eac10dc47bdee3c1a5d6bbfa00e2 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Thu, 4 Jul 2024 14:34:55 +0200 Subject: [PATCH 334/405] chore(cli): move utils to `reth-cli-utils` crate (#9297) --- Cargo.lock | 133 ++++++++++-------- Cargo.toml | 2 + bin/reth/Cargo.toml | 1 + bin/reth/src/commands/debug_cmd/execution.rs | 7 +- .../commands/debug_cmd/in_memory_merkle.rs | 3 +- bin/reth/src/commands/debug_cmd/merkle.rs | 7 +- .../src/commands/debug_cmd/replay_engine.rs | 6 +- bin/reth/src/commands/node/mod.rs | 3 +- bin/reth/src/commands/p2p.rs | 4 +- bin/reth/src/commands/stage/run.rs | 3 +- crates/cli/util/Cargo.toml | 23 +++ crates/cli/util/src/lib.rs | 17 +++ .../util/src/load_secret_key.rs} | 0 crates/cli/util/src/parsers.rs | 96 +++++++++++++ crates/node/builder/Cargo.toml | 1 + crates/node/builder/src/builder/mod.rs | 3 +- crates/node/core/Cargo.toml | 2 +- crates/node/core/src/args/mod.rs | 3 - crates/node/core/src/args/payload_builder.rs | 6 +- crates/node/core/src/args/utils.rs | 94 +------------ 20 files changed, 234 insertions(+), 180 deletions(-) create mode 100644 crates/cli/util/Cargo.toml create mode 100644 crates/cli/util/src/lib.rs rename crates/{node/core/src/args/secret_key.rs => cli/util/src/load_secret_key.rs} (100%) create mode 100644 crates/cli/util/src/parsers.rs diff --git a/Cargo.lock b/Cargo.lock index 1253e52d4e9c..794606782d0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1569,9 +1569,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779e6b7d17797c0b42023d417228c02889300190e700cb074c3438d9c541d332" +checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" dependencies = [ "jobserver", "libc", @@ -1611,7 +1611,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -3765,9 +3765,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" dependencies = [ "bytes", "futures-channel", @@ -3806,9 +3806,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" dependencies = [ "bytes", "futures-channel", @@ -4560,7 +4560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5458,7 +5458,7 @@ dependencies = [ "libc", "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5495,9 +5495,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -6300,6 +6300,7 @@ dependencies = [ "reth-chainspec", "reth-cli-commands", "reth-cli-runner", + "reth-cli-util", "reth-config", "reth-consensus", "reth-consensus-common", @@ -6619,6 +6620,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-cli-util" +version = "1.0.0" +dependencies = [ + "eyre", + "proptest", + "reth-fs-util", + "reth-network", + "reth-primitives", + "secp256k1", + "thiserror", +] + [[package]] name = "reth-codecs" version = "1.0.0" @@ -7643,6 +7657,7 @@ dependencies = [ "reth-beacon-consensus", "reth-blockchain-tree", "reth-chainspec", + "reth-cli-util", "reth-config", "reth-consensus", "reth-consensus-debug-client", @@ -7703,6 +7718,7 @@ dependencies = [ "proptest", "rand 0.8.5", "reth-chainspec", + "reth-cli-util", "reth-config", "reth-consensus-common", "reth-db", @@ -7732,7 +7748,6 @@ dependencies = [ "secp256k1", "serde_json", "shellexpand", - "thiserror", "tikv-jemalloc-ctl", "tokio", "tower", @@ -8927,9 +8942,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7699249cc2c7d71939f30868f47e9d7add0bdc030d90ee10bfd16887ff8bb1c8" +checksum = "8f4b84ba6e838ceb47b41de5194a60244fac43d9fe03b71dbe8c5a201081d6d1" dependencies = [ "bytemuck", "byteorder", @@ -9067,9 +9082,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -9123,9 +9138,9 @@ checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ "ring", "rustls-pki-types", @@ -9351,9 +9366,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "indexmap 2.2.6", "itoa", @@ -9395,9 +9410,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.1" +version = "3.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" dependencies = [ "base64 0.22.1", "chrono", @@ -9413,9 +9428,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703" dependencies = [ "darling", "proc-macro2", @@ -10934,7 +10949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core 0.52.0", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -10944,7 +10959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ "windows-core 0.57.0", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -10953,7 +10968,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -10965,7 +10980,7 @@ dependencies = [ "windows-implement", "windows-interface", "windows-result", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -10996,7 +11011,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -11014,7 +11029,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -11034,18 +11049,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -11056,9 +11071,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -11068,9 +11083,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -11080,15 +11095,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -11098,9 +11113,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -11110,9 +11125,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -11122,9 +11137,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -11134,9 +11149,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -11251,18 +11266,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 7a8627a7a153..008048ea95d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "crates/cli/cli/", "crates/cli/commands/", "crates/cli/runner/", + "crates/cli/util/", "crates/config/", "crates/consensus/auto-seal/", "crates/consensus/beacon/", @@ -276,6 +277,7 @@ reth-chainspec = { path = "crates/chainspec" } reth-cli = { path = "crates/cli/cli" } reth-cli-commands = { path = "crates/cli/commands" } reth-cli-runner = { path = "crates/cli/runner" } +reth-cli-util = { path = "crates/cli/util" } reth-codecs = { path = "crates/storage/codecs" } reth-codecs-derive = { path = "crates/storage/codecs/derive" } reth-config = { path = "crates/config" } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 6ea5c346a5e8..9f8e08b0cda7 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -31,6 +31,7 @@ reth-transaction-pool.workspace = true reth-beacon-consensus.workspace = true reth-cli-runner.workspace = true reth-cli-commands.workspace = true +reth-cli-util.workspace = true reth-consensus-common.workspace = true reth-blockchain-tree.workspace = true reth-rpc-builder.workspace = true diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index dc8e01e54f44..f645cd140f6f 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -1,15 +1,12 @@ //! Command for debugging execution. -use crate::{ - args::{get_secret_key, NetworkArgs}, - macros::block_executor, - utils::get_single_header, -}; +use crate::{args::NetworkArgs, macros::block_executor, utils::get_single_header}; use clap::Parser; use futures::{stream::select as stream_select, StreamExt}; use reth_beacon_consensus::EthBeaconConsensus; use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; +use reth_cli_util::get_secret_key; use reth_config::Config; use reth_consensus::Consensus; use reth_db::DatabaseEnv; diff --git a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs index 7fcffffcdb0b..04288d6a8baa 100644 --- a/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs +++ b/bin/reth/src/commands/debug_cmd/in_memory_merkle.rs @@ -1,7 +1,7 @@ //! Command for debugging in-memory merkle trie calculation. use crate::{ - args::{get_secret_key, NetworkArgs}, + args::NetworkArgs, macros::block_executor, utils::{get_single_body, get_single_header}, }; @@ -9,6 +9,7 @@ use backon::{ConstantBuilder, Retryable}; use clap::Parser; use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; +use reth_cli_util::get_secret_key; use reth_config::Config; use reth_db::DatabaseEnv; use reth_errors::BlockValidationError; diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 05befe374175..432e3b00b3c0 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -1,14 +1,11 @@ //! Command for debugging merkle trie calculation. -use crate::{ - args::{get_secret_key, NetworkArgs}, - macros::block_executor, - utils::get_single_header, -}; +use crate::{args::NetworkArgs, macros::block_executor, utils::get_single_header}; use backon::{ConstantBuilder, Retryable}; use clap::Parser; use reth_beacon_consensus::EthBeaconConsensus; use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; +use reth_cli_util::get_secret_key; use reth_config::Config; use reth_consensus::Consensus; use reth_db::{tables, DatabaseEnv}; diff --git a/bin/reth/src/commands/debug_cmd/replay_engine.rs b/bin/reth/src/commands/debug_cmd/replay_engine.rs index 02a7205a44d2..cc31c562f6ff 100644 --- a/bin/reth/src/commands/debug_cmd/replay_engine.rs +++ b/bin/reth/src/commands/debug_cmd/replay_engine.rs @@ -1,7 +1,4 @@ -use crate::{ - args::{get_secret_key, NetworkArgs}, - macros::block_executor, -}; +use crate::{args::NetworkArgs, macros::block_executor}; use clap::Parser; use eyre::Context; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; @@ -11,6 +8,7 @@ use reth_blockchain_tree::{ }; use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; +use reth_cli_util::get_secret_key; use reth_config::Config; use reth_consensus::Consensus; use reth_db::DatabaseEnv; diff --git a/bin/reth/src/commands/node/mod.rs b/bin/reth/src/commands/node/mod.rs index f4c355b1757f..ee3c6da74223 100644 --- a/bin/reth/src/commands/node/mod.rs +++ b/bin/reth/src/commands/node/mod.rs @@ -1,13 +1,14 @@ //! Main node command for launching a node use crate::args::{ - utils::{chain_help, chain_value_parser, parse_socket_address, SUPPORTED_CHAINS}, + utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}, DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, NetworkArgs, PayloadBuilderArgs, PruningArgs, RpcServerArgs, TxPoolArgs, }; use clap::{value_parser, Args, Parser}; use reth_chainspec::ChainSpec; use reth_cli_runner::CliContext; +use reth_cli_util::parse_socket_address; use reth_db::{init_db, DatabaseEnv}; use reth_node_builder::{NodeBuilder, WithLaunchContext}; use reth_node_core::{node_config::NodeConfig, version}; diff --git a/bin/reth/src/commands/p2p.rs b/bin/reth/src/commands/p2p.rs index 73426441c4b6..b08f09ae6756 100644 --- a/bin/reth/src/commands/p2p.rs +++ b/bin/reth/src/commands/p2p.rs @@ -2,8 +2,7 @@ use crate::{ args::{ - get_secret_key, - utils::{chain_help, chain_value_parser, hash_or_num_value_parser, SUPPORTED_CHAINS}, + utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}, DatabaseArgs, NetworkArgs, }, utils::get_single_header, @@ -11,6 +10,7 @@ use crate::{ use backon::{ConstantBuilder, Retryable}; use clap::{Parser, Subcommand}; use reth_chainspec::ChainSpec; +use reth_cli_util::{get_secret_key, hash_or_num_value_parser}; use reth_config::Config; use reth_network::NetworkConfigBuilder; use reth_network_p2p::bodies::client::BodiesClient; diff --git a/bin/reth/src/commands/stage/run.rs b/bin/reth/src/commands/stage/run.rs index 101b89e0277a..2ceb8fe852a8 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/bin/reth/src/commands/stage/run.rs @@ -2,7 +2,7 @@ //! //! Stage debugging tool use crate::{ - args::{get_secret_key, NetworkArgs, StageEnum}, + args::{NetworkArgs, StageEnum}, macros::block_executor, prometheus_exporter, }; @@ -10,6 +10,7 @@ use clap::Parser; use reth_beacon_consensus::EthBeaconConsensus; use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; +use reth_cli_util::get_secret_key; use reth_config::config::{HashingConfig, SenderRecoveryConfig, TransactionLookupConfig}; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_exex::ExExManagerHandle; diff --git a/crates/cli/util/Cargo.toml b/crates/cli/util/Cargo.toml new file mode 100644 index 000000000000..f38421bc0954 --- /dev/null +++ b/crates/cli/util/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "reth-cli-util" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +reth-fs-util.workspace = true +reth-network.workspace = true +reth-primitives.workspace = true +secp256k1.workspace = true +thiserror.workspace = true +eyre.workspace = true + +[dev-dependencies] +proptest.workspace = true + +[lints] +workspace = true diff --git a/crates/cli/util/src/lib.rs b/crates/cli/util/src/lib.rs new file mode 100644 index 000000000000..39d7b7f98a51 --- /dev/null +++ b/crates/cli/util/src/lib.rs @@ -0,0 +1,17 @@ +//! This crate defines a set of commonly used cli utils. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +/// Helper function to load a secret key from a file. +pub mod load_secret_key; +pub use load_secret_key::get_secret_key; + +/// Cli parsers functions. +pub mod parsers; +pub use parsers::{hash_or_num_value_parser, parse_duration_from_secs, parse_socket_address}; diff --git a/crates/node/core/src/args/secret_key.rs b/crates/cli/util/src/load_secret_key.rs similarity index 100% rename from crates/node/core/src/args/secret_key.rs rename to crates/cli/util/src/load_secret_key.rs diff --git a/crates/cli/util/src/parsers.rs b/crates/cli/util/src/parsers.rs new file mode 100644 index 000000000000..5e7c8c785373 --- /dev/null +++ b/crates/cli/util/src/parsers.rs @@ -0,0 +1,96 @@ +use reth_primitives::{BlockHashOrNumber, B256}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs}, + str::FromStr, + time::Duration, +}; + +/// Helper to parse a [Duration] from seconds +pub fn parse_duration_from_secs(arg: &str) -> eyre::Result { + let seconds = arg.parse()?; + Ok(Duration::from_secs(seconds)) +} + +/// Parse [`BlockHashOrNumber`] +pub fn hash_or_num_value_parser(value: &str) -> eyre::Result { + match B256::from_str(value) { + Ok(hash) => Ok(BlockHashOrNumber::Hash(hash)), + Err(_) => Ok(BlockHashOrNumber::Number(value.parse()?)), + } +} + +/// Error thrown while parsing a socket address. +#[derive(thiserror::Error, Debug)] +pub enum SocketAddressParsingError { + /// Failed to convert the string into a socket addr + #[error("could not parse socket address: {0}")] + Io(#[from] std::io::Error), + /// Input must not be empty + #[error("cannot parse socket address from empty string")] + Empty, + /// Failed to parse the address + #[error("could not parse socket address from {0}")] + Parse(String), + /// Failed to parse port + #[error("could not parse port: {0}")] + Port(#[from] std::num::ParseIntError), +} + +/// Parse a [`SocketAddr`] from a `str`. +/// +/// The following formats are checked: +/// +/// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the +/// hostname is set to `localhost`. +/// - If the value contains `:` it is assumed to be the format `:` +/// - Otherwise it is assumed to be a hostname +/// +/// An error is returned if the value is empty. +pub fn parse_socket_address(value: &str) -> eyre::Result { + if value.is_empty() { + return Err(SocketAddressParsingError::Empty) + } + + if let Some(port) = value.strip_prefix(':').or_else(|| value.strip_prefix("localhost:")) { + let port: u16 = port.parse()?; + return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) + } + if let Ok(port) = value.parse::() { + return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) + } + value + .to_socket_addrs()? + .next() + .ok_or_else(|| SocketAddressParsingError::Parse(value.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::Rng; + use secp256k1::rand::thread_rng; + + #[test] + fn parse_socket_addresses() { + for value in ["localhost:9000", ":9000", "9000"] { + let socket_addr = parse_socket_address(value) + .unwrap_or_else(|_| panic!("could not parse socket address: {value}")); + + assert!(socket_addr.ip().is_loopback()); + assert_eq!(socket_addr.port(), 9000); + } + } + + #[test] + fn parse_socket_address_random() { + let port: u16 = thread_rng().gen(); + + for value in [format!("localhost:{port}"), format!(":{port}"), port.to_string()] { + let socket_addr = parse_socket_address(&value) + .unwrap_or_else(|_| panic!("could not parse socket address: {value}")); + + assert!(socket_addr.ip().is_loopback()); + assert_eq!(socket_addr.port(), port); + } + } +} diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index 351dbc3d8501..f15ec8774611 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -46,6 +46,7 @@ reth-consensus.workspace = true reth-consensus-debug-client.workspace = true reth-rpc-types.workspace = true reth-engine-util.workspace = true +reth-cli-util.workspace = true ## async futures.workspace = true diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index dbdef2291460..23a18ee309fe 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -11,6 +11,7 @@ use crate::{ }; use futures::Future; use reth_chainspec::ChainSpec; +use reth_cli_util::get_secret_key; use reth_db::{ test_utils::{create_test_rw_db_with_path, tempdir_path, TempDatabase}, DatabaseEnv, @@ -25,7 +26,7 @@ use reth_network::{ }; use reth_node_api::{FullNodeTypes, FullNodeTypesAdapter, NodeTypes}; use reth_node_core::{ - args::{get_secret_key, DatadirArgs}, + args::DatadirArgs, cli::config::{PayloadBuilderConfig, RethTransactionPoolConfig}, dirs::{ChainPath, DataDirPath, MaybePlatformPath}, node_config::NodeConfig, diff --git a/crates/node/core/Cargo.toml b/crates/node/core/Cargo.toml index db72a5e00ee7..5dc0ed8f5cf8 100644 --- a/crates/node/core/Cargo.toml +++ b/crates/node/core/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # reth reth-chainspec.workspace = true reth-primitives.workspace = true +reth-cli-util.workspace = true reth-fs-util.workspace = true reth-db = { workspace = true, features = ["mdbx"] } reth-db-api.workspace = true @@ -57,7 +58,6 @@ metrics-util.workspace = true eyre.workspace = true clap = { workspace = true, features = ["derive"] } humantime.workspace = true -thiserror.workspace = true const_format.workspace = true rand.workspace = true derive_more.workspace = true diff --git a/crates/node/core/src/args/mod.rs b/crates/node/core/src/args/mod.rs index 469ff72ea159..7d1f61903ffb 100644 --- a/crates/node/core/src/args/mod.rs +++ b/crates/node/core/src/args/mod.rs @@ -24,9 +24,6 @@ pub use database::DatabaseArgs; mod log; pub use log::{ColorMode, LogArgs}; -mod secret_key; -pub use secret_key::{get_secret_key, SecretKeyError}; - /// `PayloadBuilderArgs` struct for configuring the payload builder mod payload_builder; pub use payload_builder::PayloadBuilderArgs; diff --git a/crates/node/core/src/args/payload_builder.rs b/crates/node/core/src/args/payload_builder.rs index b6a937156de5..7d3b5f851e86 100644 --- a/crates/node/core/src/args/payload_builder.rs +++ b/crates/node/core/src/args/payload_builder.rs @@ -1,11 +1,9 @@ -use crate::{ - args::utils::parse_duration_from_secs, cli::config::PayloadBuilderConfig, - version::default_extradata, -}; +use crate::{cli::config::PayloadBuilderConfig, version::default_extradata}; use clap::{ builder::{RangedU64ValueParser, TypedValueParser}, Arg, Args, Command, }; +use reth_cli_util::parse_duration_from_secs; use reth_primitives::constants::{ ETHEREUM_BLOCK_GAS_LIMIT, MAXIMUM_EXTRA_DATA_SIZE, SLOT_DURATION, }; diff --git a/crates/node/core/src/args/utils.rs b/crates/node/core/src/args/utils.rs index 8a54a8942e1c..c80626f884cf 100644 --- a/crates/node/core/src/args/utils.rs +++ b/crates/node/core/src/args/utils.rs @@ -3,14 +3,7 @@ use alloy_genesis::Genesis; use reth_chainspec::ChainSpec; use reth_fs_util as fs; -use reth_primitives::{BlockHashOrNumber, B256}; -use std::{ - net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs}, - path::PathBuf, - str::FromStr, - sync::Arc, - time::Duration, -}; +use std::{path::PathBuf, sync::Arc}; use reth_chainspec::DEV; @@ -27,12 +20,6 @@ pub const SUPPORTED_CHAINS: &[&str] = &["optimism", "optimism-sepolia", "base", /// Chains supported by reth. First value should be used as the default. pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "sepolia", "goerli", "holesky", "dev"]; -/// Helper to parse a [Duration] from seconds -pub fn parse_duration_from_secs(arg: &str) -> eyre::Result { - let seconds = arg.parse()?; - Ok(Duration::from_secs(seconds)) -} - /// The help info for the --chain flag pub fn chain_help() -> String { format!("The chain this node is running.\nPossible values are either a built-in chain or the path to a chain specification file.\n\nBuilt-in chains:\n {}", SUPPORTED_CHAINS.join(", ")) @@ -83,64 +70,9 @@ pub fn chain_value_parser(s: &str) -> eyre::Result, eyre::Error> }) } -/// Parse [`BlockHashOrNumber`] -pub fn hash_or_num_value_parser(value: &str) -> eyre::Result { - match B256::from_str(value) { - Ok(hash) => Ok(BlockHashOrNumber::Hash(hash)), - Err(_) => Ok(BlockHashOrNumber::Number(value.parse()?)), - } -} - -/// Error thrown while parsing a socket address. -#[derive(thiserror::Error, Debug)] -pub enum SocketAddressParsingError { - /// Failed to convert the string into a socket addr - #[error("could not parse socket address: {0}")] - Io(#[from] std::io::Error), - /// Input must not be empty - #[error("cannot parse socket address from empty string")] - Empty, - /// Failed to parse the address - #[error("could not parse socket address from {0}")] - Parse(String), - /// Failed to parse port - #[error("could not parse port: {0}")] - Port(#[from] std::num::ParseIntError), -} - -/// Parse a [`SocketAddr`] from a `str`. -/// -/// The following formats are checked: -/// -/// - If the value can be parsed as a `u16` or starts with `:` it is considered a port, and the -/// hostname is set to `localhost`. -/// - If the value contains `:` it is assumed to be the format `:` -/// - Otherwise it is assumed to be a hostname -/// -/// An error is returned if the value is empty. -pub fn parse_socket_address(value: &str) -> eyre::Result { - if value.is_empty() { - return Err(SocketAddressParsingError::Empty) - } - - if let Some(port) = value.strip_prefix(':').or_else(|| value.strip_prefix("localhost:")) { - let port: u16 = port.parse()?; - return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) - } - if let Ok(port) = value.parse::() { - return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) - } - value - .to_socket_addrs()? - .next() - .ok_or_else(|| SocketAddressParsingError::Parse(value.to_string())) -} - #[cfg(test)] mod tests { use super::*; - use proptest::prelude::Rng; - use secp256k1::rand::thread_rng; #[test] fn parse_known_chain_spec() { @@ -148,28 +80,4 @@ mod tests { chain_value_parser(chain).unwrap(); } } - - #[test] - fn parse_socket_addresses() { - for value in ["localhost:9000", ":9000", "9000"] { - let socket_addr = parse_socket_address(value) - .unwrap_or_else(|_| panic!("could not parse socket address: {value}")); - - assert!(socket_addr.ip().is_loopback()); - assert_eq!(socket_addr.port(), 9000); - } - } - - #[test] - fn parse_socket_address_random() { - let port: u16 = thread_rng().gen(); - - for value in [format!("localhost:{port}"), format!(":{port}"), port.to_string()] { - let socket_addr = parse_socket_address(&value) - .unwrap_or_else(|_| panic!("could not parse socket address: {value}")); - - assert!(socket_addr.ip().is_loopback()); - assert_eq!(socket_addr.port(), port); - } - } } From 180b81af2fb0cb18ae12fef5249e7e43fa5bab8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:19:22 +0200 Subject: [PATCH 335/405] fix: remove useless arbitrary feature (#9307) --- crates/storage/codecs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/codecs/Cargo.toml b/crates/storage/codecs/Cargo.toml index 6864a5cf64fa..6f286a95b411 100644 --- a/crates/storage/codecs/Cargo.toml +++ b/crates/storage/codecs/Cargo.toml @@ -15,7 +15,7 @@ workspace = true reth-codecs-derive = { path = "./derive", default-features = false } # eth -alloy-consensus = { workspace = true, optional = true, features = ["arbitrary"] } +alloy-consensus = { workspace = true, optional = true } alloy-eips = { workspace = true, optional = true } alloy-genesis = { workspace = true, optional = true } alloy-primitives.workspace = true From ddc6ab902bbbc4eb2a9e2c9894a191194e15193f Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 4 Jul 2024 22:57:31 +0800 Subject: [PATCH 336/405] fix(rpc/ots): set block_number as u64 instead of NumberOrTag (#9302) Signed-off-by: jsvisa --- crates/rpc/rpc-api/src/otterscan.rs | 13 +++++-------- crates/rpc/rpc-builder/tests/it/http.rs | 4 ++-- crates/rpc/rpc/src/otterscan.rs | 14 ++++++-------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/crates/rpc/rpc-api/src/otterscan.rs b/crates/rpc/rpc-api/src/otterscan.rs index ebf3c9132707..4d45cba358e1 100644 --- a/crates/rpc/rpc-api/src/otterscan.rs +++ b/crates/rpc/rpc-api/src/otterscan.rs @@ -1,5 +1,5 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; -use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, TxHash, B256}; +use reth_primitives::{Address, BlockId, Bytes, TxHash, B256}; use reth_rpc_types::{ trace::otterscan::{ BlockDetails, ContractCreator, InternalOperation, OtsBlockTransactions, TraceEntry, @@ -48,10 +48,7 @@ pub trait Otterscan { /// Tailor-made and expanded version of eth_getBlockByNumber for block details page in /// Otterscan. #[method(name = "getBlockDetails")] - async fn get_block_details( - &self, - block_number: BlockNumberOrTag, - ) -> RpcResult>; + async fn get_block_details(&self, block_number: u64) -> RpcResult>; /// Tailor-made and expanded version of eth_getBlockByHash for block details page in Otterscan. #[method(name = "getBlockDetailsByHash")] @@ -61,7 +58,7 @@ pub trait Otterscan { #[method(name = "getBlockTransactions")] async fn get_block_transactions( &self, - block_number: BlockNumberOrTag, + block_number: u64, page_number: usize, page_size: usize, ) -> RpcResult; @@ -71,7 +68,7 @@ pub trait Otterscan { async fn search_transactions_before( &self, address: Address, - block_number: BlockNumberOrTag, + block_number: u64, page_size: usize, ) -> RpcResult; @@ -80,7 +77,7 @@ pub trait Otterscan { async fn search_transactions_after( &self, address: Address, - block_number: BlockNumberOrTag, + block_number: u64, page_size: usize, ) -> RpcResult; diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 0e90eca3e874..671c5739ac2e 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -295,13 +295,13 @@ where let address = Address::default(); let sender = Address::default(); let tx_hash = TxHash::default(); - let block_number = BlockNumberOrTag::default(); + let block_number = 1; let page_number = 1; let page_size = 10; let nonce = 1; let block_hash = B256::default(); - OtterscanClient::get_header_by_number(client, 1).await.unwrap(); + OtterscanClient::get_header_by_number(client, block_number).await.unwrap(); OtterscanClient::has_code(client, address, None).await.unwrap(); diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index b3ec8baa6070..7a445a7aa274 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -99,11 +99,8 @@ where } /// Handler for `ots_getBlockDetails` - async fn get_block_details( - &self, - block_number: BlockNumberOrTag, - ) -> RpcResult> { - let block = self.eth.block_by_number(block_number, true).await?; + async fn get_block_details(&self, block_number: u64) -> RpcResult> { + let block = self.eth.block_by_number(BlockNumberOrTag::Number(block_number), true).await?; Ok(block.map(Into::into)) } @@ -116,11 +113,12 @@ where /// Handler for `getBlockTransactions` async fn get_block_transactions( &self, - block_number: BlockNumberOrTag, + block_number: u64, page_number: usize, page_size: usize, ) -> RpcResult { // retrieve full block and its receipts + let block_number = BlockNumberOrTag::Number(block_number); let block = self.eth.block_by_number(block_number, true); let receipts = self.eth.block_receipts(BlockId::Number(block_number)); let (block, receipts) = futures::try_join!(block, receipts)?; @@ -184,7 +182,7 @@ where async fn search_transactions_before( &self, _address: Address, - _block_number: BlockNumberOrTag, + _block_number: u64, _page_size: usize, ) -> RpcResult { Err(internal_rpc_err("unimplemented")) @@ -194,7 +192,7 @@ where async fn search_transactions_after( &self, _address: Address, - _block_number: BlockNumberOrTag, + _block_number: u64, _page_size: usize, ) -> RpcResult { Err(internal_rpc_err("unimplemented")) From e8af47636ca2f503d99d873969c75a248c3d0600 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 4 Jul 2024 08:06:48 -0700 Subject: [PATCH 337/405] feat: extract proof generation into `StateProofProvider` (#9303) --- crates/engine/tree/src/tree/memory_overlay.rs | 17 ++++++++++++----- crates/revm/src/test_utils.rs | 14 +++++++++----- crates/rpc/rpc-eth-types/src/cache/db.rs | 18 ++++++++++-------- .../src/providers/bundle_state_provider.rs | 13 +++++++++---- .../provider/src/providers/state/historical.rs | 13 ++++++++----- .../provider/src/providers/state/latest.rs | 15 +++++++++------ .../provider/src/providers/state/macros.rs | 14 ++++++++------ crates/storage/provider/src/test_utils/mock.rs | 11 +++++++---- crates/storage/provider/src/test_utils/noop.rs | 11 +++++++---- crates/storage/storage-api/src/state.rs | 10 ++++------ crates/storage/storage-api/src/trie.rs | 11 +++++++++-- 11 files changed, 92 insertions(+), 55 deletions(-) diff --git a/crates/engine/tree/src/tree/memory_overlay.rs b/crates/engine/tree/src/tree/memory_overlay.rs index 7e0e1d52e5be..06bfc4186383 100644 --- a/crates/engine/tree/src/tree/memory_overlay.rs +++ b/crates/engine/tree/src/tree/memory_overlay.rs @@ -1,7 +1,9 @@ use super::ExecutedBlock; use reth_errors::ProviderResult; use reth_primitives::{Account, Address, BlockNumber, Bytecode, StorageKey, StorageValue, B256}; -use reth_provider::{AccountReader, BlockHashReader, StateProvider, StateRootProvider}; +use reth_provider::{ + AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider, +}; use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::db::BundleState; @@ -89,6 +91,15 @@ where } } +impl StateProofProvider for MemoryOverlayStateProvider +where + H: StateProofProvider + Send, +{ + fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult { + todo!() + } +} + impl StateProvider for MemoryOverlayStateProvider where H: StateProvider + Send, @@ -116,8 +127,4 @@ where self.historical.bytecode_by_hash(code_hash) } - - fn proof(&self, address: Address, keys: &[B256]) -> ProviderResult { - todo!() - } } diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index 90ac4ea0466e..e3cf0cedd6cf 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -1,7 +1,9 @@ use reth_primitives::{ keccak256, Account, Address, BlockNumber, Bytecode, Bytes, StorageKey, B256, U256, }; -use reth_storage_api::{AccountReader, BlockHashReader, StateProvider, StateRootProvider}; +use reth_storage_api::{ + AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider, +}; use reth_storage_errors::provider::ProviderResult; use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::db::BundleState; @@ -76,6 +78,12 @@ impl StateRootProvider for StateProviderTest { } } +impl StateProofProvider for StateProviderTest { + fn proof(&self, _address: Address, _slots: &[B256]) -> ProviderResult { + unimplemented!("proof generation is not supported") + } +} + impl StateProvider for StateProviderTest { fn storage( &self, @@ -88,8 +96,4 @@ impl StateProvider for StateProviderTest { fn bytecode_by_hash(&self, code_hash: B256) -> ProviderResult> { Ok(self.contracts.get(&code_hash).cloned()) } - - fn proof(&self, _address: Address, _keys: &[B256]) -> ProviderResult { - unimplemented!("proof generation is not supported") - } } diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index 47af8e85df31..82713147c1ba 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -31,6 +31,16 @@ impl<'a> reth_provider::StateRootProvider for StateProviderTraitObjWrapper<'a> { } } +impl<'a> reth_provider::StateProofProvider for StateProviderTraitObjWrapper<'a> { + fn proof( + &self, + address: revm_primitives::Address, + slots: &[B256], + ) -> reth_errors::ProviderResult { + self.0.proof(address, slots) + } +} + impl<'a> reth_provider::AccountReader for StateProviderTraitObjWrapper<'a> { fn basic_account( &self, @@ -93,14 +103,6 @@ impl<'a> StateProvider for StateProviderTraitObjWrapper<'a> { self.0.bytecode_by_hash(code_hash) } - fn proof( - &self, - address: revm_primitives::Address, - keys: &[B256], - ) -> reth_errors::ProviderResult { - self.0.proof(address, keys) - } - fn storage( &self, account: revm_primitives::Address, diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index 49fb196ffb18..19dbff3862ac 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -2,6 +2,7 @@ use crate::{ AccountReader, BlockHashReader, ExecutionDataProvider, StateProvider, StateRootProvider, }; use reth_primitives::{Account, Address, BlockNumber, Bytecode, B256}; +use reth_storage_api::StateProofProvider; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::db::BundleState; @@ -80,6 +81,14 @@ impl StateRootProvider } } +impl StateProofProvider + for BundleStateProvider +{ + fn proof(&self, _address: Address, _slots: &[B256]) -> ProviderResult { + Err(ProviderError::StateRootNotAvailableForHistoricalBlock) + } +} + impl StateProvider for BundleStateProvider { fn storage( &self, @@ -107,8 +116,4 @@ impl StateProvider for BundleStat self.state_provider.bytecode_by_hash(code_hash) } - - fn proof(&self, _address: Address, _keys: &[B256]) -> ProviderResult { - Err(ProviderError::StateRootNotAvailableForHistoricalBlock) - } } diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 12545fe7838e..eaf134beba17 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -13,6 +13,7 @@ use reth_primitives::{ constants::EPOCH_SLOTS, Account, Address, BlockNumber, Bytecode, StaticFileSegment, StorageKey, StorageValue, B256, }; +use reth_storage_api::StateProofProvider; use reth_storage_errors::provider::ProviderResult; use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState}; use revm::db::BundleState; @@ -271,6 +272,13 @@ impl<'b, TX: DbTx> StateRootProvider for HistoricalStateProviderRef<'b, TX> { } } +impl<'b, TX: DbTx> StateProofProvider for HistoricalStateProviderRef<'b, TX> { + /// Get account and storage proofs. + fn proof(&self, _address: Address, _slots: &[B256]) -> ProviderResult { + Err(ProviderError::StateRootNotAvailableForHistoricalBlock) + } +} + impl<'b, TX: DbTx> StateProvider for HistoricalStateProviderRef<'b, TX> { /// Get storage. fn storage( @@ -306,11 +314,6 @@ impl<'b, TX: DbTx> StateProvider for HistoricalStateProviderRef<'b, TX> { fn bytecode_by_hash(&self, code_hash: B256) -> ProviderResult> { self.tx.get::(code_hash).map_err(Into::into) } - - /// Get account and storage proofs. - fn proof(&self, _address: Address, _keys: &[B256]) -> ProviderResult { - Err(ProviderError::StateRootNotAvailableForHistoricalBlock) - } } /// State provider for a given block number. diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 56b4ecc38b11..2ad66bbfd85a 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -10,6 +10,7 @@ use reth_db_api::{ use reth_primitives::{ Account, Address, BlockNumber, Bytecode, StaticFileSegment, StorageKey, StorageValue, B256, }; +use reth_storage_api::StateProofProvider; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{proof::Proof, updates::TrieUpdates, AccountProof, HashedPostState}; use revm::db::BundleState; @@ -90,6 +91,14 @@ impl<'b, TX: DbTx> StateRootProvider for LatestStateProviderRef<'b, TX> { } } +impl<'b, TX: DbTx> StateProofProvider for LatestStateProviderRef<'b, TX> { + fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult { + Ok(Proof::new(self.tx) + .account_proof(address, slots) + .map_err(Into::::into)?) + } +} + impl<'b, TX: DbTx> StateProvider for LatestStateProviderRef<'b, TX> { /// Get storage. fn storage( @@ -110,12 +119,6 @@ impl<'b, TX: DbTx> StateProvider for LatestStateProviderRef<'b, TX> { fn bytecode_by_hash(&self, code_hash: B256) -> ProviderResult> { self.tx.get::(code_hash).map_err(Into::into) } - - fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult { - Ok(Proof::new(self.tx) - .account_proof(address, slots) - .map_err(Into::::into)?) - } } /// State provider for the latest state. diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index a39cddfe39fc..2b4638894e2e 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -30,10 +30,6 @@ macro_rules! delegate_provider_impls { ($target:ty $(where [$($generics:tt)*])?) => { $crate::providers::state::macros::delegate_impls_to_as_ref!( for $target => - StateRootProvider $(where [$($generics)*])? { - fn state_root(&self, state: &revm::db::BundleState) -> reth_storage_errors::provider::ProviderResult; - fn state_root_with_updates(&self, state: &revm::db::BundleState) -> reth_storage_errors::provider::ProviderResult<(reth_primitives::B256, reth_trie::updates::TrieUpdates)>; - } AccountReader $(where [$($generics)*])? { fn basic_account(&self, address: reth_primitives::Address) -> reth_storage_errors::provider::ProviderResult>; } @@ -41,11 +37,17 @@ macro_rules! delegate_provider_impls { fn block_hash(&self, number: u64) -> reth_storage_errors::provider::ProviderResult>; fn canonical_hashes_range(&self, start: reth_primitives::BlockNumber, end: reth_primitives::BlockNumber) -> reth_storage_errors::provider::ProviderResult>; } - StateProvider $(where [$($generics)*])?{ + StateProvider $(where [$($generics)*])? { fn storage(&self, account: reth_primitives::Address, storage_key: reth_primitives::StorageKey) -> reth_storage_errors::provider::ProviderResult>; - fn proof(&self, address: reth_primitives::Address, keys: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult; fn bytecode_by_hash(&self, code_hash: reth_primitives::B256) -> reth_storage_errors::provider::ProviderResult>; } + StateRootProvider $(where [$($generics)*])? { + fn state_root(&self, state: &revm::db::BundleState) -> reth_storage_errors::provider::ProviderResult; + fn state_root_with_updates(&self, state: &revm::db::BundleState) -> reth_storage_errors::provider::ProviderResult<(reth_primitives::B256, reth_trie::updates::TrieUpdates)>; + } + StateProofProvider $(where [$($generics)*])? { + fn proof(&self, address: reth_primitives::Address, slots: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult; + } ); } } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 0184f57559bd..75543462d341 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -15,6 +15,7 @@ use reth_primitives::{ SealedHeader, StorageKey, StorageValue, TransactionMeta, TransactionSigned, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, }; +use reth_storage_api::StateProofProvider; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::{ @@ -553,6 +554,12 @@ impl StateRootProvider for MockEthProvider { } } +impl StateProofProvider for MockEthProvider { + fn proof(&self, address: Address, _slots: &[B256]) -> ProviderResult { + Ok(AccountProof::new(address)) + } +} + impl StateProvider for MockEthProvider { fn storage( &self, @@ -574,10 +581,6 @@ impl StateProvider for MockEthProvider { } })) } - - fn proof(&self, address: Address, _keys: &[B256]) -> ProviderResult { - Ok(AccountProof::new(address)) - } } impl EvmEnvProvider for MockEthProvider { diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 5ffdfedc55a1..fcfdb826152f 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -14,6 +14,7 @@ use reth_primitives::{ }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; +use reth_storage_api::StateProofProvider; use reth_storage_errors::provider::ProviderResult; use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::{ @@ -326,6 +327,12 @@ impl StateRootProvider for NoopProvider { } } +impl StateProofProvider for NoopProvider { + fn proof(&self, address: Address, _slots: &[B256]) -> ProviderResult { + Ok(AccountProof::new(address)) + } +} + impl StateProvider for NoopProvider { fn storage( &self, @@ -338,10 +345,6 @@ impl StateProvider for NoopProvider { fn bytecode_by_hash(&self, _code_hash: B256) -> ProviderResult> { Ok(None) } - - fn proof(&self, address: Address, _keys: &[B256]) -> ProviderResult { - Ok(AccountProof::new(address)) - } } impl EvmEnvProvider for NoopProvider { diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 059909a463b0..c432d331b635 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -1,4 +1,4 @@ -use super::{AccountReader, BlockHashReader, BlockIdReader, StateRootProvider}; +use super::{AccountReader, BlockHashReader, BlockIdReader, StateProofProvider, StateRootProvider}; use auto_impl::auto_impl; use reth_execution_types::ExecutionOutcome; use reth_primitives::{ @@ -6,14 +6,15 @@ use reth_primitives::{ StorageValue, B256, KECCAK_EMPTY, U256, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use reth_trie::AccountProof; /// Type alias of boxed [`StateProvider`]. pub type StateProviderBox = Box; /// An abstraction for a type that provides state data. #[auto_impl(&, Arc, Box)] -pub trait StateProvider: BlockHashReader + AccountReader + StateRootProvider + Send + Sync { +pub trait StateProvider: + BlockHashReader + AccountReader + StateRootProvider + StateProofProvider + Send + Sync +{ /// Get storage of given account. fn storage( &self, @@ -24,9 +25,6 @@ pub trait StateProvider: BlockHashReader + AccountReader + StateRootProvider + S /// Get account code by its hash fn bytecode_by_hash(&self, code_hash: B256) -> ProviderResult>; - /// Get account and storage proofs. - fn proof(&self, address: Address, keys: &[B256]) -> ProviderResult; - /// Get account code by its address. /// /// Returns `None` if the account doesn't exist or account is not a contract diff --git a/crates/storage/storage-api/src/trie.rs b/crates/storage/storage-api/src/trie.rs index 083f565492e4..e833234c89e4 100644 --- a/crates/storage/storage-api/src/trie.rs +++ b/crates/storage/storage-api/src/trie.rs @@ -1,6 +1,6 @@ -use reth_primitives::B256; +use reth_primitives::{Address, B256}; use reth_storage_errors::provider::ProviderResult; -use reth_trie::updates::TrieUpdates; +use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::db::BundleState; /// A type that can compute the state root of a given post state. @@ -22,3 +22,10 @@ pub trait StateRootProvider: Send + Sync { bundle_state: &BundleState, ) -> ProviderResult<(B256, TrieUpdates)>; } + +/// A type that can generate state proof on top of a given post state. +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait StateProofProvider: Send + Sync { + /// Get account and storage proofs. + fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult; +} From a6430d48fd53aa216090dfb90dc4e8791fb7cdc9 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 4 Jul 2024 08:16:47 -0700 Subject: [PATCH 338/405] chore(trie): return mutable prefix sets from `HashedPostState::construct_prefix_sets` (#9306) --- crates/blockchain-tree/src/blockchain_tree.rs | 2 +- crates/trie/parallel/benches/root.rs | 2 +- crates/trie/parallel/src/async_root.rs | 2 +- crates/trie/parallel/src/parallel_root.rs | 2 +- crates/trie/trie/src/prefix_set/mod.rs | 29 +++++++++++++++++++ crates/trie/trie/src/state.rs | 18 +++++------- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index da0a147acceb..5974d1a030a0 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1866,7 +1866,7 @@ mod tests { ); let provider = tree.externals.provider_factory.provider().unwrap(); - let prefix_sets = exec5.hash_state_slow().construct_prefix_sets(); + let prefix_sets = exec5.hash_state_slow().construct_prefix_sets().freeze(); let state_root = StateRoot::from_tx(provider.tx_ref()).with_prefix_sets(prefix_sets).root().unwrap(); assert_eq!(state_root, block5.state_root); diff --git a/crates/trie/parallel/benches/root.rs b/crates/trie/parallel/benches/root.rs index 288e930bae3c..6a7d7a81cc37 100644 --- a/crates/trie/parallel/benches/root.rs +++ b/crates/trie/parallel/benches/root.rs @@ -41,7 +41,7 @@ pub fn calculate_state_root(c: &mut Criterion) { b.to_async(&runtime).iter_with_setup( || { let sorted_state = updated_state.clone().into_sorted(); - let prefix_sets = updated_state.construct_prefix_sets(); + let prefix_sets = updated_state.construct_prefix_sets().freeze(); let provider = provider_factory.provider().unwrap(); (provider, sorted_state, prefix_sets) }, diff --git a/crates/trie/parallel/src/async_root.rs b/crates/trie/parallel/src/async_root.rs index e568be81b4c5..db6152b6a2cf 100644 --- a/crates/trie/parallel/src/async_root.rs +++ b/crates/trie/parallel/src/async_root.rs @@ -86,7 +86,7 @@ where retain_updates: bool, ) -> Result<(B256, TrieUpdates), AsyncStateRootError> { let mut tracker = ParallelTrieTracker::default(); - let prefix_sets = self.hashed_state.construct_prefix_sets(); + let prefix_sets = self.hashed_state.construct_prefix_sets().freeze(); let storage_root_targets = StorageRootTargets::new( self.hashed_state.accounts.keys().copied(), prefix_sets.storage_prefix_sets, diff --git a/crates/trie/parallel/src/parallel_root.rs b/crates/trie/parallel/src/parallel_root.rs index 5e26b97b672b..0983fd47e5a3 100644 --- a/crates/trie/parallel/src/parallel_root.rs +++ b/crates/trie/parallel/src/parallel_root.rs @@ -77,7 +77,7 @@ where retain_updates: bool, ) -> Result<(B256, TrieUpdates), ParallelStateRootError> { let mut tracker = ParallelTrieTracker::default(); - let prefix_sets = self.hashed_state.construct_prefix_sets(); + let prefix_sets = self.hashed_state.construct_prefix_sets().freeze(); let storage_root_targets = StorageRootTargets::new( self.hashed_state.accounts.keys().copied(), prefix_sets.storage_prefix_sets, diff --git a/crates/trie/trie/src/prefix_set/mod.rs b/crates/trie/trie/src/prefix_set/mod.rs index 228e0abee3c0..9d08ad36a98d 100644 --- a/crates/trie/trie/src/prefix_set/mod.rs +++ b/crates/trie/trie/src/prefix_set/mod.rs @@ -8,6 +8,35 @@ use std::{ mod loader; pub use loader::PrefixSetLoader; +/// Collection of mutable prefix sets. +#[derive(Default, Debug)] +pub struct TriePrefixSetsMut { + /// A set of account prefixes that have changed. + pub account_prefix_set: PrefixSetMut, + /// A map containing storage changes with the hashed address as key and a set of storage key + /// prefixes as the value. + pub storage_prefix_sets: HashMap, + /// A set of hashed addresses of destroyed accounts. + pub destroyed_accounts: HashSet, +} + +impl TriePrefixSetsMut { + /// Returns a `TriePrefixSets` with the same elements as these sets. + /// + /// If not yet sorted, the elements will be sorted and deduplicated. + pub fn freeze(self) -> TriePrefixSets { + TriePrefixSets { + account_prefix_set: self.account_prefix_set.freeze(), + storage_prefix_sets: self + .storage_prefix_sets + .into_iter() + .map(|(hashed_address, prefix_set)| (hashed_address, prefix_set.freeze())) + .collect(), + destroyed_accounts: self.destroyed_accounts, + } + } +} + /// Collection of trie prefix sets. #[derive(Default, Debug)] pub struct TriePrefixSets { diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index de7ecc236dbe..f92b1dd08241 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -1,6 +1,6 @@ use crate::{ hashed_cursor::HashedPostStateCursorFactory, - prefix_set::{PrefixSetMut, TriePrefixSets}, + prefix_set::{PrefixSetMut, TriePrefixSetsMut}, updates::TrieUpdates, Nibbles, StateRoot, }; @@ -170,10 +170,10 @@ impl HashedPostState { HashedPostStateSorted { accounts, storages } } - /// Construct [`TriePrefixSets`] from hashed post state. + /// Construct [`TriePrefixSetsMut`] from hashed post state. /// The prefix sets contain the hashed account and storage keys that have been changed in the /// post state. - pub fn construct_prefix_sets(&self) -> TriePrefixSets { + pub fn construct_prefix_sets(&self) -> TriePrefixSetsMut { // Populate account prefix set. let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len()); let mut destroyed_accounts = HashSet::default(); @@ -194,14 +194,10 @@ impl HashedPostState { for hashed_slot in hashed_storage.storage.keys() { prefix_set.insert(Nibbles::unpack(hashed_slot)); } - storage_prefix_sets.insert(*hashed_address, prefix_set.freeze()); + storage_prefix_sets.insert(*hashed_address, prefix_set); } - TriePrefixSets { - account_prefix_set: account_prefix_set.freeze(), - storage_prefix_sets, - destroyed_accounts, - } + TriePrefixSetsMut { account_prefix_set, storage_prefix_sets, destroyed_accounts } } /// Calculate the state root for this [`HashedPostState`]. @@ -236,7 +232,7 @@ impl HashedPostState { /// The state root for this [`HashedPostState`]. pub fn state_root(&self, tx: &TX) -> Result { let sorted = self.clone().into_sorted(); - let prefix_sets = self.construct_prefix_sets(); + let prefix_sets = self.construct_prefix_sets().freeze(); StateRoot::from_tx(tx) .with_hashed_cursor_factory(HashedPostStateCursorFactory::new(tx, &sorted)) .with_prefix_sets(prefix_sets) @@ -250,7 +246,7 @@ impl HashedPostState { tx: &TX, ) -> Result<(B256, TrieUpdates), StateRootError> { let sorted = self.clone().into_sorted(); - let prefix_sets = self.construct_prefix_sets(); + let prefix_sets = self.construct_prefix_sets().freeze(); StateRoot::from_tx(tx) .with_hashed_cursor_factory(HashedPostStateCursorFactory::new(tx, &sorted)) .with_prefix_sets(prefix_sets) From ed203356c6719e9cc9b38b4e0794f8a5115669d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A4=E7=8B=90=E4=B8=80=E5=86=B2?= <43949039+anonymousGiga@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:38:09 +0800 Subject: [PATCH 339/405] Fix: fix the issue of not being able to specify bootnode through command parameters (#9237) --- crates/node/core/src/args/network.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/node/core/src/args/network.rs b/crates/node/core/src/args/network.rs index 03bd6a307830..65f6f5834f16 100644 --- a/crates/node/core/src/args/network.rs +++ b/crates/node/core/src/args/network.rs @@ -123,6 +123,12 @@ impl NetworkArgs { /// /// The `default_peers_file` will be used as the default location to store the persistent peers /// file if `no_persist_peers` is false, and there is no provided `peers_file`. + /// + /// Configured Bootnodes are prioritized, if unset, the chain spec bootnodes are used + /// Priority order for bootnodes configuration: + /// 1. --bootnodes flag + /// 2. Network preset flags (e.g. --holesky) + /// 3. default to mainnet nodes pub fn network_config( &self, config: &Config, @@ -130,7 +136,16 @@ impl NetworkArgs { secret_key: SecretKey, default_peers_file: PathBuf, ) -> NetworkConfigBuilder { - let chain_bootnodes = chain_spec.bootnodes().unwrap_or_else(mainnet_nodes); + let chain_bootnodes = self + .bootnodes + .clone() + .map(|bootnodes| { + bootnodes + .into_iter() + .filter_map(|trusted_peer| trusted_peer.resolve_blocking().ok()) + .collect() + }) + .unwrap_or_else(|| chain_spec.bootnodes().unwrap_or_else(mainnet_nodes)); let peers_file = self.peers_file.clone().unwrap_or(default_peers_file); // Configure peer connections From 4447f658a9b7599fdafb54b78e7c3999d74cfcef Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 4 Jul 2024 08:53:22 -0700 Subject: [PATCH 340/405] feat(trie): allow setting hashed cursor factory on `Proof` (#9304) --- .../provider/src/providers/state/latest.rs | 2 +- crates/trie/trie/src/proof.rs | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 2ad66bbfd85a..a1e8256cf17c 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -93,7 +93,7 @@ impl<'b, TX: DbTx> StateRootProvider for LatestStateProviderRef<'b, TX> { impl<'b, TX: DbTx> StateProofProvider for LatestStateProviderRef<'b, TX> { fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult { - Ok(Proof::new(self.tx) + Ok(Proof::from_tx(self.tx) .account_proof(address, slots) .map_err(Into::::into)?) } diff --git a/crates/trie/trie/src/proof.rs b/crates/trie/trie/src/proof.rs index 65bce7d2865b..2342ece9869a 100644 --- a/crates/trie/trie/src/proof.rs +++ b/crates/trie/trie/src/proof.rs @@ -26,10 +26,22 @@ pub struct Proof<'a, TX, H> { hashed_cursor_factory: H, } +impl<'a, TX, H> Proof<'a, TX, H> { + /// Creates a new proof generator. + pub const fn new(tx: &'a TX, hashed_cursor_factory: H) -> Self { + Self { tx, hashed_cursor_factory } + } + + /// Set the hashed cursor factory. + pub fn with_hashed_cursor_factory(self, hashed_cursor_factory: HF) -> Proof<'a, TX, HF> { + Proof { tx: self.tx, hashed_cursor_factory } + } +} + impl<'a, TX> Proof<'a, TX, &'a TX> { - /// Create a new [Proof] instance. - pub const fn new(tx: &'a TX) -> Self { - Self { tx, hashed_cursor_factory: tx } + /// Create a new [Proof] instance from database transaction. + pub const fn from_tx(tx: &'a TX) -> Self { + Self::new(tx, tx) } } @@ -282,7 +294,8 @@ mod tests { let provider = factory.provider().unwrap(); for (target, expected_proof) in data { let target = Address::from_str(target).unwrap(); - let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &[]).unwrap(); + let account_proof = + Proof::from_tx(provider.tx_ref()).account_proof(target, &[]).unwrap(); similar_asserts::assert_eq!( account_proof.proof, expected_proof, @@ -302,7 +315,8 @@ mod tests { let slots = Vec::from([B256::with_last_byte(1), B256::with_last_byte(3)]); let provider = factory.provider().unwrap(); - let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &slots).unwrap(); + let account_proof = + Proof::from_tx(provider.tx_ref()).account_proof(target, &slots).unwrap(); assert_eq!(account_proof.storage_root, EMPTY_ROOT_HASH, "expected empty storage root"); assert_eq!(slots.len(), account_proof.storage_proofs.len()); @@ -334,7 +348,7 @@ mod tests { ]); let provider = factory.provider().unwrap(); - let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &[]).unwrap(); + let account_proof = Proof::from_tx(provider.tx_ref()).account_proof(target, &[]).unwrap(); similar_asserts::assert_eq!(account_proof.proof, expected_account_proof); assert_eq!(account_proof.verify(root), Ok(())); } @@ -357,7 +371,7 @@ mod tests { ]); let provider = factory.provider().unwrap(); - let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &[]).unwrap(); + let account_proof = Proof::from_tx(provider.tx_ref()).account_proof(target, &[]).unwrap(); similar_asserts::assert_eq!(account_proof.proof, expected_account_proof); assert_eq!(account_proof.verify(root), Ok(())); } @@ -443,7 +457,8 @@ mod tests { }; let provider = factory.provider().unwrap(); - let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &slots).unwrap(); + let account_proof = + Proof::from_tx(provider.tx_ref()).account_proof(target, &slots).unwrap(); similar_asserts::assert_eq!(account_proof, expected); assert_eq!(account_proof.verify(root), Ok(())); } From 93f9a857ce9fab74e7c3e4dcc278c119340b5820 Mon Sep 17 00:00:00 2001 From: greged93 <82421016+greged93@users.noreply.github.com> Date: Thu, 4 Jul 2024 18:33:27 +0200 Subject: [PATCH 341/405] feat: backfill job single block iterator (#9245) --- crates/ethereum/evm/src/execute.rs | 4 +- crates/evm/src/execute.rs | 2 +- crates/exex/exex/src/backfill.rs | 300 +++++++++++++++++++++++------ 3 files changed, 247 insertions(+), 59 deletions(-) diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 00267712f189..fb2f60e9698b 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -361,13 +361,11 @@ where type Output = BlockExecutionOutput; type Error = BlockExecutionError; - /// Executes the block and commits the state changes. + /// Executes the block and commits the changes to the internal state. /// /// Returns the receipts of the transactions in the block. /// /// Returns an error if the block could not be executed or failed verification. - /// - /// State changes are committed to the database. fn execute(mut self, input: Self::Input<'_>) -> Result { let BlockExecutionInput { block, total_difficulty } = input; let EthExecuteOutput { receipts, requests, gas_used } = diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 586fed53d997..9d3fd0a5e824 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -101,7 +101,7 @@ pub trait BatchExecutor { /// Contains the state changes, transaction receipts, and total gas used in the block. /// /// TODO(mattsse): combine with `ExecutionOutcome` -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct BlockExecutionOutput { /// The changed state of the block after execution. pub state: BundleState, diff --git a/crates/exex/exex/src/backfill.rs b/crates/exex/exex/src/backfill.rs index 0c7208a9c229..0b5895643d7b 100644 --- a/crates/exex/exex/src/backfill.rs +++ b/crates/exex/exex/src/backfill.rs @@ -1,7 +1,9 @@ use reth_db_api::database::Database; -use reth_evm::execute::{BatchExecutor, BlockExecutionError, BlockExecutorProvider}; +use reth_evm::execute::{ + BatchExecutor, BlockExecutionError, BlockExecutionOutput, BlockExecutorProvider, Executor, +}; use reth_node_api::FullNodeComponents; -use reth_primitives::{Block, BlockNumber}; +use reth_primitives::{Block, BlockNumber, BlockWithSenders, Receipt}; use reth_primitives_traits::format_gas_throughput; use reth_provider::{Chain, FullProvider, ProviderError, TransactionVariant}; use reth_prune_types::PruneModes; @@ -195,38 +197,124 @@ where } } +impl BackfillJob { + /// Converts the backfill job into a single block backfill job. + pub fn into_single_blocks(self) -> SingleBlockBackfillJob { + self.into() + } +} + +impl From> for SingleBlockBackfillJob { + fn from(value: BackfillJob) -> Self { + Self { + executor: value.executor, + provider: value.provider, + range: value.range, + _db: PhantomData, + } + } +} + +/// Single block Backfill job started for a specific range. +/// +/// It implements [`Iterator`] which executes a block each time the +/// iterator is advanced and yields ([`BlockWithSenders`], [`BlockExecutionOutput`]) +#[derive(Debug)] +pub struct SingleBlockBackfillJob { + executor: E, + provider: P, + range: RangeInclusive, + _db: PhantomData, +} + +impl Iterator for SingleBlockBackfillJob +where + E: BlockExecutorProvider, + DB: Database, + P: FullProvider, +{ + type Item = Result<(BlockWithSenders, BlockExecutionOutput), BlockExecutionError>; + + fn next(&mut self) -> Option { + self.range.next().map(|block_number| self.execute_block(block_number)) + } +} + +impl SingleBlockBackfillJob +where + E: BlockExecutorProvider, + DB: Database, + P: FullProvider, +{ + fn execute_block( + &self, + block_number: u64, + ) -> Result<(BlockWithSenders, BlockExecutionOutput), BlockExecutionError> { + let td = self + .provider + .header_td_by_number(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + + // Fetch the block with senders for execution. + let block_with_senders = self + .provider + .block_with_senders(block_number.into(), TransactionVariant::WithHash)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + + // Configure the executor to use the previous block's state. + let executor = self.executor.executor(StateProviderDatabase::new( + self.provider.history_by_block_number(block_number.saturating_sub(1))?, + )); + + trace!(target: "exex::backfill", number = block_number, txs = block_with_senders.block.body.len(), "Executing block"); + + let block_execution_output = executor.execute((&block_with_senders, td).into())?; + + Ok((block_with_senders, block_execution_output)) + } +} + #[cfg(test)] mod tests { use crate::BackfillJobFactory; use eyre::OptionExt; use reth_blockchain_tree::noop::NoopBlockchainTree; - use reth_chainspec::{ChainSpecBuilder, EthereumHardfork, MAINNET}; + use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, MAINNET}; use reth_db_common::init::init_genesis; - use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; + use reth_evm::execute::{ + BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, Executor, + }; use reth_evm_ethereum::execute::EthExecutorProvider; use reth_primitives::{ - b256, constants::ETH_TO_WEI, public_key_to_address, Address, Block, Genesis, - GenesisAccount, Header, Transaction, TxEip2930, TxKind, U256, + b256, constants::ETH_TO_WEI, public_key_to_address, Address, Block, BlockWithSenders, + Genesis, GenesisAccount, Header, Receipt, Requests, SealedBlockWithSenders, Transaction, + TxEip2930, TxKind, U256, }; use reth_provider::{ providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, - BlockWriter, LatestStateProviderRef, + BlockWriter, ExecutionOutcome, LatestStateProviderRef, ProviderFactory, }; use reth_revm::database::StateProviderDatabase; use reth_testing_utils::generators::{self, sign_tx_with_key_pair}; use secp256k1::Keypair; use std::sync::Arc; - #[tokio::test] - async fn test_backfill() -> eyre::Result<()> { - reth_tracing::init_test_tracing(); - - // Create a key pair for the sender - let key_pair = Keypair::new_global(&mut generators::rng()); - let address = public_key_to_address(key_pair.public_key()); + fn to_execution_outcome( + block_number: u64, + block_execution_output: &BlockExecutionOutput, + ) -> ExecutionOutcome { + ExecutionOutcome { + bundle: block_execution_output.state.clone(), + receipts: block_execution_output.receipts.clone().into(), + first_block: block_number, + requests: vec![Requests(block_execution_output.requests.clone())], + } + } - // Create a chain spec with a genesis state that contains the sender - let chain_spec = Arc::new( + fn chain_spec(address: Address) -> Arc { + // Create a chain spec with a genesis state that contains the + // provided sender + Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) .genesis(Genesis { @@ -239,16 +327,53 @@ mod tests { }) .paris_activated() .build(), - ); + ) + } - let executor = EthExecutorProvider::ethereum(chain_spec.clone()); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - init_genesis(provider_factory.clone())?; - let blockchain_db = BlockchainProvider::new( - provider_factory.clone(), - Arc::new(NoopBlockchainTree::default()), + fn execute_block_and_commit_to_database( + provider_factory: &ProviderFactory, + chain_spec: Arc, + block: &BlockWithSenders, + ) -> eyre::Result> + where + DB: reth_db_api::database::Database, + { + let provider = provider_factory.provider()?; + + // Execute the block to produce a block execution output + let mut block_execution_output = EthExecutorProvider::ethereum(chain_spec) + .executor(StateProviderDatabase::new(LatestStateProviderRef::new( + provider.tx_ref(), + provider.static_file_provider().clone(), + ))) + .execute(BlockExecutionInput { block, total_difficulty: U256::ZERO })?; + block_execution_output.state.reverts.sort(); + + // Convert the block execution output to an execution outcome for committing to the database + let execution_outcome = to_execution_outcome(block.number, &block_execution_output); + + // Commit the block's execution outcome to the database + let provider_rw = provider_factory.provider_rw()?; + let block = block.clone().seal_slow(); + provider_rw.append_blocks_with_state( + vec![block], + execution_outcome, + Default::default(), + Default::default(), )?; + provider_rw.commit()?; + + Ok(block_execution_output) + } + fn blocks_and_execution_outputs( + provider_factory: ProviderFactory, + chain_spec: Arc, + key_pair: Keypair, + ) -> eyre::Result)>> + where + DB: reth_db_api::database::Database, + { // First block has a transaction that transfers some ETH to zero address let block1 = Block { header: Header { @@ -279,52 +404,69 @@ mod tests { .with_recovered_senders() .ok_or_eyre("failed to recover senders")?; - // Second block has no state changes + // Second block resends the same transaction with increased nonce let block2 = Block { header: Header { - parent_hash: block1.hash_slow(), + parent_hash: block1.header.hash_slow(), + receipts_root: b256!( + "d3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e" + ), difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"), number: 2, + gas_limit: 21000, + gas_used: 21000, ..Default::default() }, + body: vec![sign_tx_with_key_pair( + key_pair, + Transaction::Eip2930(TxEip2930 { + chain_id: chain_spec.chain.id(), + nonce: 1, + gas_limit: 21000, + gas_price: 1_500_000_000, + to: TxKind::Call(Address::ZERO), + value: U256::from(0.1 * ETH_TO_WEI as f64), + ..Default::default() + }), + )], ..Default::default() } .with_recovered_senders() .ok_or_eyre("failed to recover senders")?; - let provider = provider_factory.provider()?; - // Execute only the first block on top of genesis state - let mut outcome_single = EthExecutorProvider::ethereum(chain_spec.clone()) - .batch_executor(StateProviderDatabase::new(LatestStateProviderRef::new( - provider.tx_ref(), - provider.static_file_provider().clone(), - ))) - .execute_and_verify_batch([(&block1, U256::ZERO).into()])?; - outcome_single.bundle.reverts.sort(); - // Execute both blocks on top of the genesis state - let outcome_batch = EthExecutorProvider::ethereum(chain_spec) - .batch_executor(StateProviderDatabase::new(LatestStateProviderRef::new( - provider.tx_ref(), - provider.static_file_provider().clone(), - ))) - .execute_and_verify_batch([ - (&block1, U256::ZERO).into(), - (&block2, U256::ZERO).into(), - ])?; - drop(provider); + let block_output1 = + execute_block_and_commit_to_database(&provider_factory, chain_spec.clone(), &block1)?; + let block_output2 = + execute_block_and_commit_to_database(&provider_factory, chain_spec, &block2)?; let block1 = block1.seal_slow(); let block2 = block2.seal_slow(); - // Update the state with the execution results of both blocks - let provider_rw = provider_factory.provider_rw()?; - provider_rw.append_blocks_with_state( - vec![block1.clone(), block2], - outcome_batch, - Default::default(), - Default::default(), + Ok(vec![(block1, block_output1), (block2, block_output2)]) + } + + #[test] + fn test_backfill() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + // Create a key pair for the sender + let key_pair = Keypair::new_global(&mut generators::rng()); + let address = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec(address); + + let executor = EthExecutorProvider::ethereum(chain_spec.clone()); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(provider_factory.clone())?; + let blockchain_db = BlockchainProvider::new( + provider_factory.clone(), + Arc::new(NoopBlockchainTree::default()), )?; - provider_rw.commit()?; + + let blocks_and_execution_outputs = + blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?; + let (block, block_execution_output) = blocks_and_execution_outputs.first().unwrap(); + let execution_outcome = to_execution_outcome(block.number, block_execution_output); // Backfill the first block let factory = BackfillJobFactory::new(executor, blockchain_db); @@ -336,8 +478,56 @@ mod tests { assert_eq!(chains.len(), 1); let mut chain = chains.into_iter().next().unwrap(); chain.execution_outcome_mut().bundle.reverts.sort(); - assert_eq!(chain.blocks(), &[(1, block1)].into()); - assert_eq!(chain.execution_outcome(), &outcome_single); + assert_eq!(chain.blocks(), &[(1, block.clone())].into()); + assert_eq!(chain.execution_outcome(), &execution_outcome); + + Ok(()) + } + + #[test] + fn test_single_block_backfill() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + // Create a key pair for the sender + let key_pair = Keypair::new_global(&mut generators::rng()); + let address = public_key_to_address(key_pair.public_key()); + + let chain_spec = chain_spec(address); + + let executor = EthExecutorProvider::ethereum(chain_spec.clone()); + let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); + init_genesis(provider_factory.clone())?; + let blockchain_db = BlockchainProvider::new( + provider_factory.clone(), + Arc::new(NoopBlockchainTree::default()), + )?; + + let blocks_and_execution_outcomes = + blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?; + + // Backfill the first block + let factory = BackfillJobFactory::new(executor, blockchain_db); + let job = factory.backfill(1..=1); + let single_job = job.into_single_blocks(); + let block_execution_it = single_job.into_iter(); + + // Assert that the backfill job only produces a single block + let blocks_and_outcomes = block_execution_it.collect::>(); + assert_eq!(blocks_and_outcomes.len(), 1); + + // Assert that the backfill job single block iterator produces the expected output for each + // block + for (i, res) in blocks_and_outcomes.into_iter().enumerate() { + let (block, mut execution_output) = res?; + execution_output.state.reverts.sort(); + + let sealed_block_with_senders = blocks_and_execution_outcomes[i].0.clone(); + let expected_block = sealed_block_with_senders.unseal(); + let expected_output = &blocks_and_execution_outcomes[i].1; + + assert_eq!(block, expected_block); + assert_eq!(&execution_output, expected_output); + } Ok(()) } From 53d0f73e10a20825d58b4b6a7eca1a7e2893a8de Mon Sep 17 00:00:00 2001 From: Krishang <93703995+kamuik16@users.noreply.github.com> Date: Thu, 4 Jul 2024 22:31:22 +0530 Subject: [PATCH 342/405] perf: resolve trusted peers (#9301) --- bin/reth/src/commands/p2p.rs | 4 +--- bin/reth/src/commands/stage/run.rs | 7 +------ crates/node/core/src/args/network.rs | 8 ++++++++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bin/reth/src/commands/p2p.rs b/bin/reth/src/commands/p2p.rs index b08f09ae6756..caa85a71dbb9 100644 --- a/bin/reth/src/commands/p2p.rs +++ b/bin/reth/src/commands/p2p.rs @@ -78,9 +78,7 @@ impl Command { let mut config: Config = confy::load_path(&config_path).unwrap_or_default(); - for peer in &self.network.trusted_peers { - config.peers.trusted_nodes.insert(peer.resolve().await?); - } + config.peers.trusted_nodes.extend(self.network.resolve_trusted_peers().await?); if config.peers.trusted_nodes.is_empty() && self.network.trusted_only { eyre::bail!("No trusted nodes. Set trusted peer with `--trusted-peer ` or set `--trusted-only` to `false`") diff --git a/bin/reth/src/commands/stage/run.rs b/bin/reth/src/commands/stage/run.rs index 2ceb8fe852a8..63aa760498ec 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/bin/reth/src/commands/stage/run.rs @@ -118,12 +118,7 @@ impl Command { let mut config = config; config.peers.trusted_nodes_only = self.network.trusted_only; - if !self.network.trusted_peers.is_empty() { - for peer in &self.network.trusted_peers { - let peer = peer.resolve().await?; - config.peers.trusted_nodes.insert(peer); - } - } + config.peers.trusted_nodes.extend(self.network.resolve_trusted_peers().await?); let network_secret_path = self .network diff --git a/crates/node/core/src/args/network.rs b/crates/node/core/src/args/network.rs index 65f6f5834f16..39af9480d05b 100644 --- a/crates/node/core/src/args/network.rs +++ b/crates/node/core/src/args/network.rs @@ -231,6 +231,14 @@ impl NetworkArgs { self.port += instance - 1; self.discovery.adjust_instance_ports(instance); } + + /// Resolve all trusted peers at once + pub async fn resolve_trusted_peers(&self) -> Result, std::io::Error> { + futures::future::try_join_all( + self.trusted_peers.iter().map(|peer| async move { peer.resolve().await }), + ) + .await + } } impl Default for NetworkArgs { From 6b1c3bade1e9d40315a48a4127630f45d6c423eb Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 4 Jul 2024 10:55:48 -0700 Subject: [PATCH 343/405] fix: holesky genesis hash (#9318) --- crates/primitives-traits/src/constants/mod.rs | 4 ++-- crates/storage/db-common/src/init.rs | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/primitives-traits/src/constants/mod.rs b/crates/primitives-traits/src/constants/mod.rs index 7ed018e8c8fb..492c402fa50c 100644 --- a/crates/primitives-traits/src/constants/mod.rs +++ b/crates/primitives-traits/src/constants/mod.rs @@ -112,9 +112,9 @@ pub const GOERLI_GENESIS_HASH: B256 = pub const SEPOLIA_GENESIS_HASH: B256 = b256!("25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9"); -/// Holesky genesis hash: `0xff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d` +/// Holesky genesis hash: `0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4` pub const HOLESKY_GENESIS_HASH: B256 = - b256!("ff9006519a8ce843ac9c28549d24211420b546e12ce2d170c77a8cca7964f23d"); + b256!("b5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4"); /// Testnet genesis hash: `0x2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c` pub const DEV_GENESIS_HASH: B256 = diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index bf0c28379c35..452507163add 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -521,7 +521,7 @@ struct GenesisAccountWithAddress { mod tests { use super::*; use alloy_genesis::Genesis; - use reth_chainspec::{Chain, GOERLI, MAINNET, SEPOLIA}; + use reth_chainspec::{Chain, GOERLI, HOLESKY, MAINNET, SEPOLIA}; use reth_db::DatabaseEnv; use reth_db_api::{ cursor::DbCursorRO, @@ -529,7 +529,9 @@ mod tests { table::{Table, TableRow}, transaction::DbTx, }; - use reth_primitives::{GOERLI_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH}; + use reth_primitives::{ + GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, + }; use reth_primitives_traits::IntegerList; use reth_provider::test_utils::create_test_provider_factory_with_chain_spec; @@ -570,6 +572,15 @@ mod tests { assert_eq!(genesis_hash, SEPOLIA_GENESIS_HASH); } + #[test] + fn success_init_genesis_holesky() { + let genesis_hash = + init_genesis(create_test_provider_factory_with_chain_spec(HOLESKY.clone())).unwrap(); + + // actual, expected + assert_eq!(genesis_hash, HOLESKY_GENESIS_HASH); + } + #[test] fn fail_init_inconsistent_db() { let factory = create_test_provider_factory_with_chain_spec(SEPOLIA.clone()); From 1fcd819461a102e46523f61db53c26e469fd3e72 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 4 Jul 2024 11:38:42 -0700 Subject: [PATCH 344/405] feat(trie): allow supplying prefix sets to `Proof` (#9317) --- crates/trie/trie/src/prefix_set/mod.rs | 9 +++++++++ crates/trie/trie/src/proof.rs | 26 ++++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/trie/trie/src/prefix_set/mod.rs b/crates/trie/trie/src/prefix_set/mod.rs index 9d08ad36a98d..3474bf74c8e1 100644 --- a/crates/trie/trie/src/prefix_set/mod.rs +++ b/crates/trie/trie/src/prefix_set/mod.rs @@ -131,6 +131,15 @@ impl PrefixSetMut { self.keys.push(nibbles); } + /// Extend prefix set keys with contents of provided iterator. + pub fn extend(&mut self, nibbles_iter: I) + where + I: IntoIterator, + { + self.sorted = false; + self.keys.extend(nibbles_iter); + } + /// Returns the number of elements in the set. pub fn len(&self) -> usize { self.keys.len() diff --git a/crates/trie/trie/src/proof.rs b/crates/trie/trie/src/proof.rs index 2342ece9869a..eb492f81f4f6 100644 --- a/crates/trie/trie/src/proof.rs +++ b/crates/trie/trie/src/proof.rs @@ -1,7 +1,7 @@ use crate::{ hashed_cursor::{HashedCursorFactory, HashedStorageCursor}, node_iter::{TrieElement, TrieNodeIter}, - prefix_set::PrefixSetMut, + prefix_set::TriePrefixSetsMut, trie_cursor::{DatabaseAccountTrieCursor, DatabaseStorageTrieCursor}, walker::TrieWalker, HashBuilder, Nibbles, @@ -24,23 +24,31 @@ pub struct Proof<'a, TX, H> { tx: &'a TX, /// The factory for hashed cursors. hashed_cursor_factory: H, + /// A set of prefix sets that have changes. + prefix_sets: TriePrefixSetsMut, } impl<'a, TX, H> Proof<'a, TX, H> { /// Creates a new proof generator. - pub const fn new(tx: &'a TX, hashed_cursor_factory: H) -> Self { - Self { tx, hashed_cursor_factory } + pub fn new(tx: &'a TX, hashed_cursor_factory: H) -> Self { + Self { tx, hashed_cursor_factory, prefix_sets: TriePrefixSetsMut::default() } } /// Set the hashed cursor factory. pub fn with_hashed_cursor_factory(self, hashed_cursor_factory: HF) -> Proof<'a, TX, HF> { - Proof { tx: self.tx, hashed_cursor_factory } + Proof { tx: self.tx, hashed_cursor_factory, prefix_sets: self.prefix_sets } + } + + /// Set the prefix sets. They have to be mutable in order to allow extension with proof target. + pub fn with_prefix_sets_mut(mut self, prefix_sets: TriePrefixSetsMut) -> Self { + self.prefix_sets = prefix_sets; + self } } impl<'a, TX> Proof<'a, TX, &'a TX> { /// Create a new [Proof] instance from database transaction. - pub const fn from_tx(tx: &'a TX) -> Self { + pub fn from_tx(tx: &'a TX) -> Self { Self::new(tx, tx) } } @@ -65,7 +73,7 @@ where DatabaseAccountTrieCursor::new(self.tx.cursor_read::()?); // Create the walker. - let mut prefix_set = PrefixSetMut::default(); + let mut prefix_set = self.prefix_sets.account_prefix_set.clone(); prefix_set.insert(target_nibbles.clone()); let walker = TrieWalker::new(trie_cursor, prefix_set.freeze()); @@ -130,12 +138,14 @@ where } let target_nibbles = proofs.iter().map(|p| p.nibbles.clone()).collect::>(); - let prefix_set = PrefixSetMut::from(target_nibbles.clone()).freeze(); + let mut prefix_set = + self.prefix_sets.storage_prefix_sets.get(&hashed_address).cloned().unwrap_or_default(); + prefix_set.extend(target_nibbles.clone()); let trie_cursor = DatabaseStorageTrieCursor::new( self.tx.cursor_dup_read::()?, hashed_address, ); - let walker = TrieWalker::new(trie_cursor, prefix_set); + let walker = TrieWalker::new(trie_cursor, prefix_set.freeze()); let retainer = ProofRetainer::from_iter(target_nibbles); let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer); From f37725f08050fe4c49efbb7782250549eeaff7e8 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 4 Jul 2024 13:53:45 -0700 Subject: [PATCH 345/405] feat(trie): `HashedPostState::account_proof` (#9319) --- crates/trie/trie/src/state.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index f92b1dd08241..c6c93c0b3620 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -1,6 +1,7 @@ use crate::{ hashed_cursor::HashedPostStateCursorFactory, prefix_set::{PrefixSetMut, TriePrefixSetsMut}, + proof::Proof, updates::TrieUpdates, Nibbles, StateRoot, }; @@ -13,6 +14,7 @@ use reth_db_api::{ }; use reth_execution_errors::StateRootError; use reth_primitives::{keccak256, Account, Address, BlockNumber, B256, U256}; +use reth_trie_common::AccountProof; use revm::db::BundleAccount; use std::{ collections::{hash_map, HashMap, HashSet}, @@ -252,6 +254,21 @@ impl HashedPostState { .with_prefix_sets(prefix_sets) .root_with_updates() } + + /// Generates the state proof for target account and slots on top of this [`HashedPostState`]. + pub fn account_proof( + &self, + tx: &TX, + address: Address, + slots: &[B256], + ) -> Result { + let sorted = self.clone().into_sorted(); + let prefix_sets = self.construct_prefix_sets(); + Proof::from_tx(tx) + .with_hashed_cursor_factory(HashedPostStateCursorFactory::new(tx, &sorted)) + .with_prefix_sets_mut(prefix_sets) + .account_proof(address, slots) + } } /// Representation of in-memory hashed storage. From 68167ef185f101db77e4b9dbcd8a87a2fc5861f4 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 5 Jul 2024 03:01:55 +0600 Subject: [PATCH 346/405] github-workflows: delete the direction of dead(deleted) code (#9316) --- docs/repo/ci.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/repo/ci.md b/docs/repo/ci.md index 18356ddb7320..d69e12c5d3a5 100644 --- a/docs/repo/ci.md +++ b/docs/repo/ci.md @@ -4,7 +4,6 @@ The CI runs a couple of workflows: ### Code -- **[ci]**: A catch-all for small jobs. Currently only runs lints (rustfmt, clippy etc.) - **[unit]**: Runs unit tests (tests in `src/`) and doc tests - **[integration]**: Runs integration tests (tests in `tests/` and sync tests) - **[bench]**: Runs benchmarks @@ -16,14 +15,11 @@ The CI runs a couple of workflows: ### Meta - **[deny]**: Runs `cargo deny` to check for license conflicts and security advisories in our dependencies -- **[sanity]**: Runs a couple of sanity checks on the code every night, such as checking for unused dependencies - **[release]**: Runs the release workflow -[ci]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/ci.yml [unit]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/unit.yml [integration]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/integration.yml [bench]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/bench.yml [book]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/book.yml [deny]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/deny.yml -[sanity]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/sanity.yml [release]: https://github.com/paradigmxyz/reth/blob/main/.github/workflows/release.yml From fe94078e004223d74f06fcf2f4a02bd2b2ee3835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:09:16 +0200 Subject: [PATCH 347/405] fix: no_std build (#9313) --- crates/chainspec/src/spec.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 33ca2f4f0244..5de303814c03 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -1,12 +1,6 @@ use crate::constants::MAINNET_DEPOSIT_CONTRACT; #[cfg(not(feature = "std"))] -use alloc::{ - collections::BTreeMap, - format, - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; +use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_chains::{Chain, ChainKind, NamedChain}; use alloy_genesis::Genesis; use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256}; @@ -293,7 +287,7 @@ impl From for BaseFeeParamsKind { #[derive(Clone, Debug, PartialEq, Eq, From)] pub struct ForkBaseFeeParams(Vec<(Box, BaseFeeParams)>); -impl std::ops::Deref for ChainSpec { +impl core::ops::Deref for ChainSpec { type Target = ChainHardforks; fn deref(&self) -> &Self::Target { From b2bbd002572469a83ae053d0b7b3bc7472673493 Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Fri, 5 Jul 2024 13:11:07 +0800 Subject: [PATCH 348/405] use op-alloy genesis types for genesis parsing (#9292) Co-authored-by: Matthias Seitz --- Cargo.lock | 30 ++++++++++++++++++++++ Cargo.toml | 3 +++ crates/chainspec/Cargo.toml | 10 +++++++- crates/chainspec/src/spec.rs | 48 ++++++++++++++++-------------------- 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 794606782d0c..76b82e90f821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5325,6 +5325,35 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "op-alloy-consensus" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "767fd3026c514f4d2ebdb4ebda5ed8857660dd1ef5bfed2aaa2ae8e42019630e" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "op-alloy-rpc-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50d6e6767b0b21efc9efb40fc0802ad6d28321c66ff17f1aaa46003cd234d4d" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "op-alloy-consensus", + "serde", + "serde_json", +] + [[package]] name = "opaque-debug" version = "0.3.1" @@ -6557,6 +6586,7 @@ dependencies = [ "derive_more", "nybbles", "once_cell", + "op-alloy-rpc-types", "rand 0.8.5", "reth-ethereum-forks", "reth-network-peers", diff --git a/Cargo.toml b/Cargo.toml index 008048ea95d0..84b4bfb81afb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -423,6 +423,9 @@ alloy-pubsub = { version = "0.1", default-features = false } alloy-json-rpc = { version = "0.1", default-features = false } alloy-rpc-client = { version = "0.1", default-features = false } +# op +op-alloy-rpc-types = "0.1" + # misc auto_impl = "1" aquamarine = "0.5" diff --git a/crates/chainspec/Cargo.toml b/crates/chainspec/Cargo.toml index f473acc4b20a..e4574acdace9 100644 --- a/crates/chainspec/Cargo.toml +++ b/crates/chainspec/Cargo.toml @@ -24,6 +24,10 @@ alloy-genesis.workspace = true alloy-primitives = { workspace = true, features = ["rand", "rlp"] } alloy-trie.workspace = true +# op +op-alloy-rpc-types = { workspace = true, optional = true } + + # misc once_cell.workspace = true serde = { workspace = true, optional = true } @@ -40,11 +44,15 @@ alloy-genesis.workspace = true reth-rpc-types.workspace = true rand.workspace = true +# op +op-alloy-rpc-types.workspace = true + [features] default = ["std"] optimism = [ "reth-ethereum-forks/optimism", - "serde" + "serde", + "dep:op-alloy-rpc-types", ] std = [] arbitrary = [ diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 5de303814c03..9f3b83f440d8 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -725,6 +725,9 @@ impl From for ChainSpec { fn from(genesis: Genesis) -> Self { #[cfg(feature = "optimism")] let optimism_genesis_info = OptimismGenesisInfo::extract_from(&genesis); + #[cfg(feature = "optimism")] + let genesis_info = + optimism_genesis_info.optimism_chain_info.genesis_info.unwrap_or_default(); // Block-based hardforks let hardfork_opts = [ @@ -742,7 +745,7 @@ impl From for ChainSpec { (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block), (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block), #[cfg(feature = "optimism")] - (OptimismHardfork::Bedrock.boxed(), optimism_genesis_info.bedrock_block), + (OptimismHardfork::Bedrock.boxed(), genesis_info.bedrock_block), ]; let mut hardforks = hardfork_opts .into_iter() @@ -771,13 +774,13 @@ impl From for ChainSpec { (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time), (EthereumHardfork::Prague.boxed(), genesis.config.prague_time), #[cfg(feature = "optimism")] - (OptimismHardfork::Regolith.boxed(), optimism_genesis_info.regolith_time), + (OptimismHardfork::Regolith.boxed(), genesis_info.regolith_time), #[cfg(feature = "optimism")] - (OptimismHardfork::Canyon.boxed(), optimism_genesis_info.canyon_time), + (OptimismHardfork::Canyon.boxed(), genesis_info.canyon_time), #[cfg(feature = "optimism")] - (OptimismHardfork::Ecotone.boxed(), optimism_genesis_info.ecotone_time), + (OptimismHardfork::Ecotone.boxed(), genesis_info.ecotone_time), #[cfg(feature = "optimism")] - (OptimismHardfork::Fjord.boxed(), optimism_genesis_info.fjord_time), + (OptimismHardfork::Fjord.boxed(), genesis_info.fjord_time), ]; let time_hardforks = time_hardfork_opts @@ -1079,33 +1082,22 @@ impl DepositContract { #[derive(Default, Debug, serde::Deserialize)] #[serde(rename_all = "camelCase")] struct OptimismGenesisInfo { - bedrock_block: Option, - regolith_time: Option, - canyon_time: Option, - ecotone_time: Option, - fjord_time: Option, + optimism_chain_info: op_alloy_rpc_types::genesis::OptimismChainInfo, #[serde(skip)] base_fee_params: BaseFeeParamsKind, } -#[cfg(feature = "optimism")] -#[derive(Debug, Eq, PartialEq, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -struct OptimismBaseFeeInfo { - eip1559_elasticity: Option, - eip1559_denominator: Option, - eip1559_denominator_canyon: Option, -} - #[cfg(feature = "optimism")] impl OptimismGenesisInfo { fn extract_from(genesis: &Genesis) -> Self { - let mut optimism_genesis_info: Self = - genesis.config.extra_fields.deserialize_as().unwrap_or_default(); - - if let Some(Ok(optimism_base_fee_info)) = - genesis.config.extra_fields.get_deserialized::("optimism") - { + let mut info = Self { + optimism_chain_info: op_alloy_rpc_types::genesis::OptimismChainInfo::extract_from( + &genesis.config.extra_fields, + ) + .unwrap_or_default(), + ..Default::default() + }; + if let Some(optimism_base_fee_info) = &info.optimism_chain_info.base_fee_info { if let (Some(elasticity), Some(denominator)) = ( optimism_base_fee_info.eip1559_elasticity, optimism_base_fee_info.eip1559_denominator, @@ -1130,11 +1122,11 @@ impl OptimismGenesisInfo { BaseFeeParams::new(denominator as u128, elasticity as u128).into() }; - optimism_genesis_info.base_fee_params = base_fee_params; + info.base_fee_params = base_fee_params; } } - optimism_genesis_info + info } } @@ -2896,6 +2888,8 @@ Post-merge hard forks (timestamp based): #[cfg(feature = "optimism")] #[test] fn parse_genesis_optimism_with_variable_base_fee_params() { + use op_alloy_rpc_types::genesis::OptimismBaseFeeInfo; + let geth_genesis = r#" { "config": { From 21a9dfc9ec0fd86ebf8c9f11e63786388b67b21b Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 5 Jul 2024 02:26:03 -0700 Subject: [PATCH 349/405] chore(evm): turn associated `ConfigureEvm` fns into methods (#9322) --- crates/ethereum/evm/src/execute.rs | 7 ++++--- crates/ethereum/evm/src/lib.rs | 4 +++- crates/ethereum/payload/src/lib.rs | 8 +++++--- crates/evm/src/lib.rs | 10 +++++++--- crates/evm/src/system_calls.rs | 14 +++++++++----- crates/optimism/evm/src/execute.rs | 5 +++-- crates/optimism/evm/src/lib.rs | 4 +++- crates/optimism/payload/src/builder.rs | 4 ++-- .../rpc/rpc-eth-api/src/helpers/pending_block.rs | 2 +- .../provider/src/providers/database/provider.rs | 8 ++++---- examples/custom-evm/src/main.rs | 4 +++- examples/exex/rollup/src/execution.rs | 8 +------- examples/stateful-precompile/src/main.rs | 6 ++++-- 13 files changed, 49 insertions(+), 35 deletions(-) diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index fb2f60e9698b..c83931df9a36 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -145,7 +145,8 @@ where DB::Error: Into + std::fmt::Display, { // apply pre execution changes - apply_beacon_root_contract_call::( + apply_beacon_root_contract_call( + &self.evm_config, &self.chain_spec, block.timestamp, block.number, @@ -220,7 +221,7 @@ where // Collect all EIP-7685 requests let withdrawal_requests = - apply_withdrawal_requests_contract_call::(&mut evm)?; + apply_withdrawal_requests_contract_call(&self.evm_config, &mut evm)?; [deposit_requests, withdrawal_requests].concat() } else { @@ -275,7 +276,7 @@ where fn evm_env_for_block(&self, header: &Header, total_difficulty: U256) -> EnvWithHandlerCfg { let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); let mut block_env = BlockEnv::default(); - EvmConfig::fill_cfg_and_block_env( + self.executor.evm_config.fill_cfg_and_block_env( &mut cfg, &mut block_env, self.chain_spec(), diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 1c0b6b83bf2f..cd8398ebe963 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -36,6 +36,7 @@ pub struct EthEvmConfig; impl ConfigureEvmEnv for EthEvmConfig { fn fill_cfg_env( + &self, cfg_env: &mut CfgEnvWithHandlerCfg, chain_spec: &ChainSpec, header: &Header, @@ -63,6 +64,7 @@ impl ConfigureEvmEnv for EthEvmConfig { } fn fill_tx_env_system_contract_call( + &self, env: &mut Env, caller: Address, contract: Address, @@ -132,7 +134,7 @@ mod tests { let chain_spec = ChainSpec::default(); let total_difficulty = U256::ZERO; - EthEvmConfig::fill_cfg_and_block_env( + EthEvmConfig::default().fill_cfg_and_block_env( &mut cfg_env, &mut block_env, &chain_spec, diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index ed1d6cdfddda..a7be685ef3a6 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -118,7 +118,7 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, - self.evm_config.clone(), + &self.evm_config, &chain_spec, &initialized_cfg, &initialized_block_env, @@ -201,6 +201,7 @@ where // We do not calculate the EIP-6110 deposit requests because there are no // transactions in an empty payload. let withdrawal_requests = post_block_withdrawal_requests_contract_call::( + &self.evm_config, &mut db, &initialized_cfg, &initialized_block_env, @@ -296,7 +297,7 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, - evm_config.clone(), + &evm_config, &chain_spec, &initialized_cfg, &initialized_block_env, @@ -443,7 +444,8 @@ where { let deposit_requests = parse_deposits_from_receipts(&chain_spec, receipts.iter().flatten()) .map_err(|err| PayloadBuilderError::Internal(RethError::Execution(err.into())))?; - let withdrawal_requests = post_block_withdrawal_requests_contract_call::( + let withdrawal_requests = post_block_withdrawal_requests_contract_call( + &evm_config, &mut db, &initialized_cfg, &initialized_block_env, diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 325eb7e29607..08c5db82f647 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -122,6 +122,7 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { /// Fill transaction environment with a system contract call. fn fill_tx_env_system_contract_call( + &self, env: &mut Env, caller: Address, contract: Address, @@ -130,6 +131,7 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { /// Fill [`CfgEnvWithHandlerCfg`] fields according to the chain spec and given header fn fill_cfg_env( + &self, cfg_env: &mut CfgEnvWithHandlerCfg, chain_spec: &ChainSpec, header: &Header, @@ -145,11 +147,12 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { after_merge: bool, ) { let coinbase = block_coinbase(chain_spec, header, after_merge); - Self::fill_block_env_with_coinbase(block_env, header, after_merge, coinbase); + self.fill_block_env_with_coinbase(block_env, header, after_merge, coinbase); } /// Fill block environment with coinbase. fn fill_block_env_with_coinbase( + &self, block_env: &mut BlockEnv, header: &Header, after_merge: bool, @@ -177,15 +180,16 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { /// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and /// [`ConfigureEvmEnv::fill_block_env`]. fn fill_cfg_and_block_env( + &self, cfg: &mut CfgEnvWithHandlerCfg, block_env: &mut BlockEnv, chain_spec: &ChainSpec, header: &Header, total_difficulty: U256, ) { - Self::fill_cfg_env(cfg, chain_spec, header, total_difficulty); + self.fill_cfg_env(cfg, chain_spec, header, total_difficulty); let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE; - Self::fill_block_env_with_coinbase( + self.fill_block_env_with_coinbase( block_env, header, after_merge, diff --git a/crates/evm/src/system_calls.rs b/crates/evm/src/system_calls.rs index e9a5518a8568..bba763f18bb2 100644 --- a/crates/evm/src/system_calls.rs +++ b/crates/evm/src/system_calls.rs @@ -24,7 +24,7 @@ use revm_primitives::{ #[allow(clippy::too_many_arguments)] pub fn pre_block_beacon_root_contract_call( db: &mut DB, - _emv_config: EvmConfig, + evm_config: &EvmConfig, chain_spec: &ChainSpec, initialized_cfg: &CfgEnvWithHandlerCfg, initialized_block_env: &BlockEnv, @@ -48,7 +48,8 @@ where .build(); // initialize a block from the env, because the pre block call needs the block itself - apply_beacon_root_contract_call::( + apply_beacon_root_contract_call( + evm_config, chain_spec, block_timestamp, block_number, @@ -66,6 +67,7 @@ where /// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788 #[inline] pub fn apply_beacon_root_contract_call( + evm_config: &EvmConfig, chain_spec: &ChainSpec, block_timestamp: u64, block_number: u64, @@ -100,7 +102,7 @@ where let previous_env = Box::new(evm.context.env().clone()); // modify env for pre block call - EvmConfig::fill_tx_env_system_contract_call( + evm_config.fill_tx_env_system_contract_call( &mut evm.context.evm.env, alloy_eips::eip4788::SYSTEM_ADDRESS, BEACON_ROOTS_ADDRESS, @@ -138,6 +140,7 @@ where /// This uses [`apply_withdrawal_requests_contract_call`] to ultimately calculate the /// [requests](Request). pub fn post_block_withdrawal_requests_contract_call( + evm_config: &EvmConfig, db: &mut DB, initialized_cfg: &CfgEnvWithHandlerCfg, initialized_block_env: &BlockEnv, @@ -158,7 +161,7 @@ where .build(); // initialize a block from the env, because the post block call needs the block itself - apply_withdrawal_requests_contract_call::(&mut evm_post_block) + apply_withdrawal_requests_contract_call::(evm_config, &mut evm_post_block) } /// Applies the post-block call to the EIP-7002 withdrawal requests contract. @@ -167,6 +170,7 @@ where /// returned. Otherwise, the withdrawal requests are returned. #[inline] pub fn apply_withdrawal_requests_contract_call( + evm_config: &EvmConfig, evm: &mut Evm<'_, EXT, DB>, ) -> Result, BlockExecutionError> where @@ -185,7 +189,7 @@ where // At the end of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. // after processing all transactions and after performing the block body withdrawal requests // validations), call the contract as `SYSTEM_ADDRESS`. - EvmConfig::fill_tx_env_system_contract_call( + evm_config.fill_tx_env_system_contract_call( &mut evm.context.evm.env, alloy_eips::eip7002::SYSTEM_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 2c797c78626e..bc937090bc77 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -122,7 +122,8 @@ where DB: Database + std::fmt::Display>, { // apply pre execution changes - apply_beacon_root_contract_call::( + apply_beacon_root_contract_call( + &self.evm_config, &self.chain_spec, block.timestamp, block.number, @@ -271,7 +272,7 @@ where fn evm_env_for_block(&self, header: &Header, total_difficulty: U256) -> EnvWithHandlerCfg { let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); let mut block_env = BlockEnv::default(); - EvmConfig::fill_cfg_and_block_env( + self.executor.evm_config.fill_cfg_and_block_env( &mut cfg, &mut block_env, self.chain_spec(), diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 61f6838ddbd8..f8e2d52ff679 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -40,6 +40,7 @@ impl ConfigureEvmEnv for OptimismEvmConfig { } fn fill_tx_env_system_contract_call( + &self, env: &mut Env, caller: Address, contract: Address, @@ -83,6 +84,7 @@ impl ConfigureEvmEnv for OptimismEvmConfig { } fn fill_cfg_env( + &self, cfg_env: &mut CfgEnvWithHandlerCfg, chain_spec: &ChainSpec, header: &Header, @@ -143,7 +145,7 @@ mod tests { let chain_spec = ChainSpec::default(); let total_difficulty = U256::ZERO; - OptimismEvmConfig::fill_cfg_and_block_env( + OptimismEvmConfig::default().fill_cfg_and_block_env( &mut cfg_env, &mut block_env, &chain_spec, diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 4df6c67d246d..5622bb695d6b 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -125,7 +125,7 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, - self.evm_config.clone(), + &self.evm_config, &chain_spec, &initialized_cfg, &initialized_block_env, @@ -288,7 +288,7 @@ where // apply eip-4788 pre block contract call pre_block_beacon_root_contract_call( &mut db, - evm_config.clone(), + &evm_config, &chain_spec, &initialized_cfg, &initialized_block_env, diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index b421d7b10b51..5f90c6e29376 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -235,7 +235,7 @@ pub trait LoadPendingBlock { // parent beacon block root pre_block_beacon_root_contract_call( &mut db, - self.evm_config().clone(), + self.evm_config(), chain_spec.as_ref(), &cfg, &block_env, diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 287575046bea..83d9da3c648f 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -2040,7 +2040,7 @@ impl EvmEnvProvider for DatabaseProvider { cfg: &mut CfgEnvWithHandlerCfg, block_env: &mut BlockEnv, header: &Header, - _evm_config: EvmConfig, + evm_config: EvmConfig, ) -> ProviderResult<()> where EvmConfig: ConfigureEvmEnv, @@ -2048,7 +2048,7 @@ impl EvmEnvProvider for DatabaseProvider { let total_difficulty = self .header_td_by_number(header.number)? .ok_or_else(|| ProviderError::HeaderNotFound(header.number.into()))?; - EvmConfig::fill_cfg_and_block_env( + evm_config.fill_cfg_and_block_env( cfg, block_env, &self.chain_spec, @@ -2076,7 +2076,7 @@ impl EvmEnvProvider for DatabaseProvider { &self, cfg: &mut CfgEnvWithHandlerCfg, header: &Header, - _evm_config: EvmConfig, + evm_config: EvmConfig, ) -> ProviderResult<()> where EvmConfig: ConfigureEvmEnv, @@ -2084,7 +2084,7 @@ impl EvmEnvProvider for DatabaseProvider { let total_difficulty = self .header_td_by_number(header.number)? .ok_or_else(|| ProviderError::HeaderNotFound(header.number.into()))?; - EvmConfig::fill_cfg_env(cfg, &self.chain_spec, header, total_difficulty); + evm_config.fill_cfg_env(cfg, &self.chain_spec, header, total_difficulty); Ok(()) } } diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index a9d8058b9193..207640dce9c9 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -68,6 +68,7 @@ impl MyEvmConfig { impl ConfigureEvmEnv for MyEvmConfig { fn fill_cfg_env( + &self, cfg_env: &mut CfgEnvWithHandlerCfg, chain_spec: &ChainSpec, header: &Header, @@ -95,12 +96,13 @@ impl ConfigureEvmEnv for MyEvmConfig { } fn fill_tx_env_system_contract_call( + &self, env: &mut Env, caller: Address, contract: Address, data: Bytes, ) { - EthEvmConfig::fill_tx_env_system_contract_call(env, caller, contract, data) + EthEvmConfig::default().fill_tx_env_system_contract_call(env, caller, contract, data) } } diff --git a/examples/exex/rollup/src/execution.rs b/examples/exex/rollup/src/execution.rs index 2746553872c9..22ec582923bd 100644 --- a/examples/exex/rollup/src/execution.rs +++ b/examples/exex/rollup/src/execution.rs @@ -107,13 +107,7 @@ fn configure_evm<'a>( ); let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(evm.cfg().clone(), evm.spec_id()); - EthEvmConfig::fill_cfg_and_block_env( - &mut cfg, - evm.block_mut(), - &CHAIN_SPEC, - header, - U256::ZERO, - ); + config.fill_cfg_and_block_env(&mut cfg, evm.block_mut(), &CHAIN_SPEC, header, U256::ZERO); *evm.cfg_mut() = cfg.cfg_env; evm diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index 038a18c4b5a6..b595647e0952 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -143,21 +143,23 @@ impl ConfigureEvmEnv for MyEvmConfig { } fn fill_cfg_env( + &self, cfg_env: &mut CfgEnvWithHandlerCfg, chain_spec: &ChainSpec, header: &Header, total_difficulty: U256, ) { - EthEvmConfig::fill_cfg_env(cfg_env, chain_spec, header, total_difficulty) + EthEvmConfig::default().fill_cfg_env(cfg_env, chain_spec, header, total_difficulty) } fn fill_tx_env_system_contract_call( + &self, env: &mut Env, caller: Address, contract: Address, data: Bytes, ) { - EthEvmConfig::fill_tx_env_system_contract_call(env, caller, contract, data) + EthEvmConfig::default().fill_tx_env_system_contract_call(env, caller, contract, data) } } From 26b7b9720c3179c791e4378d28ee619816256117 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 5 Jul 2024 03:38:58 -0700 Subject: [PATCH 350/405] qol: purge goerli (#9310) --- book/cli/reth.md | 2 +- book/cli/reth/config.md | 2 +- book/cli/reth/db.md | 2 +- book/cli/reth/debug.md | 2 +- book/cli/reth/dump-genesis.md | 2 +- book/cli/reth/import.md | 2 +- book/cli/reth/init-state.md | 2 +- book/cli/reth/init.md | 2 +- book/cli/reth/node.md | 2 +- book/cli/reth/p2p.md | 2 +- book/cli/reth/prune.md | 2 +- book/cli/reth/recover.md | 2 +- book/cli/reth/recover/storage-tries.md | 2 +- book/cli/reth/stage.md | 2 +- book/cli/reth/stage/drop.md | 2 +- book/cli/reth/stage/dump.md | 2 +- book/cli/reth/stage/run.md | 2 +- book/cli/reth/stage/unwind.md | 2 +- book/cli/reth/test-vectors.md | 2 +- book/cli/reth/test-vectors/tables.md | 2 +- crates/chainspec/src/lib.rs | 10 +- crates/chainspec/src/spec.rs | 143 +----------------- crates/consensus/common/src/calc.rs | 6 +- .../ethereum-forks/src/hardfork/ethereum.rs | 23 --- crates/ethereum/consensus/src/lib.rs | 9 +- crates/evm/src/lib.rs | 33 +--- crates/net/peers/src/bootnodes/ethereum.rs | 16 -- crates/net/peers/src/bootnodes/mod.rs | 5 - crates/node/core/src/args/utils.rs | 6 +- crates/node/core/src/dirs.rs | 8 +- crates/primitives-traits/src/constants/mod.rs | 4 - crates/primitives/Cargo.toml | 5 +- crates/primitives/src/header.rs | 72 +-------- crates/primitives/src/lib.rs | 4 +- crates/primitives/src/proofs.rs | 10 +- crates/rpc/rpc-eth-api/src/helpers/state.rs | 11 +- crates/storage/db-common/src/init.rs | 15 +- 37 files changed, 52 insertions(+), 368 deletions(-) diff --git a/book/cli/reth.md b/book/cli/reth.md index b8ac550816d0..0a761e5089cf 100644 --- a/book/cli/reth.md +++ b/book/cli/reth.md @@ -28,7 +28,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/config.md b/book/cli/reth/config.md index 1b2a89c665de..df0d261b07b1 100644 --- a/book/cli/reth/config.md +++ b/book/cli/reth/config.md @@ -18,7 +18,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/db.md b/book/cli/reth/db.md index b884b7d0f0b3..b867134a9d33 100644 --- a/book/cli/reth/db.md +++ b/book/cli/reth/db.md @@ -56,7 +56,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/debug.md b/book/cli/reth/debug.md index 2779b8d770ac..d61094834d39 100644 --- a/book/cli/reth/debug.md +++ b/book/cli/reth/debug.md @@ -20,7 +20,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/dump-genesis.md b/book/cli/reth/dump-genesis.md index 5add92402137..7197be305f26 100644 --- a/book/cli/reth/dump-genesis.md +++ b/book/cli/reth/dump-genesis.md @@ -12,7 +12,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/import.md b/book/cli/reth/import.md index 5a139e348cf4..29a67f181764 100644 --- a/book/cli/reth/import.md +++ b/book/cli/reth/import.md @@ -44,7 +44,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/init-state.md b/book/cli/reth/init-state.md index b1802b253a06..d947baec376d 100644 --- a/book/cli/reth/init-state.md +++ b/book/cli/reth/init-state.md @@ -44,7 +44,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/init.md b/book/cli/reth/init.md index 8fe3fe018c0d..5eb9d4d03ba4 100644 --- a/book/cli/reth/init.md +++ b/book/cli/reth/init.md @@ -44,7 +44,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/node.md b/book/cli/reth/node.md index c27d7251c492..fe96358e5f77 100644 --- a/book/cli/reth/node.md +++ b/book/cli/reth/node.md @@ -15,7 +15,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/p2p.md b/book/cli/reth/p2p.md index ada874d8bfa6..0177244a3a73 100644 --- a/book/cli/reth/p2p.md +++ b/book/cli/reth/p2p.md @@ -20,7 +20,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/prune.md b/book/cli/reth/prune.md index 77ea724abd88..0b3e701f6b30 100644 --- a/book/cli/reth/prune.md +++ b/book/cli/reth/prune.md @@ -44,7 +44,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/recover.md b/book/cli/reth/recover.md index 9ffd8eb70f57..4fe28211db0b 100644 --- a/book/cli/reth/recover.md +++ b/book/cli/reth/recover.md @@ -16,7 +16,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/recover/storage-tries.md b/book/cli/reth/recover/storage-tries.md index 649580382b11..d5df358a711d 100644 --- a/book/cli/reth/recover/storage-tries.md +++ b/book/cli/reth/recover/storage-tries.md @@ -44,7 +44,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/stage.md b/book/cli/reth/stage.md index 17a888b6ecde..c9ff302c1aa0 100644 --- a/book/cli/reth/stage.md +++ b/book/cli/reth/stage.md @@ -19,7 +19,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/stage/drop.md b/book/cli/reth/stage/drop.md index dc2f1330bb05..b700519e1a87 100644 --- a/book/cli/reth/stage/drop.md +++ b/book/cli/reth/stage/drop.md @@ -44,7 +44,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/stage/dump.md b/book/cli/reth/stage/dump.md index f08b9ffd81c6..a5fd3052c0b6 100644 --- a/book/cli/reth/stage/dump.md +++ b/book/cli/reth/stage/dump.md @@ -51,7 +51,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/stage/run.md b/book/cli/reth/stage/run.md index a98a2be6dab6..4fa8e0a38b23 100644 --- a/book/cli/reth/stage/run.md +++ b/book/cli/reth/stage/run.md @@ -44,7 +44,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/stage/unwind.md b/book/cli/reth/stage/unwind.md index 3af76e1d567e..b9765bd8db18 100644 --- a/book/cli/reth/stage/unwind.md +++ b/book/cli/reth/stage/unwind.md @@ -49,7 +49,7 @@ Datadir: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/test-vectors.md b/book/cli/reth/test-vectors.md index da1b3c933f62..844c5ed8455a 100644 --- a/book/cli/reth/test-vectors.md +++ b/book/cli/reth/test-vectors.md @@ -16,7 +16,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/book/cli/reth/test-vectors/tables.md b/book/cli/reth/test-vectors/tables.md index 3b8f52f2c211..2a3023817b35 100644 --- a/book/cli/reth/test-vectors/tables.md +++ b/book/cli/reth/test-vectors/tables.md @@ -16,7 +16,7 @@ Options: Possible values are either a built-in chain or the path to a chain specification file. Built-in chains: - mainnet, sepolia, goerli, holesky, dev + mainnet, sepolia, holesky, dev [default: mainnet] diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 162f281b6a4a..17f766f5b0fd 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -13,7 +13,7 @@ pub use alloy_chains::{Chain, ChainKind, NamedChain}; pub use info::ChainInfo; pub use spec::{ BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, - ForkBaseFeeParams, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, + ForkBaseFeeParams, DEV, HOLESKY, MAINNET, SEPOLIA, }; #[cfg(feature = "optimism")] pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; @@ -50,8 +50,8 @@ mod tests { #[test] fn test_named_id() { - let chain = Chain::from_named(NamedChain::Goerli); - assert_eq!(chain.id(), 5); + let chain = Chain::from_named(NamedChain::Holesky); + assert_eq!(chain.id(), 17000); } #[test] @@ -77,9 +77,9 @@ mod tests { #[test] fn test_into_u256() { - let chain = Chain::from_named(NamedChain::Goerli); + let chain = Chain::from_named(NamedChain::Holesky); let n: U256 = U256::from(chain.id()); - let expected = U256::from(5); + let expected = U256::from(17000); assert_eq!(n, expected); } diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 9f3b83f440d8..89fce23f7c30 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -29,8 +29,8 @@ pub use alloy_eips::eip1559::BaseFeeParams; #[cfg(feature = "optimism")] use reth_ethereum_forks::OptimismHardfork; use reth_network_peers::{ - base_nodes, base_testnet_nodes, goerli_nodes, holesky_nodes, mainnet_nodes, op_nodes, - op_testnet_nodes, sepolia_nodes, + base_nodes, base_testnet_nodes, holesky_nodes, mainnet_nodes, op_nodes, op_testnet_nodes, + sepolia_nodes, }; /// The Ethereum mainnet spec @@ -60,30 +60,6 @@ pub static MAINNET: Lazy> = Lazy::new(|| { .into() }); -/// The Goerli spec -pub static GOERLI: Lazy> = Lazy::new(|| { - ChainSpec { - chain: Chain::goerli(), - genesis: serde_json::from_str(include_str!("../res/genesis/goerli.json")) - .expect("Can't deserialize Goerli genesis json"), - genesis_hash: Some(b256!( - "bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" - )), - // - paris_block_and_final_difficulty: Some((7382818, U256::from(10_790_000))), - hardforks: EthereumHardfork::goerli().into(), - // https://goerli.etherscan.io/tx/0xa3c07dc59bfdb1bfc2d50920fed2ef2c1c4e0a09fe2325dbc14e07702f965a78 - deposit_contract: Some(DepositContract::new( - address!("ff50ed3d0ec03ac01d4c79aad74928bff48a7b2b"), - 4367322, - b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), - )), - base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), - prune_delete_limit: 1700, - } - .into() -}); - /// The Sepolia spec pub static SEPOLIA: Lazy> = Lazy::new(|| { ChainSpec { @@ -709,7 +685,6 @@ impl ChainSpec { let chain = self.chain; match chain.try_into().ok()? { C::Mainnet => Some(mainnet_nodes()), - C::Goerli => Some(goerli_nodes()), C::Sepolia => Some(sepolia_nodes()), C::Holesky => Some(holesky_nodes()), C::Base => Some(base_nodes()), @@ -1443,63 +1418,6 @@ Post-merge hard forks (timestamp based): ); } - #[test] - fn goerli_hardfork_fork_ids() { - test_hardfork_fork_ids( - &GOERLI, - &[ - ( - EthereumHardfork::Frontier, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - EthereumHardfork::Homestead, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - EthereumHardfork::Tangerine, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - EthereumHardfork::SpuriousDragon, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - EthereumHardfork::Byzantium, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - EthereumHardfork::Constantinople, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - EthereumHardfork::Petersburg, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - EthereumHardfork::Istanbul, - ForkId { hash: ForkHash([0xc2, 0x5e, 0xfa, 0x5c]), next: 4460644 }, - ), - ( - EthereumHardfork::Berlin, - ForkId { hash: ForkHash([0x75, 0x7a, 0x1c, 0x47]), next: 5062605 }, - ), - ( - EthereumHardfork::London, - ForkId { hash: ForkHash([0xb8, 0xc6, 0x29, 0x9d]), next: 1678832736 }, - ), - ( - EthereumHardfork::Shanghai, - ForkId { hash: ForkHash([0xf9, 0x84, 0x3a, 0xbf]), next: 1705473120 }, - ), - ( - EthereumHardfork::Cancun, - ForkId { hash: ForkHash([0x70, 0xcc, 0x14, 0xe2]), next: 0 }, - ), - ], - ); - } - #[test] fn sepolia_hardfork_fork_ids() { test_hardfork_fork_ids( @@ -1675,63 +1593,6 @@ Post-merge hard forks (timestamp based): ) } - #[test] - fn goerli_forkids() { - test_fork_ids( - &GOERLI, - &[ - ( - Head { number: 0, ..Default::default() }, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - Head { number: 1561650, ..Default::default() }, - ForkId { hash: ForkHash([0xa3, 0xf5, 0xab, 0x08]), next: 1561651 }, - ), - ( - Head { number: 1561651, ..Default::default() }, - ForkId { hash: ForkHash([0xc2, 0x5e, 0xfa, 0x5c]), next: 4460644 }, - ), - ( - Head { number: 4460643, ..Default::default() }, - ForkId { hash: ForkHash([0xc2, 0x5e, 0xfa, 0x5c]), next: 4460644 }, - ), - ( - Head { number: 4460644, ..Default::default() }, - ForkId { hash: ForkHash([0x75, 0x7a, 0x1c, 0x47]), next: 5062605 }, - ), - ( - Head { number: 5062605, ..Default::default() }, - ForkId { hash: ForkHash([0xb8, 0xc6, 0x29, 0x9d]), next: 1678832736 }, - ), - ( - Head { number: 6000000, timestamp: 1678832735, ..Default::default() }, - ForkId { hash: ForkHash([0xb8, 0xc6, 0x29, 0x9d]), next: 1678832736 }, - ), - // First Shanghai block - ( - Head { number: 6000001, timestamp: 1678832736, ..Default::default() }, - ForkId { hash: ForkHash([0xf9, 0x84, 0x3a, 0xbf]), next: 1705473120 }, - ), - // Future Shanghai block - ( - Head { number: 6500002, timestamp: 1678832736, ..Default::default() }, - ForkId { hash: ForkHash([0xf9, 0x84, 0x3a, 0xbf]), next: 1705473120 }, - ), - // First Cancun block - ( - Head { number: 6500003, timestamp: 1705473120, ..Default::default() }, - ForkId { hash: ForkHash([0x70, 0xcc, 0x14, 0xe2]), next: 0 }, - ), - // Future Cancun block - ( - Head { number: 6500003, timestamp: 2705473120, ..Default::default() }, - ForkId { hash: ForkHash([0x70, 0xcc, 0x14, 0xe2]), next: 0 }, - ), - ], - ); - } - #[test] fn sepolia_forkids() { test_fork_ids( diff --git a/crates/consensus/common/src/calc.rs b/crates/consensus/common/src/calc.rs index feb7bff0d908..e4b2abc13ba6 100644 --- a/crates/consensus/common/src/calc.rs +++ b/crates/consensus/common/src/calc.rs @@ -1,4 +1,4 @@ -use reth_chainspec::{Chain, ChainSpec, EthereumHardfork}; +use reth_chainspec::{ChainSpec, EthereumHardfork}; use reth_primitives::{constants::ETH_TO_WEI, BlockNumber, U256}; /// Calculates the base block reward. @@ -26,9 +26,7 @@ pub fn base_block_reward( block_difficulty: U256, total_difficulty: U256, ) -> Option { - if chain_spec.fork(EthereumHardfork::Paris).active_at_ttd(total_difficulty, block_difficulty) || - chain_spec.chain == Chain::goerli() - { + if chain_spec.fork(EthereumHardfork::Paris).active_at_ttd(total_difficulty, block_difficulty) { None } else { Some(base_block_reward_pre_merge(chain_spec, block_number)) diff --git a/crates/ethereum-forks/src/hardfork/ethereum.rs b/crates/ethereum-forks/src/hardfork/ethereum.rs index 9e2a8a111216..7a2618c3c70f 100644 --- a/crates/ethereum-forks/src/hardfork/ethereum.rs +++ b/crates/ethereum-forks/src/hardfork/ethereum.rs @@ -358,29 +358,6 @@ impl EthereumHardfork { ] } - /// Ethereum goerli list of hardforks. - pub const fn goerli() -> [(Self, ForkCondition); 14] { - [ - (Self::Frontier, ForkCondition::Block(0)), - (Self::Homestead, ForkCondition::Block(0)), - (Self::Dao, ForkCondition::Block(0)), - (Self::Tangerine, ForkCondition::Block(0)), - (Self::SpuriousDragon, ForkCondition::Block(0)), - (Self::Byzantium, ForkCondition::Block(0)), - (Self::Constantinople, ForkCondition::Block(0)), - (Self::Petersburg, ForkCondition::Block(0)), - (Self::Istanbul, ForkCondition::Block(1561651)), - (Self::Berlin, ForkCondition::Block(4460644)), - (Self::London, ForkCondition::Block(5062605)), - ( - Self::Paris, - ForkCondition::TTD { fork_block: None, total_difficulty: uint!(10_790_000_U256) }, - ), - (Self::Shanghai, ForkCondition::Timestamp(1678832736)), - (Self::Cancun, ForkCondition::Timestamp(1705473120)), - ] - } - /// Ethereum sepolia list of hardforks. pub const fn sepolia() -> [(Self, ForkCondition); 15] { [ diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 2027fd539c1f..09d9a6636ebe 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -8,7 +8,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use reth_chainspec::{Chain, ChainSpec, EthereumHardfork, EthereumHardforks}; +use reth_chainspec::{ChainSpec, EthereumHardfork, EthereumHardforks}; use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ validate_4844_header_standalone, validate_against_parent_4844, @@ -198,10 +198,9 @@ impl Consensus for EthBeaconConsensus { }) } - // Goerli and early OP exception: - // * If the network is goerli pre-merge, ignore the extradata check, since we do not - // support clique. Same goes for OP blocks below Bedrock. - if self.chain_spec.chain != Chain::goerli() && !self.chain_spec.is_optimism() { + // Early OP exception: + // * If the network is pre-Bedrock OP, ignore the extradata check. + if !self.chain_spec.is_optimism() { validate_header_extradata(header)?; } } diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 08c5db82f647..445d9625f80f 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -15,9 +15,7 @@ extern crate alloc; use core::ops::Deref; use reth_chainspec::ChainSpec; -use reth_primitives::{ - header::block_coinbase, Address, Header, TransactionSigned, TransactionSignedEcRecovered, U256, -}; +use reth_primitives::{Address, Header, TransactionSigned, TransactionSignedEcRecovered, U256}; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; use revm_primitives::{ BlockEnv, Bytes, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg, SpecId, TxEnv, @@ -139,27 +137,9 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { ); /// Fill [`BlockEnv`] field according to the chain spec and given header - fn fill_block_env( - &self, - block_env: &mut BlockEnv, - chain_spec: &ChainSpec, - header: &Header, - after_merge: bool, - ) { - let coinbase = block_coinbase(chain_spec, header, after_merge); - self.fill_block_env_with_coinbase(block_env, header, after_merge, coinbase); - } - - /// Fill block environment with coinbase. - fn fill_block_env_with_coinbase( - &self, - block_env: &mut BlockEnv, - header: &Header, - after_merge: bool, - coinbase: Address, - ) { + fn fill_block_env(&self, block_env: &mut BlockEnv, header: &Header, after_merge: bool) { block_env.number = U256::from(header.number); - block_env.coinbase = coinbase; + block_env.coinbase = header.beneficiary; block_env.timestamp = U256::from(header.timestamp); if after_merge { block_env.prevrandao = Some(header.mix_hash); @@ -189,11 +169,6 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { ) { self.fill_cfg_env(cfg, chain_spec, header, total_difficulty); let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE; - self.fill_block_env_with_coinbase( - block_env, - header, - after_merge, - block_coinbase(chain_spec, header, after_merge), - ); + self.fill_block_env(block_env, header, after_merge); } } diff --git a/crates/net/peers/src/bootnodes/ethereum.rs b/crates/net/peers/src/bootnodes/ethereum.rs index ba77bb701fcd..9cb6aac00e1f 100644 --- a/crates/net/peers/src/bootnodes/ethereum.rs +++ b/crates/net/peers/src/bootnodes/ethereum.rs @@ -17,22 +17,6 @@ pub static SEPOLIA_BOOTNODES : [&str; 5] = [ "enode://9e9492e2e8836114cc75f5b929784f4f46c324ad01daf87d956f98b3b6c5fcba95524d6e5cf9861dc96a2c8a171ea7105bb554a197455058de185fa870970c7c@138.68.123.152:30303", // sepolia-bootnode-1-ams3 ]; -/// Görli Bootnodes -pub static GOERLI_BOOTNODES : [&str; 7] = [ - // Upstream bootnodes - "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", - "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", - "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", - "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", - - // Ethereum Foundation bootnode - "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", - - // Goerli Initiative bootnodes - "enode://d4f764a48ec2a8ecf883735776fdefe0a3949eb0ca476bd7bc8d0954a9defe8fea15ae5da7d40b5d2d59ce9524a99daedadf6da6283fca492cc80b53689fb3b3@46.4.99.122:32109", - "enode://d2b720352e8216c9efc470091aa91ddafc53e222b32780f505c817ceef69e01d5b0b0797b69db254c586f493872352f5a022b4d8479a00fc92ec55f9ad46a27e@88.99.70.182:30303", -]; - /// Ethereum Foundation Holesky Bootnodes pub static HOLESKY_BOOTNODES : [&str; 2] = [ "enode://ac906289e4b7f12df423d654c5a962b6ebe5b3a74cc9e06292a85221f9a64a6f1cfdd6b714ed6dacef51578f92b34c60ee91e9ede9c7f8fadc4d347326d95e2b@146.190.13.128:30303", diff --git a/crates/net/peers/src/bootnodes/mod.rs b/crates/net/peers/src/bootnodes/mod.rs index ecd6de3103a6..31c91e5d1cea 100644 --- a/crates/net/peers/src/bootnodes/mod.rs +++ b/crates/net/peers/src/bootnodes/mod.rs @@ -13,11 +13,6 @@ pub fn mainnet_nodes() -> Vec { parse_nodes(&MAINNET_BOOTNODES[..]) } -/// Returns parsed goerli nodes -pub fn goerli_nodes() -> Vec { - parse_nodes(&GOERLI_BOOTNODES[..]) -} - /// Returns parsed sepolia nodes pub fn sepolia_nodes() -> Vec { parse_nodes(&SEPOLIA_BOOTNODES[..]) diff --git a/crates/node/core/src/args/utils.rs b/crates/node/core/src/args/utils.rs index c80626f884cf..064505ccb862 100644 --- a/crates/node/core/src/args/utils.rs +++ b/crates/node/core/src/args/utils.rs @@ -11,14 +11,14 @@ use reth_chainspec::DEV; use reth_chainspec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA}; #[cfg(not(feature = "optimism"))] -use reth_chainspec::{GOERLI, HOLESKY, MAINNET, SEPOLIA}; +use reth_chainspec::{HOLESKY, MAINNET, SEPOLIA}; #[cfg(feature = "optimism")] /// Chains supported by op-reth. First value should be used as the default. pub const SUPPORTED_CHAINS: &[&str] = &["optimism", "optimism-sepolia", "base", "base-sepolia"]; #[cfg(not(feature = "optimism"))] /// Chains supported by reth. First value should be used as the default. -pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "sepolia", "goerli", "holesky", "dev"]; +pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "sepolia", "holesky", "dev"]; /// The help info for the --chain flag pub fn chain_help() -> String { @@ -34,8 +34,6 @@ pub fn chain_value_parser(s: &str) -> eyre::Result, eyre::Error> #[cfg(not(feature = "optimism"))] "mainnet" => MAINNET.clone(), #[cfg(not(feature = "optimism"))] - "goerli" => GOERLI.clone(), - #[cfg(not(feature = "optimism"))] "sepolia" => SEPOLIA.clone(), #[cfg(not(feature = "optimism"))] "holesky" => HOLESKY.clone(), diff --git a/crates/node/core/src/dirs.rs b/crates/node/core/src/dirs.rs index 08f93b472a99..a43350c2890c 100644 --- a/crates/node/core/src/dirs.rs +++ b/crates/node/core/src/dirs.rs @@ -259,10 +259,10 @@ impl From for MaybePlatformPath { /// Wrapper type around `PlatformPath` that includes a `Chain`, used for separating reth data for /// different networks. /// -/// If the chain is either mainnet, goerli, or sepolia, then the path will be: +/// If the chain is either mainnet, sepolia, or holesky, then the path will be: /// * mainnet: `/mainnet` -/// * goerli: `/goerli` /// * sepolia: `/sepolia` +/// * holesky: `/holesky` /// /// Otherwise, the path will be dependent on the chain ID: /// * `/` @@ -383,10 +383,6 @@ mod tests { #[test] fn test_maybe_testnet_datadir_path() { - let path = MaybePlatformPath::::default(); - let path = path.unwrap_or_chain_default(Chain::goerli(), DatadirArgs::default()); - assert!(path.as_ref().ends_with("reth/goerli"), "{path:?}"); - let path = MaybePlatformPath::::default(); let path = path.unwrap_or_chain_default(Chain::holesky(), DatadirArgs::default()); assert!(path.as_ref().ends_with("reth/holesky"), "{path:?}"); diff --git a/crates/primitives-traits/src/constants/mod.rs b/crates/primitives-traits/src/constants/mod.rs index 492c402fa50c..34e286a3e1dd 100644 --- a/crates/primitives-traits/src/constants/mod.rs +++ b/crates/primitives-traits/src/constants/mod.rs @@ -104,10 +104,6 @@ pub const ETH_TO_WEI: u128 = FINNEY_TO_WEI * 1000; pub const MAINNET_GENESIS_HASH: B256 = b256!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"); -/// Goerli genesis hash: `0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a` -pub const GOERLI_GENESIS_HASH: B256 = - b256!("bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"); - /// Sepolia genesis hash: `0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9` pub const SEPOLIA_GENESIS_HASH: B256 = b256!("25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9"); diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 1bd31171c1ed..1e37184ab85a 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -18,8 +18,8 @@ reth-codecs.workspace = true reth-ethereum-forks.workspace = true reth-static-file-types.workspace = true reth-trie-common.workspace = true -reth-chainspec.workspace = true revm-primitives = { workspace = true, features = ["serde"] } +reth-chainspec = { workspace = true, optional = true } # ethereum alloy-primitives = { workspace = true, features = ["rand", "rlp"] } @@ -57,6 +57,7 @@ proptest = { workspace = true, optional = true } # eth reth-primitives-traits = { workspace = true, features = ["arbitrary"] } revm-primitives = { workspace = true, features = ["arbitrary"] } +reth-chainspec.workspace = true nybbles = { workspace = true, features = ["arbitrary"] } alloy-trie = { workspace = true, features = ["arbitrary"] } alloy-eips = { workspace = true, features = ["arbitrary"] } @@ -88,7 +89,7 @@ asm-keccak = ["alloy-primitives/asm-keccak"] arbitrary = [ "reth-primitives-traits/arbitrary", "revm-primitives/arbitrary", - "reth-chainspec/arbitrary", + "reth-chainspec?/arbitrary", "reth-ethereum-forks/arbitrary", "nybbles/arbitrary", "alloy-trie/arbitrary", diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index b3d9095ff07b..5642bac7c772 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -1,71 +1,12 @@ //! Header types. -use crate::{recover_signer_unchecked, Address, Bytes}; use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; -use reth_chainspec::{Chain, ChainSpec}; use reth_codecs::derive_arbitrary; use serde::{Deserialize, Serialize}; pub use reth_primitives_traits::{Header, HeaderError, SealedHeader}; -/// Return the coinbase address for the given header and chain spec. -pub fn block_coinbase(chain_spec: &ChainSpec, header: &Header, after_merge: bool) -> Address { - // Clique consensus fills the EXTRA_SEAL (last 65 bytes) of the extra data with the - // signer's signature. - // - // On the genesis block, the extra data is filled with zeros, so we should not attempt to - // recover the signer on the genesis block. - // - // From EIP-225: - // - // * `EXTRA_SEAL`: Fixed number of extra-data suffix bytes reserved for signer seal. - // * 65 bytes fixed as signatures are based on the standard `secp256k1` curve. - // * Filled with zeros on genesis block. - if chain_spec.chain == Chain::goerli() && !after_merge && header.number > 0 { - recover_header_signer(header).unwrap_or_else(|err| { - panic!( - "Failed to recover goerli Clique Consensus signer from header ({}, {}) using extradata {}: {:?}", - header.number, header.hash_slow(), header.extra_data, err - ) - }) - } else { - header.beneficiary - } -} - -/// Error type for recovering Clique signer from a header. -#[derive(Debug, thiserror_no_std::Error)] -pub enum CliqueSignerRecoveryError { - /// Header extradata is too short. - #[error("Invalid extra data length")] - InvalidExtraData, - /// Recovery failed. - #[error("Invalid signature: {0}")] - InvalidSignature(#[from] secp256k1::Error), -} - -/// Recover the account from signed header per clique consensus rules. -pub fn recover_header_signer(header: &Header) -> Result { - let extra_data_len = header.extra_data.len(); - // Fixed number of extra-data suffix bytes reserved for signer signature. - // 65 bytes fixed as signatures are based on the standard secp256k1 curve. - // Filled with zeros on genesis block. - let signature_start_byte = extra_data_len - 65; - let signature: [u8; 65] = header.extra_data[signature_start_byte..] - .try_into() - .map_err(|_| CliqueSignerRecoveryError::InvalidExtraData)?; - let seal_hash = { - let mut header_to_seal = header.clone(); - header_to_seal.extra_data = Bytes::from(header.extra_data[..signature_start_byte].to_vec()); - header_to_seal.hash_slow() - }; - - // TODO: this is currently unchecked recovery, does this need to be checked w.r.t EIP-2? - recover_signer_unchecked(&signature, &seal_hash.0) - .map_err(CliqueSignerRecoveryError::InvalidSignature) -} - /// Represents the direction for a headers request depending on the `reverse` field of the request. /// > The response must contain a number of block headers, of rising number when reverse is 0, /// > falling when 1 @@ -147,9 +88,7 @@ impl From for bool { #[cfg(test)] mod tests { use super::*; - use crate::{ - address, b256, bloom, bytes, hex, Address, Bytes, Header, HeadersDirection, B256, U256, - }; + use crate::{address, b256, bloom, bytes, hex, Address, Bytes, B256, U256}; use alloy_rlp::{Decodable, Encodable}; use std::str::FromStr; @@ -413,13 +352,4 @@ mod tests { Header::decode(&mut data.as_slice()) .expect_err("blob_gas_used size should make this header decoding fail"); } - - #[test] - fn test_recover_genesis_goerli_signer() { - // just ensures that `block_coinbase` does not panic on the genesis block - let chain_spec = reth_chainspec::GOERLI.clone(); - let header = chain_spec.genesis_header(); - let block_coinbase = block_coinbase(&chain_spec, &header, false); - assert_eq!(block_coinbase, header.beneficiary); - } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 5d3def5c3e58..666a84361b3f 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -43,8 +43,8 @@ pub use block::{ #[cfg(feature = "zstd-codec")] pub use compression::*; pub use constants::{ - DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH, - KECCAK_EMPTY, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, + DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, HOLESKY_GENESIS_HASH, KECCAK_EMPTY, + MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, }; pub use genesis::{ChainConfig, Genesis, GenesisAccount}; pub use header::{Header, HeadersDirection, SealedHeader}; diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index 9e0a0357905f..ab57be8ffd1d 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -140,7 +140,7 @@ mod tests { use alloy_genesis::GenesisAccount; use alloy_primitives::{b256, Address, LogData}; use alloy_rlp::Decodable; - use reth_chainspec::{GOERLI, HOLESKY, MAINNET, SEPOLIA}; + use reth_chainspec::{HOLESKY, MAINNET, SEPOLIA}; use reth_trie_common::root::{state_root_ref_unhashed, state_root_unhashed}; use std::collections::HashMap; @@ -535,14 +535,6 @@ mod tests { "mainnet state root mismatch" ); - let expected_goerli_state_root = - b256!("5d6cded585e73c4e322c30c2f782a336316f17dd85a4863b9d838d2d4b8b3008"); - let calculated_goerli_state_root = state_root_ref_unhashed(&GOERLI.genesis.alloc); - assert_eq!( - expected_goerli_state_root, calculated_goerli_state_root, - "goerli state root mismatch" - ); - let expected_sepolia_state_root = b256!("5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494"); let calculated_sepolia_state_root = state_root_ref_unhashed(&SEPOLIA.genesis.alloc); diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 50678a877678..107d4a1c0c6a 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -4,9 +4,7 @@ use futures::Future; use reth_evm::ConfigureEvmEnv; use reth_primitives::{Address, BlockId, Bytes, Header, B256, U256}; -use reth_provider::{ - BlockIdReader, ChainSpecProvider, StateProvider, StateProviderBox, StateProviderFactory, -}; +use reth_provider::{BlockIdReader, StateProvider, StateProviderBox, StateProviderFactory}; use reth_rpc_eth_types::{ EthApiError, EthResult, EthStateCache, PendingBlockEnv, RpcInvalidTransactionError, }; @@ -207,12 +205,7 @@ pub trait LoadState { let (cfg, mut block_env, _) = self.evm_env_at(header.parent_hash.into()).await?; let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE; - self.evm_config().fill_block_env( - &mut block_env, - &LoadPendingBlock::provider(self).chain_spec(), - header, - after_merge, - ); + self.evm_config().fill_block_env(&mut block_env, header, after_merge); Ok((cfg, block_env)) } diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 452507163add..e4e9ad75b187 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -521,7 +521,7 @@ struct GenesisAccountWithAddress { mod tests { use super::*; use alloy_genesis::Genesis; - use reth_chainspec::{Chain, GOERLI, HOLESKY, MAINNET, SEPOLIA}; + use reth_chainspec::{Chain, HOLESKY, MAINNET, SEPOLIA}; use reth_db::DatabaseEnv; use reth_db_api::{ cursor::DbCursorRO, @@ -529,9 +529,7 @@ mod tests { table::{Table, TableRow}, transaction::DbTx, }; - use reth_primitives::{ - GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, - }; + use reth_primitives::{HOLESKY_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH}; use reth_primitives_traits::IntegerList; use reth_provider::test_utils::create_test_provider_factory_with_chain_spec; @@ -554,15 +552,6 @@ mod tests { assert_eq!(genesis_hash, MAINNET_GENESIS_HASH); } - #[test] - fn success_init_genesis_goerli() { - let genesis_hash = - init_genesis(create_test_provider_factory_with_chain_spec(GOERLI.clone())).unwrap(); - - // actual, expected - assert_eq!(genesis_hash, GOERLI_GENESIS_HASH); - } - #[test] fn success_init_genesis_sepolia() { let genesis_hash = From 61b8ff1dc56f332e718ca10390a4c32041d087db Mon Sep 17 00:00:00 2001 From: kostekIV <27210860+kostekIV@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:41:58 +0200 Subject: [PATCH 351/405] Remove fullprovider trait restriction in backfill impls (#9326) --- crates/exex/exex/Cargo.toml | 2 +- crates/exex/exex/src/backfill.rs | 50 ++++++++++++-------------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/crates/exex/exex/Cargo.toml b/crates/exex/exex/Cargo.toml index e2a364988bad..ec86deb6f4b6 100644 --- a/crates/exex/exex/Cargo.toml +++ b/crates/exex/exex/Cargo.toml @@ -29,7 +29,6 @@ reth-evm.workspace = true reth-prune-types.workspace = true reth-revm.workspace = true reth-stages-api.workspace = true -reth-db-api.workspace = true ## async tokio.workspace = true @@ -48,6 +47,7 @@ reth-blockchain-tree.workspace = true reth-db-common.workspace = true reth-node-api.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } +reth-db-api.workspace = true secp256k1.workspace = true diff --git a/crates/exex/exex/src/backfill.rs b/crates/exex/exex/src/backfill.rs index 0b5895643d7b..36f00573437c 100644 --- a/crates/exex/exex/src/backfill.rs +++ b/crates/exex/exex/src/backfill.rs @@ -1,17 +1,17 @@ -use reth_db_api::database::Database; use reth_evm::execute::{ BatchExecutor, BlockExecutionError, BlockExecutionOutput, BlockExecutorProvider, Executor, }; use reth_node_api::FullNodeComponents; use reth_primitives::{Block, BlockNumber, BlockWithSenders, Receipt}; use reth_primitives_traits::format_gas_throughput; -use reth_provider::{Chain, FullProvider, ProviderError, TransactionVariant}; +use reth_provider::{ + BlockReader, Chain, HeaderProvider, ProviderError, StateProviderFactory, TransactionVariant, +}; use reth_prune_types::PruneModes; use reth_revm::database::StateProviderDatabase; use reth_stages_api::ExecutionStageThresholds; use reth_tracing::tracing::{debug, trace}; use std::{ - marker::PhantomData, ops::RangeInclusive, time::{Duration, Instant}, }; @@ -51,14 +51,13 @@ impl BackfillJobFactory { impl BackfillJobFactory { /// Creates a new backfill job for the given range. - pub fn backfill(&self, range: RangeInclusive) -> BackfillJob { + pub fn backfill(&self, range: RangeInclusive) -> BackfillJob { BackfillJob { executor: self.executor.clone(), provider: self.provider.clone(), prune_modes: self.prune_modes.clone(), range, thresholds: self.thresholds.clone(), - _db: PhantomData, } } } @@ -80,20 +79,18 @@ impl BackfillJobFactory<(), ()> { /// It implements [`Iterator`] that executes blocks in batches according to the provided thresholds /// and yields [`Chain`] #[derive(Debug)] -pub struct BackfillJob { +pub struct BackfillJob { executor: E, provider: P, prune_modes: PruneModes, thresholds: ExecutionStageThresholds, range: RangeInclusive, - _db: PhantomData, } -impl Iterator for BackfillJob +impl Iterator for BackfillJob where E: BlockExecutorProvider, - DB: Database, - P: FullProvider, + P: HeaderProvider + BlockReader + StateProviderFactory, { type Item = Result; @@ -106,11 +103,10 @@ where } } -impl BackfillJob +impl BackfillJob where E: BlockExecutorProvider, - DB: Database, - P: FullProvider, + P: BlockReader + HeaderProvider + StateProviderFactory, { fn execute_range(&mut self) -> Result { let mut executor = self.executor.batch_executor(StateProviderDatabase::new( @@ -197,21 +193,16 @@ where } } -impl BackfillJob { +impl BackfillJob { /// Converts the backfill job into a single block backfill job. - pub fn into_single_blocks(self) -> SingleBlockBackfillJob { + pub fn into_single_blocks(self) -> SingleBlockBackfillJob { self.into() } } -impl From> for SingleBlockBackfillJob { - fn from(value: BackfillJob) -> Self { - Self { - executor: value.executor, - provider: value.provider, - range: value.range, - _db: PhantomData, - } +impl From> for SingleBlockBackfillJob { + fn from(value: BackfillJob) -> Self { + Self { executor: value.executor, provider: value.provider, range: value.range } } } @@ -220,18 +211,16 @@ impl From> for SingleBlockBackfillJob /// It implements [`Iterator`] which executes a block each time the /// iterator is advanced and yields ([`BlockWithSenders`], [`BlockExecutionOutput`]) #[derive(Debug)] -pub struct SingleBlockBackfillJob { +pub struct SingleBlockBackfillJob { executor: E, provider: P, range: RangeInclusive, - _db: PhantomData, } -impl Iterator for SingleBlockBackfillJob +impl Iterator for SingleBlockBackfillJob where E: BlockExecutorProvider, - DB: Database, - P: FullProvider, + P: HeaderProvider + BlockReader + StateProviderFactory, { type Item = Result<(BlockWithSenders, BlockExecutionOutput), BlockExecutionError>; @@ -240,11 +229,10 @@ where } } -impl SingleBlockBackfillJob +impl SingleBlockBackfillJob where E: BlockExecutorProvider, - DB: Database, - P: FullProvider, + P: HeaderProvider + BlockReader + StateProviderFactory, { fn execute_block( &self, From 36d74400e692c5570fc17f6c9da0295608d6a0e4 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 5 Jul 2024 03:45:08 -0700 Subject: [PATCH 352/405] feat(trie): pass state reference to `StateProofProvider::proof` (#9308) --- crates/engine/tree/src/tree/memory_overlay.rs | 7 ++++++- crates/revm/src/test_utils.rs | 7 ++++++- crates/rpc/rpc-eth-api/src/helpers/state.rs | 3 ++- crates/rpc/rpc-eth-types/src/cache/db.rs | 3 ++- .../provider/src/providers/bundle_state_provider.rs | 7 ++++++- .../provider/src/providers/state/historical.rs | 7 ++++++- .../storage/provider/src/providers/state/latest.rs | 13 +++++++++---- .../storage/provider/src/providers/state/macros.rs | 2 +- crates/storage/provider/src/test_utils/mock.rs | 7 ++++++- crates/storage/provider/src/test_utils/noop.rs | 7 ++++++- crates/storage/storage-api/src/trie.rs | 10 ++++++++-- 11 files changed, 58 insertions(+), 15 deletions(-) diff --git a/crates/engine/tree/src/tree/memory_overlay.rs b/crates/engine/tree/src/tree/memory_overlay.rs index 06bfc4186383..f11eece8e7d0 100644 --- a/crates/engine/tree/src/tree/memory_overlay.rs +++ b/crates/engine/tree/src/tree/memory_overlay.rs @@ -95,7 +95,12 @@ impl StateProofProvider for MemoryOverlayStateProvider where H: StateProofProvider + Send, { - fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult { + fn proof( + &self, + state: &BundleState, + address: Address, + slots: &[B256], + ) -> ProviderResult { todo!() } } diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index e3cf0cedd6cf..0459cf679ec0 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -79,7 +79,12 @@ impl StateRootProvider for StateProviderTest { } impl StateProofProvider for StateProviderTest { - fn proof(&self, _address: Address, _slots: &[B256]) -> ProviderResult { + fn proof( + &self, + _state: &BundleState, + _address: Address, + _slots: &[B256], + ) -> ProviderResult { unimplemented!("proof generation is not supported") } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 107d4a1c0c6a..05af8f547acf 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -11,6 +11,7 @@ use reth_rpc_eth_types::{ use reth_rpc_types::{serde_helpers::JsonStorageKey, EIP1186AccountProofResponse}; use reth_rpc_types_compat::proof::from_primitive_account_proof; use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use revm::db::BundleState; use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, SpecId}; use super::{EthApiSpec, LoadPendingBlock, SpawnBlocking}; @@ -104,7 +105,7 @@ pub trait EthState: LoadState + SpawnBlocking { Ok(self.spawn_blocking_io(move |this| { let state = this.state_at_block_id(block_id)?; let storage_keys = keys.iter().map(|key| key.0).collect::>(); - let proof = state.proof(address, &storage_keys)?; + let proof = state.proof(&BundleState::default(), address, &storage_keys)?; Ok(from_primitive_account_proof(proof)) })) } diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index 82713147c1ba..2bd93daf748b 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -34,10 +34,11 @@ impl<'a> reth_provider::StateRootProvider for StateProviderTraitObjWrapper<'a> { impl<'a> reth_provider::StateProofProvider for StateProviderTraitObjWrapper<'a> { fn proof( &self, + state: &revm::db::BundleState, address: revm_primitives::Address, slots: &[B256], ) -> reth_errors::ProviderResult { - self.0.proof(address, slots) + self.0.proof(state, address, slots) } } diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index 19dbff3862ac..09a6de864763 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -84,7 +84,12 @@ impl StateRootProvider impl StateProofProvider for BundleStateProvider { - fn proof(&self, _address: Address, _slots: &[B256]) -> ProviderResult { + fn proof( + &self, + _bundle: &BundleState, + _address: Address, + _slots: &[B256], + ) -> ProviderResult { Err(ProviderError::StateRootNotAvailableForHistoricalBlock) } } diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index eaf134beba17..87de4102fd10 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -274,7 +274,12 @@ impl<'b, TX: DbTx> StateRootProvider for HistoricalStateProviderRef<'b, TX> { impl<'b, TX: DbTx> StateProofProvider for HistoricalStateProviderRef<'b, TX> { /// Get account and storage proofs. - fn proof(&self, _address: Address, _slots: &[B256]) -> ProviderResult { + fn proof( + &self, + _state: &BundleState, + _address: Address, + _slots: &[B256], + ) -> ProviderResult { Err(ProviderError::StateRootNotAvailableForHistoricalBlock) } } diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index a1e8256cf17c..bfc2f16ad500 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -12,7 +12,7 @@ use reth_primitives::{ }; use reth_storage_api::StateProofProvider; use reth_storage_errors::provider::{ProviderError, ProviderResult}; -use reth_trie::{proof::Proof, updates::TrieUpdates, AccountProof, HashedPostState}; +use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState}; use revm::db::BundleState; /// State provider over latest state that takes tx reference. @@ -92,9 +92,14 @@ impl<'b, TX: DbTx> StateRootProvider for LatestStateProviderRef<'b, TX> { } impl<'b, TX: DbTx> StateProofProvider for LatestStateProviderRef<'b, TX> { - fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult { - Ok(Proof::from_tx(self.tx) - .account_proof(address, slots) + fn proof( + &self, + bundle_state: &BundleState, + address: Address, + slots: &[B256], + ) -> ProviderResult { + Ok(HashedPostState::from_bundle_state(&bundle_state.state) + .account_proof(self.tx, address, slots) .map_err(Into::::into)?) } } diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index 2b4638894e2e..344a21101f43 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -46,7 +46,7 @@ macro_rules! delegate_provider_impls { fn state_root_with_updates(&self, state: &revm::db::BundleState) -> reth_storage_errors::provider::ProviderResult<(reth_primitives::B256, reth_trie::updates::TrieUpdates)>; } StateProofProvider $(where [$($generics)*])? { - fn proof(&self, address: reth_primitives::Address, slots: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult; + fn proof(&self, state: &revm::db::BundleState, address: reth_primitives::Address, slots: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult; } ); } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 75543462d341..8a9916d09c34 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -555,7 +555,12 @@ impl StateRootProvider for MockEthProvider { } impl StateProofProvider for MockEthProvider { - fn proof(&self, address: Address, _slots: &[B256]) -> ProviderResult { + fn proof( + &self, + _state: &BundleState, + address: Address, + _slots: &[B256], + ) -> ProviderResult { Ok(AccountProof::new(address)) } } diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index fcfdb826152f..445d5666ac61 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -328,7 +328,12 @@ impl StateRootProvider for NoopProvider { } impl StateProofProvider for NoopProvider { - fn proof(&self, address: Address, _slots: &[B256]) -> ProviderResult { + fn proof( + &self, + _state: &BundleState, + address: Address, + _slots: &[B256], + ) -> ProviderResult { Ok(AccountProof::new(address)) } } diff --git a/crates/storage/storage-api/src/trie.rs b/crates/storage/storage-api/src/trie.rs index e833234c89e4..0ab25d18ad8d 100644 --- a/crates/storage/storage-api/src/trie.rs +++ b/crates/storage/storage-api/src/trie.rs @@ -26,6 +26,12 @@ pub trait StateRootProvider: Send + Sync { /// A type that can generate state proof on top of a given post state. #[auto_impl::auto_impl(&, Box, Arc)] pub trait StateProofProvider: Send + Sync { - /// Get account and storage proofs. - fn proof(&self, address: Address, slots: &[B256]) -> ProviderResult; + /// Get account and storage proofs of target keys in the `BundleState` + /// on top of the current state. + fn proof( + &self, + state: &BundleState, + address: Address, + slots: &[B256], + ) -> ProviderResult; } From 3b29ed74c004fb365a4abc13cf1746111faa7bec Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 5 Jul 2024 13:05:23 +0200 Subject: [PATCH 353/405] feat: op eth api scaffolding (#9324) --- Cargo.lock | 10 ++++ crates/optimism/rpc/Cargo.toml | 14 ++++++ crates/optimism/rpc/src/eth/mod.rs | 56 +++++++++++++++++++++++ crates/optimism/rpc/src/lib.rs | 4 +- crates/rpc/rpc-eth-api/src/helpers/mod.rs | 2 + 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 crates/optimism/rpc/src/eth/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 76b82e90f821..e17cbbe1062d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7962,6 +7962,16 @@ version = "1.0.0" [[package]] name = "reth-optimism-rpc" version = "1.0.0" +dependencies = [ + "alloy-primitives", + "reth-chainspec", + "reth-errors", + "reth-rpc", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-rpc-types", +] [[package]] name = "reth-payload-builder" diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index f6645519e65a..9853e8f914b5 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -8,3 +8,17 @@ homepage.workspace = true repository.workspace = true [lints] +workspace = true + +[dependencies] +# reth +reth-rpc.workspace = true +reth-rpc-eth-api.workspace = true +reth-rpc-eth-types.workspace = true +reth-rpc-server-types.workspace = true +reth-rpc-types.workspace = true +reth-errors.workspace = true +reth-chainspec.workspace = true + +# ethereum +alloy-primitives.workspace = true \ No newline at end of file diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs new file mode 100644 index 000000000000..b9836360ad64 --- /dev/null +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -0,0 +1,56 @@ +//! OP-Reth `eth_` endpoint implementation. + +use alloy_primitives::{Address, U64}; +use reth_chainspec::ChainInfo; +use reth_errors::RethResult; +use reth_rpc_eth_api::helpers::EthApiSpec; +use reth_rpc_types::SyncStatus; +use std::future::Future; + +/// OP-Reth `Eth` API implementation. +/// +/// This type provides the functionality for handling `eth_` related requests. +/// +/// This wraps a default `Eth` implementation, and provides additional functionality where the +/// optimism spec deviates from the default (ethereum) spec, e.g. transaction forwarding to the +/// sequencer, receipts, additional RPC fields for transaction receipts. +/// +/// This type implements the [`FullEthApi`](reth_rpc_eth_api::helpers::FullEthApi) by implemented +/// all the `Eth` helper traits and prerequisite traits. +#[derive(Debug, Clone)] +pub struct OpEthApi { + inner: Eth, +} + +impl OpEthApi { + /// Creates a new `OpEthApi` from the provided `Eth` implementation. + pub const fn new(inner: Eth) -> Self { + Self { inner } + } +} + +impl EthApiSpec for OpEthApi { + fn protocol_version(&self) -> impl Future> + Send { + self.inner.protocol_version() + } + + fn chain_id(&self) -> U64 { + self.inner.chain_id() + } + + fn chain_info(&self) -> RethResult { + self.inner.chain_info() + } + + fn accounts(&self) -> Vec
{ + self.inner.accounts() + } + + fn is_syncing(&self) -> bool { + self.inner.is_syncing() + } + + fn sync_status(&self) -> RethResult { + self.inner.sync_status() + } +} diff --git a/crates/optimism/rpc/src/lib.rs b/crates/optimism/rpc/src/lib.rs index f263793af102..f2059fac16bd 100644 --- a/crates/optimism/rpc/src/lib.rs +++ b/crates/optimism/rpc/src/lib.rs @@ -5,5 +5,7 @@ html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] +// #![cfg_attr(not(test), warn(unused_crate_dependencies))] TODO: enable #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub mod eth; diff --git a/crates/rpc/rpc-eth-api/src/helpers/mod.rs b/crates/rpc/rpc-eth-api/src/helpers/mod.rs index d7d31320ce0b..72e49077efb8 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/mod.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/mod.rs @@ -47,6 +47,8 @@ pub trait TraceExt: impl TraceExt for T where T: LoadTransaction + LoadBlock + LoadPendingBlock + Trace + Call {} /// Helper trait to unify all `eth` rpc server building block traits, for simplicity. +/// +/// This trait is automatically implemented for any type that implements all the `Eth` traits. pub trait FullEthApi: EthApiSpec + EthTransactions + EthBlocks + EthState + EthCall + EthFees + Trace + LoadReceipt { From c13af1e6d1b354da8e9058a6027b5238ad3107be Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 5 Jul 2024 04:43:24 -0700 Subject: [PATCH 354/405] feat: implement `HistoricalStateProviderRef::proof` (#9327) --- crates/storage/errors/src/provider.rs | 3 --- .../provider/src/providers/bundle_state_provider.rs | 12 +++++++----- .../provider/src/providers/state/historical.rs | 12 ++++++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/storage/errors/src/provider.rs b/crates/storage/errors/src/provider.rs index 52a010474f96..db59d671fef7 100644 --- a/crates/storage/errors/src/provider.rs +++ b/crates/storage/errors/src/provider.rs @@ -96,9 +96,6 @@ pub enum ProviderError { /// Thrown when we were unable to find a state for a block hash. #[error("no state found for block {0}")] StateForHashNotFound(B256), - /// Unable to compute state root on top of historical block. - #[error("unable to compute state root on top of historical block")] - StateRootNotAvailableForHistoricalBlock, /// Unable to find the block number for a given transaction index. #[error("unable to find the block number for a given transaction index")] BlockNumberForTransactionIndexNotFound, diff --git a/crates/storage/provider/src/providers/bundle_state_provider.rs b/crates/storage/provider/src/providers/bundle_state_provider.rs index 09a6de864763..6e09ff389987 100644 --- a/crates/storage/provider/src/providers/bundle_state_provider.rs +++ b/crates/storage/provider/src/providers/bundle_state_provider.rs @@ -3,7 +3,7 @@ use crate::{ }; use reth_primitives::{Account, Address, BlockNumber, Bytecode, B256}; use reth_storage_api::StateProofProvider; -use reth_storage_errors::provider::{ProviderError, ProviderResult}; +use reth_storage_errors::provider::ProviderResult; use reth_trie::{updates::TrieUpdates, AccountProof}; use revm::db::BundleState; @@ -86,11 +86,13 @@ impl StateProofProvider { fn proof( &self, - _bundle: &BundleState, - _address: Address, - _slots: &[B256], + bundle_state: &BundleState, + address: Address, + slots: &[B256], ) -> ProviderResult { - Err(ProviderError::StateRootNotAvailableForHistoricalBlock) + let mut state = self.block_execution_data_provider.execution_outcome().state().clone(); + state.extend(bundle_state.clone()); + self.state_provider.proof(&state, address, slots) } } diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 87de4102fd10..c65c6ddc17ff 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -276,11 +276,15 @@ impl<'b, TX: DbTx> StateProofProvider for HistoricalStateProviderRef<'b, TX> { /// Get account and storage proofs. fn proof( &self, - _state: &BundleState, - _address: Address, - _slots: &[B256], + state: &BundleState, + address: Address, + slots: &[B256], ) -> ProviderResult { - Err(ProviderError::StateRootNotAvailableForHistoricalBlock) + let mut revert_state = self.revert_state()?; + revert_state.extend(HashedPostState::from_bundle_state(&state.state)); + revert_state + .account_proof(self.tx, address, slots) + .map_err(|err| ProviderError::Database(err.into())) } } From 08feab1a566c662db2950be227e90d31a34a15e1 Mon Sep 17 00:00:00 2001 From: Park Smith <161195644+sdfii@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:20:52 +0100 Subject: [PATCH 355/405] feat: log throughput in execution stage (#9253) Co-authored-by: Matthias Seitz --- .../src/constants/gas_units.rs | 12 +++++----- crates/stages/stages/src/stages/execution.rs | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/primitives-traits/src/constants/gas_units.rs b/crates/primitives-traits/src/constants/gas_units.rs index 0495a1755ef6..11caab5139be 100644 --- a/crates/primitives-traits/src/constants/gas_units.rs +++ b/crates/primitives-traits/src/constants/gas_units.rs @@ -18,11 +18,11 @@ pub const GIGAGAS: u64 = MEGAGAS * 1_000; pub fn format_gas_throughput(gas: u64, execution_duration: Duration) -> String { let gas_per_second = gas as f64 / execution_duration.as_secs_f64(); if gas_per_second < MEGAGAS as f64 { - format!("{:.} Kgas/second", gas_per_second / KILOGAS as f64) + format!("{:.2} Kgas/second", gas_per_second / KILOGAS as f64) } else if gas_per_second < GIGAGAS as f64 { - format!("{:.} Mgas/second", gas_per_second / MEGAGAS as f64) + format!("{:.2} Mgas/second", gas_per_second / MEGAGAS as f64) } else { - format!("{:.} Ggas/second", gas_per_second / GIGAGAS as f64) + format!("{:.2} Ggas/second", gas_per_second / GIGAGAS as f64) } } @@ -67,14 +67,14 @@ mod tests { let duration = Duration::from_secs(1); let gas = 100_000; let throughput = format_gas_throughput(gas, duration); - assert_eq!(throughput, "100 Kgas/second"); + assert_eq!(throughput, "100.00 Kgas/second"); let gas = 100_000_000; let throughput = format_gas_throughput(gas, duration); - assert_eq!(throughput, "100 Mgas/second"); + assert_eq!(throughput, "100.00 Mgas/second"); let gas = 100_000_000_000; let throughput = format_gas_throughput(gas, duration); - assert_eq!(throughput, "100 Ggas/second"); + assert_eq!(throughput, "100.00 Ggas/second"); } } diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index a2ee50a606d6..d3dcdd1bb2ee 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -233,6 +233,13 @@ where let mut fetch_block_duration = Duration::default(); let mut execution_duration = Duration::default(); + + let mut last_block = start_block; + let mut last_execution_duration = Duration::default(); + let mut last_cumulative_gas = 0; + let mut last_log_instant = Instant::now(); + let log_duration = Duration::from_secs(10); + debug!(target: "sync::stages::execution", start = start_block, end = max_block, "Executing range"); // Execute block range @@ -271,6 +278,22 @@ where })?; execution_duration += execute_start.elapsed(); + // Log execution throughput + if last_log_instant.elapsed() >= log_duration { + info!( + target: "sync::stages::execution", + start = last_block, + end = block_number, + throughput = format_gas_throughput(cumulative_gas - last_cumulative_gas, execution_duration - last_execution_duration), + "Executed block range" + ); + + last_block = block_number + 1; + last_execution_duration = execution_duration; + last_cumulative_gas = cumulative_gas; + last_log_instant = Instant::now(); + } + // Gas metrics if let Some(metrics_tx) = &mut self.metrics_tx { let _ = From 239b98451433097b413199230e00df549585d2ac Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:20:49 +0200 Subject: [PATCH 356/405] chore: use `*_GENESIS_HASH` constants on ethereum chainspecs (#9328) --- crates/chainspec/src/spec.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 89fce23f7c30..d8c52839fa87 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -13,7 +13,10 @@ use reth_ethereum_forks::{ }; use reth_network_peers::NodeRecord; use reth_primitives_traits::{ - constants::{EIP1559_INITIAL_BASE_FEE, EMPTY_WITHDRAWALS}, + constants::{ + DEV_GENESIS_HASH, EIP1559_INITIAL_BASE_FEE, EMPTY_WITHDRAWALS, HOLESKY_GENESIS_HASH, + MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, + }, Header, SealedHeader, }; use reth_trie_common::root::state_root_ref_unhashed; @@ -39,9 +42,7 @@ pub static MAINNET: Lazy> = Lazy::new(|| { chain: Chain::mainnet(), genesis: serde_json::from_str(include_str!("../res/genesis/mainnet.json")) .expect("Can't deserialize Mainnet genesis json"), - genesis_hash: Some(b256!( - "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - )), + genesis_hash: Some(MAINNET_GENESIS_HASH), // paris_block_and_final_difficulty: Some(( 15537394, @@ -66,9 +67,7 @@ pub static SEPOLIA: Lazy> = Lazy::new(|| { chain: Chain::sepolia(), genesis: serde_json::from_str(include_str!("../res/genesis/sepolia.json")) .expect("Can't deserialize Sepolia genesis json"), - genesis_hash: Some(b256!( - "25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9" - )), + genesis_hash: Some(SEPOLIA_GENESIS_HASH), // paris_block_and_final_difficulty: Some((1450409, U256::from(17_000_018_015_853_232u128))), hardforks: EthereumHardfork::sepolia().into(), @@ -90,9 +89,7 @@ pub static HOLESKY: Lazy> = Lazy::new(|| { chain: Chain::holesky(), genesis: serde_json::from_str(include_str!("../res/genesis/holesky.json")) .expect("Can't deserialize Holesky genesis json"), - genesis_hash: Some(b256!( - "b5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4" - )), + genesis_hash: Some(HOLESKY_GENESIS_HASH), paris_block_and_final_difficulty: Some((0, U256::from(1))), hardforks: EthereumHardfork::holesky().into(), deposit_contract: Some(DepositContract::new( @@ -115,9 +112,7 @@ pub static DEV: Lazy> = Lazy::new(|| { chain: Chain::dev(), genesis: serde_json::from_str(include_str!("../res/genesis/dev.json")) .expect("Can't deserialize Dev testnet genesis json"), - genesis_hash: Some(b256!( - "2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c" - )), + genesis_hash: Some(DEV_GENESIS_HASH), paris_block_and_final_difficulty: Some((0, U256::from(0))), hardforks: DEV_HARDFORKS.clone(), base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), From a41c2169742f92c316cf36f657eec3a95452bd65 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:43:46 +0200 Subject: [PATCH 357/405] chore(ci): improve `hive` workflow (#9320) --- .github/assets/hive/build_simulators.sh | 38 +++++++++++ .github/assets/hive/expected_failures.yaml | 8 --- .github/assets/hive/load_images.sh | 25 +++++++ .github/assets/hive/no_sim_build.diff | 53 +++++++++++++++ .github/assets/hive/run_simulator.sh | 38 +++++++++++ .github/workflows/hive.yml | 78 ++++++++++------------ 6 files changed, 188 insertions(+), 52 deletions(-) create mode 100755 .github/assets/hive/build_simulators.sh create mode 100755 .github/assets/hive/load_images.sh create mode 100644 .github/assets/hive/no_sim_build.diff create mode 100755 .github/assets/hive/run_simulator.sh diff --git a/.github/assets/hive/build_simulators.sh b/.github/assets/hive/build_simulators.sh new file mode 100755 index 000000000000..45583d549a37 --- /dev/null +++ b/.github/assets/hive/build_simulators.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Create the hive_assets directory +mkdir hive_assets/ + +cd hivetests +go build . + +./hive -client reth # first builds and caches the client + +# Run each hive command in the background for each simulator and wait +echo "Building images" +./hive -client reth --sim "pyspec" -sim.timelimit 1s || true & +./hive -client reth --sim "ethereum/engine" -sim.timelimit 1s || true & +./hive -client reth --sim "devp2p" -sim.timelimit 1s || true & +./hive -client reth --sim "ethereum/rpc-compat" -sim.timelimit 1s || true & +./hive -client reth --sim "smoke/genesis" -sim.timelimit 1s || true & +./hive -client reth --sim "smoke/network" -sim.timelimit 1s || true & +./hive -client reth --sim "ethereum/sync" -sim.timelimit 1s || true & +wait + +# Run docker save in parallel and wait +echo "Saving images" +docker save hive/hiveproxy:latest -o ../hive_assets/hiveproxy.tar & +docker save hive/simulators/devp2p:latest -o ../hive_assets/devp2p.tar & +docker save hive/simulators/ethereum/engine:latest -o ../hive_assets/engine.tar & +docker save hive/simulators/ethereum/rpc-compat:latest -o ../hive_assets/rpc_compat.tar & +docker save hive/simulators/ethereum/pyspec:latest -o ../hive_assets/pyspec.tar & +docker save hive/simulators/smoke/genesis:latest -o ../hive_assets/smoke_genesis.tar & +docker save hive/simulators/smoke/network:latest -o ../hive_assets/smoke_network.tar & +docker save hive/simulators/ethereum/sync:latest -o ../hive_assets/ethereum_sync.tar & +wait + +# Make sure we don't rebuild images on the CI jobs +git apply ../.github/assets/hive/no_sim_build.diff +go build . +mv ./hive ../hive_assets/ \ No newline at end of file diff --git a/.github/assets/hive/expected_failures.yaml b/.github/assets/hive/expected_failures.yaml index 831cb966fb64..ba29ca5e7097 100644 --- a/.github/assets/hive/expected_failures.yaml +++ b/.github/assets/hive/expected_failures.yaml @@ -36,14 +36,6 @@ engine-withdrawals: - Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org Sync (Paris) (reth) - Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org (Paris) (reth) - Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org Sync (Paris) (reth) - - # https://github.com/paradigmxyz/reth/issues/8304#issuecomment-2208515839 - - Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account - No Transactions (Paris) (reth) - - Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account (Paris) (reth) - - Sync after 2 blocks - Withdrawals on Genesis - Single Withdrawal Account (Paris) (reth) - - Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts - No Transactions (Paris) (reth) - - Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts (Paris) (reth) - - Sync after 128 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts (Paris) (reth) # https://github.com/paradigmxyz/reth/issues/8305 # https://github.com/paradigmxyz/reth/issues/6217 diff --git a/.github/assets/hive/load_images.sh b/.github/assets/hive/load_images.sh new file mode 100755 index 000000000000..05e1cb9905fa --- /dev/null +++ b/.github/assets/hive/load_images.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -eo pipefail + +# List of tar files to load +IMAGES=( + "/tmp/hiveproxy.tar" + "/tmp/devp2p.tar" + "/tmp/engine.tar" + "/tmp/rpc_compat.tar" + "/tmp/pyspec.tar" + "/tmp/smoke_genesis.tar" + "/tmp/smoke_network.tar" + "/tmp/ethereum_sync.tar" + "/tmp/reth_image.tar" +) + +# Loop through the images and load them +for IMAGE_TAR in "${IMAGES[@]}"; do + echo "Loading image $IMAGE_TAR..." + docker load -i "$IMAGE_TAR" & +done + +wait + +docker image ls -a \ No newline at end of file diff --git a/.github/assets/hive/no_sim_build.diff b/.github/assets/hive/no_sim_build.diff new file mode 100644 index 000000000000..0b109efe7cd0 --- /dev/null +++ b/.github/assets/hive/no_sim_build.diff @@ -0,0 +1,53 @@ +diff --git a/internal/libdocker/builder.go b/internal/libdocker/builder.go +index 4731c9d..d717f52 100644 +--- a/internal/libdocker/builder.go ++++ b/internal/libdocker/builder.go +@@ -7,9 +7,7 @@ import ( + "fmt" + "io" + "io/fs" +- "os" + "path/filepath" +- "strings" + + "github.com/ethereum/hive/internal/libhive" + docker "github.com/fsouza/go-dockerclient" +@@ -53,24 +51,8 @@ func (b *Builder) BuildClientImage(ctx context.Context, client libhive.ClientDes + + // BuildSimulatorImage builds a docker image of a simulator. + func (b *Builder) BuildSimulatorImage(ctx context.Context, name string) (string, error) { +- dir := b.config.Inventory.SimulatorDirectory(name) +- buildContextPath := dir +- buildDockerfile := "Dockerfile" +- // build context dir of simulator can be overridden with "hive_context.txt" file containing the desired build path +- if contextPathBytes, err := os.ReadFile(filepath.Join(filepath.FromSlash(dir), "hive_context.txt")); err == nil { +- buildContextPath = filepath.Join(dir, strings.TrimSpace(string(contextPathBytes))) +- if strings.HasPrefix(buildContextPath, "../") { +- return "", fmt.Errorf("cannot access build directory outside of Hive root: %q", buildContextPath) +- } +- if p, err := filepath.Rel(buildContextPath, filepath.Join(filepath.FromSlash(dir), "Dockerfile")); err != nil { +- return "", fmt.Errorf("failed to derive relative simulator Dockerfile path: %v", err) +- } else { +- buildDockerfile = p +- } +- } + tag := fmt.Sprintf("hive/simulators/%s:latest", name) +- err := b.buildImage(ctx, buildContextPath, buildDockerfile, tag, nil) +- return tag, err ++ return tag, nil + } + + // BuildImage creates a container by archiving the given file system, +diff --git a/internal/libdocker/proxy.go b/internal/libdocker/proxy.go +index a53e5af..0bb2ea9 100644 +--- a/internal/libdocker/proxy.go ++++ b/internal/libdocker/proxy.go +@@ -16,7 +16,7 @@ const hiveproxyTag = "hive/hiveproxy" + + // Build builds the hiveproxy image. + func (cb *ContainerBackend) Build(ctx context.Context, b libhive.Builder) error { +- return b.BuildImage(ctx, hiveproxyTag, hiveproxy.Source) ++ return nil + } + + // ServeAPI starts the API server. diff --git a/.github/assets/hive/run_simulator.sh b/.github/assets/hive/run_simulator.sh new file mode 100755 index 000000000000..018077bdca38 --- /dev/null +++ b/.github/assets/hive/run_simulator.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# set -x + +cd hivetests/ + +sim="${1}" +limit="${2}" + +run_hive() { + hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 4 --client reth 2>&1 | tee /tmp/log || true +} + +check_log() { + tail -n 1 /tmp/log | sed -r 's/\x1B\[[0-9;]*[mK]//g' +} + +attempt=0 +max_attempts=5 + +while [ $attempt -lt $max_attempts ]; do + run_hive + + # Check if no tests were run. sed removes ansi colors + if check_log | grep -q "suites=0"; then + echo "no tests were run, retrying in 5 seconds" + sleep 5 + attempt=$((attempt + 1)) + continue + fi + + # Check the last line of the log for "finished", "tests failed", or "test failed" + if check_log | grep -Eq "(finished|tests? failed)"; then + exit 0 + else + exit 1 + fi +done +exit 1 \ No newline at end of file diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index d18ffd65ff91..65063dd018d4 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -16,7 +16,7 @@ concurrency: cancel-in-progress: true jobs: - prepare: + prepare-reth: if: github.repository == 'paradigmxyz/reth' timeout-minutes: 45 runs-on: @@ -44,6 +44,19 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max + - name: Upload reth image + uses: actions/upload-artifact@v4 + with: + name: artifacts + path: ./artifacts + + prepare-hive: + if: github.repository == 'paradigmxyz/reth' + timeout-minutes: 45 + runs-on: + group: Reth + steps: + - uses: actions/checkout@v4 - name: Checkout hive tests uses: actions/checkout@v4 with: @@ -55,18 +68,15 @@ jobs: with: go-version: "^1.13.1" - run: go version - - name: Build hive tool - run: | - cd hivetests - go build . - mv ./hive ../artifacts/ - - name: Upload artifacts + - name: Build hive assets + run: .github/assets/hive/build_simulators.sh + + - name: Upload hive assets uses: actions/upload-artifact@v4 with: - name: artifacts - path: ./artifacts - + name: hive_assets + path: ./hive_assets test: timeout-minutes: 60 strategy: @@ -105,8 +115,6 @@ jobs: limit: engine-withdrawals - sim: ethereum/engine limit: engine-auth - - sim: ethereum/engine - limit: engine-transition - sim: ethereum/engine limit: engine-api - sim: ethereum/engine @@ -167,7 +175,9 @@ jobs: include: [homestead/] - sim: pyspec include: [frontier/] - needs: prepare + needs: + - prepare-reth + - prepare-hive name: run runs-on: group: Reth @@ -178,16 +188,21 @@ jobs: with: fetch-depth: 0 - - name: Download artifacts + - name: Download hive assets + uses: actions/download-artifact@v4 + with: + name: hive_assets + path: /tmp + + - name: Download reth image uses: actions/download-artifact@v4 with: name: artifacts path: /tmp - - name: Load Docker image - run: | - docker load --input /tmp/reth_image.tar - docker image ls -a + - name: Load Docker images + run: .github/assets/hive/load_images.sh + - name: Move hive binary run: | mv /tmp/hive /usr/local/bin @@ -201,37 +216,12 @@ jobs: path: hivetests - name: Run ${{ matrix.sim }} simulator - run: | - cd hivetests - hive --sim "${{ matrix.sim }}$" --sim.limit "${{matrix.limit}}/${{join(matrix.include, '|')}}" --sim.parallelism 2 --client reth || true + run: .github/assets/hive/run_simulator.sh "${{ matrix.sim }}$" "${{matrix.limit}}/${{join(matrix.include, '|')}}" - name: Parse hive output run: | find hivetests/workspace/logs -type f -name "*.json" ! -name "hive.json" | xargs -I {} python .github/assets/hive/parse.py {} --exclusion .github/assets/hive/expected_failures.yaml - - name: Create github issue if sim failed - env: - GH_TOKEN: ${{ github.token }} - if: ${{ failure() }} - run: | - echo "Simulator failed, creating issue" - # Check if issue already exists - # get all issues with the label C-hivetest, loop over each page and check if the issue already exists - - existing_issues=$(gh api /repos/paradigmxyz/reth/issues -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" -F "labels=C-hivetest" --method GET | jq '.[].title') - if [[ $existing_issues == *"Hive Test Failure: ${{ matrix.sim }}"* ]]; then - echo "Issue already exists" - exit 0 - fi - gh api \ - --method POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - /repos/${{ github.repository }}/issues \ - -f title='Hive Test Failure: ${{ matrix.sim }}' \ - -f body="!!!!!!! This is an automated issue created by the hive test failure !!!!!!!

The hive test for ${{ matrix.sim }} failed. Please investigate and fix the issue.

[Link to the failed run](https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }})" \ - -f "labels[]=C-hivetest" - - name: Print simulator output if: ${{ failure() }} run: | From c7e34fbd4af4d8c8cc53c5734a5074dbb6564146 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:49:52 +0200 Subject: [PATCH 358/405] chore: move `reth test-vectors` to `cli/commands` with feature (#9329) --- .github/workflows/bench.yml | 2 +- Cargo.lock | 6 +++--- bin/reth/Cargo.toml | 9 ++------- bin/reth/src/cli/mod.rs | 6 ++++-- bin/reth/src/commands/mod.rs | 1 - book/SUMMARY.md | 2 -- book/cli/SUMMARY.md | 2 -- book/cli/reth.md | 1 - crates/cli/commands/Cargo.toml | 17 ++++++++++++++++- crates/cli/commands/src/lib.rs | 2 ++ .../cli/commands/src}/test_vectors/mod.rs | 0 .../cli/commands/src}/test_vectors/tables.rs | 0 12 files changed, 28 insertions(+), 20 deletions(-) rename {bin/reth/src/commands => crates/cli/commands/src}/test_vectors/mod.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/test_vectors/tables.rs (100%) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 7d1524e60375..f8d1d475e300 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -49,7 +49,7 @@ jobs: with: ref: ${{ github.base_ref || 'main' }} - name: Generate test vectors - run: cargo run --bin reth -- test-vectors tables + run: cargo run --bin reth --features dev -- test-vectors tables - name: Save baseline run: cargo bench -p reth-db --bench iai --profile profiling --features test-utils -- --save-baseline=$BASELINE - name: Checkout PR diff --git a/Cargo.lock b/Cargo.lock index e17cbbe1062d..7f4b8d4ee9e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6310,7 +6310,6 @@ version = "1.0.0" dependencies = [ "alloy-rlp", "aquamarine", - "arbitrary", "backon", "clap", "confy", @@ -6321,8 +6320,6 @@ dependencies = [ "itertools 0.13.0", "libc", "metrics-process", - "proptest", - "proptest-arbitrary-interop", "reth-basic-payload-builder", "reth-beacon-consensus", "reth-blockchain-tree", @@ -6612,6 +6609,7 @@ name = "reth-cli-commands" version = "1.0.0" dependencies = [ "ahash", + "arbitrary", "clap", "comfy-table", "confy", @@ -6619,6 +6617,8 @@ dependencies = [ "eyre", "human_bytes", "itertools 0.13.0", + "proptest", + "proptest-arbitrary-interop", "ratatui", "reth-beacon-consensus", "reth-chainspec", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 9f8e08b0cda7..d67436121e7c 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -87,12 +87,6 @@ toml = { workspace = true, features = ["display"] } # metrics metrics-process.workspace = true -# test vectors generation -proptest.workspace = true -arbitrary.workspace = true -proptest-arbitrary-interop.workspace = true - - # async tokio = { workspace = true, features = [ "sync", @@ -122,10 +116,11 @@ libc = "0.2" reth-discv4.workspace = true - [features] default = ["jemalloc"] +dev = ["reth-cli-commands/dev"] + asm-keccak = ["reth-primitives/asm-keccak"] jemalloc = ["dep:tikv-jemallocator", "reth-node-core/jemalloc"] diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index a7e8a759a0f7..cc9911898b0a 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -8,7 +8,7 @@ use crate::{ commands::{ config_cmd, debug_cmd, dump_genesis, import, init_cmd, init_state, node::{self, NoArgs}, - p2p, prune, recover, stage, test_vectors, + p2p, prune, recover, stage, }, version::{LONG_VERSION, SHORT_VERSION}, }; @@ -161,6 +161,7 @@ impl Cli { Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute()), Commands::Stage(command) => runner.run_command_until_exit(|ctx| command.execute(ctx)), Commands::P2P(command) => runner.run_until_ctrl_c(command.execute()), + #[cfg(feature = "dev")] Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), Commands::Debug(command) => runner.run_command_until_exit(|ctx| command.execute(ctx)), @@ -214,8 +215,9 @@ pub enum Commands { #[command(name = "p2p")] P2P(p2p::Command), /// Generate Test Vectors + #[cfg(feature = "dev")] #[command(name = "test-vectors")] - TestVectors(test_vectors::Command), + TestVectors(reth_cli_commands::test_vectors::Command), /// Write config to stdout #[command(name = "config")] Config(config_cmd::Command), diff --git a/bin/reth/src/commands/mod.rs b/bin/reth/src/commands/mod.rs index a8f5ff3fdf8a..23d54e098228 100644 --- a/bin/reth/src/commands/mod.rs +++ b/bin/reth/src/commands/mod.rs @@ -11,4 +11,3 @@ pub mod p2p; pub mod prune; pub mod recover; pub mod stage; -pub mod test_vectors; diff --git a/book/SUMMARY.md b/book/SUMMARY.md index ad1a54633674..af03aa32a849 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -61,8 +61,6 @@ - [`reth p2p`](./cli/reth/p2p.md) - [`reth p2p header`](./cli/reth/p2p/header.md) - [`reth p2p body`](./cli/reth/p2p/body.md) - - [`reth test-vectors`](./cli/reth/test-vectors.md) - - [`reth test-vectors tables`](./cli/reth/test-vectors/tables.md) - [`reth config`](./cli/reth/config.md) - [`reth debug`](./cli/reth/debug.md) - [`reth debug execution`](./cli/reth/debug/execution.md) diff --git a/book/cli/SUMMARY.md b/book/cli/SUMMARY.md index 9f9d0fdb1dc3..5f02f1e9ee04 100644 --- a/book/cli/SUMMARY.md +++ b/book/cli/SUMMARY.md @@ -32,8 +32,6 @@ - [`reth p2p`](./reth/p2p.md) - [`reth p2p header`](./reth/p2p/header.md) - [`reth p2p body`](./reth/p2p/body.md) - - [`reth test-vectors`](./reth/test-vectors.md) - - [`reth test-vectors tables`](./reth/test-vectors/tables.md) - [`reth config`](./reth/config.md) - [`reth debug`](./reth/debug.md) - [`reth debug execution`](./reth/debug/execution.md) diff --git a/book/cli/reth.md b/book/cli/reth.md index 0a761e5089cf..cebeb44e2378 100644 --- a/book/cli/reth.md +++ b/book/cli/reth.md @@ -15,7 +15,6 @@ Commands: db Database debugging utilities stage Manipulate individual stages p2p P2P Debugging utilities - test-vectors Generate Test Vectors config Write config to stdout debug Various debug routines recover Scripts for node recovery diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index bc9532b24349..909e00eb749a 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -44,4 +44,19 @@ comfy-table = "7.0" crossterm = "0.27.0" ratatui = { version = "0.27", default-features = false, features = [ "crossterm", -] } \ No newline at end of file +] } + +# reth test-vectors +proptest = { workspace = true, optional = true } +arbitrary = { workspace = true, optional = true } +proptest-arbitrary-interop = { workspace = true, optional = true } + +[features] +default = [] +dev = [ + "dep:proptest", + "dep:arbitrary", + "dep:proptest-arbitrary-interop", + "reth-primitives/arbitrary", + "reth-db-api/arbitrary" +] \ No newline at end of file diff --git a/crates/cli/commands/src/lib.rs b/crates/cli/commands/src/lib.rs index 5f94de798c7f..e63d039d1cfd 100644 --- a/crates/cli/commands/src/lib.rs +++ b/crates/cli/commands/src/lib.rs @@ -10,3 +10,5 @@ pub mod common; pub mod db; +#[cfg(feature = "dev")] +pub mod test_vectors; diff --git a/bin/reth/src/commands/test_vectors/mod.rs b/crates/cli/commands/src/test_vectors/mod.rs similarity index 100% rename from bin/reth/src/commands/test_vectors/mod.rs rename to crates/cli/commands/src/test_vectors/mod.rs diff --git a/bin/reth/src/commands/test_vectors/tables.rs b/crates/cli/commands/src/test_vectors/tables.rs similarity index 100% rename from bin/reth/src/commands/test_vectors/tables.rs rename to crates/cli/commands/src/test_vectors/tables.rs From b3a5593455bd4addde64ef4051c62970294f7e1e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:25:08 +0200 Subject: [PATCH 359/405] chore: disable `test-utils` for `stages-api` on `stages` (#9331) --- crates/stages/stages/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stages/stages/Cargo.toml b/crates/stages/stages/Cargo.toml index 75531e1ce580..5d8ed3d5286c 100644 --- a/crates/stages/stages/Cargo.toml +++ b/crates/stages/stages/Cargo.toml @@ -30,7 +30,7 @@ reth-execution-types.workspace = true reth-prune-types.workspace = true reth-storage-errors.workspace = true reth-revm.workspace = true -reth-stages-api = { workspace = true, features = ["test-utils"] } +reth-stages-api.workspace = true reth-trie = { workspace = true, features = ["metrics"] } reth-testing-utils = { workspace = true, optional = true } From 6e1521456623aa3dbec586e3aa48b642c1fe28d5 Mon Sep 17 00:00:00 2001 From: Park Smith <161195644+sdfii@users.noreply.github.com> Date: Sat, 6 Jul 2024 04:58:49 +0100 Subject: [PATCH 360/405] fix: format_gas show two decimal places (#9336) --- crates/primitives-traits/src/constants/gas_units.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/primitives-traits/src/constants/gas_units.rs b/crates/primitives-traits/src/constants/gas_units.rs index 11caab5139be..0af0d2c24ce1 100644 --- a/crates/primitives-traits/src/constants/gas_units.rs +++ b/crates/primitives-traits/src/constants/gas_units.rs @@ -35,11 +35,11 @@ pub fn format_gas_throughput(gas: u64, execution_duration: Duration) -> String { pub fn format_gas(gas: u64) -> String { let gas = gas as f64; if gas < MEGAGAS as f64 { - format!("{:.} Kgas", gas / KILOGAS as f64) + format!("{:.2} Kgas", gas / KILOGAS as f64) } else if gas < GIGAGAS as f64 { - format!("{:.} Mgas", gas / MEGAGAS as f64) + format!("{:.2} Mgas", gas / MEGAGAS as f64) } else { - format!("{:.} Ggas", gas / GIGAGAS as f64) + format!("{:.2} Ggas", gas / GIGAGAS as f64) } } @@ -51,15 +51,15 @@ mod tests { fn test_gas_fmt() { let gas = 100_000; let gas_unit = format_gas(gas); - assert_eq!(gas_unit, "100 Kgas"); + assert_eq!(gas_unit, "100.00 Kgas"); let gas = 100_000_000; let gas_unit = format_gas(gas); - assert_eq!(gas_unit, "100 Mgas"); + assert_eq!(gas_unit, "100.00 Mgas"); let gas = 100_000_000_000; let gas_unit = format_gas(gas); - assert_eq!(gas_unit, "100 Ggas"); + assert_eq!(gas_unit, "100.00 Ggas"); } #[test] From ebe3ef5e7903066ca98f7cfa8a19e15ee444bb72 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 09:13:11 +0200 Subject: [PATCH 361/405] chore(deps): trim tokio features in eth-wire (#9343) --- crates/net/downloaders/Cargo.toml | 2 +- crates/net/eth-wire/Cargo.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/net/downloaders/Cargo.toml b/crates/net/downloaders/Cargo.toml index 4f009b44527a..6f8e2a309261 100644 --- a/crates/net/downloaders/Cargo.toml +++ b/crates/net/downloaders/Cargo.toml @@ -33,7 +33,7 @@ alloy-rlp.workspace = true futures.workspace = true futures-util.workspace = true pin-project.workspace = true -tokio = { workspace = true, features = ["sync"] } +tokio = { workspace = true, features = ["sync", "fs"] } tokio-stream.workspace = true tokio-util = { workspace = true, features = ["codec"] } diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 120512af1014..31ea291c4dd0 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -29,7 +29,7 @@ bytes.workspace = true derive_more.workspace = true thiserror.workspace = true serde = { workspace = true, optional = true } -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = ["net", "sync", "time"] } tokio-util = { workspace = true, features = ["io", "codec"] } futures.workspace = true tokio-stream.workspace = true @@ -45,6 +45,7 @@ reth-primitives = { workspace = true, features = ["arbitrary"] } reth-tracing.workspace = true test-fuzz.workspace = true +tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } tokio-util = { workspace = true, features = ["io", "codec"] } rand.workspace = true secp256k1 = { workspace = true, features = [ From 0ce192921fc5d5361ba321cd20364d70faf2911f Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Sat, 6 Jul 2024 18:29:02 +0800 Subject: [PATCH 362/405] move header.rs to eth-wire-types (#9345) --- Cargo.lock | 1 + crates/consensus/auto-seal/src/client.rs | 4 ++-- crates/net/downloaders/src/file_client.rs | 5 ++--- .../net/downloaders/src/headers/reverse_headers.rs | 6 ++---- crates/net/eth-wire-types/Cargo.toml | 2 +- crates/net/eth-wire-types/src/blocks.rs | 13 +++++++++---- .../eth-wire-types}/src/header.rs | 4 +--- crates/net/eth-wire-types/src/lib.rs | 3 +++ crates/net/network/src/eth_requests.rs | 6 +++--- crates/net/network/tests/it/connect.rs | 3 +-- crates/net/network/tests/it/requests.rs | 5 +++-- crates/net/p2p/src/full_block.rs | 5 ++--- crates/net/p2p/src/headers/client.rs | 4 ++-- crates/net/p2p/src/test_utils/full_block.rs | 4 ++-- crates/net/p2p/src/test_utils/headers.rs | 3 ++- crates/node/core/Cargo.toml | 2 +- crates/node/core/src/utils.rs | 4 ++-- crates/primitives/src/lib.rs | 6 ++---- 18 files changed, 41 insertions(+), 39 deletions(-) rename crates/{primitives => net/eth-wire-types}/src/header.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 7f4b8d4ee9e0..eaf400babd61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7155,6 +7155,7 @@ dependencies = [ "proptest-derive 0.5.0", "rand 0.8.5", "reth-chainspec", + "reth-codecs", "reth-codecs-derive", "reth-primitives", "serde", diff --git a/crates/consensus/auto-seal/src/client.rs b/crates/consensus/auto-seal/src/client.rs index b9befa73857b..d55cf6443139 100644 --- a/crates/consensus/auto-seal/src/client.rs +++ b/crates/consensus/auto-seal/src/client.rs @@ -4,11 +4,11 @@ use crate::Storage; use reth_network_p2p::{ bodies::client::{BodiesClient, BodiesFut}, download::DownloadClient, - headers::client::{HeadersClient, HeadersFut, HeadersRequest}, + headers::client::{HeadersClient, HeadersDirection, HeadersFut, HeadersRequest}, priority::Priority, }; use reth_network_peers::{PeerId, WithPeerId}; -use reth_primitives::{BlockBody, BlockHashOrNumber, Header, HeadersDirection, B256}; +use reth_primitives::{BlockBody, BlockHashOrNumber, Header, B256}; use std::fmt::Debug; use tracing::{trace, warn}; diff --git a/crates/net/downloaders/src/file_client.rs b/crates/net/downloaders/src/file_client.rs index 3d00c389237b..eaf39267755f 100644 --- a/crates/net/downloaders/src/file_client.rs +++ b/crates/net/downloaders/src/file_client.rs @@ -5,13 +5,12 @@ use reth_network_p2p::{ bodies::client::{BodiesClient, BodiesFut}, download::DownloadClient, error::RequestError, - headers::client::{HeadersClient, HeadersFut, HeadersRequest}, + headers::client::{HeadersClient, HeadersDirection, HeadersFut, HeadersRequest}, priority::Priority, }; use reth_network_peers::PeerId; use reth_primitives::{ - BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, Header, HeadersDirection, SealedHeader, - B256, + BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, Header, SealedHeader, B256, }; use std::{collections::HashMap, io, path::Path}; use thiserror::Error; diff --git a/crates/net/downloaders/src/headers/reverse_headers.rs b/crates/net/downloaders/src/headers/reverse_headers.rs index e123ce71229a..c9e4a51c2b31 100644 --- a/crates/net/downloaders/src/headers/reverse_headers.rs +++ b/crates/net/downloaders/src/headers/reverse_headers.rs @@ -10,16 +10,14 @@ use reth_consensus::Consensus; use reth_network_p2p::{ error::{DownloadError, DownloadResult, PeerRequestResult}, headers::{ - client::{HeadersClient, HeadersRequest}, + client::{HeadersClient, HeadersDirection, HeadersRequest}, downloader::{validate_header_download, HeaderDownloader, SyncTarget}, error::{HeadersDownloaderError, HeadersDownloaderResult}, }, priority::Priority, }; use reth_network_peers::PeerId; -use reth_primitives::{ - BlockHashOrNumber, BlockNumber, GotExpected, Header, HeadersDirection, SealedHeader, B256, -}; +use reth_primitives::{BlockHashOrNumber, BlockNumber, GotExpected, Header, SealedHeader, B256}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use std::{ cmp::{Ordering, Reverse}, diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index 242a7324367a..245643569d20 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] # reth reth-chainspec.workspace = true +reth-codecs.workspace = true reth-codecs-derive.workspace = true reth-primitives.workspace = true alloy-rlp = { workspace = true, features = ["derive"] } @@ -47,4 +48,3 @@ arbitrary = [ "dep:proptest-arbitrary-interop", ] serde = ["dep:serde"] - diff --git a/crates/net/eth-wire-types/src/blocks.rs b/crates/net/eth-wire-types/src/blocks.rs index c8668c251f19..536ef7a7512e 100644 --- a/crates/net/eth-wire-types/src/blocks.rs +++ b/crates/net/eth-wire-types/src/blocks.rs @@ -5,11 +5,13 @@ use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWra use reth_codecs_derive::{add_arbitrary_tests, derive_arbitrary}; #[cfg(any(test, feature = "arbitrary"))] use reth_primitives::generate_valid_header; -use reth_primitives::{BlockBody, BlockHashOrNumber, Header, HeadersDirection, B256}; +use reth_primitives::{BlockBody, BlockHashOrNumber, Header, B256}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use crate::HeadersDirection; + /// A request for a peer to return block headers starting at the requested block. /// The peer must return at most [`limit`](#structfield.limit) headers. /// If the [`reverse`](#structfield.reverse) field is `true`, the headers will be returned starting @@ -107,11 +109,14 @@ impl From> for BlockBodies { #[cfg(test)] mod tests { - use crate::{message::RequestPair, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders}; + use crate::{ + message::RequestPair, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders, + HeadersDirection, + }; use alloy_rlp::{Decodable, Encodable}; use reth_primitives::{ - hex, BlockHashOrNumber, Header, HeadersDirection, Signature, Transaction, - TransactionSigned, TxKind, TxLegacy, U256, + hex, BlockHashOrNumber, Header, Signature, Transaction, TransactionSigned, TxKind, + TxLegacy, U256, }; use std::str::FromStr; diff --git a/crates/primitives/src/header.rs b/crates/net/eth-wire-types/src/header.rs similarity index 99% rename from crates/primitives/src/header.rs rename to crates/net/eth-wire-types/src/header.rs index 5642bac7c772..c9589527b34f 100644 --- a/crates/primitives/src/header.rs +++ b/crates/net/eth-wire-types/src/header.rs @@ -5,8 +5,6 @@ use bytes::BufMut; use reth_codecs::derive_arbitrary; use serde::{Deserialize, Serialize}; -pub use reth_primitives_traits::{Header, HeaderError, SealedHeader}; - /// Represents the direction for a headers request depending on the `reverse` field of the request. /// > The response must contain a number of block headers, of rising number when reverse is 0, /// > falling when 1 @@ -88,8 +86,8 @@ impl From for bool { #[cfg(test)] mod tests { use super::*; - use crate::{address, b256, bloom, bytes, hex, Address, Bytes, B256, U256}; use alloy_rlp::{Decodable, Encodable}; + use reth_primitives::{address, b256, bloom, bytes, hex, Address, Bytes, Header, B256, U256}; use std::str::FromStr; // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 diff --git a/crates/net/eth-wire-types/src/lib.rs b/crates/net/eth-wire-types/src/lib.rs index e75898a1ff70..f14bc4739824 100644 --- a/crates/net/eth-wire-types/src/lib.rs +++ b/crates/net/eth-wire-types/src/lib.rs @@ -18,6 +18,9 @@ pub use version::EthVersion; pub mod message; pub use message::{EthMessage, EthMessageID, ProtocolMessage}; +pub mod header; +pub use header::*; + pub mod blocks; pub use blocks::*; diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 7df124375e67..8ee31755450c 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -7,12 +7,12 @@ use crate::{ use alloy_rlp::Encodable; use futures::StreamExt; use reth_eth_wire::{ - BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders, GetNodeData, GetReceipts, NodeData, - Receipts, + BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders, GetNodeData, GetReceipts, + HeadersDirection, NodeData, Receipts, }; use reth_network_p2p::error::RequestResult; use reth_network_peers::PeerId; -use reth_primitives::{BlockBody, BlockHashOrNumber, Header, HeadersDirection}; +use reth_primitives::{BlockBody, BlockHashOrNumber, Header}; use reth_storage_api::{BlockReader, HeaderProvider, ReceiptProvider}; use std::{ future::Future, diff --git a/crates/net/network/tests/it/connect.rs b/crates/net/network/tests/it/connect.rs index 2dbd311cb9f1..b379a67044c6 100644 --- a/crates/net/network/tests/it/connect.rs +++ b/crates/net/network/tests/it/connect.rs @@ -4,7 +4,7 @@ use alloy_node_bindings::Geth; use alloy_provider::{ext::AdminApi, ProviderBuilder}; use futures::StreamExt; use reth_discv4::Discv4Config; -use reth_eth_wire::DisconnectReason; +use reth_eth_wire::{DisconnectReason, HeadersDirection}; use reth_net_banlist::BanList; use reth_network::{ test_utils::{enr_to_peer_id, NetworkEventStream, PeerConfig, Testnet, GETH_TIMEOUT}, @@ -16,7 +16,6 @@ use reth_network_p2p::{ sync::{NetworkSyncUpdater, SyncState}, }; use reth_network_peers::{mainnet_nodes, NodeRecord}; -use reth_primitives::HeadersDirection; use reth_provider::test_utils::NoopProvider; use reth_transaction_pool::test_utils::testing_pool; use secp256k1::SecretKey; diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 555acd08b248..85669dc9f8a0 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -2,6 +2,7 @@ //! Tests for eth related requests use rand::Rng; +use reth_eth_wire::HeadersDirection; use reth_network::{ test_utils::{NetworkEventStream, Testnet}, NetworkEvents, @@ -12,8 +13,8 @@ use reth_network_p2p::{ headers::client::{HeadersClient, HeadersRequest}, }; use reth_primitives::{ - Block, BlockBody, Bytes, Header, HeadersDirection, Signature, Transaction, TransactionSigned, - TxEip2930, TxKind, U256, + Block, BlockBody, Bytes, Header, Signature, Transaction, TransactionSigned, TxEip2930, TxKind, + U256, }; use reth_provider::test_utils::MockEthProvider; use std::sync::Arc; diff --git a/crates/net/p2p/src/full_block.rs b/crates/net/p2p/src/full_block.rs index 93af03d5b076..fcee4c52b13e 100644 --- a/crates/net/p2p/src/full_block.rs +++ b/crates/net/p2p/src/full_block.rs @@ -6,10 +6,9 @@ use crate::{ }; use futures::Stream; use reth_consensus::{Consensus, ConsensusError}; +use reth_eth_wire_types::HeadersDirection; use reth_network_peers::WithPeerId; -use reth_primitives::{ - BlockBody, GotExpected, Header, HeadersDirection, SealedBlock, SealedHeader, B256, -}; +use reth_primitives::{BlockBody, GotExpected, Header, SealedBlock, SealedHeader, B256}; use std::{ cmp::Reverse, collections::{HashMap, VecDeque}, diff --git a/crates/net/p2p/src/headers/client.rs b/crates/net/p2p/src/headers/client.rs index 5b70aa1e5282..4a4b903a8261 100644 --- a/crates/net/p2p/src/headers/client.rs +++ b/crates/net/p2p/src/headers/client.rs @@ -1,7 +1,7 @@ use crate::{download::DownloadClient, error::PeerRequestResult, priority::Priority}; use futures::{Future, FutureExt}; -pub use reth_eth_wire_types::BlockHeaders; -use reth_primitives::{BlockHashOrNumber, Header, HeadersDirection}; +pub use reth_eth_wire_types::{BlockHeaders, HeadersDirection}; +use reth_primitives::{BlockHashOrNumber, Header}; use std::{ fmt::Debug, pin::Pin, diff --git a/crates/net/p2p/src/test_utils/full_block.rs b/crates/net/p2p/src/test_utils/full_block.rs index cfba59dbf745..731aa39e7e91 100644 --- a/crates/net/p2p/src/test_utils/full_block.rs +++ b/crates/net/p2p/src/test_utils/full_block.rs @@ -6,10 +6,10 @@ use crate::{ priority::Priority, }; use parking_lot::Mutex; +use reth_eth_wire_types::HeadersDirection; use reth_network_peers::{PeerId, WithPeerId}; use reth_primitives::{ - BlockBody, BlockHashOrNumber, BlockNumHash, Header, HeadersDirection, SealedBlock, - SealedHeader, B256, + BlockBody, BlockHashOrNumber, BlockNumHash, Header, SealedBlock, SealedHeader, B256, }; use std::{collections::HashMap, sync::Arc}; diff --git a/crates/net/p2p/src/test_utils/headers.rs b/crates/net/p2p/src/test_utils/headers.rs index 73dd04849d44..a47753539ddc 100644 --- a/crates/net/p2p/src/test_utils/headers.rs +++ b/crates/net/p2p/src/test_utils/headers.rs @@ -12,8 +12,9 @@ use crate::{ }; use futures::{Future, FutureExt, Stream, StreamExt}; use reth_consensus::{test_utils::TestConsensus, Consensus}; +use reth_eth_wire_types::HeadersDirection; use reth_network_peers::{PeerId, WithPeerId}; -use reth_primitives::{Header, HeadersDirection, SealedHeader}; +use reth_primitives::{Header, SealedHeader}; use std::{ fmt, pin::Pin, diff --git a/crates/node/core/Cargo.toml b/crates/node/core/Cargo.toml index 5dc0ed8f5cf8..997aacc6330c 100644 --- a/crates/node/core/Cargo.toml +++ b/crates/node/core/Cargo.toml @@ -102,7 +102,7 @@ optimism = [ "reth-provider/optimism", "reth-rpc-types-compat/optimism", "reth-rpc-eth-api/optimism", - "reth-rpc-eth-types/optimism" + "reth-rpc-eth-types/optimism", ] jemalloc = ["dep:tikv-jemalloc-ctl"] diff --git a/crates/node/core/src/utils.rs b/crates/node/core/src/utils.rs index 7bf3314f503a..75672cd34968 100644 --- a/crates/node/core/src/utils.rs +++ b/crates/node/core/src/utils.rs @@ -6,10 +6,10 @@ use reth_chainspec::ChainSpec; use reth_consensus_common::validation::validate_block_pre_execution; use reth_network_p2p::{ bodies::client::BodiesClient, - headers::client::{HeadersClient, HeadersRequest}, + headers::client::{HeadersClient, HeadersDirection, HeadersRequest}, priority::Priority, }; -use reth_primitives::{BlockHashOrNumber, HeadersDirection, SealedBlock, SealedHeader}; +use reth_primitives::{BlockHashOrNumber, SealedBlock, SealedHeader}; use reth_rpc_types::engine::{JwtError, JwtSecret}; use std::{ env::VarError, diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 666a84361b3f..29154d591e8e 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -29,7 +29,6 @@ mod compression; pub mod constants; pub mod eip4844; pub mod genesis; -pub mod header; pub mod proofs; mod receipt; pub use reth_static_file_types as static_file; @@ -47,13 +46,12 @@ pub use constants::{ MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, }; pub use genesis::{ChainConfig, Genesis, GenesisAccount}; -pub use header::{Header, HeadersDirection, SealedHeader}; pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; pub use reth_primitives_traits::{ - logs_bloom, Account, Bytecode, GotExpected, GotExpectedBoxed, Log, LogData, Request, Requests, - StorageEntry, Withdrawal, Withdrawals, + logs_bloom, Account, Bytecode, GotExpected, GotExpectedBoxed, Header, HeaderError, Log, + LogData, Request, Requests, SealedHeader, StorageEntry, Withdrawal, Withdrawals, }; pub use static_file::StaticFileSegment; From 569555516b956642b6717d1a3209983c2d260174 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Sat, 6 Jul 2024 12:34:45 +0200 Subject: [PATCH 363/405] chore: move featureless commands from `bin` to `reth-cli-commands` (#9333) --- Cargo.lock | 8 ++++++ bin/reth/src/cli/mod.rs | 6 ++-- bin/reth/src/commands/mod.rs | 7 ----- crates/cli/commands/Cargo.toml | 28 +++++++++++++------ .../cli/commands/src}/config_cmd.rs | 0 .../cli/commands/src}/dump_genesis.rs | 2 +- .../cli/commands/src}/init_cmd.rs | 2 +- .../cli/commands/src}/init_state.rs | 2 +- crates/cli/commands/src/lib.rs | 7 +++++ .../cli/commands/src}/p2p.rs | 15 +++++----- .../cli/commands/src}/prune.rs | 2 +- .../cli/commands/src}/recover/mod.rs | 0 .../commands/src}/recover/storage_tries.rs | 2 +- 13 files changed, 49 insertions(+), 32 deletions(-) rename {bin/reth/src/commands => crates/cli/commands/src}/config_cmd.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/dump_genesis.rs (93%) rename {bin/reth/src/commands => crates/cli/commands/src}/init_cmd.rs (90%) rename {bin/reth/src/commands => crates/cli/commands/src}/init_state.rs (96%) rename {bin/reth/src/commands => crates/cli/commands/src}/p2p.rs (98%) rename {bin/reth/src/commands => crates/cli/commands/src}/prune.rs (95%) rename {bin/reth/src/commands => crates/cli/commands/src}/recover/mod.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/recover/storage_tries.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index eaf400babd61..8d62a42cb3e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6610,6 +6610,7 @@ version = "1.0.0" dependencies = [ "ahash", "arbitrary", + "backon", "clap", "comfy-table", "confy", @@ -6622,6 +6623,8 @@ dependencies = [ "ratatui", "reth-beacon-consensus", "reth-chainspec", + "reth-cli-runner", + "reth-cli-util", "reth-config", "reth-db", "reth-db-api", @@ -6629,15 +6632,20 @@ dependencies = [ "reth-downloaders", "reth-evm", "reth-fs-util", + "reth-network", + "reth-network-p2p", "reth-node-core", "reth-primitives", "reth-provider", + "reth-prune", "reth-stages", "reth-static-file", "reth-static-file-types", + "reth-trie", "serde", "serde_json", "tokio", + "toml", "tracing", ] diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index cc9911898b0a..baa617b2601e 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -6,15 +6,15 @@ use crate::{ LogArgs, }, commands::{ - config_cmd, debug_cmd, dump_genesis, import, init_cmd, init_state, + debug_cmd, import, node::{self, NoArgs}, - p2p, prune, recover, stage, + stage, }, version::{LONG_VERSION, SHORT_VERSION}, }; use clap::{value_parser, Parser, Subcommand}; use reth_chainspec::ChainSpec; -use reth_cli_commands::db; +use reth_cli_commands::{config_cmd, db, dump_genesis, init_cmd, init_state, p2p, prune, recover}; use reth_cli_runner::CliRunner; use reth_db::DatabaseEnv; use reth_node_builder::{NodeBuilder, WithLaunchContext}; diff --git a/bin/reth/src/commands/mod.rs b/bin/reth/src/commands/mod.rs index 23d54e098228..2bd023f4aec0 100644 --- a/bin/reth/src/commands/mod.rs +++ b/bin/reth/src/commands/mod.rs @@ -1,13 +1,6 @@ //! This contains all of the `reth` commands -pub mod config_cmd; pub mod debug_cmd; -pub mod dump_genesis; pub mod import; -pub mod init_cmd; -pub mod init_state; pub mod node; -pub mod p2p; -pub mod prune; -pub mod recover; pub mod stage; diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index 909e00eb749a..d413815f57ce 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -10,23 +10,28 @@ repository.workspace = true [lints] [dependencies] -reth-db = { workspace = true, features = ["mdbx"] } -reth-db-api.workspace = true -reth-provider.workspace = true -reth-primitives.workspace = true -reth-node-core.workspace = true -reth-fs-util.workspace = true -reth-db-common.workspace = true -reth-static-file-types.workspace = true reth-beacon-consensus.workspace = true reth-chainspec.workspace = true +reth-cli-runner.workspace = true +reth-cli-util.workspace = true reth-config.workspace = true +reth-db = { workspace = true, features = ["mdbx"] } +reth-db-api.workspace = true +reth-db-common.workspace = true reth-downloaders.workspace = true reth-evm.workspace = true +reth-fs-util.workspace = true +reth-network = { workspace = true, features = ["serde"] } +reth-network-p2p.workspace = true +reth-node-core.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true +reth-prune.workspace = true reth-stages.workspace = true +reth-static-file-types.workspace = true reth-static-file.workspace = true +reth-trie = { workspace = true, features = ["metrics"] } -confy.workspace = true tokio.workspace = true itertools.workspace = true @@ -38,6 +43,11 @@ clap = { workspace = true, features = ["derive", "env"] } serde.workspace = true serde_json.workspace = true tracing.workspace = true +backon.workspace = true + +# io +confy.workspace = true +toml = { workspace = true, features = ["display"] } # tui comfy-table = "7.0" diff --git a/bin/reth/src/commands/config_cmd.rs b/crates/cli/commands/src/config_cmd.rs similarity index 100% rename from bin/reth/src/commands/config_cmd.rs rename to crates/cli/commands/src/config_cmd.rs diff --git a/bin/reth/src/commands/dump_genesis.rs b/crates/cli/commands/src/dump_genesis.rs similarity index 93% rename from bin/reth/src/commands/dump_genesis.rs rename to crates/cli/commands/src/dump_genesis.rs index 70b95e73639b..ae425ca8c29d 100644 --- a/bin/reth/src/commands/dump_genesis.rs +++ b/crates/cli/commands/src/dump_genesis.rs @@ -1,7 +1,7 @@ //! Command that dumps genesis block JSON configuration to stdout -use crate::args::utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}; use clap::Parser; use reth_chainspec::ChainSpec; +use reth_node_core::args::utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}; use std::sync::Arc; /// Dumps genesis block JSON configuration to stdout diff --git a/bin/reth/src/commands/init_cmd.rs b/crates/cli/commands/src/init_cmd.rs similarity index 90% rename from bin/reth/src/commands/init_cmd.rs rename to crates/cli/commands/src/init_cmd.rs index df407c0659f0..933527cc565a 100644 --- a/bin/reth/src/commands/init_cmd.rs +++ b/crates/cli/commands/src/init_cmd.rs @@ -1,7 +1,7 @@ //! Command that initializes the node from a genesis file. +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_provider::BlockHashReader; use tracing::info; diff --git a/bin/reth/src/commands/init_state.rs b/crates/cli/commands/src/init_state.rs similarity index 96% rename from bin/reth/src/commands/init_state.rs rename to crates/cli/commands/src/init_state.rs index 4324b7f46882..af26d15e0176 100644 --- a/bin/reth/src/commands/init_state.rs +++ b/crates/cli/commands/src/init_state.rs @@ -1,7 +1,7 @@ //! Command that initializes the node from a genesis file. +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_config::config::EtlConfig; use reth_db_api::database::Database; use reth_db_common::init::init_from_state_dump; diff --git a/crates/cli/commands/src/lib.rs b/crates/cli/commands/src/lib.rs index e63d039d1cfd..3ddbd91a9362 100644 --- a/crates/cli/commands/src/lib.rs +++ b/crates/cli/commands/src/lib.rs @@ -9,6 +9,13 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod common; +pub mod config_cmd; pub mod db; +pub mod dump_genesis; +pub mod init_cmd; +pub mod init_state; +pub mod p2p; +pub mod prune; +pub mod recover; #[cfg(feature = "dev")] pub mod test_vectors; diff --git a/bin/reth/src/commands/p2p.rs b/crates/cli/commands/src/p2p.rs similarity index 98% rename from bin/reth/src/commands/p2p.rs rename to crates/cli/commands/src/p2p.rs index caa85a71dbb9..0fdefac8bd88 100644 --- a/bin/reth/src/commands/p2p.rs +++ b/crates/cli/commands/src/p2p.rs @@ -1,12 +1,5 @@ //! P2P Debugging tool -use crate::{ - args::{ - utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}, - DatabaseArgs, NetworkArgs, - }, - utils::get_single_header, -}; use backon::{ConstantBuilder, Retryable}; use clap::{Parser, Subcommand}; use reth_chainspec::ChainSpec; @@ -14,7 +7,13 @@ use reth_cli_util::{get_secret_key, hash_or_num_value_parser}; use reth_config::Config; use reth_network::NetworkConfigBuilder; use reth_network_p2p::bodies::client::BodiesClient; -use reth_node_core::args::DatadirArgs; +use reth_node_core::{ + args::{ + utils::{chain_help, chain_value_parser, SUPPORTED_CHAINS}, + DatabaseArgs, DatadirArgs, NetworkArgs, + }, + utils::get_single_header, +}; use reth_primitives::BlockHashOrNumber; use std::{path::PathBuf, sync::Arc}; diff --git a/bin/reth/src/commands/prune.rs b/crates/cli/commands/src/prune.rs similarity index 95% rename from bin/reth/src/commands/prune.rs rename to crates/cli/commands/src/prune.rs index cd9cfabb26a3..6cc5e033bc04 100644 --- a/bin/reth/src/commands/prune.rs +++ b/crates/cli/commands/src/prune.rs @@ -1,6 +1,6 @@ //! Command that runs pruning without any limits. +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_prune::PrunerBuilder; use reth_static_file::StaticFileProducer; use tracing::info; diff --git a/bin/reth/src/commands/recover/mod.rs b/crates/cli/commands/src/recover/mod.rs similarity index 100% rename from bin/reth/src/commands/recover/mod.rs rename to crates/cli/commands/src/recover/mod.rs diff --git a/bin/reth/src/commands/recover/storage_tries.rs b/crates/cli/commands/src/recover/storage_tries.rs similarity index 96% rename from bin/reth/src/commands/recover/storage_tries.rs rename to crates/cli/commands/src/recover/storage_tries.rs index 7cab05ff8527..2b4087144805 100644 --- a/bin/reth/src/commands/recover/storage_tries.rs +++ b/crates/cli/commands/src/recover/storage_tries.rs @@ -1,5 +1,5 @@ +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_cli_runner::CliContext; use reth_db::tables; use reth_db_api::{ From b4577597c52cc187110d82a24d238992fc39b0b4 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Sat, 6 Jul 2024 14:02:43 +0200 Subject: [PATCH 364/405] chore: remove `test-utils`, `arbitrary` and `proptest` from built binary (#9332) --- .github/workflows/lint.yml | 10 ++++++++ Cargo.lock | 1 + crates/e2e-test-utils/Cargo.toml | 4 ++-- crates/ethereum/node/Cargo.toml | 4 ++++ crates/exex/test-utils/Cargo.toml | 4 ++-- crates/node/builder/Cargo.toml | 6 ++++- crates/node/builder/src/builder/mod.rs | 24 +++++++++---------- crates/optimism/node/Cargo.toml | 5 +++- crates/rpc/rpc/Cargo.toml | 4 ++-- .../src/providers/database/provider.rs | 5 ++-- examples/custom-dev-node/Cargo.toml | 2 +- examples/custom-engine-types/Cargo.toml | 2 +- examples/custom-evm/Cargo.toml | 2 +- examples/custom-rlpx-subprotocol/Cargo.toml | 2 +- examples/rpc-db/Cargo.toml | 1 + examples/rpc-db/src/main.rs | 6 ++--- examples/stateful-precompile/Cargo.toml | 2 +- 17 files changed, 53 insertions(+), 31 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d0329aefc89f..3aefc21c8389 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -160,6 +160,15 @@ jobs: with: cmd: jq empty etc/grafana/dashboards/overview.json + no-test-deps: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Ensure no arbitrary or proptest dependency on default build + run: cargo tree --package reth -e=features,no-dev | grep -Eq "arbitrary|proptest" && exit 1 || exit 0 + lint-success: name: lint success runs-on: ubuntu-latest @@ -173,6 +182,7 @@ jobs: - book - codespell - grafana + - no-test-deps timeout-minutes: 30 steps: - name: Decide whether the needed jobs succeeded or failed diff --git a/Cargo.lock b/Cargo.lock index 8d62a42cb3e2..d7bc1dfa4019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3056,6 +3056,7 @@ dependencies = [ "reth-db", "reth-db-api", "reth-node-ethereum", + "reth-provider", "tokio", ] diff --git a/crates/e2e-test-utils/Cargo.toml b/crates/e2e-test-utils/Cargo.toml index a610d5569684..f472da06bc12 100644 --- a/crates/e2e-test-utils/Cargo.toml +++ b/crates/e2e-test-utils/Cargo.toml @@ -13,12 +13,12 @@ reth.workspace = true reth-chainspec.workspace = true reth-primitives.workspace = true reth-tracing.workspace = true -reth-db.workspace = true +reth-db = { workspace = true, features = ["test-utils"] } reth-rpc.workspace = true reth-rpc-layer.workspace = true reth-payload-builder = { workspace = true, features = ["test-utils"] } reth-provider.workspace = true -reth-node-builder.workspace = true +reth-node-builder = { workspace = true, features = ["test-utils"] } reth-tokio-util.workspace = true reth-stages-types.workspace = true reth-network-peers.workspace = true diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index 54e54a0ebb76..f053b35b911c 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -43,3 +43,7 @@ futures.workspace = true tokio.workspace = true futures-util.workspace = true serde_json.workspace = true + +[features] +default = [] +test-utils = ["reth-node-builder/test-utils"] diff --git a/crates/exex/test-utils/Cargo.toml b/crates/exex/test-utils/Cargo.toml index b7db9a98f02c..b5b62471b6f7 100644 --- a/crates/exex/test-utils/Cargo.toml +++ b/crates/exex/test-utils/Cargo.toml @@ -24,11 +24,11 @@ reth-exex.workspace = true reth-network.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true -reth-node-builder.workspace = true +reth-node-builder = { workspace = true, features = ["test-utils"] } reth-node-ethereum.workspace = true reth-payload-builder.workspace = true reth-primitives.workspace = true -reth-provider.workspace = true +reth-provider = { workspace = true, features = ["test-utils"] } reth-tasks.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index f15ec8774611..5a29b6e778a4 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -21,7 +21,7 @@ reth-db-common.workspace = true reth-exex.workspace = true reth-evm.workspace = true reth-provider.workspace = true -reth-db = { workspace = true, features = ["mdbx"] } +reth-db = { workspace = true, features = ["mdbx"], optional = true } reth-db-api.workspace = true reth-rpc-engine-api.workspace = true reth-rpc.workspace = true @@ -78,3 +78,7 @@ tracing.workspace = true [dev-dependencies] tempfile.workspace = true + +[features] +default = [] +test-utils = ["reth-db/test-utils"] diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 23a18ee309fe..d46b73d76872 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -12,10 +12,6 @@ use crate::{ use futures::Future; use reth_chainspec::ChainSpec; use reth_cli_util::get_secret_key; -use reth_db::{ - test_utils::{create_test_rw_db_with_path, tempdir_path, TempDatabase}, - DatabaseEnv, -}; use reth_db_api::{ database::Database, database_metrics::{DatabaseMetadata, DatabaseMetrics}, @@ -26,9 +22,8 @@ use reth_network::{ }; use reth_node_api::{FullNodeTypes, FullNodeTypesAdapter, NodeTypes}; use reth_node_core::{ - args::DatadirArgs, cli::config::{PayloadBuilderConfig, RethTransactionPoolConfig}, - dirs::{ChainPath, DataDirPath, MaybePlatformPath}, + dirs::{ChainPath, DataDirPath}, node_config::NodeConfig, primitives::Head, }; @@ -176,19 +171,24 @@ impl NodeBuilder { } /// Creates an _ephemeral_ preconfigured node for testing purposes. + #[cfg(feature = "test-utils")] pub fn testing_node( mut self, task_executor: TaskExecutor, - ) -> WithLaunchContext>>> { - let path = MaybePlatformPath::::from(tempdir_path()); - self.config = self - .config - .with_datadir_args(DatadirArgs { datadir: path.clone(), ..Default::default() }); + ) -> WithLaunchContext>>> + { + let path = reth_node_core::dirs::MaybePlatformPath::::from( + reth_db::test_utils::tempdir_path(), + ); + self.config = self.config.with_datadir_args(reth_node_core::args::DatadirArgs { + datadir: path.clone(), + ..Default::default() + }); let data_dir = path.unwrap_or_chain_default(self.config.chain.chain, self.config.datadir.clone()); - let db = create_test_rw_db_with_path(data_dir.db()); + let db = reth_db::test_utils::create_test_rw_db_with_path(data_dir.db()); WithLaunchContext { builder: self.with_database(db), task_executor } } diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index 5d172ec93b73..1a32bcad6ec4 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -58,8 +58,10 @@ serde_json.workspace = true [dev-dependencies] reth.workspace = true reth-db.workspace = true -reth-revm = { workspace = true, features = ["test-utils"] } reth-e2e-test-utils.workspace = true +reth-node-builder = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } +reth-revm = { workspace = true, features = ["test-utils"] } tokio.workspace = true alloy-primitives.workspace = true alloy-genesis.workspace = true @@ -78,3 +80,4 @@ optimism = [ "reth-auto-seal-consensus/optimism", "reth-rpc-eth-types/optimism" ] +test-utils = ["reth-node-builder/test-utils"] diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index df6787199ce3..256fc156bad4 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -19,8 +19,8 @@ reth-rpc-api.workspace = true reth-rpc-eth-api.workspace = true reth-rpc-types.workspace = true reth-errors.workspace = true -reth-provider = { workspace = true, features = ["test-utils"] } -reth-transaction-pool = { workspace = true, features = ["test-utils"] } +reth-provider.workspace = true +reth-transaction-pool.workspace = true reth-network-api.workspace = true reth-rpc-engine-api.workspace = true reth-revm.workspace = true diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 83d9da3c648f..970c77b39997 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -172,8 +172,9 @@ impl DatabaseProvider { } impl DatabaseProvider { - #[cfg(any(test, feature = "test-utils"))] - /// Inserts an historical block. Used for setting up test environments + // TODO: uncomment below, once `reth debug_cmd` has been feature gated with dev. + // #[cfg(any(test, feature = "test-utils"))] + /// Inserts an historical block. **Used for setting up test environments** pub fn insert_historical_block( &self, block: SealedBlockWithSenders, diff --git a/examples/custom-dev-node/Cargo.toml b/examples/custom-dev-node/Cargo.toml index cc21c97d2a80..d40c97ca658d 100644 --- a/examples/custom-dev-node/Cargo.toml +++ b/examples/custom-dev-node/Cargo.toml @@ -11,7 +11,7 @@ reth.workspace = true reth-chainspec.workspace = true reth-node-core.workspace = true reth-primitives.workspace = true -reth-node-ethereum.workspace = true +reth-node-ethereum = { workspace = true, features = ["test-utils"] } futures-util.workspace = true eyre.workspace = true diff --git a/examples/custom-engine-types/Cargo.toml b/examples/custom-engine-types/Cargo.toml index 3b6a796ba0ad..c00863147f9b 100644 --- a/examples/custom-engine-types/Cargo.toml +++ b/examples/custom-engine-types/Cargo.toml @@ -15,7 +15,7 @@ reth-primitives.workspace = true reth-payload-builder.workspace = true reth-basic-payload-builder.workspace = true reth-ethereum-payload-builder.workspace = true -reth-node-ethereum.workspace = true +reth-node-ethereum = { workspace = true, features = ["test-utils"] } reth-tracing.workspace = true alloy-genesis.workspace = true diff --git a/examples/custom-evm/Cargo.toml b/examples/custom-evm/Cargo.toml index a85b6ce8aadb..7642dc80cf2f 100644 --- a/examples/custom-evm/Cargo.toml +++ b/examples/custom-evm/Cargo.toml @@ -12,7 +12,7 @@ reth-evm-ethereum.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true reth-primitives.workspace = true -reth-node-ethereum.workspace = true +reth-node-ethereum = { workspace = true, features = ["test-utils"] } reth-tracing.workspace = true alloy-genesis.workspace = true diff --git a/examples/custom-rlpx-subprotocol/Cargo.toml b/examples/custom-rlpx-subprotocol/Cargo.toml index ae3a7c088c04..d2d1caab6355 100644 --- a/examples/custom-rlpx-subprotocol/Cargo.toml +++ b/examples/custom-rlpx-subprotocol/Cargo.toml @@ -13,7 +13,7 @@ reth-eth-wire.workspace = true reth-network.workspace = true reth-network-api.workspace = true reth-node-ethereum.workspace = true -reth-provider.workspace = true +reth-provider = { workspace = true, features = ["test-utils"] } reth-primitives.workspace = true reth-rpc-types.workspace = true reth.workspace = true diff --git a/examples/rpc-db/Cargo.toml b/examples/rpc-db/Cargo.toml index 8bcab0e2be52..007a488b8174 100644 --- a/examples/rpc-db/Cargo.toml +++ b/examples/rpc-db/Cargo.toml @@ -13,5 +13,6 @@ reth-chainspec.workspace = true reth-db.workspace = true reth-db-api.workspace = true reth-node-ethereum.workspace = true +reth-provider = { workspace = true, features = ["test-utils"] } tokio = { workspace = true, features = ["full"] } eyre.workspace = true diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index 6d18640f7f96..85ad28d6a051 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -31,11 +31,9 @@ use reth::rpc::builder::{ }; // Configuring the network parts, ideally also wouldn't need to think about this. use myrpc_ext::{MyRpcExt, MyRpcExtApiServer}; -use reth::{ - blockchain_tree::noop::NoopBlockchainTree, providers::test_utils::TestCanonStateSubscriptions, - tasks::TokioTaskExecutor, -}; +use reth::{blockchain_tree::noop::NoopBlockchainTree, tasks::TokioTaskExecutor}; use reth_node_ethereum::EthEvmConfig; +use reth_provider::test_utils::TestCanonStateSubscriptions; // Custom rpc extension pub mod myrpc_ext; diff --git a/examples/stateful-precompile/Cargo.toml b/examples/stateful-precompile/Cargo.toml index c983ef80d95e..2ae4656eee86 100644 --- a/examples/stateful-precompile/Cargo.toml +++ b/examples/stateful-precompile/Cargo.toml @@ -11,7 +11,7 @@ reth-chainspec.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true reth-primitives.workspace = true -reth-node-ethereum.workspace = true +reth-node-ethereum = { workspace = true, features = ["test-utils"] } reth-tracing.workspace = true alloy-genesis.workspace = true From 862304770459ec0af4c97465a843bc8f2e08c674 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 15:05:08 +0200 Subject: [PATCH 365/405] chore: use usize in internal functions (#9337) --- crates/rpc/rpc-eth-api/src/core.rs | 17 ++++++++++++----- .../rpc/rpc-eth-api/src/helpers/transaction.rs | 12 ++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index cf11c6d3196b..3ba0a59e1000 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -463,7 +463,8 @@ where index: Index, ) -> RpcResult> { trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getRawTransactionByBlockHashAndIndex"); - Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, hash.into(), index).await?) + Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, hash.into(), index.into()) + .await?) } /// Handler for: `eth_getTransactionByBlockHashAndIndex` @@ -473,7 +474,8 @@ where index: Index, ) -> RpcResult> { trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getTransactionByBlockHashAndIndex"); - Ok(EthTransactions::transaction_by_block_and_tx_index(self, hash.into(), index).await?) + Ok(EthTransactions::transaction_by_block_and_tx_index(self, hash.into(), index.into()) + .await?) } /// Handler for: `eth_getRawTransactionByBlockNumberAndIndex` @@ -483,8 +485,12 @@ where index: Index, ) -> RpcResult> { trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getRawTransactionByBlockNumberAndIndex"); - Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, number.into(), index) - .await?) + Ok(EthTransactions::raw_transaction_by_block_and_tx_index( + self, + number.into(), + index.into(), + ) + .await?) } /// Handler for: `eth_getTransactionByBlockNumberAndIndex` @@ -494,7 +500,8 @@ where index: Index, ) -> RpcResult> { trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getTransactionByBlockNumberAndIndex"); - Ok(EthTransactions::transaction_by_block_and_tx_index(self, number.into(), index).await?) + Ok(EthTransactions::transaction_by_block_and_tx_index(self, number.into(), index.into()) + .await?) } /// Handler for: `eth_getTransactionReceipt` diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 0402200cefbf..73355b47a781 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -19,7 +19,7 @@ use reth_rpc_types::{ EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest, LegacyTransactionRequest, }, - AnyTransactionReceipt, Index, Transaction, TransactionRequest, TypedTransactionRequest, + AnyTransactionReceipt, Transaction, TransactionRequest, TypedTransactionRequest, }; use reth_rpc_types_compat::transaction::from_recovered_with_block_context; use reth_transaction_pool::{TransactionOrigin, TransactionPool}; @@ -184,7 +184,7 @@ pub trait EthTransactions: LoadTransaction { fn transaction_by_block_and_tx_index( &self, block_id: BlockId, - index: Index, + index: usize, ) -> impl Future>> + Send where Self: LoadBlock, @@ -194,13 +194,13 @@ pub trait EthTransactions: LoadTransaction { let block_hash = block.hash(); let block_number = block.number; let base_fee_per_gas = block.base_fee_per_gas; - if let Some(tx) = block.into_transactions_ecrecovered().nth(index.into()) { + if let Some(tx) = block.into_transactions_ecrecovered().nth(index) { return Ok(Some(from_recovered_with_block_context( tx, block_hash, block_number, base_fee_per_gas, - index.into(), + index, ))) } } @@ -215,14 +215,14 @@ pub trait EthTransactions: LoadTransaction { fn raw_transaction_by_block_and_tx_index( &self, block_id: BlockId, - index: Index, + index: usize, ) -> impl Future>> + Send where Self: LoadBlock, { async move { if let Some(block) = self.block_with_senders(block_id).await? { - if let Some(tx) = block.transactions().nth(index.into()) { + if let Some(tx) = block.transactions().nth(index) { return Ok(Some(tx.envelope_encoded())) } } From d7f54664332e723680ce89b052ff2e8aa8449de3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 15:09:04 +0200 Subject: [PATCH 366/405] chore: rm unused optimism feature (#9342) --- crates/net/eth-wire/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 31ea291c4dd0..1190a411dad0 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -66,7 +66,6 @@ arbitrary = [ "reth-primitives/arbitrary", "dep:arbitrary", ] -optimism = ["reth-primitives/optimism"] serde = ["dep:serde"] [[test]] From 3d999c3a2a66904f2de21f1231fdedce965b4298 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 18:50:16 +0200 Subject: [PATCH 367/405] test: make eth-wire tests compile with --all-features (#9340) --- Cargo.lock | 1 + crates/net/eth-wire-types/Cargo.toml | 6 +++++- crates/net/eth-wire-types/src/status.rs | 3 ++- crates/net/eth-wire/Cargo.toml | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7bc1dfa4019..02e7721de11c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7154,6 +7154,7 @@ dependencies = [ name = "reth-eth-wire-types" version = "1.0.0" dependencies = [ + "alloy-chains", "alloy-genesis", "alloy-rlp", "arbitrary", diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index 245643569d20..bda6a327c67e 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -17,6 +17,9 @@ reth-chainspec.workspace = true reth-codecs.workspace = true reth-codecs-derive.workspace = true reth-primitives.workspace = true + +# ethereum +alloy-chains = { workspace = true, features = ["rlp"] } alloy-rlp = { workspace = true, features = ["derive"] } alloy-genesis.workspace = true @@ -32,7 +35,7 @@ proptest-arbitrary-interop = { workspace = true, optional = true } [dev-dependencies] reth-primitives = { workspace = true, features = ["arbitrary"] } - +alloy-chains = { workspace = true, features = ["arbitrary"] } arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true proptest-arbitrary-interop.workspace = true @@ -43,6 +46,7 @@ rand.workspace = true default = ["serde"] arbitrary = [ "reth-primitives/arbitrary", + "alloy-chains/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-arbitrary-interop", diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 873af22274bd..85d09dcd5923 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -1,7 +1,8 @@ use crate::EthVersion; +use alloy_chains::{Chain, NamedChain}; use alloy_genesis::Genesis; use alloy_rlp::{RlpDecodable, RlpEncodable}; -use reth_chainspec::{Chain, ChainSpec, NamedChain, MAINNET}; +use reth_chainspec::{ChainSpec, MAINNET}; use reth_codecs_derive::derive_arbitrary; use reth_primitives::{hex, EthereumHardfork, ForkId, Head, B256, U256}; #[cfg(feature = "serde")] diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 1190a411dad0..70970f5cac8c 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -64,6 +64,7 @@ async-stream.workspace = true default = ["serde"] arbitrary = [ "reth-primitives/arbitrary", + "reth-eth-wire-types/arbitrary", "dep:arbitrary", ] serde = ["dep:serde"] From 98b755b98aedd078449297230231c554a02433b2 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:43:10 +0200 Subject: [PATCH 368/405] chore(meta): fix link in issue template config (#9349) --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ee2646490db5..120ee1dc1137 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: GitHub Discussions - url: https://github.com/foundry-rs/reth/discussions + url: https://github.com/paradigmxyz/reth/discussions about: Please ask and answer questions here to keep the issue tracker clean. - name: Security url: mailto:georgios@paradigm.xyz From b94d8324cfced838d444ad0adae0a90ec70be2be Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 19:51:38 +0200 Subject: [PATCH 369/405] chore(deps): rm discv4 dep from eth-wire (#9344) --- Cargo.lock | 1 - crates/net/downloaders/Cargo.toml | 2 +- crates/net/eth-wire/Cargo.toml | 3 +-- crates/net/eth-wire/src/ethstream.rs | 6 +++--- crates/net/eth-wire/src/hello.rs | 24 ++++++++++++++---------- crates/net/eth-wire/src/test_utils.rs | 6 +++--- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02e7721de11c..5553427b3bf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7132,7 +7132,6 @@ dependencies = [ "rand 0.8.5", "reth-chainspec", "reth-codecs", - "reth-discv4", "reth-ecies", "reth-eth-wire-types", "reth-metrics", diff --git a/crates/net/downloaders/Cargo.toml b/crates/net/downloaders/Cargo.toml index 6f8e2a309261..f17ce036d15e 100644 --- a/crates/net/downloaders/Cargo.toml +++ b/crates/net/downloaders/Cargo.toml @@ -33,7 +33,7 @@ alloy-rlp.workspace = true futures.workspace = true futures-util.workspace = true pin-project.workspace = true -tokio = { workspace = true, features = ["sync", "fs"] } +tokio = { workspace = true, features = ["sync", "fs", "io-util"] } tokio-stream.workspace = true tokio-util = { workspace = true, features = ["codec"] } diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 70970f5cac8c..242c4b0b31f9 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -18,7 +18,6 @@ reth-codecs.workspace = true reth-primitives.workspace = true reth-ecies.workspace = true alloy-rlp = { workspace = true, features = ["derive"] } -reth-discv4.workspace = true reth-eth-wire-types.workspace = true reth-network-peers.workspace = true @@ -29,7 +28,7 @@ bytes.workspace = true derive_more.workspace = true thiserror.workspace = true serde = { workspace = true, optional = true } -tokio = { workspace = true, features = ["net", "sync", "time"] } +tokio = { workspace = true, features = ["macros", "net", "sync", "time"] } tokio-util = { workspace = true, features = ["io", "codec"] } futures.workspace = true tokio-stream.workspace = true diff --git a/crates/net/eth-wire/src/ethstream.rs b/crates/net/eth-wire/src/ethstream.rs index fac5e05495a7..3979a822a927 100644 --- a/crates/net/eth-wire/src/ethstream.rs +++ b/crates/net/eth-wire/src/ethstream.rs @@ -348,12 +348,12 @@ mod tests { use crate::{ broadcast::BlockHashNumber, errors::{EthHandshakeError, EthStreamError}, + hello::DEFAULT_TCP_PORT, p2pstream::{ProtocolVersion, UnauthedP2PStream}, EthMessage, EthStream, EthVersion, HelloMessageWithProtocols, PassthroughCodec, Status, }; use futures::{SinkExt, StreamExt}; use reth_chainspec::NamedChain; - use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_ecies::stream::ECIESStream; use reth_network_peers::pk2id; use reth_primitives::{ForkFilter, Head, B256, U256}; @@ -624,7 +624,7 @@ mod tests { protocol_version: ProtocolVersion::V5, client_version: "bitcoind/1.0.0".to_string(), protocols: vec![EthVersion::Eth67.into()], - port: DEFAULT_DISCOVERY_PORT, + port: DEFAULT_TCP_PORT, id: pk2id(&server_key.public_key(SECP256K1)), }; @@ -652,7 +652,7 @@ mod tests { protocol_version: ProtocolVersion::V5, client_version: "bitcoind/1.0.0".to_string(), protocols: vec![EthVersion::Eth67.into()], - port: DEFAULT_DISCOVERY_PORT, + port: DEFAULT_TCP_PORT, id: pk2id(&client_key.public_key(SECP256K1)), }; diff --git a/crates/net/eth-wire/src/hello.rs b/crates/net/eth-wire/src/hello.rs index fbdffecec38a..2e95e2c7e4e9 100644 --- a/crates/net/eth-wire/src/hello.rs +++ b/crates/net/eth-wire/src/hello.rs @@ -1,10 +1,14 @@ use crate::{capability::Capability, EthVersion, ProtocolVersion}; use alloy_rlp::{RlpDecodable, RlpEncodable}; use reth_codecs::derive_arbitrary; -use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_network_peers::PeerId; use reth_primitives::constants::RETH_CLIENT_VERSION; +/// The default tcp port for p2p. +/// +/// Note: this is the same as discovery port: `DEFAULT_DISCOVERY_PORT` +pub(crate) const DEFAULT_TCP_PORT: u16 = 30303; + use crate::protocol::Protocol; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -29,6 +33,8 @@ pub struct HelloMessageWithProtocols { /// The list of supported capabilities and their versions. pub protocols: Vec, /// The port that the client is listening on, zero indicates the client is not listening. + /// + /// By default this is `30303` which is the same as the default discovery port. pub port: u16, /// The secp256k1 public key corresponding to the node's private key. pub id: PeerId, @@ -200,7 +206,7 @@ impl HelloMessageBuilder { protocols: protocols.unwrap_or_else(|| { vec![EthVersion::Eth68.into(), EthVersion::Eth67.into(), EthVersion::Eth66.into()] }), - port: port.unwrap_or(DEFAULT_DISCOVERY_PORT), + port: port.unwrap_or(DEFAULT_TCP_PORT), id, } } @@ -208,14 +214,12 @@ impl HelloMessageBuilder { #[cfg(test)] mod tests { - use alloy_rlp::{Decodable, Encodable, EMPTY_STRING_CODE}; - use reth_discv4::DEFAULT_DISCOVERY_PORT; - use reth_network_peers::pk2id; - use secp256k1::{SecretKey, SECP256K1}; - use crate::{ capability::Capability, p2pstream::P2PMessage, EthVersion, HelloMessage, ProtocolVersion, }; + use alloy_rlp::{Decodable, Encodable, EMPTY_STRING_CODE}; + use reth_network_peers::pk2id; + use secp256k1::{SecretKey, SECP256K1}; #[test] fn test_hello_encoding_round_trip() { @@ -225,7 +229,7 @@ mod tests { protocol_version: ProtocolVersion::V5, client_version: "reth/0.1.0".to_string(), capabilities: vec![Capability::new_static("eth", EthVersion::Eth67 as usize)], - port: DEFAULT_DISCOVERY_PORT, + port: 30303, id, }); @@ -245,7 +249,7 @@ mod tests { protocol_version: ProtocolVersion::V5, client_version: "reth/0.1.0".to_string(), capabilities: vec![Capability::new_static("eth", EthVersion::Eth67 as usize)], - port: DEFAULT_DISCOVERY_PORT, + port: 30303, id, }); @@ -264,7 +268,7 @@ mod tests { protocol_version: ProtocolVersion::V5, client_version: "reth/0.1.0".to_string(), capabilities: vec![Capability::new_static("eth", EthVersion::Eth67 as usize)], - port: DEFAULT_DISCOVERY_PORT, + port: 30303, id, }); diff --git a/crates/net/eth-wire/src/test_utils.rs b/crates/net/eth-wire/src/test_utils.rs index 466bc0f1cefa..2d74cd18403c 100644 --- a/crates/net/eth-wire/src/test_utils.rs +++ b/crates/net/eth-wire/src/test_utils.rs @@ -1,10 +1,10 @@ //! Utilities for testing p2p protocol. use crate::{ - EthVersion, HelloMessageWithProtocols, P2PStream, ProtocolVersion, Status, UnauthedP2PStream, + hello::DEFAULT_TCP_PORT, EthVersion, HelloMessageWithProtocols, P2PStream, ProtocolVersion, + Status, UnauthedP2PStream, }; use reth_chainspec::Chain; -use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_network_peers::pk2id; use reth_primitives::{ForkFilter, Head, B256, U256}; use secp256k1::{SecretKey, SECP256K1}; @@ -22,7 +22,7 @@ pub fn eth_hello() -> (HelloMessageWithProtocols, SecretKey) { protocol_version: ProtocolVersion::V5, client_version: "eth/1.0.0".to_string(), protocols, - port: DEFAULT_DISCOVERY_PORT, + port: DEFAULT_TCP_PORT, id: pk2id(&server_key.public_key(SECP256K1)), }; (hello, server_key) From 1498acb096108764c72681cb977601448c6033bc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 19:55:08 +0200 Subject: [PATCH 370/405] chore: dont enable serde by default for eth-wire (#9339) --- crates/net/eth-wire-types/Cargo.toml | 1 - crates/net/eth-wire-types/src/header.rs | 4 ++-- crates/net/eth-wire/Cargo.toml | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index bda6a327c67e..e9d502850ab3 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -43,7 +43,6 @@ proptest-derive.workspace = true rand.workspace = true [features] -default = ["serde"] arbitrary = [ "reth-primitives/arbitrary", "alloy-chains/arbitrary", diff --git a/crates/net/eth-wire-types/src/header.rs b/crates/net/eth-wire-types/src/header.rs index c9589527b34f..f6b3b8ac4cdb 100644 --- a/crates/net/eth-wire-types/src/header.rs +++ b/crates/net/eth-wire-types/src/header.rs @@ -3,7 +3,6 @@ use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; use reth_codecs::derive_arbitrary; -use serde::{Deserialize, Serialize}; /// Represents the direction for a headers request depending on the `reverse` field of the request. /// > The response must contain a number of block headers, of rising number when reverse is 0, @@ -16,7 +15,8 @@ use serde::{Deserialize, Serialize}; /// /// See also #[derive_arbitrary(rlp)] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum HeadersDirection { /// Falling block number. Falling, diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 242c4b0b31f9..2846c0f7cf02 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -58,15 +58,15 @@ proptest.workspace = true proptest-arbitrary-interop.workspace = true proptest-derive.workspace = true async-stream.workspace = true +serde.workspace = true [features] -default = ["serde"] arbitrary = [ "reth-primitives/arbitrary", "reth-eth-wire-types/arbitrary", "dep:arbitrary", ] -serde = ["dep:serde"] +serde = ["dep:serde", "reth-eth-wire-types/serde"] [[test]] name = "fuzz_roundtrip" From a4ba294fa5c6a6158a0c993ee453afcb4e57197c Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:56:16 +0200 Subject: [PATCH 371/405] chore(meta): remove security link from issue template config (#9350) --- .github/ISSUE_TEMPLATE/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 120ee1dc1137..cfefdb13a695 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,6 +3,3 @@ contact_links: - name: GitHub Discussions url: https://github.com/paradigmxyz/reth/discussions about: Please ask and answer questions here to keep the issue tracker clean. - - name: Security - url: mailto:georgios@paradigm.xyz - about: Please report security vulnerabilities here. From 3b976bc9f5ee7a058604cab6f22d2845dc77b97a Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 6 Jul 2024 20:26:43 +0200 Subject: [PATCH 372/405] chore: make eyre optional in reth-db (#9351) --- crates/storage/db/Cargo.toml | 16 +++++++++------- crates/storage/db/src/mdbx.rs | 24 ++++++++++-------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 336cc75d2700..117ec5ccc7b6 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -18,16 +18,19 @@ reth-primitives.workspace = true reth-primitives-traits.workspace = true reth-fs-util.workspace = true reth-storage-errors.workspace = true -reth-libmdbx = { workspace = true, optional = true, features = [ - "return-borrowed", - "read-tx-timeouts", -] } reth-nippy-jar.workspace = true reth-prune-types.workspace = true reth-stages-types.workspace = true reth-tracing.workspace = true reth-trie-common.workspace = true +# mdbx +reth-libmdbx = { workspace = true, optional = true, features = [ + "return-borrowed", + "read-tx-timeouts", +] } +eyre = { workspace = true, optional = true } + # codecs serde = { workspace = true, default-features = false } @@ -41,7 +44,6 @@ page_size = "0.6.0" thiserror.workspace = true tempfile = { workspace = true, optional = true } derive_more.workspace = true -eyre.workspace = true paste.workspace = true rustc-hash.workspace = true sysinfo = { version = "0.30", default-features = false } @@ -75,8 +77,8 @@ assert_matches.workspace = true [features] default = ["mdbx"] -test-utils = ["tempfile", "arbitrary"] -mdbx = ["reth-libmdbx"] +mdbx = ["dep:reth-libmdbx", "dep:eyre"] +test-utils = ["dep:tempfile", "arbitrary"] bench = [] arbitrary = ["reth-primitives/arbitrary", "reth-db-api/arbitrary"] optimism = [] diff --git a/crates/storage/db/src/mdbx.rs b/crates/storage/db/src/mdbx.rs index 328b9caabfdf..d6947e10bd2b 100644 --- a/crates/storage/db/src/mdbx.rs +++ b/crates/storage/db/src/mdbx.rs @@ -1,12 +1,12 @@ //! Bindings for [MDBX](https://libmdbx.dqdkfa.ru/). -pub use crate::implementation::mdbx::*; -pub use reth_libmdbx::*; - use crate::is_database_empty; use eyre::Context; use std::path::Path; +pub use crate::implementation::mdbx::*; +pub use reth_libmdbx::*; + /// Creates a new database at the specified path if it doesn't exist. Does NOT create tables. Check /// [`init_db`]. pub fn create_db>(path: P, args: DatabaseArguments) -> eyre::Result { @@ -31,21 +31,17 @@ pub fn create_db>(path: P, args: DatabaseArguments) -> eyre::Resu /// Opens up an existing database or creates a new one at the specified path. Creates tables if /// necessary. Read/Write mode. pub fn init_db>(path: P, args: DatabaseArguments) -> eyre::Result { - { - let client_version = args.client_version().clone(); - let db = create_db(path, args)?; - db.create_tables()?; - db.record_client_version(client_version)?; - Ok(db) - } + let client_version = args.client_version().clone(); + let db = create_db(path, args)?; + db.create_tables()?; + db.record_client_version(client_version)?; + Ok(db) } /// Opens up an existing database. Read only mode. It doesn't create it or create tables if missing. pub fn open_db_read_only(path: &Path, args: DatabaseArguments) -> eyre::Result { - { - DatabaseEnv::open(path, DatabaseEnvKind::RO, args) - .with_context(|| format!("Could not open database at path: {}", path.display())) - } + DatabaseEnv::open(path, DatabaseEnvKind::RO, args) + .with_context(|| format!("Could not open database at path: {}", path.display())) } /// Opens up an existing database. Read/Write mode with `WriteMap` enabled. It doesn't create it or From 7579f91d0a6be10c5c6db0eb3eedfde3cbd110c8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 21:29:27 +0200 Subject: [PATCH 373/405] chore: remove cfg'ed use serde (#9352) --- crates/net/eth-wire-types/src/blocks.rs | 18 ++++++------------ crates/net/eth-wire-types/src/broadcast.rs | 15 ++++++--------- crates/net/eth-wire-types/src/message.rs | 10 ++++------ crates/net/eth-wire-types/src/receipts.rs | 7 ++----- crates/net/eth-wire-types/src/state.rs | 7 ++----- crates/net/eth-wire-types/src/status.rs | 4 +--- crates/net/eth-wire-types/src/transactions.rs | 7 ++----- 7 files changed, 23 insertions(+), 45 deletions(-) diff --git a/crates/net/eth-wire-types/src/blocks.rs b/crates/net/eth-wire-types/src/blocks.rs index 536ef7a7512e..f6a8b020d3ef 100644 --- a/crates/net/eth-wire-types/src/blocks.rs +++ b/crates/net/eth-wire-types/src/blocks.rs @@ -1,17 +1,11 @@ //! Implements the `GetBlockHeaders`, `GetBlockBodies`, `BlockHeaders`, and `BlockBodies` message //! types. +use crate::HeadersDirection; use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use reth_codecs_derive::{add_arbitrary_tests, derive_arbitrary}; -#[cfg(any(test, feature = "arbitrary"))] -use reth_primitives::generate_valid_header; use reth_primitives::{BlockBody, BlockHashOrNumber, Header, B256}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::HeadersDirection; - /// A request for a peer to return block headers starting at the requested block. /// The peer must return at most [`limit`](#structfield.limit) headers. /// If the [`reverse`](#structfield.reverse) field is `true`, the headers will be returned starting @@ -23,7 +17,7 @@ use crate::HeadersDirection; /// in the direction specified by [`reverse`](#structfield.reverse). #[derive_arbitrary(rlp)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GetBlockHeaders { /// The block number or hash that the peer should start returning headers from. pub start_block: BlockHashOrNumber, @@ -43,7 +37,7 @@ pub struct GetBlockHeaders { /// The response to [`GetBlockHeaders`], containing headers if any headers were found. #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[add_arbitrary_tests(rlp, 10)] pub struct BlockHeaders( /// The requested headers. @@ -57,7 +51,7 @@ impl<'a> arbitrary::Arbitrary<'a> for BlockHeaders { let mut headers = Vec::with_capacity(headers_count); for _ in 0..headers_count { - headers.push(generate_valid_header( + headers.push(reth_primitives::generate_valid_header( u.arbitrary()?, u.arbitrary()?, u.arbitrary()?, @@ -79,7 +73,7 @@ impl From> for BlockHeaders { /// A request for a peer to return block bodies for the given block hashes. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GetBlockBodies( /// The block hashes to request bodies for. pub Vec, @@ -95,7 +89,7 @@ impl From> for GetBlockBodies { /// any were found. #[derive_arbitrary(rlp, 16)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BlockBodies( /// The requested block bodies, each of which should correspond to a hash in the request. pub Vec, diff --git a/crates/net/eth-wire-types/src/broadcast.rs b/crates/net/eth-wire-types/src/broadcast.rs index 1b5a3b4115bd..6b4d57a0b546 100644 --- a/crates/net/eth-wire-types/src/broadcast.rs +++ b/crates/net/eth-wire-types/src/broadcast.rs @@ -22,13 +22,10 @@ use proptest::{collection::vec, prelude::*}; #[cfg(feature = "arbitrary")] use proptest_arbitrary_interop::arb; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - /// This informs peers of new blocks that have appeared on the network. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct NewBlockHashes( /// New block hashes and the block number for each blockhash. /// Clients should request blocks using a [`GetBlockBodies`](crate::GetBlockBodies) message. @@ -52,7 +49,7 @@ impl NewBlockHashes { /// A block hash _and_ a block number. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BlockHashNumber { /// The block hash pub hash: B256, @@ -75,7 +72,7 @@ impl From for Vec { /// A new block with the current total difficulty, which includes the difficulty of the returned /// block. #[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive_arbitrary(rlp, 25)] pub struct NewBlock { /// A new block. @@ -88,7 +85,7 @@ pub struct NewBlock { /// in a block. #[derive_arbitrary(rlp, 10)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Transactions( /// New transactions for the peer to include in its mempool. pub Vec, @@ -293,7 +290,7 @@ impl From for NewPooledTransactionHashes { /// but have not been included in a block. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct NewPooledTransactionHashes66( /// Transaction hashes for new transactions that have appeared on the network. /// Clients should request the transactions with the given hashes using a @@ -310,7 +307,7 @@ impl From> for NewPooledTransactionHashes66 { /// Same as [`NewPooledTransactionHashes66`] but extends that that beside the transaction hashes, /// the node sends the transaction types and their sizes (as defined in EIP-2718) as well. #[derive(Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct NewPooledTransactionHashes68 { /// Transaction types for new transactions that have appeared on the network. /// diff --git a/crates/net/eth-wire-types/src/message.rs b/crates/net/eth-wire-types/src/message.rs index cbd5ca536108..920f48b894e2 100644 --- a/crates/net/eth-wire-types/src/message.rs +++ b/crates/net/eth-wire-types/src/message.rs @@ -15,8 +15,6 @@ use crate::{EthVersion, SharedTransactions}; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; use reth_primitives::bytes::{Buf, BufMut}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use std::{fmt::Debug, sync::Arc}; /// [`MAX_MESSAGE_SIZE`] is the maximum cap on the size of a protocol message. @@ -36,7 +34,7 @@ pub enum MessageError { /// An `eth` protocol message, containing a message ID and payload. #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ProtocolMessage { /// The unique identifier representing the type of the Ethereum message. pub message_type: EthMessageID, @@ -182,7 +180,7 @@ impl From for ProtocolBroadcastMessage { /// it, `NewPooledTransactionHashes` is renamed as [`NewPooledTransactionHashes66`] and /// [`NewPooledTransactionHashes68`] is defined. #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum EthMessage { /// Represents a Status message required for the protocol handshake. Status(Status), @@ -333,7 +331,7 @@ impl Encodable for EthBroadcastMessage { /// Represents message IDs for eth protocol messages. #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum EthMessageID { /// Status message. Status = 0x00, @@ -437,7 +435,7 @@ impl TryFrom for EthMessageID { /// This can represent either a request or a response, since both include a message payload and /// request id. #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct RequestPair { /// id for the contained request or response message pub request_id: u64, diff --git a/crates/net/eth-wire-types/src/receipts.rs b/crates/net/eth-wire-types/src/receipts.rs index 4816f85548ce..cbe74f9642ec 100644 --- a/crates/net/eth-wire-types/src/receipts.rs +++ b/crates/net/eth-wire-types/src/receipts.rs @@ -4,13 +4,10 @@ use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use reth_codecs_derive::derive_arbitrary; use reth_primitives::{ReceiptWithBloom, B256}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - /// A request for transaction receipts from the given block hashes. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GetReceipts( /// The block hashes to request receipts for. pub Vec, @@ -20,7 +17,7 @@ pub struct GetReceipts( /// requested. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Receipts( /// Each receipt hash should correspond to a block hash in the request. pub Vec>, diff --git a/crates/net/eth-wire-types/src/state.rs b/crates/net/eth-wire-types/src/state.rs index 5f3dc833950f..aa1e064d0818 100644 --- a/crates/net/eth-wire-types/src/state.rs +++ b/crates/net/eth-wire-types/src/state.rs @@ -4,15 +4,12 @@ use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use reth_codecs_derive::derive_arbitrary; use reth_primitives::{Bytes, B256}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - /// A request for state tree nodes corresponding to the given hashes. /// This message was removed in `eth/67`, only clients running `eth/66` or earlier will respond to /// this message. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GetNodeData(pub Vec); /// The response to [`GetNodeData`], containing the state tree nodes or contract bytecode @@ -22,7 +19,7 @@ pub struct GetNodeData(pub Vec); /// This message was removed in `eth/67`. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct NodeData(pub Vec); #[cfg(test)] diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 85d09dcd5923..8d4626900661 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -5,8 +5,6 @@ use alloy_rlp::{RlpDecodable, RlpEncodable}; use reth_chainspec::{ChainSpec, MAINNET}; use reth_codecs_derive::derive_arbitrary; use reth_primitives::{hex, EthereumHardfork, ForkId, Head, B256, U256}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; /// The status message is used in the eth protocol handshake to ensure that peers are on the same @@ -16,7 +14,7 @@ use std::fmt::{Debug, Display}; /// hash. This information should be treated as untrusted. #[derive_arbitrary(rlp)] #[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Status { /// The current protocol version. For example, peers running `eth/66` would have a version of /// 66. diff --git a/crates/net/eth-wire-types/src/transactions.rs b/crates/net/eth-wire-types/src/transactions.rs index d0a42d49beec..a5bf40b798b5 100644 --- a/crates/net/eth-wire-types/src/transactions.rs +++ b/crates/net/eth-wire-types/src/transactions.rs @@ -7,13 +7,10 @@ use reth_primitives::{ transaction::TransactionConversionError, PooledTransactionsElement, TransactionSigned, B256, }; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - /// A list of transaction hashes that the peer would like transaction bodies for. #[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GetPooledTransactions( /// The transaction hashes to request transaction bodies for. pub Vec, @@ -48,7 +45,7 @@ where Deref, Constructor, )] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PooledTransactions( /// The transaction bodies, each of which should correspond to a requested hash. pub Vec, From abf3aff194de97386d24c0b75bce502e380f83be Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 6 Jul 2024 21:43:29 +0200 Subject: [PATCH 374/405] fix: encode block as is in debug_getRawBlock (#9353) --- crates/rpc/rpc-builder/tests/it/http.rs | 2 +- crates/rpc/rpc/src/debug.rs | 20 ++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 671c5739ac2e..fd4a2b1db212 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -234,7 +234,7 @@ where let block_id = BlockId::Number(BlockNumberOrTag::default()); DebugApiClient::raw_header(client, block_id).await.unwrap(); - DebugApiClient::raw_block(client, block_id).await.unwrap(); + DebugApiClient::raw_block(client, block_id).await.unwrap_err(); DebugApiClient::raw_transaction(client, B256::default()).await.unwrap(); DebugApiClient::raw_receipts(client, block_id).await.unwrap(); assert!(is_unimplemented(DebugApiClient::bad_blocks(client).await.err().unwrap())); diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 67363dff31c1..8e04c6256661 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -6,8 +6,7 @@ use jsonrpsee::core::RpcResult; use reth_chainspec::EthereumHardforks; use reth_evm::ConfigureEvmEnv; use reth_primitives::{ - Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, Withdrawals, - B256, U256, + Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256, }; use reth_provider::{ BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, @@ -674,17 +673,14 @@ where /// Handler for `debug_getRawBlock` async fn raw_block(&self, block_id: BlockId) -> RpcResult { - let block = self.inner.provider.block_by_id(block_id).to_rpc_result()?; - + let block = self + .inner + .provider + .block_by_id(block_id) + .to_rpc_result()? + .ok_or_else(|| EthApiError::UnknownBlockNumber)?; let mut res = Vec::new(); - if let Some(mut block) = block { - // In RPC withdrawals are always present - if block.withdrawals.is_none() { - block.withdrawals = Some(Withdrawals::default()); - } - block.encode(&mut res); - } - + block.encode(&mut res); Ok(res.into()) } From 2adf2d33649f952eb8ec71f5eaf4d8e9660ae4d6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 7 Jul 2024 11:07:52 +0200 Subject: [PATCH 375/405] chore: remove unused private stream type (#9357) --- crates/net/p2p/src/full_block.rs | 102 ------------------------------- 1 file changed, 102 deletions(-) diff --git a/crates/net/p2p/src/full_block.rs b/crates/net/p2p/src/full_block.rs index fcee4c52b13e..724290ec3f65 100644 --- a/crates/net/p2p/src/full_block.rs +++ b/crates/net/p2p/src/full_block.rs @@ -4,7 +4,6 @@ use crate::{ error::PeerRequestResult, headers::client::{HeadersClient, SingleHeaderRequest}, }; -use futures::Stream; use reth_consensus::{Consensus, ConsensusError}; use reth_eth_wire_types::HeadersDirection; use reth_network_peers::WithPeerId; @@ -634,69 +633,6 @@ where } } -/// A type that buffers the result of a range request so we can return it as a `Stream`. -struct FullBlockRangeStream -where - Client: BodiesClient + HeadersClient, -{ - /// The inner [`FetchFullBlockRangeFuture`] that is polled. - inner: FetchFullBlockRangeFuture, - /// The blocks that have been received so far. - /// - /// If this is `None` then the request is still in progress. If the vec is empty, then all of - /// the response values have been consumed. - blocks: Option>, -} - -impl From> for FullBlockRangeStream -where - Client: BodiesClient + HeadersClient, -{ - fn from(inner: FetchFullBlockRangeFuture) -> Self { - Self { inner, blocks: None } - } -} - -impl Stream for FullBlockRangeStream -where - Client: BodiesClient + HeadersClient + Unpin + 'static, -{ - type Item = SealedBlock; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.get_mut(); - - // If all blocks have been consumed, then return `None`. - if let Some(blocks) = &mut this.blocks { - if blocks.is_empty() { - // Stream is finished - return Poll::Ready(None) - } - - // return the next block if it's ready - the vec should be in ascending order since it - // is reversed right after it is received from the future, so we can just pop() the - // elements to return them from the stream in descending order - return Poll::Ready(blocks.pop()) - } - - // poll the inner future if the blocks are not yet ready - let mut blocks = ready!(Pin::new(&mut this.inner).poll(cx)); - - // the blocks are returned in descending order, reverse the list so we can just pop() the - // vec to yield the next block in the stream - blocks.reverse(); - - // pop the first block from the vec as the first stream element and store the rest - let first_result = blocks.pop(); - - // if the inner future is ready, then we can return the blocks - this.blocks = Some(blocks); - - // return the first block - Poll::Ready(first_result) - } -} - /// A request for a range of full blocks. Polling this will poll the inner headers and bodies /// futures until they return responses. It will return either the header or body result, depending /// on which future successfully returned. @@ -742,7 +678,6 @@ enum RangeResponseResult { mod tests { use super::*; use crate::test_utils::TestFullBlockClient; - use futures::StreamExt; use std::ops::Range; #[tokio::test] @@ -808,43 +743,6 @@ mod tests { } } - #[tokio::test] - async fn download_full_block_range_stream() { - let client = TestFullBlockClient::default(); - let (header, body) = insert_headers_into_client(&client, 0..50); - let client = FullBlockClient::test_client(client); - - let future = client.get_full_block_range(header.hash(), 1); - let mut stream = FullBlockRangeStream::from(future); - - // ensure only block in the stream is the one we requested - let received = stream.next().await.expect("response should not be None"); - assert_eq!(received, SealedBlock::new(header.clone(), body.clone())); - - // stream should be done now - assert_eq!(stream.next().await, None); - - // there are 11 total blocks - let future = client.get_full_block_range(header.hash(), 11); - let mut stream = FullBlockRangeStream::from(future); - - // check first header - let received = stream.next().await.expect("response should not be None"); - let mut curr_number = received.number; - assert_eq!(received, SealedBlock::new(header.clone(), body.clone())); - - // check the rest of the headers - for _ in 0..10 { - let received = stream.next().await.expect("response should not be None"); - assert_eq!(received.number, curr_number - 1); - curr_number = received.number; - } - - // ensure stream is done - let received = stream.next().await; - assert!(received.is_none()); - } - #[tokio::test] async fn download_full_block_range_over_soft_limit() { // default soft limit is 20, so we will request 50 blocks From 6e4bc4f1f796ceceaa358a5cdae700f76af6d427 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 09:24:23 +0000 Subject: [PATCH 376/405] chore(deps): weekly `cargo update` (#9354) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: Matthias Seitz --- Cargo.lock | 201 ++++++++++++++++++++++++----------------------------- 1 file changed, 92 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5553427b3bf3..9a98619a4cf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,7 +348,7 @@ checksum = "d83524c1f6162fcb5b0decf775498a125066c86dda6066ed609531b0e912f85a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -548,7 +548,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -565,7 +565,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", "syn-solidity", "tiny-keccak", ] @@ -583,7 +583,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.68", + "syn 2.0.69", "syn-solidity", ] @@ -786,7 +786,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -1006,7 +1006,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -1017,7 +1017,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -1055,7 +1055,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -1169,15 +1169,12 @@ dependencies = [ "itertools 0.12.1", "lazy_static", "lazycell", - "log", - "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.68", - "which", + "syn 2.0.69", ] [[package]] @@ -1373,7 +1370,7 @@ checksum = "6be9c93793b60dac381af475b98634d4b451e28336e72218cad9a20176218dbc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", "synstructure", ] @@ -1482,7 +1479,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -1560,9 +1557,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "castaway" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ "rustversion", ] @@ -1702,7 +1699,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2188,7 +2185,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2212,7 +2209,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2223,7 +2220,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2329,7 +2326,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2342,7 +2339,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2456,7 +2453,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2613,7 +2610,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -2624,7 +2621,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -3318,7 +3315,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -3639,15 +3636,6 @@ dependencies = [ "hmac 0.8.1", ] -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "hostname" version = "0.3.1" @@ -3827,16 +3815,13 @@ dependencies = [ [[package]] name = "iai-callgrind" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b780c98c212412a6d54b5d3d7cf62fb20d88cd32c0653d6df2a03d63e52a903" +checksum = "146bf76de95f03c5f4b118f0f2f350ef18df47cc0595755bd29d8f668209466c" dependencies = [ "bincode", - "bindgen", - "cc", "iai-callgrind-macros", "iai-callgrind-runner", - "regex", ] [[package]] @@ -3848,14 +3833,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] name = "iai-callgrind-runner" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8d015de54e6431004efede625ee79e3b4105dcb2100cd574de914e06fd4f7c" +checksum = "60484b2e469ef4f1af6f196af738889ff375151dd11ac223647ed8a97529107d" dependencies = [ "serde", ] @@ -3998,7 +3983,7 @@ checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -4402,7 +4387,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -4619,15 +4604,14 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.41.2" +version = "0.41.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8130a8269e65a2554d55131c770bdf4bcd94d2b8d4efb24ca23699be65066c05" +checksum = "a5a8920cbd8540059a01950c1e5c96ea8d89eb50c51cd366fc18bdf540a6e48f" dependencies = [ "either", "fnv", "futures", "futures-timer", - "instant", "libp2p-identity", "multiaddr", "multihash", @@ -4643,6 +4627,7 @@ dependencies = [ "tracing", "unsigned-varint 0.8.0", "void", + "web-time", ] [[package]] @@ -5027,7 +5012,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -5279,7 +5264,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -5322,9 +5307,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "op-alloy-consensus" @@ -5587,7 +5572,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -5616,7 +5601,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -5775,7 +5760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -5917,7 +5902,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -6700,7 +6685,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -7504,7 +7489,7 @@ dependencies = [ "quote", "regex", "serial_test", - "syn 2.0.68", + "syn 2.0.69", "trybuild", ] @@ -9249,9 +9234,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ad2bbb0ae5100a07b7a6f2ed7ab5fd0045551a4c507989b7a620046ea3efdc" +checksum = "af947d0ca10a2f3e00c7ec1b515b7c83e5cb3fa62d4c11a64301d9eec54440e9" dependencies = [ "sdd", ] @@ -9387,9 +9372,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -9405,13 +9390,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -9485,7 +9470,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -9510,7 +9495,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -9761,12 +9746,12 @@ checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" [[package]] name = "stability" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -9812,7 +9797,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -9880,9 +9865,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "201fcda3845c23e8212cd466bfebf0bd20694490fc0356ae8e428e0824a915a6" dependencies = [ "proc-macro2", "quote", @@ -9898,7 +9883,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -9915,7 +9900,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -10000,7 +9985,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -10039,7 +10024,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -10177,9 +10162,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" dependencies = [ "tinyvec_macros", ] @@ -10217,7 +10202,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -10415,7 +10400,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -10567,9 +10552,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a5f13f11071020bb12de7a16b925d2d58636175c20c11dc5f96cb64bb6c9b3" +checksum = "5b1e5645f2ee8025c2f1d75e1138f2dd034d74e6ba54620f3c569ba2a2a1ea06" dependencies = [ "glob", "serde", @@ -10866,7 +10851,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", "wasm-bindgen-shared", ] @@ -10900,7 +10885,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10935,24 +10920,22 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.26.3" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "rustls-pki-types", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "which" -version = "4.4.2" +name = "webpki-roots" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ - "either", - "home", - "once_cell", - "rustix", + "rustls-pki-types", ] [[package]] @@ -11041,7 +11024,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -11052,7 +11035,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -11310,7 +11293,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", "synstructure", ] @@ -11331,7 +11314,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -11351,7 +11334,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", "synstructure", ] @@ -11372,7 +11355,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] @@ -11394,32 +11377,32 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.69", ] [[package]] name = "zstd" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", "pkg-config", From 61f2505d4dd2ea2befd37be712d6d4496a5dc844 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:04:10 +0200 Subject: [PATCH 377/405] test(tx-pool): add unit tests for `BestTransactions` `add_new_transactions` (#9355) --- crates/transaction-pool/src/pool/best.rs | 118 +++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/crates/transaction-pool/src/pool/best.rs b/crates/transaction-pool/src/pool/best.rs index 23f2652c30ca..1b4a8eafe30b 100644 --- a/crates/transaction-pool/src/pool/best.rs +++ b/crates/transaction-pool/src/pool/best.rs @@ -268,7 +268,9 @@ mod tests { use crate::{ pool::pending::PendingPool, test_utils::{MockOrdering, MockTransaction, MockTransactionFactory}, + Priority, }; + use reth_primitives::U256; #[test] fn test_best_iter() { @@ -478,4 +480,120 @@ mod tests { // No more transactions should be returned assert!(best.next().is_none()); } + + #[test] + fn test_best_add_transaction_with_next_nonce() { + let mut pool = PendingPool::new(MockOrdering::default()); + let mut f = MockTransactionFactory::default(); + + // Add 5 transactions with increasing nonces to the pool + let num_tx = 5; + let tx = MockTransaction::eip1559(); + for nonce in 0..num_tx { + let tx = tx.clone().rng_hash().with_nonce(nonce); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx), 0); + } + + // Create a BestTransactions iterator from the pool + let mut best = pool.best(); + + // Use a broadcast channel for transaction updates + let (tx_sender, tx_receiver) = + tokio::sync::broadcast::channel::>(1000); + best.new_transaction_receiver = Some(tx_receiver); + + // Create a new transaction with nonce 5 and validate it + let new_tx = MockTransaction::eip1559().rng_hash().with_nonce(5); + let valid_new_tx = f.validated(new_tx); + + // Send the new transaction through the broadcast channel + let pending_tx = PendingTransaction { + submission_id: 10, + transaction: Arc::new(valid_new_tx.clone()), + priority: Priority::Value(U256::from(1000)), + }; + tx_sender.send(pending_tx.clone()).unwrap(); + + // Add new transactions to the iterator + best.add_new_transactions(); + + // Verify that the new transaction has been added to the 'all' map + assert_eq!(best.all.len(), 6); + assert!(best.all.contains_key(valid_new_tx.id())); + + // Verify that the new transaction has been added to the 'independent' set + assert_eq!(best.independent.len(), 2); + assert!(best.independent.contains(&pending_tx)); + } + + #[test] + fn test_best_add_transaction_with_ancestor() { + // Initialize a new PendingPool with default MockOrdering and MockTransactionFactory + let mut pool = PendingPool::new(MockOrdering::default()); + let mut f = MockTransactionFactory::default(); + + // Add 5 transactions with increasing nonces to the pool + let num_tx = 5; + let tx = MockTransaction::eip1559(); + for nonce in 0..num_tx { + let tx = tx.clone().rng_hash().with_nonce(nonce); + let valid_tx = f.validated(tx); + pool.add_transaction(Arc::new(valid_tx), 0); + } + + // Create a BestTransactions iterator from the pool + let mut best = pool.best(); + + // Use a broadcast channel for transaction updates + let (tx_sender, tx_receiver) = + tokio::sync::broadcast::channel::>(1000); + best.new_transaction_receiver = Some(tx_receiver); + + // Create a new transaction with nonce 5 and validate it + let base_tx1 = MockTransaction::eip1559().rng_hash().with_nonce(5); + let valid_new_tx1 = f.validated(base_tx1.clone()); + + // Send the new transaction through the broadcast channel + let pending_tx1 = PendingTransaction { + submission_id: 10, + transaction: Arc::new(valid_new_tx1.clone()), + priority: Priority::Value(U256::from(1000)), + }; + tx_sender.send(pending_tx1.clone()).unwrap(); + + // Add new transactions to the iterator + best.add_new_transactions(); + + // Verify that the new transaction has been added to the 'all' map + assert_eq!(best.all.len(), 6); + assert!(best.all.contains_key(valid_new_tx1.id())); + + // Verify that the new transaction has been added to the 'independent' set + assert_eq!(best.independent.len(), 2); + assert!(best.independent.contains(&pending_tx1)); + + // Attempt to add a new transaction with a different nonce (not a duplicate) + let base_tx2 = base_tx1.with_nonce(6); + let valid_new_tx2 = f.validated(base_tx2); + + // Send the new transaction through the broadcast channel + let pending_tx2 = PendingTransaction { + submission_id: 11, // Different submission ID + transaction: Arc::new(valid_new_tx2.clone()), + priority: Priority::Value(U256::from(1000)), + }; + tx_sender.send(pending_tx2.clone()).unwrap(); + + // Add new transactions to the iterator + best.add_new_transactions(); + + // Verify that the new transaction has been added to 'all' + assert_eq!(best.all.len(), 7); + assert!(best.all.contains_key(valid_new_tx2.id())); + + // Verify that the new transaction has not been added to the 'independent' set + assert_eq!(best.independent.len(), 2); + assert!(!best.independent.contains(&pending_tx2)); + } } From 1b3209ae0e937fb81debe53ca2eb95f52b95141b Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Mon, 8 Jul 2024 12:09:15 +0200 Subject: [PATCH 378/405] feat: add entrypoint and main loop to EngineApiTreeHandlerImpl (#9334) --- .../beacon/src/engine/invalid_headers.rs | 3 +- crates/engine/tree/src/engine.rs | 9 +- crates/engine/tree/src/tree/mod.rs | 135 ++++++++++++++++-- 3 files changed, 131 insertions(+), 16 deletions(-) diff --git a/crates/consensus/beacon/src/engine/invalid_headers.rs b/crates/consensus/beacon/src/engine/invalid_headers.rs index ebce1faf92dc..fbe6bf462bb3 100644 --- a/crates/consensus/beacon/src/engine/invalid_headers.rs +++ b/crates/consensus/beacon/src/engine/invalid_headers.rs @@ -23,7 +23,8 @@ pub struct InvalidHeaderCache { } impl InvalidHeaderCache { - pub(crate) fn new(max_length: u32) -> Self { + /// Invalid header cache constructor. + pub fn new(max_length: u32) -> Self { Self { headers: LruMap::new(ByLength::new(max_length)), metrics: Default::default() } } diff --git a/crates/engine/tree/src/engine.rs b/crates/engine/tree/src/engine.rs index 25d4fabf7832..9b965e892268 100644 --- a/crates/engine/tree/src/engine.rs +++ b/crates/engine/tree/src/engine.rs @@ -3,6 +3,7 @@ use crate::{ chain::{ChainHandler, FromOrchestrator, HandlerEvent}, download::{BlockDownloader, DownloadAction, DownloadOutcome}, + tree::TreeEvent, }; use futures::{Stream, StreamExt}; use reth_beacon_consensus::BeaconEngineMessage; @@ -150,7 +151,6 @@ pub struct EngineApiRequestHandler { to_tree: Sender>>, /// channel to receive messages from the tree. from_tree: UnboundedReceiver, - // TODO add db controller } impl EngineApiRequestHandler @@ -178,13 +178,16 @@ where } fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { - todo!("poll tree and handle db") + todo!("poll tree") } } /// Events emitted by the engine API handler. #[derive(Debug)] -pub enum EngineApiEvent {} +pub enum EngineApiEvent { + /// Bubbled from tree. + FromTree(TreeEvent), +} #[derive(Debug)] pub enum FromEngine { diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index a4ccea51044e..9ac472961033 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -1,12 +1,18 @@ -use crate::{backfill::BackfillAction, engine::DownloadRequest}; -use reth_beacon_consensus::{ForkchoiceStateTracker, InvalidHeaderCache, OnForkChoiceUpdated}; +use crate::{ + backfill::BackfillAction, + chain::FromOrchestrator, + engine::{DownloadRequest, EngineApiEvent, FromEngine}, +}; +use reth_beacon_consensus::{ + BeaconEngineMessage, ForkchoiceStateTracker, InvalidHeaderCache, OnForkChoiceUpdated, +}; use reth_blockchain_tree::{ error::InsertBlockErrorKind, BlockAttachment, BlockBuffer, BlockStatus, }; use reth_blockchain_tree_api::{error::InsertBlockError, InsertPayloadOk}; use reth_consensus::{Consensus, PostExecutionInput}; use reth_engine_primitives::EngineTypes; -use reth_errors::{ConsensusError, ProviderResult}; +use reth_errors::{ConsensusError, ProviderResult, RethError}; use reth_evm::execute::{BlockExecutorProvider, Executor}; use reth_payload_primitives::PayloadTypes; use reth_payload_validator::ExecutionPayloadValidator; @@ -27,8 +33,9 @@ use reth_trie::{updates::TrieUpdates, HashedPostState}; use std::{ collections::{BTreeMap, HashMap}, marker::PhantomData, - sync::Arc, + sync::{mpsc::Receiver, Arc}, }; +use tokio::sync::mpsc::UnboundedSender; use tracing::*; mod memory_overlay; @@ -72,7 +79,7 @@ impl ExecutedBlock { } /// Keeps track of the state of the tree. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct TreeState { /// All executed blocks by hash. blocks_by_hash: HashMap, @@ -129,11 +136,22 @@ pub struct EngineApiTreeState { invalid_headers: InvalidHeaderCache, } +impl EngineApiTreeState { + fn new(block_buffer_limit: u32, max_invalid_header_cache_length: u32) -> Self { + Self { + invalid_headers: InvalidHeaderCache::new(max_invalid_header_cache_length), + buffer: BlockBuffer::new(block_buffer_limit), + tree_state: TreeState::default(), + forkchoice_state_tracker: ForkchoiceStateTracker::default(), + } + } +} + /// The type responsible for processing engine API requests. /// /// TODO: design: should the engine handler functions also accept the response channel or return the /// result and the caller redirects the response -pub trait EngineApiTreeHandler: Send + Sync { +pub trait EngineApiTreeHandler { /// The engine type that this handler is for. type Engine: EngineTypes; @@ -170,7 +188,7 @@ pub trait EngineApiTreeHandler: Send + Sync { &mut self, state: ForkchoiceState, attrs: Option<::PayloadAttributes>, - ) -> TreeOutcome>; + ) -> TreeOutcome>; } /// The outcome of a tree operation. @@ -220,6 +238,8 @@ pub struct EngineApiTreeHandlerImpl { consensus: Arc, payload_validator: ExecutionPayloadValidator, state: EngineApiTreeState, + incoming: Receiver>>, + outgoing: UnboundedSender, /// (tmp) The flag indicating whether the pipeline is active. is_pipeline_active: bool, _marker: PhantomData, @@ -227,10 +247,101 @@ pub struct EngineApiTreeHandlerImpl { impl EngineApiTreeHandlerImpl where - P: BlockReader + StateProviderFactory, + P: BlockReader + StateProviderFactory + Clone + 'static, E: BlockExecutorProvider, - T: EngineTypes, + T: EngineTypes + 'static, { + #[allow(clippy::too_many_arguments)] + fn new( + provider: P, + executor_provider: E, + consensus: Arc, + payload_validator: ExecutionPayloadValidator, + incoming: Receiver>>, + outgoing: UnboundedSender, + state: EngineApiTreeState, + ) -> Self { + Self { + provider, + executor_provider, + consensus, + payload_validator, + incoming, + outgoing, + is_pipeline_active: false, + state, + _marker: PhantomData, + } + } + + #[allow(clippy::too_many_arguments)] + fn spawn_new( + provider: P, + executor_provider: E, + consensus: Arc, + payload_validator: ExecutionPayloadValidator, + incoming: Receiver>>, + state: EngineApiTreeState, + ) -> UnboundedSender { + let (outgoing, rx) = tokio::sync::mpsc::unbounded_channel(); + let task = Self::new( + provider, + executor_provider, + consensus, + payload_validator, + incoming, + outgoing.clone(), + state, + ); + std::thread::Builder::new().name("Tree Task".to_string()).spawn(|| task.run()).unwrap(); + outgoing + } + + fn run(mut self) { + loop { + while let Ok(msg) = self.incoming.recv() { + match msg { + FromEngine::Event(event) => match event { + FromOrchestrator::BackfillSyncFinished => { + todo!() + } + FromOrchestrator::BackfillSyncStarted => { + todo!() + } + }, + FromEngine::Request(request) => match request { + BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => { + let output = self.on_forkchoice_updated(state, payload_attrs); + if let Err(err) = tx.send(output.outcome) { + error!("Failed to send event: {err:?}"); + } + } + BeaconEngineMessage::NewPayload { payload, cancun_fields, tx } => { + let output = self.on_new_payload(payload, cancun_fields); + if let Err(err) = tx.send(output.map(|o| o.outcome).map_err(|e| { + reth_beacon_consensus::BeaconOnNewPayloadError::Internal(Box::new( + e, + )) + })) { + error!("Failed to send event: {err:?}"); + } + } + BeaconEngineMessage::TransitionConfigurationExchanged => { + todo!() + } + }, + FromEngine::DownloadedBlocks(blocks) => { + if let Some(event) = self.on_downloaded(blocks) { + if let Err(err) = self.outgoing.send(EngineApiEvent::FromTree(event)) { + error!("Failed to send event: {err:?}"); + } + } + } + } + } + } + } + /// Return block from database or in-memory state by hash. fn block_by_hash(&self, hash: B256) -> ProviderResult> { // check database first @@ -463,9 +574,9 @@ where impl EngineApiTreeHandler for EngineApiTreeHandlerImpl where - P: BlockReader + StateProviderFactory + Clone, + P: BlockReader + StateProviderFactory + Clone + 'static, E: BlockExecutorProvider, - T: EngineTypes, + T: EngineTypes + 'static, { type Engine = T; @@ -588,7 +699,7 @@ where &mut self, state: ForkchoiceState, attrs: Option<::PayloadAttributes>, - ) -> TreeOutcome> { + ) -> TreeOutcome> { todo!() } } From 5b85567aa64c595e8c371db435c3cdd43fef133c Mon Sep 17 00:00:00 2001 From: d3or <97639237+d3or@users.noreply.github.com> Date: Mon, 8 Jul 2024 06:26:13 -0400 Subject: [PATCH 379/405] feat: adds eth request panels to grafana (#9026) --- etc/grafana/dashboards/overview.json | 523 +++++++++++++++++++++++++++ 1 file changed, 523 insertions(+) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index e56c94b112c9..d9b3cdefd0e5 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -8177,6 +8177,529 @@ ], "title": "Number of ExExs", "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 298 + }, + "id": 226, + "panels": [], + "title": "Eth Requests", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "http" + }, + "properties": [ + { + "id": "displayName", + "value": "HTTP" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ws" + }, + "properties": [ + { + "id": "displayName", + "value": "WebSocket" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 299 + }, + "id": 225, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(reth_network_eth_headers_requests_received_total{instance=~\"$instance\"}[$__rate_interval])", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Headers Requests/s", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Headers Requests Received", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "http" + }, + "properties": [ + { + "id": "displayName", + "value": "HTTP" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ws" + }, + "properties": [ + { + "id": "displayName", + "value": "WebSocket" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 299 + }, + "id": 227, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(reth_network_eth_receipts_requests_received_total{instance=~\"$instance\"}[$__rate_interval])", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Receipts Requests/s", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Receipts Requests Received", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "http" + }, + "properties": [ + { + "id": "displayName", + "value": "HTTP" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ws" + }, + "properties": [ + { + "id": "displayName", + "value": "WebSocket" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 306 + }, + "id": 235, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(reth_network_eth_bodies_requests_received_total{instance=~\"$instance\"}[$__rate_interval])", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Bodies Requests/s", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Bodies Requests Received", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "http" + }, + "properties": [ + { + "id": "displayName", + "value": "HTTP" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ws" + }, + "properties": [ + { + "id": "displayName", + "value": "WebSocket" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 306 + }, + "id": 234, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(reth_network_eth_node_data_requests_received_total{instance=~\"$instance\"}[$__rate_interval])", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "legendFormat": "Node Data Requests/s", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Node Data Requests Received", + "type": "timeseries" } ], "refresh": "30s", From 9e45c82fe9bb11bc1a26e617f7a8acfc05bddafc Mon Sep 17 00:00:00 2001 From: SHio <161311766+shiowp@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:09:48 +0100 Subject: [PATCH 380/405] feat: Add required trait impls for OpEthApi (#9341) --- Cargo.lock | 8 +- crates/optimism/rpc/Cargo.toml | 14 ++- crates/optimism/rpc/src/eth/mod.rs | 157 ++++++++++++++++++++++++++++- crates/optimism/rpc/src/lib.rs | 2 +- 4 files changed, 173 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a98619a4cf8..1782ad8cda48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7959,13 +7959,17 @@ name = "reth-optimism-rpc" version = "1.0.0" dependencies = [ "alloy-primitives", + "parking_lot 0.12.3", "reth-chainspec", "reth-errors", - "reth-rpc", + "reth-evm", + "reth-provider", "reth-rpc-eth-api", "reth-rpc-eth-types", - "reth-rpc-server-types", "reth-rpc-types", + "reth-tasks", + "reth-transaction-pool", + "tokio", ] [[package]] diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 9853e8f914b5..f7599b4a07a3 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -12,13 +12,19 @@ workspace = true [dependencies] # reth -reth-rpc.workspace = true +reth-errors.workspace = true +reth-evm.workspace = true reth-rpc-eth-api.workspace = true reth-rpc-eth-types.workspace = true -reth-rpc-server-types.workspace = true reth-rpc-types.workspace = true -reth-errors.workspace = true reth-chainspec.workspace = true +reth-provider.workspace = true +reth-tasks = { workspace = true, features = ["rayon"] } +reth-transaction-pool.workspace = true # ethereum -alloy-primitives.workspace = true \ No newline at end of file +alloy-primitives.workspace = true + +# async +parking_lot.workspace = true +tokio.workspace = true \ No newline at end of file diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index b9836360ad64..b73311cb7768 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -3,9 +3,24 @@ use alloy_primitives::{Address, U64}; use reth_chainspec::ChainInfo; use reth_errors::RethResult; -use reth_rpc_eth_api::helpers::EthApiSpec; +use reth_evm::ConfigureEvm; +use reth_provider::{ + BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, +}; +use reth_rpc_eth_api::{ + helpers::{ + Call, EthApiSpec, EthBlocks, EthCall, EthFees, EthSigner, EthState, EthTransactions, + LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction, + SpawnBlocking, Trace, + }, + RawTransactionForwarder, +}; +use reth_rpc_eth_types::{EthStateCache, PendingBlock}; use reth_rpc_types::SyncStatus; +use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; +use reth_transaction_pool::TransactionPool; use std::future::Future; +use tokio::sync::Mutex; /// OP-Reth `Eth` API implementation. /// @@ -54,3 +69,143 @@ impl EthApiSpec for OpEthApi { self.inner.sync_status() } } + +impl LoadBlock for OpEthApi { + fn provider(&self) -> impl BlockReaderIdExt { + LoadBlock::provider(&self.inner) + } + + fn cache(&self) -> &reth_rpc_eth_types::EthStateCache { + self.inner.cache() + } +} + +impl LoadPendingBlock for OpEthApi { + fn provider( + &self, + ) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory { + self.inner.provider() + } + + fn pool(&self) -> impl TransactionPool { + self.inner.pool() + } + + fn pending_block(&self) -> &Mutex> { + self.inner.pending_block() + } + + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} + +impl SpawnBlocking for OpEthApi { + fn io_task_spawner(&self) -> impl TaskSpawner { + self.inner.io_task_spawner() + } + + fn tracing_task_pool(&self) -> &BlockingTaskPool { + self.inner.tracing_task_pool() + } +} + +impl LoadReceipt for OpEthApi { + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } +} + +impl LoadFee for OpEthApi { + fn provider(&self) -> impl reth_provider::BlockIdReader + HeaderProvider + ChainSpecProvider { + LoadFee::provider(&self.inner) + } + + fn cache(&self) -> &EthStateCache { + LoadFee::cache(&self.inner) + } + + fn gas_oracle(&self) -> &reth_rpc_eth_types::GasPriceOracle { + self.inner.gas_oracle() + } + + fn fee_history_cache(&self) -> &reth_rpc_eth_types::FeeHistoryCache { + self.inner.fee_history_cache() + } +} + +impl Call for OpEthApi { + fn call_gas_limit(&self) -> u64 { + self.inner.call_gas_limit() + } + + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} + +impl LoadState for OpEthApi { + fn provider(&self) -> impl StateProviderFactory { + LoadState::provider(&self.inner) + } + + fn cache(&self) -> &EthStateCache { + LoadState::cache(&self.inner) + } + + fn pool(&self) -> impl TransactionPool { + LoadState::pool(&self.inner) + } +} + +impl LoadTransaction for OpEthApi { + type Pool = Eth::Pool; + + fn provider(&self) -> impl reth_provider::TransactionsProvider { + LoadTransaction::provider(&self.inner) + } + + fn cache(&self) -> &EthStateCache { + LoadTransaction::cache(&self.inner) + } + + fn pool(&self) -> &Self::Pool { + LoadTransaction::pool(&self.inner) + } +} + +impl EthTransactions for OpEthApi { + fn provider(&self) -> impl BlockReaderIdExt { + EthTransactions::provider(&self.inner) + } + + fn raw_tx_forwarder(&self) -> Option> { + self.inner.raw_tx_forwarder() + } + + fn signers(&self) -> &parking_lot::RwLock>> { + self.inner.signers() + } +} + +impl EthBlocks for OpEthApi { + fn provider(&self) -> impl HeaderProvider { + EthBlocks::provider(&self.inner) + } +} + +impl EthState for OpEthApi { + fn max_proof_window(&self) -> u64 { + self.inner.max_proof_window() + } +} + +impl EthCall for OpEthApi {} + +impl EthFees for OpEthApi {} + +impl Trace for OpEthApi { + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} diff --git a/crates/optimism/rpc/src/lib.rs b/crates/optimism/rpc/src/lib.rs index f2059fac16bd..cad90bc42bf8 100644 --- a/crates/optimism/rpc/src/lib.rs +++ b/crates/optimism/rpc/src/lib.rs @@ -5,7 +5,7 @@ html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] -// #![cfg_attr(not(test), warn(unused_crate_dependencies))] TODO: enable +#![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod eth; From 92fb25137e71b21d54cb0fcc83399b1f9311224d Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 8 Jul 2024 14:48:22 +0200 Subject: [PATCH 381/405] feat: eip-7251 (#9335) --- crates/ethereum/evm/src/execute.rs | 11 +- crates/evm/execution-errors/src/lib.rs | 8 ++ crates/evm/src/system_calls.rs | 136 +++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index c83931df9a36..cfee186c6334 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -11,7 +11,10 @@ use reth_evm::{ BatchExecutor, BlockExecutionError, BlockExecutionInput, BlockExecutionOutput, BlockExecutorProvider, BlockValidationError, Executor, ProviderError, }, - system_calls::{apply_beacon_root_contract_call, apply_withdrawal_requests_contract_call}, + system_calls::{ + apply_beacon_root_contract_call, apply_consolidation_requests_contract_call, + apply_withdrawal_requests_contract_call, + }, ConfigureEvm, }; use reth_execution_types::ExecutionOutcome; @@ -223,7 +226,11 @@ where let withdrawal_requests = apply_withdrawal_requests_contract_call(&self.evm_config, &mut evm)?; - [deposit_requests, withdrawal_requests].concat() + // Collect all EIP-7251 requests + let consolidation_requests = + apply_consolidation_requests_contract_call(&self.evm_config, &mut evm)?; + + [deposit_requests, withdrawal_requests, consolidation_requests].concat() } else { vec![] }; diff --git a/crates/evm/execution-errors/src/lib.rs b/crates/evm/execution-errors/src/lib.rs index 9cc4d2ec1285..1fdee985606b 100644 --- a/crates/evm/execution-errors/src/lib.rs +++ b/crates/evm/execution-errors/src/lib.rs @@ -98,6 +98,14 @@ pub enum BlockValidationError { /// The error message. message: String, }, + /// EVM error during consolidation requests contract call [EIP-7251] + /// + /// [EIP-7251]: https://eips.ethereum.org/EIPS/eip-7251 + #[error("failed to apply consolidation requests contract call: {message}")] + ConsolidationRequestsContractCall { + /// The error message. + message: String, + }, /// Error when decoding deposit requests from receipts [EIP-6110] /// /// [EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110 diff --git a/crates/evm/src/system_calls.rs b/crates/evm/src/system_calls.rs index bba763f18bb2..9d493f51795e 100644 --- a/crates/evm/src/system_calls.rs +++ b/crates/evm/src/system_calls.rs @@ -4,6 +4,7 @@ use crate::ConfigureEvm; use alloy_eips::{ eip4788::BEACON_ROOTS_ADDRESS, eip7002::{WithdrawalRequest, WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS}, + eip7251::{ConsolidationRequest, CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS}, }; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_execution_errors::{BlockExecutionError, BlockValidationError}; @@ -264,3 +265,138 @@ where Ok(withdrawal_requests) } + +/// Apply the [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) post block contract call. +/// +/// This constructs a new [Evm] with the given DB, and environment +/// ([`CfgEnvWithHandlerCfg`] and [`BlockEnv`]) to execute the post block contract call. +/// +/// This uses [`apply_consolidation_requests_contract_call`] to ultimately calculate the +/// [requests](Request). +pub fn post_block_consolidation_requests_contract_call( + evm_config: &EvmConfig, + db: &mut DB, + initialized_cfg: &CfgEnvWithHandlerCfg, + initialized_block_env: &BlockEnv, +) -> Result, BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: std::fmt::Display, + EvmConfig: ConfigureEvm, +{ + // apply post-block EIP-7251 contract call + let mut evm_post_block = Evm::builder() + .with_db(db) + .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + Default::default(), + )) + .build(); + + // initialize a block from the env, because the post block call needs the block itself + apply_consolidation_requests_contract_call::(evm_config, &mut evm_post_block) +} + +/// Applies the post-block call to the EIP-7251 consolidation requests contract. +/// +/// If Prague is not active at the given timestamp, then this is a no-op, and an empty vector is +/// returned. Otherwise, the consolidation requests are returned. +#[inline] +pub fn apply_consolidation_requests_contract_call( + evm_config: &EvmConfig, + evm: &mut Evm<'_, EXT, DB>, +) -> Result, BlockExecutionError> +where + DB: Database + DatabaseCommit, + DB::Error: core::fmt::Display, + EvmConfig: ConfigureEvm, +{ + // get previous env + let previous_env = Box::new(evm.context.env().clone()); + + // Fill transaction environment with the EIP-7251 consolidation requests contract message data. + // + // This requirement for the consolidation requests contract call defined by + // [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) is: + // + // At the end of processing any execution block where block.timestamp >= FORK_TIMESTAMP (i.e. + // after processing all transactions and after performing the block body requests validations) + // clienst software MUST [..] call the contract as `SYSTEM_ADDRESS` and empty input data to + // trigger the system subroutine execute. + evm_config.fill_tx_env_system_contract_call( + &mut evm.context.evm.env, + alloy_eips::eip7002::SYSTEM_ADDRESS, + CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, + Bytes::new(), + ); + + let ResultAndState { result, mut state } = match evm.transact() { + Ok(res) => res, + Err(e) => { + evm.context.evm.env = previous_env; + return Err(BlockValidationError::ConsolidationRequestsContractCall { + message: format!("execution failed: {e}"), + } + .into()) + } + }; + + // cleanup the state + state.remove(&alloy_eips::eip7002::SYSTEM_ADDRESS); + state.remove(&evm.block().coinbase); + evm.context.evm.db.commit(state); + + // re-set the previous env + evm.context.evm.env = previous_env; + + let mut data = match result { + ExecutionResult::Success { output, .. } => Ok(output.into_data()), + ExecutionResult::Revert { output, .. } => { + Err(BlockValidationError::ConsolidationRequestsContractCall { + message: format!("execution reverted: {output}"), + }) + } + ExecutionResult::Halt { reason, .. } => { + Err(BlockValidationError::ConsolidationRequestsContractCall { + message: format!("execution halted: {reason:?}"), + }) + } + }?; + + // Consolidations are encoded as a series of consolidation requests, each with the following + // format: + // + // +------+--------+---------------+ + // | addr | pubkey | target pubkey | + // +------+--------+---------------+ + // 20 48 48 + + const CONSOLIDATION_REQUEST_SIZE: usize = 20 + 48 + 48; + let mut consolidation_requests = Vec::with_capacity(data.len() / CONSOLIDATION_REQUEST_SIZE); + while data.has_remaining() { + if data.remaining() < CONSOLIDATION_REQUEST_SIZE { + return Err(BlockValidationError::ConsolidationRequestsContractCall { + message: "invalid consolidation request length".to_string(), + } + .into()) + } + + let mut source_address = Address::ZERO; + data.copy_to_slice(source_address.as_mut_slice()); + + let mut source_pubkey = FixedBytes::<48>::ZERO; + data.copy_to_slice(source_pubkey.as_mut_slice()); + + let mut target_pubkey = FixedBytes::<48>::ZERO; + data.copy_to_slice(target_pubkey.as_mut_slice()); + + consolidation_requests.push(Request::ConsolidationRequest(ConsolidationRequest { + source_address, + source_pubkey, + target_pubkey, + })); + } + + Ok(consolidation_requests) +} From cbf19c16568f09c8dfdac94237ea5295af88ec33 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Mon, 8 Jul 2024 15:01:51 +0200 Subject: [PATCH 382/405] replacing network_handle with peer_info trait object (#9367) --- bin/reth/src/commands/debug_cmd/execution.rs | 2 +- crates/node/builder/src/launch/mod.rs | 2 +- crates/node/events/src/node.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index f645cd140f6f..3298fe5d6367 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -204,7 +204,7 @@ impl Command { ctx.task_executor.spawn_critical( "events task", reth_node_events::node::handle_events( - Some(network.clone()), + Some(Box::new(network)), latest_block_number, events, provider_factory.db_ref().clone(), diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 9af5a765c31d..328a77e0db87 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -279,7 +279,7 @@ where ctx.task_executor().spawn_critical( "events task", node::handle_events( - Some(ctx.components().network().clone()), + Some(Box::new(ctx.components().network().clone())), Some(ctx.head().number), events, database.clone(), diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index 77c8fd4684fc..2a6a55abd871 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -7,7 +7,7 @@ use reth_beacon_consensus::{ BeaconConsensusEngineEvent, ConsensusEngineLiveSyncProgress, ForkchoiceStatus, }; use reth_db_api::{database::Database, database_metrics::DatabaseMetadata}; -use reth_network::{NetworkEvent, NetworkHandle}; +use reth_network::NetworkEvent; use reth_network_api::PeersInfo; use reth_primitives::{constants, BlockNumber, B256}; use reth_primitives_traits::{format_gas, format_gas_throughput}; @@ -36,8 +36,8 @@ struct NodeState { /// Used for freelist calculation reported in the "Status" log message. /// See [`EventHandler::poll`]. db: DB, - /// Connection to the network. - network: Option, + /// Information about connected peers. + peers_info: Option>, /// The stage currently being executed. current_stage: Option, /// The latest block reached by either pipeline or consensus engine. @@ -55,12 +55,12 @@ struct NodeState { impl NodeState { const fn new( db: DB, - network: Option, + peers_info: Option>, latest_block: Option, ) -> Self { Self { db, - network, + peers_info, current_stage: None, latest_block, latest_block_time: None, @@ -71,7 +71,7 @@ impl NodeState { } fn num_connected_peers(&self) -> usize { - self.network.as_ref().map(|net| net.num_connected_peers()).unwrap_or_default() + self.peers_info.as_ref().map(|info| info.num_connected_peers()).unwrap_or_default() } /// Processes an event emitted by the pipeline @@ -438,7 +438,7 @@ impl From for NodeEvent { /// Displays relevant information to the user from components of the node, and periodically /// displays the high-level status of the node. pub async fn handle_events( - network: Option, + peers_info: Option>, latest_block_number: Option, events: E, db: DB, @@ -446,7 +446,7 @@ pub async fn handle_events( E: Stream + Unpin, DB: DatabaseMetadata + Database + 'static, { - let state = NodeState::new(db, network, latest_block_number); + let state = NodeState::new(db, peers_info, latest_block_number); let start = tokio::time::Instant::now() + Duration::from_secs(3); let mut info_interval = tokio::time::interval_at(start, INFO_MESSAGE_INTERVAL); From 923cda79cf598f88b6a96fb7d1f3d0e2120ea10b Mon Sep 17 00:00:00 2001 From: daobaniw <161275194+daobaniw@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:54:14 +0100 Subject: [PATCH 383/405] book: add troubleshooting commands to check disk and memory health and performance (#9364) Co-authored-by: Alexey Shekhirin --- book/run/troubleshooting.md | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/book/run/troubleshooting.md b/book/run/troubleshooting.md index 68a7cc29ea85..7368b6631abb 100644 --- a/book/run/troubleshooting.md +++ b/book/run/troubleshooting.md @@ -109,3 +109,71 @@ pthread_mutex_lock.c:438: __pthread_mutex_lock_full: Assertion `e != ESRCH || !r If you are using Docker, a possible solution is to run all database-accessing containers with `--pid=host` flag. For more information, check out the `Containers` section in the [libmdbx README](https://github.com/erthink/libmdbx#containers). + +## Hardware Performance Testing + +If you're experiencing degraded performance, it may be related to hardware issues. Below are some tools and tests you can run to evaluate your hardware performance. + +If your hardware performance is significantly lower than these reference numbers, it may explain degraded node performance. Consider upgrading your hardware or investigating potential issues with your current setup. + +### Disk Speed Testing with [IOzone](https://linux.die.net/man/1/iozone) + +1. Test disk speed: + ```bash + iozone -e -t1 -i0 -i2 -r1k -s1g /tmp + ``` + Reference numbers (on Latitude c3.large.x86): + + ```console + Children see throughput for 1 initial writers = 907733.81 kB/sec + Parent sees throughput for 1 initial writers = 907239.68 kB/sec + Children see throughput for 1 rewriters = 1765222.62 kB/sec + Parent sees throughput for 1 rewriters = 1763433.35 kB/sec + Children see throughput for 1 random readers = 1557497.38 kB/sec + Parent sees throughput for 1 random readers = 1554846.58 kB/sec + Children see throughput for 1 random writers = 984428.69 kB/sec + Parent sees throughput for 1 random writers = 983476.67 kB/sec + ``` +2. Test disk speed with memory-mapped files: + ```bash + iozone -B -G -e -t1 -i0 -i2 -r1k -s1g /tmp + ``` + Reference numbers (on Latitude c3.large.x86): + + ```console + Children see throughput for 1 initial writers = 56471.06 kB/sec + Parent sees throughput for 1 initial writers = 56365.14 kB/sec + Children see throughput for 1 rewriters = 241650.69 kB/sec + Parent sees throughput for 1 rewriters = 239067.96 kB/sec + Children see throughput for 1 random readers = 6833161.00 kB/sec + Parent sees throughput for 1 random readers = 5597659.65 kB/sec + Children see throughput for 1 random writers = 220248.53 kB/sec + Parent sees throughput for 1 random writers = 219112.26 kB/sec + ``` + +### RAM Speed and Health Testing + +1. Check RAM speed with [lshw](https://linux.die.net/man/1/lshw): + ```bash + sudo lshw -short -C memory + ``` + Look for the frequency in the output. Reference output: + + ```console + H/W path Device Class Description + ================================================================ + /0/24/0 memory 64GiB DIMM DDR4 Synchronous Registered (Buffered) 3200 MHz (0.3 ns) + /0/24/1 memory 64GiB DIMM DDR4 Synchronous Registered (Buffered) 3200 MHz (0.3 ns) + ... + ``` + +2. Test RAM health with [memtester](https://linux.die.net/man/8/memtester): + ```bash + sudo memtester 10G + ``` + This will take a while. You can test with a smaller amount first: + + ```bash + sudo memtester 1G 1 + ``` + All checks should report "ok". From 1b41bc8e14ea489e5f4c1d77b58b29b858439625 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 8 Jul 2024 16:07:07 +0200 Subject: [PATCH 384/405] chore(deps): bump alloy 0.1.4 (#9368) --- Cargo.lock | 156 +++++++++--------- .../rpc-types-compat/src/engine/payload.rs | 23 ++- 2 files changed, 96 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1782ad8cda48..9d0406630e1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f63a6c9eb45684a5468536bc55379a2af0f45ffa5d756e4e4964532737e1836" +checksum = "da374e868f54c7f4ad2ad56829827badca388efd645f8cf5fccc61c2b5343504" dependencies = [ "alloy-eips", "alloy-primitives", @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6e6436a9530f25010d13653e206fab4c9feddacf21a54de8d7311b275bc56b" +checksum = "413902aa18a97569e60f679c23f46a18db1656d87ab4d4e49d0e1e52042f66df" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4b0fc6a572ef2eebda0a31a5e393d451abda703fec917c75d9615d8c978cf2" +checksum = "f76ecab54890cdea1e4808fc0891c7e6cfcf71fe1a9fe26810c7280ef768f4ed" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48450f9c6f0821c1eee00ed912942492ed4f11dd69532825833de23ecc7a2256" +checksum = "bca15afde1b6d15e3fc1c97421262b1bbb37aee45752e3c8b6d6f13f776554ff" dependencies = [ "alloy-primitives", "alloy-serde", @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeaccd50238126e3a0ff9387c7c568837726ad4f4e399b528ca88104d6c25ef" +checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d484c2a934d0a4d86f8ad4db8113cb1d607707a6c54f6e78f4f1b4451b47aa70" +checksum = "6d6f34930b7e3e2744bcc79056c217f00cb2abb33bc5d4ff88da7623c5bb078b" dependencies = [ "alloy-primitives", "serde", @@ -213,9 +213,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a20eba9bc551037f0626d6d29e191888638d979943fa4e842e9e6fc72bf0565" +checksum = "25f6895fc31b48fa12306ef9b4f78b7764f8bd6d7d91cdb0a40e233704a0f23f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e07c66b8b0ba8c87461a15fe3247c5b46fb500e103111b0ad4798738e45b1e" +checksum = "494b2fb0276a78ec13791446a417c2517eee5c8e8a8c520ae0681975b8056e5c" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" dependencies = [ "alloy-rlp", "arbitrary", @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad5d89acb7339fad13bc69e7b925232f242835bfd91c82fcb9326b36481bd0f0" +checksum = "9c538bfa893d07e27cb4f3c1ab5f451592b7c526d511d62b576a2ce59e146e4a" dependencies = [ "alloy-chains", "alloy-consensus", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034258dfaa51c278e1f7fcc46e587d10079ec9372866fa48c5df9d908fc1f6b1" +checksum = "0a7341322d9bc0e49f6e9fd9f2eb8e30f73806f2dd12cbb3d6bab2694c921f87" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ce003e8c74bbbc7d4235131c1d6b7eaf14a533ae850295b90d240340989cb" +checksum = "5ba31bae67773fd5a60020bea900231f8396202b7feca4d0c70c6b59308ab4a8" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dfa1dd3e0bc3a3d89744fba8d1511216e83257160da2cd028a18b7d9c026030" +checksum = "184a7a42c7ba9141cc9e76368356168c282c3bc3d9e5d78f3556bdfe39343447" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae99de76a362c4311f0892e286eb752cf2a3a6ef6555dff6d93f51de2c24648" +checksum = "7e953064025c49dc9f6a3f3ac07a713487849065692228b33948f2714f2bb60d" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67aec11f9f3bc5e96c2b7f342dba6e9541a8a48d2cfbe27b6b195136aa18eee" +checksum = "8c7cf4356a9d00df76d6e90d002e2a7b5edc1c8476e90e6f17ab868d99db6435" dependencies = [ "alloy-primitives", "alloy-serde", @@ -412,9 +412,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2c363d49f460538899aaeb3325918f55fa01841fd7f3f11f58d438343ea083" +checksum = "a5f2e67d3e2478902b71bbadcd564ee5bbcc71945a0010a1f0e87a2339c6f3f9" dependencies = [ "alloy-eips", "alloy-primitives", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc40df2dda7561d1406d0bee1d19c8787483a2cf2ee8011c05909475e7bc102d" +checksum = "6e765962e3b82fd6f276a0873b5bd897e5d75a25f78fa9a6a21bd350d8e98a4e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -445,9 +445,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd7aa9ff9e67f1ba7ee0dd8cebfc95831d1649b0e4eeefae940dc3681079fa" +checksum = "ab4123ee21f99ba4bd31bfa36ba89112a18a500f8b452f02b35708b1b951e2b9" dependencies = [ "alloy-consensus", "alloy-eips", @@ -467,9 +467,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d26db98ac320a0d1637faf3e210328c3df3b1998abd7e72343d3857058efe" +checksum = "567933b1d95fd42cb70b75126e32afec2e5e2c3c16e7100a3f83dc1c80f4dc0e" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971c92989c6a5588d3f6d1e99e5328fba6e68694efbe969d6ec96ae5b9d1037" +checksum = "3115f4eb1bb9ae9aaa0b24ce875a1d86d6689b16438a12377832def2b09e373c" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -493,9 +493,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8913f9e825068d77c516188c221c44f78fd814fce8effe550a783295a2757d19" +checksum = "9416c52959e66ead795a11f4a86c248410e9e368a0765710e57055b8a1774dd6" dependencies = [ "alloy-primitives", "arbitrary", @@ -507,9 +507,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f740e13eb4c6a0e4d0e49738f1e86f31ad2d7ef93be499539f492805000f7237" +checksum = "b33753c09fa1ad85e5b092b8dc2372f1e337a42e84b9b4cff9fede75ba4adb32" dependencies = [ "alloy-primitives", "async-trait", @@ -521,9 +521,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87db68d926887393a1d0f9c43833b44446ea29d603291e7b20e5d115f31aa4e3" +checksum = "6dfc9c26fe6c6f1bad818c9a976de9044dd12e1f75f1f156a801ee3e8148c1b6" dependencies = [ "alloy-consensus", "alloy-network", @@ -539,9 +539,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -553,9 +553,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -572,9 +572,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" dependencies = [ "alloy-json-abi", "const-hex", @@ -589,18 +589,19 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa2fbd22d353d8685bd9fee11ba2d8b5c3b1d11e56adb3265fcf1f32bfdf404" +checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" dependencies = [ + "serde", "winnow 0.6.13", ] [[package]] name = "alloy-sol-types" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -611,9 +612,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd9773e4ec6832346171605c776315544bd06e40f803e7b5b7824b325d5442ca" +checksum = "01b51a291f949f755e6165c3ed562883175c97423703703355f4faa4b7d0a57c" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -624,14 +625,15 @@ dependencies = [ "thiserror", "tokio", "tower", + "tracing", "url", ] [[package]] name = "alloy-transport-http" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8ef947b901c0d4e97370f9fa25844cf8b63b1a58fd4011ee82342dc8a9fc6b" +checksum = "86d65871f9f1cafe1ed25cde2f1303be83e6473e995a2d56c275ae4fcce6119c" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -644,9 +646,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb40ee66887a66d875a5bb5e01cee4c9a467c263ef28c865cd4b0ebf15f705af" +checksum = "cd7fbc8b6282ce41b01cbddef7bffb133fe6e1bf65dcd39770d45a905c051179" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -663,9 +665,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d92049d6642a18c9849ce7659430151e7c92b51552a0cabdc038c1af4cd7308" +checksum = "aec83fd052684556c78c54df111433493267234d82321c2236560c752f595f20" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -1011,9 +1013,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -1566,9 +1568,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.104" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" dependencies = [ "jobserver", "libc", @@ -4546,7 +4548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -5313,9 +5315,9 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "op-alloy-consensus" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767fd3026c514f4d2ebdb4ebda5ed8857660dd1ef5bfed2aaa2ae8e42019630e" +checksum = "f491085509d77ebd05dbf75592093a9bebc8e7fc642b90fb4ac13b747d48b2fc" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5327,9 +5329,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50d6e6767b0b21efc9efb40fc0802ad6d28321c66ff17f1aaa46003cd234d4d" +checksum = "3f26a0cb2f7183c5e51d2806bf4ab9ec050e47c4595deff9bec7f2ba218db9d7" dependencies = [ "alloy-network", "alloy-primitives", @@ -8923,9 +8925,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.40" +version = "0.8.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" +checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9" dependencies = [ "bytemuck", ] @@ -9880,9 +9882,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" dependencies = [ "paste", "proc-macro2", diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index dacfab06418e..75b42cafe8fd 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -97,7 +97,12 @@ pub fn try_payload_v3_to_block(payload: ExecutionPayloadV3) -> Result Result { - let ExecutionPayloadV4 { payload_inner, deposit_requests, withdrawal_requests } = payload; + let ExecutionPayloadV4 { + payload_inner, + deposit_requests, + withdrawal_requests, + consolidation_requests, + } = payload; let mut block = try_payload_v3_to_block(payload_inner)?; // attach requests with asc type identifiers @@ -105,6 +110,7 @@ pub fn try_payload_v4_to_block(payload: ExecutionPayloadV4) -> Result>(); let requests_root = proofs::calculate_requests_root(&requests); @@ -211,10 +217,10 @@ pub fn block_to_payload_v3(value: SealedBlock) -> (ExecutionPayloadV3, Option ExecutionPayloadV4 { - let (deposit_requests, withdrawal_requests) = + let (deposit_requests, withdrawal_requests, consolidation_requests) = value.requests.take().unwrap_or_default().into_iter().fold( - (Vec::new(), Vec::new()), - |(mut deposits, mut withdrawals), request| { + (Vec::new(), Vec::new(), Vec::new()), + |(mut deposits, mut withdrawals, mut consolidation_requests), request| { match request { Request::DepositRequest(r) => { deposits.push(r); @@ -222,16 +228,20 @@ pub fn block_to_payload_v4(mut value: SealedBlock) -> ExecutionPayloadV4 { Request::WithdrawalRequest(r) => { withdrawals.push(r); } + Request::ConsolidationRequest(r) => { + consolidation_requests.push(r); + } _ => {} }; - (deposits, withdrawals) + (deposits, withdrawals, consolidation_requests) }, ); ExecutionPayloadV4 { deposit_requests, withdrawal_requests, + consolidation_requests, payload_inner: block_to_payload_v3(value).0, } } @@ -661,7 +671,8 @@ mod tests { "0x02f9021e8330182401843b9aca0085174876e80083030d40944242424242424242424242424242424242424242893635c9adc5dea00000b901a422895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120d694d6a0b0103651aafd87db6c88297175d7317c6e6da53ccf706c3c991c91fd0000000000000000000000000000000000000000000000000000000000000030b0b1b3b51cf688ead965a954c5cc206ba4e76f3f8efac60656ae708a9aad63487a2ca1fb30ccaf2ebe1028a2b2886b1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020002d2b75f4a27f78e585a4735a40ab2437eceb12ec39938a94dc785a54d625130000000000000000000000000000000000000000000000000000000000000060b9759766e9bb191b1c457ae1da6cdf71a23fb9d8bc9f845eaa49ee4af280b3b9720ac4d81e64b1b50a65db7b8b4e76f1176a12e19d293d75574600e99fbdfecc1ab48edaeeffb3226cd47691d24473821dad0c6ff3973f03e4aa89f418933a56c080a099dc5b94a51e9b91a6425b1fed9792863006496ab71a4178524819d7db0c5e88a0119748e62700234079d91ae80f4676f9e0f71b260e9b46ef9b4aff331d3c2318" ], "withdrawalRequests": [], - "withdrawals": [] + "withdrawals": [], + "consolidationRequests": [] }"#; let payload = serde_json::from_str::(s).unwrap(); From aaa27d6f5c1adc2985ff7cd147a15d1bb7a4ff2d Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 8 Jul 2024 15:29:43 +0100 Subject: [PATCH 385/405] feat(pruner): log stats as an ordered list of segments (#9370) --- crates/node/events/src/node.rs | 4 ++-- crates/prune/prune/src/event.rs | 4 ++-- crates/prune/prune/src/pruner.rs | 9 +++------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index 2a6a55abd871..bc0cfb1373a9 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -280,8 +280,8 @@ impl NodeState { hash=?block.hash(), peers=self.num_connected_peers(), txs=block.body.len(), - gas=format_gas(block.header.gas_used), - gas_throughput=format_gas_throughput(block.header.gas_used, elapsed), + gas=%format_gas(block.header.gas_used), + gas_throughput=%format_gas_throughput(block.header.gas_used, elapsed), full=%format!("{:.1}%", block.header.gas_used as f64 * 100.0 / block.header.gas_limit as f64), base_fee=%format!("{:.2}gwei", block.header.base_fee_per_gas.unwrap_or(0) as f64 / constants::GWEI_TO_WEI as f64), blobs=block.header.blob_gas_used.unwrap_or(0) / constants::eip4844::DATA_GAS_PER_BLOB, diff --git a/crates/prune/prune/src/event.rs b/crates/prune/prune/src/event.rs index 7007e3f47568..95a90d7628cc 100644 --- a/crates/prune/prune/src/event.rs +++ b/crates/prune/prune/src/event.rs @@ -1,6 +1,6 @@ use alloy_primitives::BlockNumber; use reth_prune_types::{PruneProgress, PruneSegment}; -use std::{collections::BTreeMap, time::Duration}; +use std::time::Duration; /// An event emitted by a [Pruner][crate::Pruner]. #[derive(Debug, PartialEq, Eq, Clone)] @@ -11,6 +11,6 @@ pub enum PrunerEvent { Finished { tip_block_number: BlockNumber, elapsed: Duration, - stats: BTreeMap, + stats: Vec<(PruneSegment, usize, PruneProgress)>, }, } diff --git a/crates/prune/prune/src/pruner.rs b/crates/prune/prune/src/pruner.rs index 68a1e6751e7b..60ff1ee80620 100644 --- a/crates/prune/prune/src/pruner.rs +++ b/crates/prune/prune/src/pruner.rs @@ -14,10 +14,7 @@ use reth_provider::{ use reth_prune_types::{PruneLimiter, PruneMode, PruneProgress, PrunePurpose, PruneSegment}; use reth_static_file_types::StaticFileSegment; use reth_tokio_util::{EventSender, EventStream}; -use std::{ - collections::BTreeMap, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; use tokio::sync::watch; use tracing::debug; @@ -27,7 +24,7 @@ pub type PrunerResult = Result; /// The pruner type itself with the result of [`Pruner::run`] pub type PrunerWithResult = (Pruner, PrunerResult); -type PrunerStats = BTreeMap; +type PrunerStats = Vec<(PruneSegment, usize, PruneProgress)>; /// Pruning routine. Main pruning logic happens in [`Pruner::run`]. #[derive(Debug)] @@ -241,7 +238,7 @@ impl Pruner { if output.pruned > 0 { limiter.increment_deleted_entries_count_by(output.pruned); pruned += output.pruned; - stats.insert(segment.segment(), (output.progress, output.pruned)); + stats.push((segment.segment(), output.pruned, output.progress)); } } else { debug!(target: "pruner", segment = ?segment.segment(), ?purpose, "Nothing to prune for the segment"); From 2c2098c05c2e695d96049c2552ff855a9801e6a0 Mon Sep 17 00:00:00 2001 From: jn Date: Mon, 8 Jul 2024 08:59:17 -0700 Subject: [PATCH 386/405] feat: move mev rpc types to alloy (#9108) Co-authored-by: Matthias Seitz --- Cargo.lock | 14 + Cargo.toml | 1 + crates/rpc/rpc-api/src/mev.rs | 2 +- crates/rpc/rpc-eth-api/src/bundle.rs | 2 +- crates/rpc/rpc-types/Cargo.toml | 7 +- crates/rpc/rpc-types/src/lib.rs | 11 +- crates/rpc/rpc-types/src/mev.rs | 1047 -------------------------- crates/rpc/rpc-types/src/rpc.rs | 1 + crates/rpc/rpc/src/eth/bundle.rs | 4 +- 9 files changed, 30 insertions(+), 1059 deletions(-) delete mode 100644 crates/rpc/rpc-types/src/mev.rs diff --git a/Cargo.lock b/Cargo.lock index 9d0406630e1f..5515adabba80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -465,6 +465,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "alloy-rpc-types-mev" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd8624e01721deacad6bc9af75abdf2e99d248df0e1ad5f3f0bda0b3c1d50fd" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", + "serde_json", +] + [[package]] name = "alloy-rpc-types-trace" version = "0.1.4" @@ -8474,6 +8487,7 @@ dependencies = [ "alloy-rpc-types-anvil", "alloy-rpc-types-beacon", "alloy-rpc-types-engine", + "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", "alloy-serde", diff --git a/Cargo.toml b/Cargo.toml index 84b4bfb81afb..ae22ca07874a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -402,6 +402,7 @@ alloy-rpc-types-txpool = { version = "0.1", default-features = false } alloy-serde = { version = "0.1", default-features = false } alloy-rpc-types-engine = { version = "0.1", default-features = false } alloy-rpc-types-eth = { version = "0.1", default-features = false } +alloy-rpc-types-mev = { version = "0.1", default-features = false } alloy-rpc-types-trace = { version = "0.1", default-features = false } alloy-genesis = { version = "0.1", default-features = false } alloy-node-bindings = { version = "0.1", default-features = false } diff --git a/crates/rpc/rpc-api/src/mev.rs b/crates/rpc/rpc-api/src/mev.rs index 008535276328..ebe6f5ee8708 100644 --- a/crates/rpc/rpc-api/src/mev.rs +++ b/crates/rpc/rpc-api/src/mev.rs @@ -1,5 +1,5 @@ use jsonrpsee::proc_macros::rpc; -use reth_rpc_types::{ +use reth_rpc_types::mev::{ SendBundleRequest, SendBundleResponse, SimBundleOverrides, SimBundleResponse, }; diff --git a/crates/rpc/rpc-eth-api/src/bundle.rs b/crates/rpc/rpc-eth-api/src/bundle.rs index f657e30c430c..bf3a623df2f1 100644 --- a/crates/rpc/rpc-eth-api/src/bundle.rs +++ b/crates/rpc/rpc-eth-api/src/bundle.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; use reth_primitives::{Bytes, B256}; -use reth_rpc_types::{ +use reth_rpc_types::mev::{ CancelBundleRequest, CancelPrivateTransactionRequest, EthBundleHash, EthCallBundle, EthCallBundleResponse, EthSendBundle, PrivateTransactionRequest, }; diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index 7a6e081a24ff..fa5c8b79c9ae 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -16,17 +16,17 @@ workspace = true # ethereum alloy-primitives = { workspace = true, features = ["rand", "rlp", "serde"] } alloy-rpc-types = { workspace = true, features = ["jsonrpsee-types"] } +alloy-rpc-types-admin.workspace = true alloy-rpc-types-anvil.workspace = true -alloy-rpc-types-trace.workspace = true alloy-rpc-types-beacon.workspace = true -alloy-rpc-types-admin.workspace = true +alloy-rpc-types-mev.workspace = true +alloy-rpc-types-trace.workspace = true alloy-rpc-types-txpool.workspace = true alloy-serde.workspace = true alloy-rpc-types-engine = { workspace = true, features = ["jsonrpsee-types"] } # misc serde = { workspace = true, features = ["derive"] } -serde_json.workspace = true jsonrpsee-types = { workspace = true, optional = true } [dev-dependencies] @@ -38,6 +38,7 @@ proptest-derive.workspace = true rand.workspace = true similar-asserts.workspace = true bytes.workspace = true +serde_json.workspace = true [features] default = ["jsonrpsee-types"] diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index 5df802da09fa..7f578ab29400 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -11,7 +11,6 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #[allow(hidden_glob_reexports)] mod eth; -mod mev; mod peer; mod rpc; @@ -29,15 +28,18 @@ pub mod trace { pub use alloy_rpc_types_trace::*; } +// re-export admin +pub use alloy_rpc_types_admin as admin; + // Anvil specific rpc types coming from alloy. pub use alloy_rpc_types_anvil as anvil; +// re-export mev +pub use alloy_rpc_types_mev as mev; + // re-export beacon pub use alloy_rpc_types_beacon as beacon; -// re-export admin -pub use alloy_rpc_types_admin as admin; - // re-export txpool pub use alloy_rpc_types_txpool as txpool; @@ -51,6 +53,5 @@ pub use eth::{ transaction::{self, TransactionRequest, TypedTransactionRequest}, }; -pub use mev::*; pub use peer::*; pub use rpc::*; diff --git a/crates/rpc/rpc-types/src/mev.rs b/crates/rpc/rpc-types/src/mev.rs deleted file mode 100644 index 20c92f1a6cd8..000000000000 --- a/crates/rpc/rpc-types/src/mev.rs +++ /dev/null @@ -1,1047 +0,0 @@ -//! MEV bundle type bindings - -use crate::{BlockId, BlockNumberOrTag, Log}; -use alloy_primitives::{Address, Bytes, TxHash, B256, U256}; -use serde::{ - ser::{SerializeSeq, Serializer}, - Deserialize, Deserializer, Serialize, -}; -/// A bundle of transactions to send to the matchmaker. -/// -/// Note: this is for `mev_sendBundle` and not `eth_sendBundle`. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SendBundleRequest { - /// The version of the MEV-share API to use. - #[serde(rename = "version")] - pub protocol_version: ProtocolVersion, - /// Data used by block builders to check if the bundle should be considered for inclusion. - #[serde(rename = "inclusion")] - pub inclusion: Inclusion, - /// The transactions to include in the bundle. - #[serde(rename = "body")] - pub bundle_body: Vec, - /// Requirements for the bundle to be included in the block. - #[serde(rename = "validity", skip_serializing_if = "Option::is_none")] - pub validity: Option, - /// Preferences on what data should be shared about the bundle and its transactions - #[serde(rename = "privacy", skip_serializing_if = "Option::is_none")] - pub privacy: Option, -} - -/// Data used by block builders to check if the bundle should be considered for inclusion. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Inclusion { - /// The first block the bundle is valid for. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub block: u64, - /// The last block the bundle is valid for. - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub max_block: Option, -} - -impl Inclusion { - /// Creates a new inclusion with the given min block.. - pub const fn at_block(block: u64) -> Self { - Self { block, max_block: None } - } - - /// Returns the block number of the first block the bundle is valid for. - #[inline] - pub const fn block_number(&self) -> u64 { - self.block - } - - /// Returns the block number of the last block the bundle is valid for. - #[inline] - pub fn max_block_number(&self) -> Option { - self.max_block.as_ref().map(|b| *b) - } -} - -/// A bundle tx, which can either be a transaction hash, or a full tx. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(untagged)] -#[serde(rename_all = "camelCase")] -pub enum BundleItem { - /// The hash of either a transaction or bundle we are trying to backrun. - Hash { - /// Tx hash. - hash: TxHash, - }, - /// A new signed transaction. - #[serde(rename_all = "camelCase")] - Tx { - /// Bytes of the signed transaction. - tx: Bytes, - /// If true, the transaction can revert without the bundle being considered invalid. - can_revert: bool, - }, -} - -/// Requirements for the bundle to be included in the block. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Validity { - /// Specifies the minimum percent of a given bundle's earnings to redistribute - /// for it to be included in a builder's block. - #[serde(skip_serializing_if = "Option::is_none")] - pub refund: Option>, - /// Specifies what addresses should receive what percent of the overall refund for this bundle, - /// if it is enveloped by another bundle (eg. a searcher backrun). - #[serde(skip_serializing_if = "Option::is_none")] - pub refund_config: Option>, -} - -/// Specifies the minimum percent of a given bundle's earnings to redistribute -/// for it to be included in a builder's block. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Refund { - /// The index of the transaction in the bundle. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub body_idx: u64, - /// The minimum percent of the bundle's earnings to redistribute. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub percent: u64, -} - -/// Specifies what addresses should receive what percent of the overall refund for this bundle, -/// if it is enveloped by another bundle (eg. a searcher backrun). -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct RefundConfig { - /// The address to refund. - pub address: Address, - /// The minimum percent of the bundle's earnings to redistribute. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub percent: u64, -} - -/// Preferences on what data should be shared about the bundle and its transactions -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Privacy { - /// Hints on what data should be shared about the bundle and its transactions - #[serde(skip_serializing_if = "Option::is_none")] - pub hints: Option, - /// The addresses of the builders that should be allowed to see the bundle/transaction. - #[serde(skip_serializing_if = "Option::is_none")] - pub builders: Option>, -} - -/// Hints on what data should be shared about the bundle and its transactions -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct PrivacyHint { - /// The calldata of the bundle's transactions should be shared. - pub calldata: bool, - /// The address of the bundle's transactions should be shared. - pub contract_address: bool, - /// The logs of the bundle's transactions should be shared. - pub logs: bool, - /// The function selector of the bundle's transactions should be shared. - pub function_selector: bool, - /// The hash of the bundle's transactions should be shared. - pub hash: bool, - /// The hash of the bundle should be shared. - pub tx_hash: bool, -} - -impl PrivacyHint { - /// Sets the flag indicating inclusion of calldata and returns the modified `PrivacyHint` - /// instance. - pub const fn with_calldata(mut self) -> Self { - self.calldata = true; - self - } - - /// Sets the flag indicating inclusion of contract address and returns the modified - /// `PrivacyHint` instance. - pub const fn with_contract_address(mut self) -> Self { - self.contract_address = true; - self - } - - /// Sets the flag indicating inclusion of logs and returns the modified `PrivacyHint` instance. - pub const fn with_logs(mut self) -> Self { - self.logs = true; - self - } - - /// Sets the flag indicating inclusion of function selector and returns the modified - /// `PrivacyHint` instance. - pub const fn with_function_selector(mut self) -> Self { - self.function_selector = true; - self - } - - /// Sets the flag indicating inclusion of hash and returns the modified `PrivacyHint` instance. - pub const fn with_hash(mut self) -> Self { - self.hash = true; - self - } - - /// Sets the flag indicating inclusion of transaction hash and returns the modified - /// `PrivacyHint` instance. - pub const fn with_tx_hash(mut self) -> Self { - self.tx_hash = true; - self - } - - /// Checks if calldata inclusion flag is set. - pub const fn has_calldata(&self) -> bool { - self.calldata - } - - /// Checks if contract address inclusion flag is set. - pub const fn has_contract_address(&self) -> bool { - self.contract_address - } - - /// Checks if logs inclusion flag is set. - pub const fn has_logs(&self) -> bool { - self.logs - } - - /// Checks if function selector inclusion flag is set. - pub const fn has_function_selector(&self) -> bool { - self.function_selector - } - - /// Checks if hash inclusion flag is set. - pub const fn has_hash(&self) -> bool { - self.hash - } - - /// Checks if transaction hash inclusion flag is set. - pub const fn has_tx_hash(&self) -> bool { - self.tx_hash - } - - /// Calculates the number of hints set within the `PrivacyHint` instance. - const fn num_hints(&self) -> usize { - let mut num_hints = 0; - if self.calldata { - num_hints += 1; - } - if self.contract_address { - num_hints += 1; - } - if self.logs { - num_hints += 1; - } - if self.function_selector { - num_hints += 1; - } - if self.hash { - num_hints += 1; - } - if self.tx_hash { - num_hints += 1; - } - num_hints - } -} - -impl Serialize for PrivacyHint { - fn serialize(&self, serializer: S) -> Result { - let mut seq = serializer.serialize_seq(Some(self.num_hints()))?; - if self.calldata { - seq.serialize_element("calldata")?; - } - if self.contract_address { - seq.serialize_element("contract_address")?; - } - if self.logs { - seq.serialize_element("logs")?; - } - if self.function_selector { - seq.serialize_element("function_selector")?; - } - if self.hash { - seq.serialize_element("hash")?; - } - if self.tx_hash { - seq.serialize_element("tx_hash")?; - } - seq.end() - } -} - -impl<'de> Deserialize<'de> for PrivacyHint { - fn deserialize>(deserializer: D) -> Result { - let hints = Vec::::deserialize(deserializer)?; - let mut privacy_hint = Self::default(); - for hint in hints { - match hint.as_str() { - "calldata" => privacy_hint.calldata = true, - "contract_address" => privacy_hint.contract_address = true, - "logs" => privacy_hint.logs = true, - "function_selector" => privacy_hint.function_selector = true, - "hash" => privacy_hint.hash = true, - "tx_hash" => privacy_hint.tx_hash = true, - _ => return Err(serde::de::Error::custom("invalid privacy hint")), - } - } - Ok(privacy_hint) - } -} - -/// Response from the matchmaker after sending a bundle. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SendBundleResponse { - /// Hash of the bundle bodies. - pub bundle_hash: B256, -} - -/// The version of the MEV-share API to use. -#[derive(Deserialize, Debug, Serialize, Clone, Default, PartialEq, Eq)] -pub enum ProtocolVersion { - #[default] - #[serde(rename = "beta-1")] - /// The beta-1 version of the API. - Beta1, - /// The 0.1 version of the API. - #[serde(rename = "v0.1")] - V0_1, -} - -/// Optional fields to override simulation state. -#[derive(Deserialize, Debug, Serialize, Clone, Default, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SimBundleOverrides { - /// Block used for simulation state. Defaults to latest block. - /// Block header data will be derived from parent block by default. - /// Specify other params to override the default values. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub parent_block: Option, - /// Block number used for simulation, defaults to parentBlock.number + 1 - #[serde(default, with = "alloy_rpc_types::serde_helpers::quantity::opt")] - pub block_number: Option, - /// Coinbase used for simulation, defaults to parentBlock.coinbase - #[serde(default, skip_serializing_if = "Option::is_none")] - pub coinbase: Option
, - /// Timestamp used for simulation, defaults to parentBlock.timestamp + 12 - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub timestamp: Option, - /// Gas limit used for simulation, defaults to parentBlock.gasLimit - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub gas_limit: Option, - /// Base fee used for simulation, defaults to parentBlock.baseFeePerGas - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub base_fee: Option, - /// Timeout in seconds, defaults to 5 - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub timeout: Option, -} - -/// Response from the matchmaker after sending a simulation request. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SimBundleResponse { - /// Whether the simulation was successful. - pub success: bool, - /// Error message if the simulation failed. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub error: Option, - /// The block number of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub state_block: u64, - /// The gas price of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub mev_gas_price: u64, - /// The profit of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub profit: u64, - /// The refundable value of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub refundable_value: u64, - /// The gas used by the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub gas_used: u64, - /// Logs returned by `mev_simBundle`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub logs: Option>, -} - -/// Logs returned by `mev_simBundle`. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SimBundleLogs { - /// Logs for transactions in bundle. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub tx_logs: Option>, - /// Logs for bundles in bundle. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub bundle_logs: Option>, -} - -impl SendBundleRequest { - /// Create a new bundle request. - pub const fn new( - block_num: u64, - max_block: Option, - protocol_version: ProtocolVersion, - bundle_body: Vec, - ) -> Self { - Self { - protocol_version, - inclusion: Inclusion { block: block_num, max_block }, - bundle_body, - validity: None, - privacy: None, - } - } -} - -/// Request for `eth_cancelBundle` -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct CancelBundleRequest { - /// Bundle hash of the bundle to be canceled - pub bundle_hash: String, -} - -/// Request for `eth_sendPrivateTransaction` -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct PrivateTransactionRequest { - /// raw signed transaction - pub tx: Bytes, - /// Hex-encoded number string, optional. Highest block number in which the transaction should - /// be included. - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub max_block_number: Option, - /// Preferences for private transaction. - #[serde(default, skip_serializing_if = "PrivateTransactionPreferences::is_empty")] - pub preferences: PrivateTransactionPreferences, -} - -/// Additional preferences for `eth_sendPrivateTransaction` -#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] -pub struct PrivateTransactionPreferences { - /// Requirements for the bundle to be included in the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub validity: Option, - /// Preferences on what data should be shared about the bundle and its transactions - #[serde(default, skip_serializing_if = "Option::is_none")] - pub privacy: Option, -} - -impl PrivateTransactionPreferences { - /// Returns true if the preferences are empty. - pub const fn is_empty(&self) -> bool { - self.validity.is_none() && self.privacy.is_none() - } -} - -/// Request for `eth_cancelPrivateTransaction` -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct CancelPrivateTransactionRequest { - /// Transaction hash of the transaction to be canceled - pub tx_hash: B256, -} - -// TODO(@optimiz-r): Revisit after is closed. -/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle -/// -/// Note: this is V2: -/// -/// Timestamp format: "2022-10-06T21:36:06.322Z" -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub enum BundleStats { - /// The relayer has not yet seen the bundle. - #[default] - Unknown, - /// The relayer has seen the bundle, but has not simulated it yet. - Seen(StatsSeen), - /// The relayer has seen the bundle and has simulated it. - Simulated(StatsSimulated), -} - -impl Serialize for BundleStats { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Unknown => serde_json::json!({"isSimulated": false}).serialize(serializer), - Self::Seen(stats) => stats.serialize(serializer), - Self::Simulated(stats) => stats.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for BundleStats { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let map = serde_json::Map::deserialize(deserializer)?; - - if map.get("receivedAt").is_none() { - Ok(Self::Unknown) - } else if map["isSimulated"] == false { - StatsSeen::deserialize(serde_json::Value::Object(map)) - .map(BundleStats::Seen) - .map_err(serde::de::Error::custom) - } else { - StatsSimulated::deserialize(serde_json::Value::Object(map)) - .map(BundleStats::Simulated) - .map_err(serde::de::Error::custom) - } - } -} - -/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle -/// -/// Note: this is V2: -/// -/// Timestamp format: "2022-10-06T21:36:06.322Z -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct StatsSeen { - /// boolean representing if this searcher has a high enough reputation to be in the high - /// priority queue - pub is_high_priority: bool, - /// representing whether the bundle gets simulated. All other fields will be omitted except - /// simulated field if API didn't receive bundle - pub is_simulated: bool, - /// time at which the bundle API received the bundle - pub received_at: String, -} - -/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle -/// -/// Note: this is V2: -/// -/// Timestamp format: "2022-10-06T21:36:06.322Z -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct StatsSimulated { - /// boolean representing if this searcher has a high enough reputation to be in the high - /// priority queue - pub is_high_priority: bool, - /// representing whether the bundle gets simulated. All other fields will be omitted except - /// simulated field if API didn't receive bundle - pub is_simulated: bool, - /// time at which the bundle gets simulated - pub simulated_at: String, - /// time at which the bundle API received the bundle - pub received_at: String, - /// indicates time at which each builder selected the bundle to be included in the target - /// block - #[serde(default = "Vec::new")] - pub considered_by_builders_at: Vec, - /// indicates time at which each builder sealed a block containing the bundle - #[serde(default = "Vec::new")] - pub sealed_by_builders_at: Vec, -} - -/// Represents information about when a bundle was considered by a builder. -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ConsideredByBuildersAt { - /// The public key of the builder. - pub pubkey: String, - /// The timestamp indicating when the bundle was considered by the builder. - pub timestamp: String, -} - -/// Represents information about when a bundle was sealed by a builder. -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SealedByBuildersAt { - /// The public key of the builder. - pub pubkey: String, - /// The timestamp indicating when the bundle was sealed by the builder. - pub timestamp: String, -} - -/// Response for `flashbots_getUserStatsV2` represents stats for a searcher. -/// -/// Note: this is V2: -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UserStats { - /// Represents whether this searcher has a high enough reputation to be in the high priority - /// queue. - pub is_high_priority: bool, - /// The total amount paid to validators over all time. - #[serde(with = "u256_numeric_string")] - pub all_time_validator_payments: U256, - /// The total amount of gas simulated across all bundles submitted to Flashbots. - /// This is the actual gas used in simulations, not gas limit. - #[serde(with = "u256_numeric_string")] - pub all_time_gas_simulated: U256, - /// The total amount paid to validators the last 7 days. - #[serde(with = "u256_numeric_string")] - pub last_7d_validator_payments: U256, - /// The total amount of gas simulated across all bundles submitted to Flashbots in the last 7 - /// days. This is the actual gas used in simulations, not gas limit. - #[serde(with = "u256_numeric_string")] - pub last_7d_gas_simulated: U256, - /// The total amount paid to validators the last day. - #[serde(with = "u256_numeric_string")] - pub last_1d_validator_payments: U256, - /// The total amount of gas simulated across all bundles submitted to Flashbots in the last - /// day. This is the actual gas used in simulations, not gas limit. - #[serde(with = "u256_numeric_string")] - pub last_1d_gas_simulated: U256, -} - -/// Bundle of transactions for `eth_sendBundle` -/// -/// Note: this is for `eth_sendBundle` and not `mev_sendBundle` -/// -/// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EthSendBundle { - /// A list of hex-encoded signed transactions - pub txs: Vec, - /// hex-encoded block number for which this bundle is valid - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub block_number: u64, - /// unix timestamp when this bundle becomes active - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub min_timestamp: Option, - /// unix timestamp how long this bundle stays valid - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub max_timestamp: Option, - /// list of hashes of possibly reverting txs - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub reverting_tx_hashes: Vec, - /// UUID that can be used to cancel/replace this bundle - #[serde(default, rename = "replacementUuid", skip_serializing_if = "Option::is_none")] - pub replacement_uuid: Option, -} - -/// Response from the matchmaker after sending a bundle. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct EthBundleHash { - /// Hash of the bundle bodies. - pub bundle_hash: B256, -} - -/// Bundle of transactions for `eth_callBundle` -/// -/// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EthCallBundle { - /// A list of hex-encoded signed transactions - pub txs: Vec, - /// hex encoded block number for which this bundle is valid on - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub block_number: u64, - /// Either a hex encoded number or a block tag for which state to base this simulation on - pub state_block_number: BlockNumberOrTag, - /// the timestamp to use for this bundle simulation, in seconds since the unix epoch - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub timestamp: Option, -} - -/// Response for `eth_callBundle` -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct EthCallBundleResponse { - /// The hash of the bundle bodies. - pub bundle_hash: B256, - /// The gas price of the entire bundle - #[serde(with = "u256_numeric_string")] - pub bundle_gas_price: U256, - /// The difference in Ether sent to the coinbase after all transactions in the bundle - #[serde(with = "u256_numeric_string")] - pub coinbase_diff: U256, - /// The total amount of Ether sent to the coinbase after all transactions in the bundle - #[serde(with = "u256_numeric_string")] - pub eth_sent_to_coinbase: U256, - /// The total gas fees paid for all transactions in the bundle - #[serde(with = "u256_numeric_string")] - pub gas_fees: U256, - /// Results of individual transactions within the bundle - pub results: Vec, - /// The block number used as a base for this simulation - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub state_block_number: u64, - /// The total gas used by all transactions in the bundle - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub total_gas_used: u64, -} - -/// Result of a single transaction in a bundle for `eth_callBundle` -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EthCallBundleTransactionResult { - /// The difference in Ether sent to the coinbase after the transaction - #[serde(with = "u256_numeric_string")] - pub coinbase_diff: U256, - /// The amount of Ether sent to the coinbase after the transaction - #[serde(with = "u256_numeric_string")] - pub eth_sent_to_coinbase: U256, - /// The address from which the transaction originated - pub from_address: Address, - /// The gas fees paid for the transaction - #[serde(with = "u256_numeric_string")] - pub gas_fees: U256, - /// The gas price used for the transaction - #[serde(with = "u256_numeric_string")] - pub gas_price: U256, - /// The amount of gas used by the transaction - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub gas_used: u64, - /// The address to which the transaction is sent (optional) - pub to_address: Option
, - /// The transaction hash - pub tx_hash: B256, - /// Contains the return data if the transaction succeeded - /// - /// Note: this is mutually exclusive with `revert` - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - /// Contains the return data if the transaction reverted - #[serde(skip_serializing_if = "Option::is_none")] - pub revert: Option, -} - -mod u256_numeric_string { - use alloy_primitives::U256; - use serde::{de, Deserialize, Serializer}; - use std::str::FromStr; - - pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let val = serde_json::Value::deserialize(deserializer)?; - match val { - serde_json::Value::String(s) => { - if let Ok(val) = s.parse::() { - return Ok(U256::from(val)) - } - U256::from_str(&s).map_err(de::Error::custom) - } - serde_json::Value::Number(num) => { - num.as_u64().map(U256::from).ok_or_else(|| de::Error::custom("invalid u256")) - } - _ => Err(de::Error::custom("invalid u256")), - } - } - - pub(crate) fn serialize(val: &U256, serializer: S) -> Result - where - S: Serializer, - { - let val: u128 = (*val).try_into().map_err(serde::ser::Error::custom)?; - serializer.serialize_str(&val.to_string()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str::FromStr; - - #[test] - fn can_deserialize_simple() { - let str = r#" - [{ - "version": "v0.1", - "inclusion": { - "block": "0x1" - }, - "body": [{ - "tx": "0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260", - "canRevert": false - }] - }] - "#; - let res: Result, _> = serde_json::from_str(str); - assert!(res.is_ok()); - } - - #[test] - fn can_deserialize_complex() { - let str = r#" - [{ - "version": "v0.1", - "inclusion": { - "block": "0x1" - }, - "body": [{ - "tx": "0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260", - "canRevert": false - }], - "privacy": { - "hints": [ - "calldata" - ] - }, - "validity": { - "refundConfig": [ - { - "address": "0x8EC1237b1E80A6adf191F40D4b7D095E21cdb18f", - "percent": 100 - } - ] - } - }] - "#; - let res: Result, _> = serde_json::from_str(str); - assert!(res.is_ok()); - } - - #[test] - fn can_serialize_complex() { - let str = r#" - [{ - "version": "v0.1", - "inclusion": { - "block": "0x1" - }, - "body": [{ - "tx": "0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260", - "canRevert": false - }], - "privacy": { - "hints": [ - "calldata" - ] - }, - "validity": { - "refundConfig": [ - { - "address": "0x8EC1237b1E80A6adf191F40D4b7D095E21cdb18f", - "percent": 100 - } - ] - } - }] - "#; - let bundle_body = vec![BundleItem::Tx { - tx: Bytes::from_str("0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260").unwrap(), - can_revert: false, - }]; - - let validity = Some(Validity { - refund_config: Some(vec![RefundConfig { - address: "0x8EC1237b1E80A6adf191F40D4b7D095E21cdb18f".parse().unwrap(), - percent: 100, - }]), - ..Default::default() - }); - - let privacy = Some(Privacy { - hints: Some(PrivacyHint { calldata: true, ..Default::default() }), - ..Default::default() - }); - - let bundle = SendBundleRequest { - protocol_version: ProtocolVersion::V0_1, - inclusion: Inclusion { block: 1, max_block: None }, - bundle_body, - validity, - privacy, - }; - let expected = serde_json::from_str::>(str).unwrap(); - assert_eq!(bundle, expected[0]); - } - - #[test] - fn can_serialize_privacy_hint() { - let hint = PrivacyHint { - calldata: true, - contract_address: true, - logs: true, - function_selector: true, - hash: true, - tx_hash: true, - }; - let expected = - r#"["calldata","contract_address","logs","function_selector","hash","tx_hash"]"#; - let actual = serde_json::to_string(&hint).unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn can_deserialize_privacy_hint() { - let hint = PrivacyHint { - calldata: true, - contract_address: false, - logs: true, - function_selector: false, - hash: true, - tx_hash: false, - }; - let expected = r#"["calldata","logs","hash"]"#; - let actual: PrivacyHint = serde_json::from_str(expected).unwrap(); - assert_eq!(actual, hint); - } - - #[test] - fn can_dererialize_sim_response() { - let expected = r#" - { - "success": true, - "stateBlock": "0x8b8da8", - "mevGasPrice": "0x74c7906005", - "profit": "0x4bc800904fc000", - "refundableValue": "0x4bc800904fc000", - "gasUsed": "0xa620", - "logs": [{},{}] - } - "#; - let actual: SimBundleResponse = serde_json::from_str(expected).unwrap(); - assert!(actual.success); - } - - #[test] - fn can_deserialize_eth_call_resp() { - let s = r#"{ - "bundleGasPrice": "476190476193", - "bundleHash": "0x73b1e258c7a42fd0230b2fd05529c5d4b6fcb66c227783f8bece8aeacdd1db2e", - "coinbaseDiff": "20000000000126000", - "ethSentToCoinbase": "20000000000000000", - "gasFees": "126000", - "results": [ - { - "coinbaseDiff": "10000000000063000", - "ethSentToCoinbase": "10000000000000000", - "fromAddress": "0x02A727155aeF8609c9f7F2179b2a1f560B39F5A0", - "gasFees": "63000", - "gasPrice": "476190476193", - "gasUsed": 21000, - "toAddress": "0x73625f59CAdc5009Cb458B751b3E7b6b48C06f2C", - "txHash": "0x669b4704a7d993a946cdd6e2f95233f308ce0c4649d2e04944e8299efcaa098a", - "value": "0x" - }, - { - "coinbaseDiff": "10000000000063000", - "ethSentToCoinbase": "10000000000000000", - "fromAddress": "0x02A727155aeF8609c9f7F2179b2a1f560B39F5A0", - "gasFees": "63000", - "gasPrice": "476190476193", - "gasUsed": 21000, - "toAddress": "0x73625f59CAdc5009Cb458B751b3E7b6b48C06f2C", - "txHash": "0xa839ee83465657cac01adc1d50d96c1b586ed498120a84a64749c0034b4f19fa", - "value": "0x" - } - ], - "stateBlockNumber": 5221585, - "totalGasUsed": 42000 - }"#; - - let _call = serde_json::from_str::(s).unwrap(); - } - - #[test] - fn can_serialize_deserialize_bundle_stats() { - let fixtures = [ - ( - r#"{ - "isSimulated": false - }"#, - BundleStats::Unknown, - ), - ( - r#"{ - "isHighPriority": false, - "isSimulated": false, - "receivedAt": "476190476193" - }"#, - BundleStats::Seen(StatsSeen { - is_high_priority: false, - is_simulated: false, - received_at: "476190476193".to_string(), - }), - ), - ( - r#"{ - "isHighPriority": true, - "isSimulated": true, - "simulatedAt": "111", - "receivedAt": "222", - "consideredByBuildersAt":[], - "sealedByBuildersAt": [ - { - "pubkey": "333", - "timestamp": "444" - }, - { - "pubkey": "555", - "timestamp": "666" - } - ] - }"#, - BundleStats::Simulated(StatsSimulated { - is_high_priority: true, - is_simulated: true, - simulated_at: String::from("111"), - received_at: String::from("222"), - considered_by_builders_at: vec![], - sealed_by_builders_at: vec![ - SealedByBuildersAt { - pubkey: String::from("333"), - timestamp: String::from("444"), - }, - SealedByBuildersAt { - pubkey: String::from("555"), - timestamp: String::from("666"), - }, - ], - }), - ), - ]; - - let strip_whitespaces = - |input: &str| input.chars().filter(|&c| !c.is_whitespace()).collect::(); - - for (serialized, deserialized) in fixtures { - // Check de-serialization - let deserialized_expected = serde_json::from_str::(serialized).unwrap(); - assert_eq!(deserialized, deserialized_expected); - - // Check serialization - let serialized_expected = &serde_json::to_string(&deserialized).unwrap(); - assert_eq!(strip_whitespaces(serialized), strip_whitespaces(serialized_expected)); - } - } -} diff --git a/crates/rpc/rpc-types/src/rpc.rs b/crates/rpc/rpc-types/src/rpc.rs index bb5ae5d77fbb..0b9afeb79a67 100644 --- a/crates/rpc/rpc-types/src/rpc.rs +++ b/crates/rpc/rpc-types/src/rpc.rs @@ -24,6 +24,7 @@ impl RpcModules { #[cfg(test)] mod tests { use super::*; + #[test] fn test_parse_module_versions_roundtrip() { let s = r#"{"txpool":"1.0","trace":"1.0","eth":"1.0","web3":"1.0","net":"1.0"}"#; diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index 894f2f80404b..aab706b28b23 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -10,7 +10,7 @@ use reth_primitives::{ PooledTransactionsElement, U256, }; use reth_revm::database::StateProviderDatabase; -use reth_rpc_types::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; +use reth_rpc_types::mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; use reth_tasks::pool::BlockingTaskGuard; use revm::{ db::CacheDB, @@ -48,7 +48,7 @@ where /// state, or it can be used to simulate a past block. The sender is responsible for signing the /// transactions and using the correct nonce and ensuring validity pub async fn call_bundle(&self, bundle: EthCallBundle) -> EthResult { - let EthCallBundle { txs, block_number, state_block_number, timestamp } = bundle; + let EthCallBundle { txs, block_number, state_block_number, timestamp, .. } = bundle; if txs.is_empty() { return Err(EthApiError::InvalidParams( EthBundleError::EmptyBundleTransactions.to_string(), From b14192fcaff70e7b9a9ff53891788dbe3337fd85 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 8 Jul 2024 09:29:29 -0700 Subject: [PATCH 387/405] feat(tree): validate state root (#9369) --- crates/engine/tree/src/tree/mod.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 9ac472961033..ebe4aa09b15d 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -17,10 +17,12 @@ use reth_evm::execute::{BlockExecutorProvider, Executor}; use reth_payload_primitives::PayloadTypes; use reth_payload_validator::ExecutionPayloadValidator; use reth_primitives::{ - Address, Block, BlockNumber, Receipts, Requests, SealedBlock, SealedBlockWithSenders, B256, - U256, + Address, Block, BlockNumber, GotExpected, Receipts, Requests, SealedBlock, + SealedBlockWithSenders, B256, U256, +}; +use reth_provider::{ + BlockReader, ExecutionOutcome, StateProvider, StateProviderFactory, StateRootProvider, }; -use reth_provider::{BlockReader, ExecutionOutcome, StateProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_rpc_types::{ engine::{ @@ -548,10 +550,16 @@ where PostExecutionInput::new(&output.receipts, &output.requests), )?; + // TODO: change StateRootProvider API to accept hashed post state let hashed_state = HashedPostState::from_bundle_state(&output.state.state); - // TODO: compute and validate state root - let trie_output = TrieUpdates::default(); + let (state_root, trie_output) = state_provider.state_root_with_updates(&output.state)?; + if state_root != block.state_root { + return Err(ConsensusError::BodyStateRootDiff( + GotExpected { got: state_root, expected: block.state_root }.into(), + ) + .into()) + } let executed = ExecutedBlock { block: Arc::new(block.block.seal(block_hash)), From ad403b46714c79418e485c31e0dc3c07a0af7151 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 8 Jul 2024 17:45:18 +0100 Subject: [PATCH 388/405] docs: typos (#9379) --- docs/crates/stages.md | 2 +- docs/design/review.md | 2 +- testing/ef-tests/src/cases/blockchain_test.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/crates/stages.md b/docs/crates/stages.md index 8e3de4a044a9..c7815b453b4e 100644 --- a/docs/crates/stages.md +++ b/docs/crates/stages.md @@ -90,7 +90,7 @@ pub struct SealedHeader { Each `SealedHeader` is then validated to ensure that it has the proper parent. Note that this is only a basic response validation, and the `HeaderDownloader` uses the `validate` method during the `stream`, so that each header is validated according to the consensus specification before the header is yielded from the stream. After this, each header is then written to the database. If a header is not valid or the stream encounters any other error, the error is propagated up through the stage execution, the changes to the database are unwound and the stage is resumed from the most recent valid state. -This process continues until all of the headers have been downloaded and written to the database. Finally, the total difficulty of the chain's head is updated and the function returns `Ok(ExecOutput { stage_progress, done: true })`, signaling that the header sync has completed successfully. +This process continues until all of the headers have been downloaded and written to the database. Finally, the total difficulty of the chain's head is updated and the function returns `Ok(ExecOutput { stage_progress, done: true })`, signaling that the header sync has been completed successfully.
diff --git a/docs/design/review.md b/docs/design/review.md index 329d7b2d4764..693c991a777f 100644 --- a/docs/design/review.md +++ b/docs/design/review.md @@ -25,7 +25,7 @@ This document contains some of our research in how other codebases designed vari ## Header Downloaders * Erigon Header Downloader: - * A header downloader algo was introduced in [`erigon#1016`](https://github.com/ledgerwatch/erigon/pull/1016) and finished in [`erigon#1145`](https://github.com/ledgerwatch/erigon/pull/1145). At a high level, the downloader concurrently requested headers by hash, then sorted, validated and fused the responses into chain segments. Smaller segments were fused into larger as the gaps between them were filled. The downloader also used to maintain hardcoded hashes (later renamed to preverified) to bootstrap the sync. + * A header downloader algo was introduced in [`erigon#1016`](https://github.com/ledgerwatch/erigon/pull/1016) and finished in [`erigon#1145`](https://github.com/ledgerwatch/erigon/pull/1145). At a high level, the downloader concurrently requested headers by hash, then sorted, validated and fused the responses into chain segments. Smaller segments were fused into larger as the gaps between them were filled. The downloader is also used to maintain hardcoded hashes (later renamed to preverified) to bootstrap the sync. * The downloader was refactored multiple times: [`erigon#1471`](https://github.com/ledgerwatch/erigon/pull/1471), [`erigon#1559`](https://github.com/ledgerwatch/erigon/pull/1559) and [`erigon#2035`](https://github.com/ledgerwatch/erigon/pull/2035). * With PoS transition in [`erigon#3075`](https://github.com/ledgerwatch/erigon/pull/3075) terminal td was introduced to the algo to stop forward syncing. For the downward sync (post merge), the download was now delegated to [`EthBackendServer`](https://github.com/ledgerwatch/erigon/blob/3c95db00788dc740849c2207d886fe4db5a8c473/ethdb/privateapi/ethbackend.go#L245) * Proper reverse PoS downloader was introduced in [`erigon#3092`](https://github.com/ledgerwatch/erigon/pull/3092) which downloads the header batches from tip until local head is reached. Refactored later in [`erigon#3340`](https://github.com/ledgerwatch/erigon/pull/3340) and [`erigon#3717`](https://github.com/ledgerwatch/erigon/pull/3717). diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 7ecd7cb33d83..41642f0f9728 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -187,7 +187,7 @@ pub fn should_skip(path: &Path) -> bool { | "ValueOverflow.json" | "ValueOverflowParis.json" - // txbyte is of type 02 and we dont parse tx bytes for this test to fail. + // txbyte is of type 02 and we don't parse tx bytes for this test to fail. | "typeTwoBerlin.json" // Test checks if nonce overflows. We are handling this correctly but we are not parsing From a9ebab4c798264613996c62a0d542df0c2ea286c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 8 Jul 2024 17:46:29 +0100 Subject: [PATCH 389/405] perf(pruner): delete history indices by changeset keys (#9312) Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com> Co-authored-by: Emilia Hane --- Cargo.lock | 1 + crates/prune/prune/Cargo.toml | 1 + .../prune/src/segments/account_history.rs | 37 +++- crates/prune/prune/src/segments/history.rs | 206 +++++++++++------- .../prune/src/segments/storage_history.rs | 42 +++- 5 files changed, 192 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5515adabba80..62727689e741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8170,6 +8170,7 @@ dependencies = [ "reth-testing-utils", "reth-tokio-util", "reth-tracing", + "rustc-hash 2.0.0", "thiserror", "tokio", "tracing", diff --git a/crates/prune/prune/Cargo.toml b/crates/prune/prune/Cargo.toml index b5d9059c95b8..2f2a37d5ba66 100644 --- a/crates/prune/prune/Cargo.toml +++ b/crates/prune/prune/Cargo.toml @@ -35,6 +35,7 @@ thiserror.workspace = true itertools.workspace = true rayon.workspace = true tokio.workspace = true +rustc-hash.workspace = true [dev-dependencies] # reth diff --git a/crates/prune/prune/src/segments/account_history.rs b/crates/prune/prune/src/segments/account_history.rs index ab2800a31718..28e448560b89 100644 --- a/crates/prune/prune/src/segments/account_history.rs +++ b/crates/prune/prune/src/segments/account_history.rs @@ -4,10 +4,12 @@ use crate::{ }, PrunerError, }; +use itertools::Itertools; use reth_db::tables; use reth_db_api::{database::Database, models::ShardedKey}; use reth_provider::DatabaseProviderRW; use reth_prune_types::{PruneInterruptReason, PruneMode, PruneProgress, PruneSegment}; +use rustc_hash::FxHashMap; use tracing::{instrument, trace}; /// Number of account history tables to prune in one step. @@ -64,34 +66,53 @@ impl Segment for AccountHistory { } let mut last_changeset_pruned_block = None; + // Deleted account changeset keys (account addresses) with the highest block number deleted + // for that key. + // + // The size of this map it's limited by `prune_delete_limit * blocks_since_last_run / + // ACCOUNT_HISTORY_TABLES_TO_PRUNE`, and with current default it's usually `3500 * 5 + // / 2`, so 8750 entries. Each entry is `160 bit + 256 bit + 64 bit`, so the total + // size should be up to 0.5MB + some hashmap overhead. `blocks_since_last_run` is + // additionally limited by the `max_reorg_depth`, so no OOM is expected here. + let mut highest_deleted_accounts = FxHashMap::default(); let (pruned_changesets, done) = provider .prune_table_with_range::( range, &mut limiter, |_| false, - |row| last_changeset_pruned_block = Some(row.0), + |(block_number, account)| { + highest_deleted_accounts.insert(account.address, block_number); + last_changeset_pruned_block = Some(block_number); + }, )?; trace!(target: "pruner", pruned = %pruned_changesets, %done, "Pruned account history (changesets)"); let last_changeset_pruned_block = last_changeset_pruned_block - // If there's more account account changesets to prune, set the checkpoint block number - // to previous, so we could finish pruning its account changesets on the next run. + // If there's more account changesets to prune, set the checkpoint block number to + // previous, so we could finish pruning its account changesets on the next run. .map(|block_number| if done { block_number } else { block_number.saturating_sub(1) }) .unwrap_or(range_end); - let (processed, pruned_indices) = prune_history_indices::( + // Sort highest deleted block numbers by account address and turn them into sharded keys. + // We did not use `BTreeMap` from the beginning, because it's inefficient for hashes. + let highest_sharded_keys = highest_deleted_accounts + .into_iter() + .sorted_unstable() // Unstable is fine because no equal keys exist in the map + .map(|(address, block_number)| { + ShardedKey::new(address, block_number.min(last_changeset_pruned_block)) + }); + let outcomes = prune_history_indices::( provider, - last_changeset_pruned_block, + highest_sharded_keys, |a, b| a.key == b.key, - |key| ShardedKey::last(key.key), )?; - trace!(target: "pruner", %processed, pruned = %pruned_indices, %done, "Pruned account history (history)"); + trace!(target: "pruner", ?outcomes, %done, "Pruned account history (indices)"); let progress = PruneProgress::new(done, &limiter); Ok(PruneOutput { progress, - pruned: pruned_changesets + pruned_indices, + pruned: pruned_changesets + outcomes.deleted, checkpoint: Some(PruneOutputCheckpoint { block_number: Some(last_changeset_pruned_block), tx_number: None, diff --git a/crates/prune/prune/src/segments/history.rs b/crates/prune/prune/src/segments/history.rs index ee841ef89717..ff477a39f942 100644 --- a/crates/prune/prune/src/segments/history.rs +++ b/crates/prune/prune/src/segments/history.rs @@ -1,5 +1,5 @@ use alloy_primitives::BlockNumber; -use reth_db::BlockNumberList; +use reth_db::{BlockNumberList, RawKey, RawTable, RawValue}; use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW}, database::Database, @@ -10,103 +10,151 @@ use reth_db_api::{ }; use reth_provider::DatabaseProviderRW; -/// Prune history indices up to the provided block, inclusive. +enum PruneShardOutcome { + Deleted, + Updated, + Unchanged, +} + +#[derive(Debug, Default)] +pub(crate) struct PrunedIndices { + pub(crate) deleted: usize, + pub(crate) updated: usize, + pub(crate) unchanged: usize, +} + +/// Prune history indices according to the provided list of highest sharded keys. /// -/// Returns total number of processed (walked) and deleted entities. +/// Returns total number of deleted, updated and unchanged entities. pub(crate) fn prune_history_indices( provider: &DatabaseProviderRW, - to_block: BlockNumber, + highest_sharded_keys: impl IntoIterator, key_matches: impl Fn(&T::Key, &T::Key) -> bool, - last_key: impl Fn(&T::Key) -> T::Key, -) -> Result<(usize, usize), DatabaseError> +) -> Result where DB: Database, T: Table, T::Key: AsRef>, { - let mut processed = 0; - let mut deleted = 0; - let mut cursor = provider.tx_ref().cursor_write::()?; + let mut outcomes = PrunedIndices::default(); + let mut cursor = provider.tx_ref().cursor_write::>()?; + + for sharded_key in highest_sharded_keys { + // Seek to the shard that has the key >= the given sharded key + // TODO: optimize + let mut shard = cursor.seek(RawKey::new(sharded_key.clone()))?; - // Prune history table: - // 1. If the shard has `highest_block_number` less than or equal to the target block number - // for pruning, delete the shard completely. - // 2. If the shard has `highest_block_number` greater than the target block number for - // pruning, filter block numbers inside the shard which are less than the target - // block number for pruning. - while let Some(result) = cursor.next()? { - let (key, blocks): (T::Key, BlockNumberList) = result; + // Get the highest block number that needs to be deleted for this sharded key + let to_block = sharded_key.as_ref().highest_block_number; - // If shard consists only of block numbers less than the target one, delete shard - // completely. - if key.as_ref().highest_block_number <= to_block { - cursor.delete_current()?; - deleted += 1; - if key.as_ref().highest_block_number == to_block { - // Shard contains only block numbers up to the target one, so we can skip to - // the last shard for this key. It is guaranteed that further shards for this - // sharded key will not contain the target block number, as it's in this shard. - cursor.seek_exact(last_key(&key))?; + 'shard: loop { + let Some((key, block_nums)) = + shard.map(|(k, v)| Result::<_, DatabaseError>::Ok((k.key()?, v))).transpose()? + else { + break + }; + + if key_matches(&key, &sharded_key) { + match prune_shard(&mut cursor, key, block_nums, to_block, &key_matches)? { + PruneShardOutcome::Deleted => outcomes.deleted += 1, + PruneShardOutcome::Updated => outcomes.updated += 1, + PruneShardOutcome::Unchanged => outcomes.unchanged += 1, + } + } else { + // If such shard doesn't exist, skip to the next sharded key + break 'shard } + + shard = cursor.next()?; } - // Shard contains block numbers that are higher than the target one, so we need to - // filter it. It is guaranteed that further shards for this sharded key will not - // contain the target block number, as it's in this shard. - else { - let higher_blocks = - blocks.iter().skip_while(|block| *block <= to_block).collect::>(); + } - // If there were blocks less than or equal to the target one - // (so the shard has changed), update the shard. - if blocks.len() as usize != higher_blocks.len() { - // If there will be no more blocks in the shard after pruning blocks below target - // block, we need to remove it, as empty shards are not allowed. - if higher_blocks.is_empty() { - if key.as_ref().highest_block_number == u64::MAX { - let prev_row = cursor.prev()?; - match prev_row { - // If current shard is the last shard for the sharded key that - // has previous shards, replace it with the previous shard. - Some((prev_key, prev_value)) if key_matches(&prev_key, &key) => { - cursor.delete_current()?; - deleted += 1; - // Upsert will replace the last shard for this sharded key with - // the previous value. - cursor.upsert(key.clone(), prev_value)?; - } - // If there's no previous shard for this sharded key, - // just delete last shard completely. - _ => { - // If we successfully moved the cursor to a previous row, - // jump to the original last shard. - if prev_row.is_some() { - cursor.next()?; - } - // Delete shard. - cursor.delete_current()?; - deleted += 1; + Ok(outcomes) +} + +/// Prunes one shard of a history table. +/// +/// 1. If the shard has `highest_block_number` less than or equal to the target block number for +/// pruning, delete the shard completely. +/// 2. If the shard has `highest_block_number` greater than the target block number for pruning, +/// filter block numbers inside the shard which are less than the target block number for +/// pruning. +fn prune_shard( + cursor: &mut C, + key: T::Key, + raw_blocks: RawValue, + to_block: BlockNumber, + key_matches: impl Fn(&T::Key, &T::Key) -> bool, +) -> Result +where + C: DbCursorRO> + DbCursorRW>, + T: Table, + T::Key: AsRef>, +{ + // If shard consists only of block numbers less than the target one, delete shard + // completely. + if key.as_ref().highest_block_number <= to_block { + cursor.delete_current()?; + Ok(PruneShardOutcome::Deleted) + } + // Shard contains block numbers that are higher than the target one, so we need to + // filter it. It is guaranteed that further shards for this sharded key will not + // contain the target block number, as it's in this shard. + else { + let blocks = raw_blocks.value()?; + let higher_blocks = + blocks.iter().skip_while(|block| *block <= to_block).collect::>(); + + // If there were blocks less than or equal to the target one + // (so the shard has changed), update the shard. + if blocks.len() as usize != higher_blocks.len() { + // If there will be no more blocks in the shard after pruning blocks below target + // block, we need to remove it, as empty shards are not allowed. + if higher_blocks.is_empty() { + if key.as_ref().highest_block_number == u64::MAX { + let prev_row = cursor + .prev()? + .map(|(k, v)| Result::<_, DatabaseError>::Ok((k.key()?, v))) + .transpose()?; + match prev_row { + // If current shard is the last shard for the sharded key that + // has previous shards, replace it with the previous shard. + Some((prev_key, prev_value)) if key_matches(&prev_key, &key) => { + cursor.delete_current()?; + // Upsert will replace the last shard for this sharded key with + // the previous value. + cursor.upsert(RawKey::new(key), prev_value)?; + Ok(PruneShardOutcome::Updated) + } + // If there's no previous shard for this sharded key, + // just delete last shard completely. + _ => { + // If we successfully moved the cursor to a previous row, + // jump to the original last shard. + if prev_row.is_some() { + cursor.next()?; } + // Delete shard. + cursor.delete_current()?; + Ok(PruneShardOutcome::Deleted) } } - // If current shard is not the last shard for this sharded key, - // just delete it. - else { - cursor.delete_current()?; - deleted += 1; - } - } else { - cursor.upsert(key.clone(), BlockNumberList::new_pre_sorted(higher_blocks))?; } + // If current shard is not the last shard for this sharded key, + // just delete it. + else { + cursor.delete_current()?; + Ok(PruneShardOutcome::Deleted) + } + } else { + cursor.upsert( + RawKey::new(key), + RawValue::new(BlockNumberList::new_pre_sorted(higher_blocks)), + )?; + Ok(PruneShardOutcome::Updated) } - - // Jump to the last shard for this key, if current key isn't already the last shard. - if key.as_ref().highest_block_number != u64::MAX { - cursor.seek_exact(last_key(&key))?; - } + } else { + Ok(PruneShardOutcome::Unchanged) } - - processed += 1; } - - Ok((processed, deleted)) } diff --git a/crates/prune/prune/src/segments/storage_history.rs b/crates/prune/prune/src/segments/storage_history.rs index 3e7ad86a7da4..95e9afa0a55c 100644 --- a/crates/prune/prune/src/segments/storage_history.rs +++ b/crates/prune/prune/src/segments/storage_history.rs @@ -4,6 +4,7 @@ use crate::{ }, PrunerError, }; +use itertools::Itertools; use reth_db::tables; use reth_db_api::{ database::Database, @@ -11,6 +12,7 @@ use reth_db_api::{ }; use reth_provider::DatabaseProviderRW; use reth_prune_types::{PruneInterruptReason, PruneMode, PruneProgress, PruneSegment}; +use rustc_hash::FxHashMap; use tracing::{instrument, trace}; /// Number of storage history tables to prune in one step @@ -67,34 +69,58 @@ impl Segment for StorageHistory { } let mut last_changeset_pruned_block = None; + // Deleted storage changeset keys (account addresses and storage slots) with the highest + // block number deleted for that key. + // + // The size of this map it's limited by `prune_delete_limit * blocks_since_last_run / + // ACCOUNT_HISTORY_TABLES_TO_PRUNE`, and with current default it's usually `3500 * 5 + // / 2`, so 8750 entries. Each entry is `160 bit + 256 bit + 64 bit`, so the total + // size should be up to 0.5MB + some hashmap overhead. `blocks_since_last_run` is + // additionally limited by the `max_reorg_depth`, so no OOM is expected here. + let mut highest_deleted_storages = FxHashMap::default(); let (pruned_changesets, done) = provider .prune_table_with_range::( BlockNumberAddress::range(range), &mut limiter, |_| false, - |row| last_changeset_pruned_block = Some(row.0.block_number()), + |(BlockNumberAddress((block_number, address)), entry)| { + highest_deleted_storages.insert((address, entry.key), block_number); + last_changeset_pruned_block = Some(block_number); + }, )?; trace!(target: "pruner", deleted = %pruned_changesets, %done, "Pruned storage history (changesets)"); let last_changeset_pruned_block = last_changeset_pruned_block - // If there's more storage storage changesets to prune, set the checkpoint block number - // to previous, so we could finish pruning its storage changesets on the next run. + // If there's more storage changesets to prune, set the checkpoint block number to + // previous, so we could finish pruning its storage changesets on the next run. .map(|block_number| if done { block_number } else { block_number.saturating_sub(1) }) .unwrap_or(range_end); - let (processed, pruned_indices) = prune_history_indices::( + // Sort highest deleted block numbers by account address and storage key and turn them into + // sharded keys. + // We did not use `BTreeMap` from the beginning, because it's inefficient for hashes. + let highest_sharded_keys = highest_deleted_storages + .into_iter() + .sorted_unstable() // Unstable is fine because no equal keys exist in the map + .map(|((address, storage_key), block_number)| { + StorageShardedKey::new( + address, + storage_key, + block_number.min(last_changeset_pruned_block), + ) + }); + let outcomes = prune_history_indices::( provider, - last_changeset_pruned_block, + highest_sharded_keys, |a, b| a.address == b.address && a.sharded_key.key == b.sharded_key.key, - |key| StorageShardedKey::last(key.address, key.sharded_key.key), )?; - trace!(target: "pruner", %processed, deleted = %pruned_indices, %done, "Pruned storage history (history)"); + trace!(target: "pruner", ?outcomes, %done, "Pruned storage history (indices)"); let progress = PruneProgress::new(done, &limiter); Ok(PruneOutput { progress, - pruned: pruned_changesets + pruned_indices, + pruned: pruned_changesets + outcomes.deleted, checkpoint: Some(PruneOutputCheckpoint { block_number: Some(last_changeset_pruned_block), tx_number: None, From 5b81fc1ee465354049239418f24bf48b71d7ceae Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:06:45 +0200 Subject: [PATCH 390/405] clippy: rm useless clippy statement (#9380) --- crates/transaction-pool/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 527c3412e1aa..2484f6784f4c 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -148,7 +148,6 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -#![warn(clippy::missing_const_for_fn)] use crate::{identifier::TransactionId, pool::PoolInner}; use aquamarine as _; From 2f8a860bb8865c25ace556ffa7569287c0c17ddc Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 8 Jul 2024 10:47:49 -0700 Subject: [PATCH 391/405] feat(tree): pre-validate fcu (#9371) --- .../consensus/beacon/src/engine/forkchoice.rs | 2 +- crates/consensus/beacon/src/engine/message.rs | 10 ++-- crates/engine/tree/src/tree/mod.rs | 52 +++++++++++++++++-- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/crates/consensus/beacon/src/engine/forkchoice.rs b/crates/consensus/beacon/src/engine/forkchoice.rs index 491d0ff8aade..ba09dff6c017 100644 --- a/crates/consensus/beacon/src/engine/forkchoice.rs +++ b/crates/consensus/beacon/src/engine/forkchoice.rs @@ -20,7 +20,7 @@ impl ForkchoiceStateTracker { /// /// If the status is `VALID`, we also update the last valid forkchoice state and set the /// `sync_target` to `None`, since we're now fully synced. - pub(crate) fn set_latest(&mut self, state: ForkchoiceState, status: ForkchoiceStatus) { + pub fn set_latest(&mut self, state: ForkchoiceState, status: ForkchoiceStatus) { if status.is_valid() { self.set_valid(state); } else if status.is_syncing() { diff --git a/crates/consensus/beacon/src/engine/message.rs b/crates/consensus/beacon/src/engine/message.rs index 052b275c181d..f58f620b44ac 100644 --- a/crates/consensus/beacon/src/engine/message.rs +++ b/crates/consensus/beacon/src/engine/message.rs @@ -48,7 +48,7 @@ impl OnForkChoiceUpdated { /// Creates a new instance of `OnForkChoiceUpdated` if the forkchoice update succeeded and no /// payload attributes were provided. - pub(crate) fn valid(status: PayloadStatus) -> Self { + pub fn valid(status: PayloadStatus) -> Self { Self { forkchoice_status: ForkchoiceStatus::from_payload_status(&status.status), fut: Either::Left(futures::future::ready(Ok(ForkchoiceUpdated::new(status)))), @@ -57,7 +57,7 @@ impl OnForkChoiceUpdated { /// Creates a new instance of `OnForkChoiceUpdated` with the given payload status, if the /// forkchoice update failed due to an invalid payload. - pub(crate) fn with_invalid(status: PayloadStatus) -> Self { + pub fn with_invalid(status: PayloadStatus) -> Self { Self { forkchoice_status: ForkchoiceStatus::from_payload_status(&status.status), fut: Either::Left(futures::future::ready(Ok(ForkchoiceUpdated::new(status)))), @@ -66,7 +66,7 @@ impl OnForkChoiceUpdated { /// Creates a new instance of `OnForkChoiceUpdated` if the forkchoice update failed because the /// given state is considered invalid - pub(crate) fn invalid_state() -> Self { + pub fn invalid_state() -> Self { Self { forkchoice_status: ForkchoiceStatus::Invalid, fut: Either::Left(futures::future::ready(Err(ForkchoiceUpdateError::InvalidState))), @@ -75,7 +75,7 @@ impl OnForkChoiceUpdated { /// Creates a new instance of `OnForkChoiceUpdated` if the forkchoice update was successful but /// payload attributes were invalid. - pub(crate) fn invalid_payload_attributes() -> Self { + pub fn invalid_payload_attributes() -> Self { Self { // This is valid because this is only reachable if the state and payload is valid forkchoice_status: ForkchoiceStatus::Valid, @@ -86,7 +86,7 @@ impl OnForkChoiceUpdated { } /// If the forkchoice update was successful and no payload attributes were provided, this method - pub(crate) const fn updated_with_pending_payload_id( + pub const fn updated_with_pending_payload_id( payload_status: PayloadStatus, pending_payload_id: oneshot::Receiver>, ) -> Self { diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index ebe4aa09b15d..8afed31043c0 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -12,7 +12,7 @@ use reth_blockchain_tree::{ use reth_blockchain_tree_api::{error::InsertBlockError, InsertPayloadOk}; use reth_consensus::{Consensus, PostExecutionInput}; use reth_engine_primitives::EngineTypes; -use reth_errors::{ConsensusError, ProviderResult, RethError}; +use reth_errors::{ConsensusError, ProviderResult}; use reth_evm::execute::{BlockExecutorProvider, Executor}; use reth_payload_primitives::PayloadTypes; use reth_payload_validator::ExecutionPayloadValidator; @@ -190,7 +190,7 @@ pub trait EngineApiTreeHandler { &mut self, state: ForkchoiceState, attrs: Option<::PayloadAttributes>, - ) -> TreeOutcome>; + ) -> ProviderResult>; } /// The outcome of a tree operation. @@ -314,7 +314,8 @@ where FromEngine::Request(request) => match request { BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => { let output = self.on_forkchoice_updated(state, payload_attrs); - if let Err(err) = tx.send(output.outcome) { + if let Err(err) = tx.send(output.map(|o| o.outcome).map_err(Into::into)) + { error!("Failed to send event: {err:?}"); } } @@ -468,6 +469,15 @@ where Ok(Some(status)) } + /// Checks if the given `head` points to an invalid header, which requires a specific response + /// to a forkchoice update. + fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult> { + // check if the head was previously marked as invalid + let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) }; + // populate the latest valid hash field + Ok(Some(self.prepare_invalid_response(header.parent_hash)?)) + } + /// Validate if block is correct and satisfies all the consensus rules that concern the header /// and block body itself. fn validate_block(&self, block: &SealedBlockWithSenders) -> Result<(), ConsensusError> { @@ -578,6 +588,35 @@ where let attachment = BlockAttachment::Canonical; // TODO: remove or revise attachment Ok(InsertPayloadOk::Inserted(BlockStatus::Valid(attachment))) } + + /// Pre-validate forkchoice update and check whether it can be processed. + /// + /// This method returns the update outcome if validation fails or + /// the node is syncing and the update cannot be processed at the moment. + fn pre_validate_forkchoice_update( + &mut self, + state: ForkchoiceState, + ) -> ProviderResult> { + if state.head_block_hash.is_zero() { + return Ok(Some(OnForkChoiceUpdated::invalid_state())) + } + + // check if the new head hash is connected to any ancestor that we previously marked as + // invalid + let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash); + if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu)? { + return Ok(Some(OnForkChoiceUpdated::with_invalid(status))) + } + + if self.is_pipeline_active { + // We can only process new forkchoice updates if the pipeline is idle, since it requires + // exclusive access to the database + trace!(target: "consensus::engine", "Pipeline is syncing, skipping forkchoice update"); + return Ok(Some(OnForkChoiceUpdated::syncing())) + } + + Ok(None) + } } impl EngineApiTreeHandler for EngineApiTreeHandlerImpl @@ -707,7 +746,12 @@ where &mut self, state: ForkchoiceState, attrs: Option<::PayloadAttributes>, - ) -> TreeOutcome> { + ) -> ProviderResult> { + if let Some(on_updated) = self.pre_validate_forkchoice_update(state)? { + self.state.forkchoice_state_tracker.set_latest(state, on_updated.forkchoice_status()); + return Ok(TreeOutcome::new(on_updated)) + } + todo!() } } From 3e8a2a29c59d07c31d1ec5d075af33a49848f5aa Mon Sep 17 00:00:00 2001 From: kostekIV <27210860+kostekIV@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:51:08 +0200 Subject: [PATCH 392/405] Integrate permits for getproof (#9363) --- book/cli/reth/node.md | 5 ++++ crates/node/core/src/args/rpc_server.rs | 5 ++++ crates/optimism/rpc/src/eth/mod.rs | 15 +++++++++- crates/rpc/rpc-builder/src/config.rs | 1 + crates/rpc/rpc-builder/src/eth.rs | 12 +++++++- .../rpc-eth-api/src/helpers/blocking_task.rs | 13 +++++++- crates/rpc/rpc-eth-api/src/helpers/state.rs | 20 +++++++++---- crates/rpc/rpc-server-types/src/constants.rs | 3 ++ crates/rpc/rpc/src/eth/core.rs | 30 +++++++++++++++++-- crates/rpc/rpc/src/eth/helpers/state.rs | 4 ++- crates/rpc/rpc/src/eth/helpers/transaction.rs | 3 +- 11 files changed, 97 insertions(+), 14 deletions(-) diff --git a/book/cli/reth/node.md b/book/cli/reth/node.md index fe96358e5f77..61759a694e99 100644 --- a/book/cli/reth/node.md +++ b/book/cli/reth/node.md @@ -318,6 +318,11 @@ RPC: [default: 0] + --rpc.proof-permits + Maximum number of concurrent getproof requests + + [default: 25] + RPC State Cache: --rpc-cache.max-blocks Max number of blocks in cache diff --git a/crates/node/core/src/args/rpc_server.rs b/crates/node/core/src/args/rpc_server.rs index 9b562329b463..761f0c3f709f 100644 --- a/crates/node/core/src/args/rpc_server.rs +++ b/crates/node/core/src/args/rpc_server.rs @@ -166,6 +166,10 @@ pub struct RpcServerArgs { )] pub rpc_eth_proof_window: u64, + /// Maximum number of concurrent getproof requests. + #[arg(long = "rpc.proof-permits", alias = "rpc-proof-permits", value_name = "COUNT", default_value_t = constants::DEFAULT_PROOF_PERMITS)] + pub rpc_proof_permits: usize, + /// State cache configuration. #[command(flatten)] pub rpc_state_cache: RpcStateCacheArgs, @@ -299,6 +303,7 @@ impl Default for RpcServerArgs { rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW, gas_price_oracle: GasPriceOracleArgs::default(), rpc_state_cache: RpcStateCacheArgs::default(), + rpc_proof_permits: constants::DEFAULT_PROOF_PERMITS, } } } diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index b73311cb7768..d580a30e96f4 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -20,7 +20,7 @@ use reth_rpc_types::SyncStatus; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; use std::future::Future; -use tokio::sync::Mutex; +use tokio::sync::{AcquireError, Mutex, OwnedSemaphorePermit}; /// OP-Reth `Eth` API implementation. /// @@ -108,6 +108,19 @@ impl SpawnBlocking for OpEthApi { fn tracing_task_pool(&self) -> &BlockingTaskPool { self.inner.tracing_task_pool() } + + fn acquire_owned( + &self, + ) -> impl Future> + Send { + self.inner.acquire_owned() + } + + fn acquire_many_owned( + &self, + n: u32, + ) -> impl Future> + Send { + self.inner.acquire_many_owned(n) + } } impl LoadReceipt for OpEthApi { diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index 837b80a99b00..1f61f57919f2 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -95,6 +95,7 @@ impl RethRpcServerConfig for RpcServerArgs { .rpc_gas_cap(self.rpc_gas_cap) .state_cache(self.state_cache_config()) .gpo_config(self.gas_price_oracle_config()) + .proof_permits(self.rpc_proof_permits) } fn state_cache_config(&self) -> EthStateCacheConfig { diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 8e897b7710a2..b9b2d63ef331 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -14,7 +14,7 @@ use reth_rpc_eth_types::{ }; use reth_rpc_server_types::constants::{ default_max_tracing_requests, DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKS_PER_FILTER, - DEFAULT_MAX_LOGS_PER_RESPONSE, + DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_PROOF_PERMITS, }; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; @@ -159,6 +159,8 @@ pub struct EthConfig { pub stale_filter_ttl: Duration, /// Settings for the fee history cache pub fee_history_cache: FeeHistoryCacheConfig, + /// The maximum number of getproof calls that can be executed concurrently. + pub proof_permits: usize, } impl EthConfig { @@ -183,6 +185,7 @@ impl Default for EthConfig { rpc_gas_cap: RPC_DEFAULT_GAS_CAP.into(), stale_filter_ttl: DEFAULT_STALE_FILTER_TTL, fee_history_cache: FeeHistoryCacheConfig::default(), + proof_permits: DEFAULT_PROOF_PERMITS, } } } @@ -229,6 +232,12 @@ impl EthConfig { self.eth_proof_window = window; self } + + /// Configures the number of getproof requests + pub const fn proof_permits(mut self, permits: usize) -> Self { + self.proof_permits = permits; + self + } } /// Context for building the `eth` namespace API. @@ -285,6 +294,7 @@ impl EthApiBuild { fee_history_cache, ctx.evm_config.clone(), None, + ctx.config.proof_permits, ) } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs b/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs index c199d4de6178..4a2c81b0fdfe 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs @@ -4,7 +4,7 @@ use futures::Future; use reth_rpc_eth_types::{EthApiError, EthResult}; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; -use tokio::sync::oneshot; +use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit}; /// Executes code on a blocking thread. pub trait SpawnBlocking: Clone + Send + Sync + 'static { @@ -18,6 +18,17 @@ pub trait SpawnBlocking: Clone + Send + Sync + 'static { /// Thread pool access in default trait method implementations. fn tracing_task_pool(&self) -> &BlockingTaskPool; + /// See also [`Semaphore::acquire_owned`](`tokio::sync::Semaphore::acquire_owned`). + fn acquire_owned( + &self, + ) -> impl Future> + Send; + + /// See also [`Semaphore::acquire_many_owned`](`tokio::sync::Semaphore::acquire_many_owned`). + fn acquire_many_owned( + &self, + n: u32, + ) -> impl Future> + Send; + /// Executes the future on a new blocking task. /// /// Note: This is expected for futures that are dominated by blocking IO operations, for tracing diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 05af8f547acf..4b0d629259a9 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -2,6 +2,7 @@ //! RPC methods. use futures::Future; +use reth_errors::RethError; use reth_evm::ConfigureEvmEnv; use reth_primitives::{Address, BlockId, Bytes, Header, B256, U256}; use reth_provider::{BlockIdReader, StateProvider, StateProviderBox, StateProviderFactory}; @@ -102,12 +103,19 @@ pub trait EthState: LoadState + SpawnBlocking { return Err(EthApiError::ExceedsMaxProofWindow) } - Ok(self.spawn_blocking_io(move |this| { - let state = this.state_at_block_id(block_id)?; - let storage_keys = keys.iter().map(|key| key.0).collect::>(); - let proof = state.proof(&BundleState::default(), address, &storage_keys)?; - Ok(from_primitive_account_proof(proof)) - })) + Ok(async move { + let _permit = self + .acquire_owned() + .await + .map_err(|err| EthApiError::Internal(RethError::other(err)))?; + self.spawn_blocking_io(move |this| { + let state = this.state_at_block_id(block_id)?; + let storage_keys = keys.iter().map(|key| key.0).collect::>(); + let proof = state.proof(&BundleState::default(), address, &storage_keys)?; + Ok(from_primitive_account_proof(proof)) + }) + .await + }) } } diff --git a/crates/rpc/rpc-server-types/src/constants.rs b/crates/rpc/rpc-server-types/src/constants.rs index f1af5fb26f75..e3c129bf6e28 100644 --- a/crates/rpc/rpc-server-types/src/constants.rs +++ b/crates/rpc/rpc-server-types/src/constants.rs @@ -26,6 +26,9 @@ pub fn default_max_tracing_requests() -> usize { .map_or(25, |cpus| max(cpus.get().saturating_sub(RESERVED), RESERVED)) } +/// The default number of getproof calls we are allowing to run concurrently. +pub const DEFAULT_PROOF_PERMITS: usize = 25; + /// The default IPC endpoint #[cfg(windows)] pub const DEFAULT_IPC_ENDPOINT: &str = r"\\.\pipe\reth.ipc"; diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index c82e306252b3..36030741ff3e 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -1,6 +1,7 @@ //! Implementation of the [`jsonrpsee`] generated [`EthApiServer`](crate::EthApi) trait //! Handles RPC requests for the `eth_` namespace. +use futures::Future; use std::sync::Arc; use derive_more::Deref; @@ -11,8 +12,11 @@ use reth_rpc_eth_api::{ RawTransactionForwarder, }; use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock}; -use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor}; -use tokio::sync::Mutex; +use reth_tasks::{ + pool::{BlockingTaskGuard, BlockingTaskPool}, + TaskSpawner, TokioTaskExecutor, +}; +use tokio::sync::{AcquireError, Mutex, OwnedSemaphorePermit}; use crate::eth::DevSigner; @@ -49,6 +53,7 @@ where fee_history_cache: FeeHistoryCache, evm_config: EvmConfig, raw_transaction_forwarder: Option>, + proof_permits: usize, ) -> Self { Self::with_spawner( provider, @@ -63,6 +68,7 @@ where fee_history_cache, evm_config, raw_transaction_forwarder, + proof_permits, ) } @@ -81,6 +87,7 @@ where fee_history_cache: FeeHistoryCache, evm_config: EvmConfig, raw_transaction_forwarder: Option>, + proof_permits: usize, ) -> Self { // get the block number of the latest block let latest_block = provider @@ -106,6 +113,7 @@ where fee_history_cache, evm_config, raw_transaction_forwarder: parking_lot::RwLock::new(raw_transaction_forwarder), + blocking_task_guard: BlockingTaskGuard::new(proof_permits), }; Self { inner: Arc::new(inner) } @@ -140,6 +148,19 @@ where fn tracing_task_pool(&self) -> &reth_tasks::pool::BlockingTaskPool { self.inner.blocking_task_pool() } + + fn acquire_owned( + &self, + ) -> impl Future> + Send { + self.blocking_task_guard.clone().acquire_owned() + } + + fn acquire_many_owned( + &self, + n: u32, + ) -> impl Future> + Send { + self.blocking_task_guard.clone().acquire_many_owned(n) + } } impl EthApi { @@ -184,6 +205,8 @@ pub struct EthApiInner { evm_config: EvmConfig, /// Allows forwarding received raw transactions raw_transaction_forwarder: parking_lot::RwLock>>, + /// Guard for getproof calls + blocking_task_guard: BlockingTaskGuard, } impl EthApiInner { @@ -304,7 +327,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; - use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; + use reth_rpc_server_types::constants::{DEFAULT_ETH_PROOF_WINDOW, DEFAULT_PROOF_PERMITS}; use reth_rpc_types::FeeHistory; use reth_tasks::pool::BlockingTaskPool; use reth_testing_utils::{generators, generators::Rng}; @@ -341,6 +364,7 @@ mod tests { fee_history_cache, evm_config, None, + DEFAULT_PROOF_PERMITS, ) } diff --git a/crates/rpc/rpc/src/eth/helpers/state.rs b/crates/rpc/rpc/src/eth/helpers/state.rs index 369bd6ba7529..d3a99d2f83ab 100644 --- a/crates/rpc/rpc/src/eth/helpers/state.rs +++ b/crates/rpc/rpc/src/eth/helpers/state.rs @@ -51,7 +51,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; - use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; + use reth_rpc_server_types::constants::{DEFAULT_ETH_PROOF_WINDOW, DEFAULT_PROOF_PERMITS}; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::test_utils::testing_pool; @@ -76,6 +76,7 @@ mod tests { FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), evm_config, None, + DEFAULT_PROOF_PERMITS, ); let address = Address::random(); let storage = eth_api.storage_at(address, U256::ZERO.into(), None).await.unwrap(); @@ -102,6 +103,7 @@ mod tests { FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), evm_config, None, + DEFAULT_PROOF_PERMITS, ); let storage_key: U256 = storage_key.into(); diff --git a/crates/rpc/rpc/src/eth/helpers/transaction.rs b/crates/rpc/rpc/src/eth/helpers/transaction.rs index 13e3dbd5c0aa..872af0cee451 100644 --- a/crates/rpc/rpc/src/eth/helpers/transaction.rs +++ b/crates/rpc/rpc/src/eth/helpers/transaction.rs @@ -68,7 +68,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; - use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; + use reth_rpc_server_types::constants::{DEFAULT_ETH_PROOF_WINDOW, DEFAULT_PROOF_PERMITS}; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::{test_utils::testing_pool, TransactionPool}; @@ -97,6 +97,7 @@ mod tests { fee_history_cache, evm_config, None, + DEFAULT_PROOF_PERMITS, ); // https://etherscan.io/tx/0xa694b71e6c128a2ed8e2e0f6770bddbe52e3bb8f10e8472f9a79ab81497a8b5d From 454416809854b9ee9a5c64c90b0b48f39eb8891a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 8 Jul 2024 19:56:08 +0200 Subject: [PATCH 393/405] feat: add support for payload bodies (#9378) --- crates/rpc/rpc-api/src/engine.rs | 24 +++- crates/rpc/rpc-engine-api/src/engine_api.rs | 130 ++++++++++++++---- crates/rpc/rpc-engine-api/src/metrics.rs | 4 + .../rpc-types-compat/src/engine/payload.rs | 50 ++++++- 4 files changed, 174 insertions(+), 34 deletions(-) diff --git a/crates/rpc/rpc-api/src/engine.rs b/crates/rpc/rpc-api/src/engine.rs index e858f62df0d5..986dd76b14ab 100644 --- a/crates/rpc/rpc-api/src/engine.rs +++ b/crates/rpc/rpc-api/src/engine.rs @@ -8,14 +8,13 @@ use reth_engine_primitives::EngineTypes; use reth_primitives::{Address, BlockHash, BlockId, BlockNumberOrTag, Bytes, B256, U256, U64}; use reth_rpc_types::{ engine::{ - ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV1, - ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId, - PayloadStatus, TransitionConfiguration, + ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadBodiesV2, + ExecutionPayloadInputV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4, + ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, TransitionConfiguration, }, state::StateOverride, BlockOverrides, Filter, Log, RichBlock, SyncStatus, TransactionRequest, }; - // NOTE: We can't use associated types in the `EngineApi` trait because of jsonrpsee, so we use a // generic here. It would be nice if the rpc macro would understand which types need to have serde. // By default, if the trait has a generic, the rpc macro will add e.g. `Engine: DeserializeOwned` to @@ -144,6 +143,13 @@ pub trait EngineApi { block_hashes: Vec, ) -> RpcResult; + /// See also + #[method(name = "getPayloadBodiesByHashV2")] + async fn get_payload_bodies_by_hash_v2( + &self, + block_hashes: Vec, + ) -> RpcResult; + /// See also /// /// Returns the execution payload bodies by the range starting at `start`, containing `count` @@ -163,6 +169,16 @@ pub trait EngineApi { count: U64, ) -> RpcResult; + /// See also + /// + /// Similar to `getPayloadBodiesByRangeV1`, but returns [`ExecutionPayloadBodiesV2`] + #[method(name = "getPayloadBodiesByRangeV2")] + async fn get_payload_bodies_by_range_v2( + &self, + start: U64, + count: U64, + ) -> RpcResult; + /// See also /// /// Note: This method will be deprecated after the cancun hardfork: diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index b0f53667a695..b64b9fa20e44 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -10,16 +10,18 @@ use reth_payload_primitives::{ validate_payload_timestamp, EngineApiMessageVersion, PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes, }; -use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, EthereumHardfork, B256, U64}; +use reth_primitives::{ + Block, BlockHash, BlockHashOrNumber, BlockNumber, EthereumHardfork, B256, U64, +}; use reth_rpc_api::EngineApiServer; use reth_rpc_types::engine::{ CancunPayloadFields, ClientVersionV1, ExecutionPayload, ExecutionPayloadBodiesV1, - ExecutionPayloadInputV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4, - ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, TransitionConfiguration, - CAPABILITIES, + ExecutionPayloadBodiesV2, ExecutionPayloadInputV2, ExecutionPayloadV1, ExecutionPayloadV3, + ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, + TransitionConfiguration, CAPABILITIES, }; use reth_rpc_types_compat::engine::payload::{ - convert_payload_input_v2_to_payload, convert_to_payload_body_v1, + convert_payload_input_v2_to_payload, convert_to_payload_body_v1, convert_to_payload_body_v2, }; use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory}; use reth_tasks::TaskSpawner; @@ -359,21 +361,18 @@ where }) } - /// Returns the execution payload bodies by the range starting at `start`, containing `count` - /// blocks. - /// - /// WARNING: This method is associated with the `BeaconBlocksByRange` message in the consensus - /// layer p2p specification, meaning the input should be treated as untrusted or potentially - /// adversarial. - /// - /// Implementers should take care when acting on the input to this method, specifically - /// ensuring that the range is limited properly, and that the range boundaries are computed - /// correctly and without panics. - pub async fn get_payload_bodies_by_range( + /// Fetches all the blocks for the provided range starting at `start`, containing `count` + /// blocks and returns the mapped payload bodies. + async fn get_payload_bodies_by_range_with( &self, start: BlockNumber, count: u64, - ) -> EngineApiResult { + f: F, + ) -> EngineApiResult>> + where + F: Fn(Block) -> R + Send + 'static, + R: Send + 'static, + { let (tx, rx) = oneshot::channel(); let inner = self.inner.clone(); @@ -405,7 +404,7 @@ where let block_result = inner.provider.block(BlockHashOrNumber::Number(num)); match block_result { Ok(block) => { - result.push(block.map(convert_to_payload_body_v1)); + result.push(block.map(&f)); } Err(err) => { tx.send(Err(EngineApiError::Internal(Box::new(err)))).ok(); @@ -419,11 +418,45 @@ where rx.await.map_err(|err| EngineApiError::Internal(Box::new(err)))? } + /// Returns the execution payload bodies by the range starting at `start`, containing `count` + /// blocks. + /// + /// WARNING: This method is associated with the `BeaconBlocksByRange` message in the consensus + /// layer p2p specification, meaning the input should be treated as untrusted or potentially + /// adversarial. + /// + /// Implementers should take care when acting on the input to this method, specifically + /// ensuring that the range is limited properly, and that the range boundaries are computed + /// correctly and without panics. + pub async fn get_payload_bodies_by_range_v1( + &self, + start: BlockNumber, + count: u64, + ) -> EngineApiResult { + self.get_payload_bodies_by_range_with(start, count, convert_to_payload_body_v1).await + } + + /// Returns the execution payload bodies by the range starting at `start`, containing `count` + /// blocks. + /// + /// Same as [`Self::get_payload_bodies_by_range_v1`] but as [`ExecutionPayloadBodiesV2`]. + pub async fn get_payload_bodies_by_range_v2( + &self, + start: BlockNumber, + count: u64, + ) -> EngineApiResult { + self.get_payload_bodies_by_range_with(start, count, convert_to_payload_body_v2).await + } + /// Called to retrieve execution payload bodies by hashes. - pub fn get_payload_bodies_by_hash( + fn get_payload_bodies_by_hash_with( &self, hashes: Vec, - ) -> EngineApiResult { + f: F, + ) -> EngineApiResult>> + where + F: Fn(Block) -> R, + { let len = hashes.len() as u64; if len > MAX_PAYLOAD_BODIES_LIMIT { return Err(EngineApiError::PayloadRequestTooLarge { len }) @@ -436,12 +469,30 @@ where .provider .block(BlockHashOrNumber::Hash(hash)) .map_err(|err| EngineApiError::Internal(Box::new(err)))?; - result.push(block.map(convert_to_payload_body_v1)); + result.push(block.map(&f)); } Ok(result) } + /// Called to retrieve execution payload bodies by hashes. + pub fn get_payload_bodies_by_hash_v1( + &self, + hashes: Vec, + ) -> EngineApiResult { + self.get_payload_bodies_by_hash_with(hashes, convert_to_payload_body_v1) + } + + /// Called to retrieve execution payload bodies by hashes. + /// + /// Same as [`Self::get_payload_bodies_by_hash_v1`] but as [`ExecutionPayloadBodiesV2`]. + pub fn get_payload_bodies_by_hash_v2( + &self, + hashes: Vec, + ) -> EngineApiResult { + self.get_payload_bodies_by_hash_with(hashes, convert_to_payload_body_v2) + } + /// Called to verify network configuration parameters and ensure that Consensus and Execution /// layers are using the latest configuration. pub fn exchange_transition_configuration( @@ -760,11 +811,22 @@ where ) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV1"); let start = Instant::now(); - let res = Self::get_payload_bodies_by_hash(self, block_hashes); + let res = Self::get_payload_bodies_by_hash_v1(self, block_hashes); self.inner.metrics.latency.get_payload_bodies_by_hash_v1.record(start.elapsed()); Ok(res?) } + async fn get_payload_bodies_by_hash_v2( + &self, + block_hashes: Vec, + ) -> RpcResult { + trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV2"); + let start = Instant::now(); + let res = Self::get_payload_bodies_by_hash_v2(self, block_hashes); + self.inner.metrics.latency.get_payload_bodies_by_hash_v2.record(start.elapsed()); + Ok(res?) + } + /// Handler for `engine_getPayloadBodiesByRangeV1` /// /// See also @@ -788,11 +850,23 @@ where ) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV1"); let start_time = Instant::now(); - let res = Self::get_payload_bodies_by_range(self, start.to(), count.to()).await; + let res = Self::get_payload_bodies_by_range_v1(self, start.to(), count.to()).await; self.inner.metrics.latency.get_payload_bodies_by_range_v1.record(start_time.elapsed()); Ok(res?) } + async fn get_payload_bodies_by_range_v2( + &self, + start: U64, + count: U64, + ) -> RpcResult { + trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV2"); + let start_time = Instant::now(); + let res = Self::get_payload_bodies_by_range_v2(self, start.to(), count.to()).await; + self.inner.metrics.latency.get_payload_bodies_by_range_v2.record(start_time.elapsed()); + Ok(res?) + } + /// Handler for `engine_exchangeTransitionConfigurationV1` /// See also async fn exchange_transition_configuration( @@ -929,7 +1003,7 @@ mod tests { // test [EngineApiMessage::GetPayloadBodiesByRange] for (start, count) in by_range_tests { - let res = api.get_payload_bodies_by_range(start, count).await; + let res = api.get_payload_bodies_by_range_v1(start, count).await; assert_matches!(res, Err(EngineApiError::InvalidBodiesRange { .. })); } } @@ -939,7 +1013,7 @@ mod tests { let (_, api) = setup_engine_api(); let request_count = MAX_PAYLOAD_BODIES_LIMIT + 1; - let res = api.get_payload_bodies_by_range(0, request_count).await; + let res = api.get_payload_bodies_by_range_v1(0, request_count).await; assert_matches!(res, Err(EngineApiError::PayloadRequestTooLarge { .. })); } @@ -959,7 +1033,7 @@ mod tests { .map(|b| Some(convert_to_payload_body_v1(b.unseal()))) .collect::>(); - let res = api.get_payload_bodies_by_range(start, count).await.unwrap(); + let res = api.get_payload_bodies_by_range_v1(start, count).await.unwrap(); assert_eq!(res, expected); } @@ -1000,7 +1074,7 @@ mod tests { }) .collect::>(); - let res = api.get_payload_bodies_by_range(start, count).await.unwrap(); + let res = api.get_payload_bodies_by_range_v1(start, count).await.unwrap(); assert_eq!(res, expected); let expected = blocks @@ -1020,7 +1094,7 @@ mod tests { .collect::>(); let hashes = blocks.iter().map(|b| b.hash()).collect(); - let res = api.get_payload_bodies_by_hash(hashes).unwrap(); + let res = api.get_payload_bodies_by_hash_v1(hashes).unwrap(); assert_eq!(res, expected); } } diff --git a/crates/rpc/rpc-engine-api/src/metrics.rs b/crates/rpc/rpc-engine-api/src/metrics.rs index 73489b7557b6..0ae97768b6c0 100644 --- a/crates/rpc/rpc-engine-api/src/metrics.rs +++ b/crates/rpc/rpc-engine-api/src/metrics.rs @@ -44,8 +44,12 @@ pub(crate) struct EngineApiLatencyMetrics { pub(crate) get_payload_v4: Histogram, /// Latency for `engine_getPayloadBodiesByRangeV1` pub(crate) get_payload_bodies_by_range_v1: Histogram, + /// Latency for `engine_getPayloadBodiesByRangeV2` + pub(crate) get_payload_bodies_by_range_v2: Histogram, /// Latency for `engine_getPayloadBodiesByHashV1` pub(crate) get_payload_bodies_by_hash_v1: Histogram, + /// Latency for `engine_getPayloadBodiesByHashV2` + pub(crate) get_payload_bodies_by_hash_v2: Histogram, /// Latency for `engine_exchangeTransitionConfigurationV1` pub(crate) exchange_transition_configuration: Histogram, } diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index 75b42cafe8fd..9867b2500c5f 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -8,8 +8,8 @@ use reth_primitives::{ }; use reth_rpc_types::engine::{ payload::{ExecutionPayloadBodyV1, ExecutionPayloadFieldV2, ExecutionPayloadInputV2}, - ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, - ExecutionPayloadV4, PayloadError, + ExecutionPayload, ExecutionPayloadBodyV2, ExecutionPayloadV1, ExecutionPayloadV2, + ExecutionPayloadV3, ExecutionPayloadV4, PayloadError, }; /// Converts [`ExecutionPayloadV1`] to [Block] @@ -378,6 +378,52 @@ pub fn convert_to_payload_body_v1(value: Block) -> ExecutionPayloadBodyV1 { } } +/// Converts [Block] to [`ExecutionPayloadBodyV2`] +pub fn convert_to_payload_body_v2(value: Block) -> ExecutionPayloadBodyV2 { + let transactions = value.body.into_iter().map(|tx| { + let mut out = Vec::new(); + tx.encode_enveloped(&mut out); + out.into() + }); + + let mut payload = ExecutionPayloadBodyV2 { + transactions: transactions.collect(), + withdrawals: value.withdrawals.map(Withdrawals::into_inner), + deposit_requests: None, + withdrawal_requests: None, + consolidation_requests: None, + }; + + if let Some(requests) = value.requests { + let (deposit_requests, withdrawal_requests, consolidation_requests) = + requests.into_iter().fold( + (Vec::new(), Vec::new(), Vec::new()), + |(mut deposits, mut withdrawals, mut consolidation_requests), request| { + match request { + Request::DepositRequest(r) => { + deposits.push(r); + } + Request::WithdrawalRequest(r) => { + withdrawals.push(r); + } + Request::ConsolidationRequest(r) => { + consolidation_requests.push(r); + } + _ => {} + }; + + (deposits, withdrawals, consolidation_requests) + }, + ); + + payload.deposit_requests = Some(deposit_requests); + payload.withdrawal_requests = Some(withdrawal_requests); + payload.consolidation_requests = Some(consolidation_requests); + } + + payload +} + /// Transforms a [`SealedBlock`] into a [`ExecutionPayloadV1`] pub fn execution_payload_from_sealed_block(value: SealedBlock) -> ExecutionPayloadV1 { let transactions = value.raw_transactions(); From 17af21f826faae2ae3701bc4ce1713b29924363f Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:29:50 +0200 Subject: [PATCH 394/405] chore: move `stage` command to `reth-cli-commands` (#9384) --- Cargo.lock | 4 ++ bin/reth/src/cli/mod.rs | 10 ++-- bin/reth/src/commands/mod.rs | 1 - crates/cli/commands/Cargo.toml | 8 ++- crates/cli/commands/src/lib.rs | 1 + .../cli/commands/src}/stage/drop.rs | 4 +- .../cli/commands/src}/stage/dump/execution.rs | 27 ++++++---- .../src}/stage/dump/hashing_account.rs | 0 .../src}/stage/dump/hashing_storage.rs | 0 .../cli/commands/src}/stage/dump/merkle.rs | 6 +-- .../cli/commands/src}/stage/dump/mod.rs | 29 ++++++++--- .../cli/commands/src}/stage/mod.rs | 14 +++-- .../cli/commands/src}/stage/run.rs | 52 ++++++++++--------- .../cli/commands/src}/stage/unwind.rs | 8 +-- 14 files changed, 107 insertions(+), 57 deletions(-) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/drop.rs (98%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/dump/execution.rs (90%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/dump/hashing_account.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/dump/hashing_storage.rs (100%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/dump/merkle.rs (97%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/dump/mod.rs (78%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/mod.rs (73%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/run.rs (91%) rename {bin/reth/src/commands => crates/cli/commands/src}/stage/unwind.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index 62727689e741..5236fb38ee33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6617,8 +6617,10 @@ dependencies = [ "confy", "crossterm", "eyre", + "fdlimit", "human_bytes", "itertools 0.13.0", + "metrics-process", "proptest", "proptest-arbitrary-interop", "ratatui", @@ -6627,11 +6629,13 @@ dependencies = [ "reth-cli-runner", "reth-cli-util", "reth-config", + "reth-consensus", "reth-db", "reth-db-api", "reth-db-common", "reth-downloaders", "reth-evm", + "reth-exex", "reth-fs-util", "reth-network", "reth-network-p2p", diff --git a/bin/reth/src/cli/mod.rs b/bin/reth/src/cli/mod.rs index baa617b2601e..e369307267b8 100644 --- a/bin/reth/src/cli/mod.rs +++ b/bin/reth/src/cli/mod.rs @@ -8,13 +8,15 @@ use crate::{ commands::{ debug_cmd, import, node::{self, NoArgs}, - stage, }, + macros::block_executor, version::{LONG_VERSION, SHORT_VERSION}, }; use clap::{value_parser, Parser, Subcommand}; use reth_chainspec::ChainSpec; -use reth_cli_commands::{config_cmd, db, dump_genesis, init_cmd, init_state, p2p, prune, recover}; +use reth_cli_commands::{ + config_cmd, db, dump_genesis, init_cmd, init_state, p2p, prune, recover, stage, +}; use reth_cli_runner::CliRunner; use reth_db::DatabaseEnv; use reth_node_builder::{NodeBuilder, WithLaunchContext}; @@ -159,7 +161,9 @@ impl Cli { } Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()), Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute()), - Commands::Stage(command) => runner.run_command_until_exit(|ctx| command.execute(ctx)), + Commands::Stage(command) => runner.run_command_until_exit(|ctx| { + command.execute(ctx, |chain_spec| block_executor!(chain_spec)) + }), Commands::P2P(command) => runner.run_until_ctrl_c(command.execute()), #[cfg(feature = "dev")] Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), diff --git a/bin/reth/src/commands/mod.rs b/bin/reth/src/commands/mod.rs index 2bd023f4aec0..cf1b79be59c5 100644 --- a/bin/reth/src/commands/mod.rs +++ b/bin/reth/src/commands/mod.rs @@ -3,4 +3,3 @@ pub mod debug_cmd; pub mod import; pub mod node; -pub mod stage; diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index d413815f57ce..1bb1a4e00e2f 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -15,11 +15,13 @@ reth-chainspec.workspace = true reth-cli-runner.workspace = true reth-cli-util.workspace = true reth-config.workspace = true +reth-consensus.workspace = true reth-db = { workspace = true, features = ["mdbx"] } reth-db-api.workspace = true reth-db-common.workspace = true reth-downloaders.workspace = true reth-evm.workspace = true +reth-exex.workspace = true reth-fs-util.workspace = true reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true @@ -46,6 +48,7 @@ tracing.workspace = true backon.workspace = true # io +fdlimit.workspace = true confy.workspace = true toml = { workspace = true, features = ["display"] } @@ -56,6 +59,9 @@ ratatui = { version = "0.27", default-features = false, features = [ "crossterm", ] } +# metrics +metrics-process.workspace = true + # reth test-vectors proptest = { workspace = true, optional = true } arbitrary = { workspace = true, optional = true } @@ -69,4 +75,4 @@ dev = [ "dep:proptest-arbitrary-interop", "reth-primitives/arbitrary", "reth-db-api/arbitrary" -] \ No newline at end of file +] diff --git a/crates/cli/commands/src/lib.rs b/crates/cli/commands/src/lib.rs index 3ddbd91a9362..16767544e7ca 100644 --- a/crates/cli/commands/src/lib.rs +++ b/crates/cli/commands/src/lib.rs @@ -17,5 +17,6 @@ pub mod init_state; pub mod p2p; pub mod prune; pub mod recover; +pub mod stage; #[cfg(feature = "dev")] pub mod test_vectors; diff --git a/bin/reth/src/commands/stage/drop.rs b/crates/cli/commands/src/stage/drop.rs similarity index 98% rename from bin/reth/src/commands/stage/drop.rs rename to crates/cli/commands/src/stage/drop.rs index 88f5650d558c..8278185df09a 100644 --- a/bin/reth/src/commands/stage/drop.rs +++ b/crates/cli/commands/src/stage/drop.rs @@ -1,14 +1,14 @@ //! Database debugging tool -use crate::args::StageEnum; +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; use itertools::Itertools; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_db::{static_file::iter_static_files, tables, DatabaseEnv}; use reth_db_api::transaction::DbTxMut; use reth_db_common::{ init::{insert_genesis_header, insert_genesis_history, insert_genesis_state}, DbTool, }; +use reth_node_core::args::StageEnum; use reth_provider::{providers::StaticFileWriter, StaticFileProviderFactory}; use reth_stages::StageId; use reth_static_file_types::{find_fixed_range, StaticFileSegment}; diff --git a/bin/reth/src/commands/stage/dump/execution.rs b/crates/cli/commands/src/stage/dump/execution.rs similarity index 90% rename from bin/reth/src/commands/stage/dump/execution.rs rename to crates/cli/commands/src/stage/dump/execution.rs index 05df9f4b90dc..61fc5e41ceff 100644 --- a/bin/reth/src/commands/stage/dump/execution.rs +++ b/crates/cli/commands/src/stage/dump/execution.rs @@ -1,22 +1,27 @@ use super::setup; -use crate::macros::block_executor; use reth_db::{tables, DatabaseEnv}; use reth_db_api::{ cursor::DbCursorRO, database::Database, table::TableImporter, transaction::DbTx, }; use reth_db_common::DbTool; +use reth_evm::{execute::BlockExecutorProvider, noop::NoopBlockExecutorProvider}; use reth_node_core::dirs::{ChainPath, DataDirPath}; -use reth_provider::{providers::StaticFileProvider, ChainSpecProvider, ProviderFactory}; +use reth_provider::{providers::StaticFileProvider, ProviderFactory}; use reth_stages::{stages::ExecutionStage, Stage, StageCheckpoint, UnwindInput}; use tracing::info; -pub(crate) async fn dump_execution_stage( +pub(crate) async fn dump_execution_stage( db_tool: &DbTool, from: u64, to: u64, output_datadir: ChainPath, should_run: bool, -) -> eyre::Result<()> { + executor: E, +) -> eyre::Result<()> +where + DB: Database, + E: BlockExecutorProvider, +{ let (output_db, tip_block_number) = setup(from, to, &output_datadir.db(), db_tool)?; import_tables_with_range(&output_db, db_tool, from, to)?; @@ -32,6 +37,7 @@ pub(crate) async fn dump_execution_stage( ), to, from, + executor, )?; } @@ -127,8 +133,7 @@ fn unwind_and_copy( ) -> eyre::Result<()> { let provider = db_tool.provider_factory.provider_rw()?; - let executor = block_executor!(db_tool.chain()); - let mut exec_stage = ExecutionStage::new_with_executor(executor); + let mut exec_stage = ExecutionStage::new_with_executor(NoopBlockExecutorProvider::default()); exec_stage.unwind( &provider, @@ -150,14 +155,18 @@ fn unwind_and_copy( } /// Try to re-execute the stage without committing -fn dry_run( +fn dry_run( output_provider_factory: ProviderFactory, to: u64, from: u64, -) -> eyre::Result<()> { + executor: E, +) -> eyre::Result<()> +where + DB: Database, + E: BlockExecutorProvider, +{ info!(target: "reth::cli", "Executing stage. [dry-run]"); - let executor = block_executor!(output_provider_factory.chain_spec()); let mut exec_stage = ExecutionStage::new_with_executor(executor); let input = diff --git a/bin/reth/src/commands/stage/dump/hashing_account.rs b/crates/cli/commands/src/stage/dump/hashing_account.rs similarity index 100% rename from bin/reth/src/commands/stage/dump/hashing_account.rs rename to crates/cli/commands/src/stage/dump/hashing_account.rs diff --git a/bin/reth/src/commands/stage/dump/hashing_storage.rs b/crates/cli/commands/src/stage/dump/hashing_storage.rs similarity index 100% rename from bin/reth/src/commands/stage/dump/hashing_storage.rs rename to crates/cli/commands/src/stage/dump/hashing_storage.rs diff --git a/bin/reth/src/commands/stage/dump/merkle.rs b/crates/cli/commands/src/stage/dump/merkle.rs similarity index 97% rename from bin/reth/src/commands/stage/dump/merkle.rs rename to crates/cli/commands/src/stage/dump/merkle.rs index f004a4a1a3e9..2d13c15153df 100644 --- a/bin/reth/src/commands/stage/dump/merkle.rs +++ b/crates/cli/commands/src/stage/dump/merkle.rs @@ -1,10 +1,10 @@ use super::setup; -use crate::macros::block_executor; use eyre::Result; use reth_config::config::EtlConfig; use reth_db::{tables, DatabaseEnv}; use reth_db_api::{database::Database, table::TableImporter}; use reth_db_common::DbTool; +use reth_evm::noop::NoopBlockExecutorProvider; use reth_exex::ExExManagerHandle; use reth_node_core::dirs::{ChainPath, DataDirPath}; use reth_primitives::BlockNumber; @@ -86,11 +86,9 @@ fn unwind_and_copy( MerkleStage::default_unwind().unwind(&provider, unwind)?; - let executor = block_executor!(db_tool.chain()); - // Bring Plainstate to TO (hashing stage execution requires it) let mut exec_stage = ExecutionStage::new( - executor, + NoopBlockExecutorProvider::default(), // Not necessary for unwinding. ExecutionStageThresholds { max_blocks: Some(u64::MAX), max_changes: None, diff --git a/bin/reth/src/commands/stage/dump/mod.rs b/crates/cli/commands/src/stage/dump/mod.rs similarity index 78% rename from bin/reth/src/commands/stage/dump/mod.rs rename to crates/cli/commands/src/stage/dump/mod.rs index 4cdf3af8d2a3..7366ff9981e0 100644 --- a/bin/reth/src/commands/stage/dump/mod.rs +++ b/crates/cli/commands/src/stage/dump/mod.rs @@ -1,15 +1,19 @@ //! Database debugging tool -use crate::{args::DatadirArgs, dirs::DataDirPath}; +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; +use reth_chainspec::ChainSpec; use reth_db::{init_db, mdbx::DatabaseArguments, tables, DatabaseEnv}; use reth_db_api::{ cursor::DbCursorRO, database::Database, models::ClientVersion, table::TableImporter, transaction::DbTx, }; use reth_db_common::DbTool; -use reth_node_core::dirs::PlatformPath; -use std::path::PathBuf; +use reth_evm::execute::BlockExecutorProvider; +use reth_node_core::{ + args::DatadirArgs, + dirs::{DataDirPath, PlatformPath}, +}; +use std::{path::PathBuf, sync::Arc}; use tracing::info; mod hashing_storage; @@ -72,16 +76,29 @@ macro_rules! handle_stage { let output_datadir = output_datadir.with_chain($tool.chain().chain, DatadirArgs::default()); $stage_fn($tool, *from, *to, output_datadir, *dry_run).await? }}; + + ($stage_fn:ident, $tool:expr, $command:expr, $executor:expr) => {{ + let StageCommand { output_datadir, from, to, dry_run, .. } = $command; + let output_datadir = output_datadir.with_chain($tool.chain().chain, DatadirArgs::default()); + $stage_fn($tool, *from, *to, output_datadir, *dry_run, $executor).await? + }}; } impl Command { /// Execute `dump-stage` command - pub async fn execute(self) -> eyre::Result<()> { + pub async fn execute(self, executor: F) -> eyre::Result<()> + where + E: BlockExecutorProvider, + F: FnOnce(Arc) -> E, + { let Environment { provider_factory, .. } = self.env.init(AccessRights::RO)?; let tool = DbTool::new(provider_factory)?; match &self.command { - Stages::Execution(cmd) => handle_stage!(dump_execution_stage, &tool, cmd), + Stages::Execution(cmd) => { + let executor = executor(tool.chain()); + handle_stage!(dump_execution_stage, &tool, cmd, executor) + } Stages::StorageHashing(cmd) => handle_stage!(dump_hashing_storage_stage, &tool, cmd), Stages::AccountHashing(cmd) => handle_stage!(dump_hashing_account_stage, &tool, cmd), Stages::Merkle(cmd) => handle_stage!(dump_merkle_stage, &tool, cmd), diff --git a/bin/reth/src/commands/stage/mod.rs b/crates/cli/commands/src/stage/mod.rs similarity index 73% rename from bin/reth/src/commands/stage/mod.rs rename to crates/cli/commands/src/stage/mod.rs index 8f514295e25c..e0365c879d7e 100644 --- a/bin/reth/src/commands/stage/mod.rs +++ b/crates/cli/commands/src/stage/mod.rs @@ -1,7 +1,11 @@ //! `reth stage` command +use std::sync::Arc; + use clap::{Parser, Subcommand}; +use reth_chainspec::ChainSpec; use reth_cli_runner::CliContext; +use reth_evm::execute::BlockExecutorProvider; pub mod drop; pub mod dump; @@ -35,11 +39,15 @@ pub enum Subcommands { impl Command { /// Execute `stage` command - pub async fn execute(self, ctx: CliContext) -> eyre::Result<()> { + pub async fn execute(self, ctx: CliContext, executor: F) -> eyre::Result<()> + where + E: BlockExecutorProvider, + F: FnOnce(Arc) -> E, + { match self.command { - Subcommands::Run(command) => command.execute(ctx).await, + Subcommands::Run(command) => command.execute(ctx, executor).await, Subcommands::Drop(command) => command.execute().await, - Subcommands::Dump(command) => command.execute().await, + Subcommands::Dump(command) => command.execute(executor).await, Subcommands::Unwind(command) => command.execute().await, } } diff --git a/bin/reth/src/commands/stage/run.rs b/crates/cli/commands/src/stage/run.rs similarity index 91% rename from bin/reth/src/commands/stage/run.rs rename to crates/cli/commands/src/stage/run.rs index 63aa760498ec..2a2dd6f8a25e 100644 --- a/bin/reth/src/commands/stage/run.rs +++ b/crates/cli/commands/src/stage/run.rs @@ -1,19 +1,20 @@ //! Main `stage` command //! //! Stage debugging tool -use crate::{ - args::{NetworkArgs, StageEnum}, - macros::block_executor, - prometheus_exporter, -}; +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::Parser; use reth_beacon_consensus::EthBeaconConsensus; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; +use reth_chainspec::ChainSpec; use reth_cli_runner::CliContext; use reth_cli_util::get_secret_key; use reth_config::config::{HashingConfig, SenderRecoveryConfig, TransactionLookupConfig}; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; +use reth_evm::execute::BlockExecutorProvider; use reth_exex::ExExManagerHandle; +use reth_node_core::{ + args::{NetworkArgs, StageEnum}, + prometheus_exporter, +}; use reth_provider::{ ChainSpecProvider, StageCheckpointReader, StageCheckpointWriter, StaticFileProviderFactory, StaticFileWriter, @@ -83,7 +84,11 @@ pub struct Command { impl Command { /// Execute `stage` command - pub async fn execute(self, ctx: CliContext) -> eyre::Result<()> { + pub async fn execute(self, ctx: CliContext, executor: F) -> eyre::Result<()> + where + E: BlockExecutorProvider, + F: FnOnce(Arc) -> E, + { // Raise the fd limit of the process. // Does not do anything on windows. let _ = fdlimit::raise_fd_limit(); @@ -163,24 +168,21 @@ impl Command { })), None, ), - StageEnum::Execution => { - let executor = block_executor!(provider_factory.chain_spec()); - ( - Box::new(ExecutionStage::new( - executor, - ExecutionStageThresholds { - max_blocks: Some(batch_size), - max_changes: None, - max_cumulative_gas: None, - max_duration: None, - }, - config.stages.merkle.clean_threshold, - prune_modes, - ExExManagerHandle::empty(), - )), - None, - ) - } + StageEnum::Execution => ( + Box::new(ExecutionStage::new( + executor(provider_factory.chain_spec()), + ExecutionStageThresholds { + max_blocks: Some(batch_size), + max_changes: None, + max_cumulative_gas: None, + max_duration: None, + }, + config.stages.merkle.clean_threshold, + prune_modes, + ExExManagerHandle::empty(), + )), + None, + ), StageEnum::TxLookup => ( Box::new(TransactionLookupStage::new( TransactionLookupConfig { chunk_size: batch_size }, diff --git a/bin/reth/src/commands/stage/unwind.rs b/crates/cli/commands/src/stage/unwind.rs similarity index 97% rename from bin/reth/src/commands/stage/unwind.rs rename to crates/cli/commands/src/stage/unwind.rs index e5c4fde963b8..7659fdfc1501 100644 --- a/bin/reth/src/commands/stage/unwind.rs +++ b/crates/cli/commands/src/stage/unwind.rs @@ -1,13 +1,13 @@ //! Unwinding a certain block range -use crate::macros::block_executor; +use crate::common::{AccessRights, Environment, EnvironmentArgs}; use clap::{Parser, Subcommand}; use reth_beacon_consensus::EthBeaconConsensus; -use reth_cli_commands::common::{AccessRights, Environment, EnvironmentArgs}; use reth_config::Config; use reth_consensus::Consensus; use reth_db_api::database::Database; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; +use reth_evm::noop::NoopBlockExecutorProvider; use reth_exex::ExExManagerHandle; use reth_node_core::args::NetworkArgs; use reth_primitives::{BlockHashOrNumber, BlockNumber, B256}; @@ -119,7 +119,9 @@ impl Command { let prune_modes = config.prune.clone().map(|prune| prune.segments).unwrap_or_default(); let (tip_tx, tip_rx) = watch::channel(B256::ZERO); - let executor = block_executor!(provider_factory.chain_spec()); + + // Unwinding does not require a valid executor + let executor = NoopBlockExecutorProvider::default(); let builder = if self.offline { Pipeline::builder().add_stages( From 94f1adfa7c5d15c39d21fa3e8af6e8d8abb5f63f Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 9 Jul 2024 04:39:50 +0800 Subject: [PATCH 395/405] feat(rpc/ots): implement ots_getContractCreator (#9236) Signed-off-by: jsvisa Co-authored-by: Alexey Shekhirin Co-authored-by: Matthias Seitz --- crates/rpc/rpc-builder/tests/it/http.rs | 4 +- crates/rpc/rpc/src/otterscan.rs | 81 +++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index fd4a2b1db212..968280296b06 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -342,9 +342,7 @@ where .err() .unwrap() )); - assert!(is_unimplemented( - OtterscanClient::get_contract_creator(client, address).await.err().unwrap() - )); + assert!(OtterscanClient::get_contract_creator(client, address).await.unwrap().is_none()); } #[tokio::test(flavor = "multi_thread")] diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index 7a445a7aa274..214315f8aa3a 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -4,15 +4,22 @@ use jsonrpsee::core::RpcResult; use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256}; use reth_rpc_api::{EthApiServer, OtterscanServer}; use reth_rpc_eth_api::helpers::TraceExt; +use reth_rpc_eth_types::EthApiError; use reth_rpc_server_types::result::internal_rpc_err; use reth_rpc_types::{ - trace::otterscan::{ - BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions, - OtsReceipt, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, + trace::{ + otterscan::{ + BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions, + OtsReceipt, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, + }, + parity::{Action, CreateAction, CreateOutput, TraceOutput}, }, BlockTransactions, Header, Transaction, }; -use revm_inspectors::transfer::{TransferInspector, TransferKind}; +use revm_inspectors::{ + tracing::TracingInspectorConfig, + transfer::{TransferInspector, TransferKind}, +}; use revm_primitives::ExecutionResult; const API_LEVEL: u64 = 8; @@ -208,7 +215,69 @@ where } /// Handler for `getContractCreator` - async fn get_contract_creator(&self, _address: Address) -> RpcResult> { - Err(internal_rpc_err("unimplemented")) + async fn get_contract_creator(&self, address: Address) -> RpcResult> { + if !self.has_code(address, None).await? { + return Ok(None); + } + + // use binary search from block [1, latest block number] to find the first block where the + // contract was deployed + let mut low = 1; + let mut high = self.eth.block_number()?.saturating_to::(); + let mut num = high; + + while low <= high { + let mid = (low + high) / 2; + if self.eth.get_code(address, Some(mid.into())).await?.is_empty() { + // not found in current block, need to search in the later blocks + low = mid + 1; + } else { + // found in current block, try to find a lower block + high = mid - 1; + num = mid; + } + } + + let traces = self + .eth + .trace_block_with( + num.into(), + TracingInspectorConfig::default_parity(), + |tx_info, inspector, _, _, _| { + Ok(inspector.into_parity_builder().into_localized_transaction_traces(tx_info)) + }, + ) + .await? + .map(|traces| { + traces + .into_iter() + .flatten() + .map(|tx_trace| { + let trace = tx_trace.trace; + Ok(match (trace.action, trace.result, trace.error) { + ( + Action::Create(CreateAction { from: creator, .. }), + Some(TraceOutput::Create(CreateOutput { + address: contract, .. + })), + None, + ) if contract == address => Some(ContractCreator { + hash: tx_trace + .transaction_hash + .ok_or_else(|| EthApiError::TransactionNotFound)?, + creator, + }), + _ => None, + }) + }) + .filter_map(Result::transpose) + .collect::, EthApiError>>() + }) + .transpose()?; + + // A contract maybe created and then destroyed in multiple transactions, here we + // return the first found transaction, this behavior is consistent with etherscan's + let found = traces.and_then(|traces| traces.first().cloned()); + Ok(found) } } From 9c0bc8477a85fc973511034ca03f7a79dfceb3d9 Mon Sep 17 00:00:00 2001 From: Sean Matt Date: Mon, 8 Jul 2024 17:06:52 -0400 Subject: [PATCH 396/405] refactor(rpc): remove intermediate types from rpc start up process (#9180) Co-authored-by: Matthias Seitz --- crates/node/builder/src/rpc.rs | 3 +- crates/rpc/rpc-builder/src/lib.rs | 346 ++++++--------------- crates/rpc/rpc-builder/tests/it/startup.rs | 52 ++-- crates/rpc/rpc-builder/tests/it/utils.rs | 34 +- examples/rpc-db/src/main.rs | 2 +- 5 files changed, 132 insertions(+), 305 deletions(-) diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 7f6cb5e898cc..03ae899cba8b 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -289,7 +289,8 @@ where extend_rpc_modules.extend_rpc_modules(ctx)?; let server_config = config.rpc.rpc_server_config(); - let launch_rpc = modules.clone().start_server(server_config).map_ok(|handle| { + let cloned_modules = modules.clone(); + let launch_rpc = server_config.start(&cloned_modules).map_ok(|handle| { if let Some(path) = handle.ipc_endpoint() { info!(target: "reth::cli", %path, "RPC IPC server started"); } diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index ee57e79e9aa7..8a6dce5ae6d7 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -8,9 +8,8 @@ //! transaction pool. [`RpcModuleBuilder::build`] returns a [`TransportRpcModules`] which contains //! the transport specific config (what APIs are available via this transport). //! -//! The [`RpcServerConfig`] is used to configure the [`RpcServer`] type which contains all transport -//! implementations (http server, ws server, ipc server). [`RpcServer::start`] requires the -//! [`TransportRpcModules`] so it can start the servers with the configured modules. +//! The [`RpcServerConfig`] is used to assemble and start the http server, ws server, ipc servers, +//! it requires the [`TransportRpcModules`] so it can start the servers with the configured modules. //! //! # Examples //! @@ -58,9 +57,8 @@ //! .build(transports, EthApiBuild::build); //! let handle = RpcServerConfig::default() //! .with_http(ServerBuilder::default()) -//! .start(transport_modules) -//! .await -//! .unwrap(); +//! .start(&transport_modules) +//! .await; //! } //! ``` //! @@ -122,8 +120,7 @@ //! let config = RpcServerConfig::default(); //! //! let (_rpc_handle, _auth_handle) = -//! try_join!(modules.start_server(config), auth_module.start_server(auth_config),) -//! .unwrap(); +//! try_join!(config.start(&modules), auth_module.start_server(auth_config),).unwrap(); //! } //! ``` @@ -137,7 +134,6 @@ use std::{ collections::HashMap, - fmt, net::{Ipv4Addr, SocketAddr, SocketAddrV4}, sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, @@ -147,12 +143,11 @@ use error::{ConflictingModules, RpcError, ServerKind}; use http::{header::AUTHORIZATION, HeaderMap}; use jsonrpsee::{ core::RegisterMethodError, - server::{AlreadyStoppedError, IdProvider, RpcServiceBuilder, Server, ServerHandle}, + server::{AlreadyStoppedError, IdProvider, RpcServiceBuilder, ServerHandle}, Methods, RpcModule, }; use reth_engine_primitives::EngineTypes; use reth_evm::ConfigureEvm; -use reth_ipc::server::IpcServer; use reth_network_api::{noop::NoopNetwork, NetworkInfo, Peers}; use reth_provider::{ AccountReader, BlockReader, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, @@ -175,7 +170,6 @@ use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, TransactionPool}; use serde::{Deserialize, Serialize}; use tower_http::cors::CorsLayer; -use tracing::{instrument, trace}; use crate::{ auth::AuthRpcModule, cors::CorsDomainError, error::WsHttpSamePortError, @@ -237,10 +231,12 @@ where EthApi: FullEthApiServer, { let module_config = module_config.into(); - let server_config = server_config.into(); - RpcModuleBuilder::new(provider, pool, network, executor, events, evm_config) - .build(module_config, eth) - .start_server(server_config) + server_config + .into() + .start( + &RpcModuleBuilder::new(provider, pool, network, executor, events, evm_config) + .build(module_config, eth), + ) .await } @@ -501,8 +497,6 @@ where /// Configures all [`RpcModule`]s specific to the given [`TransportRpcModuleConfig`] which can /// be used to start the transport server(s). - /// - /// See also [`RpcServer::start`] pub fn build( self, module_config: TransportRpcModuleConfig, @@ -1283,28 +1277,26 @@ impl RpcServerConfig { self.ipc_endpoint.clone() } - /// Convenience function to do [`RpcServerConfig::build`] and [`RpcServer::start`] in one step - pub async fn start(self, modules: TransportRpcModules) -> Result { - self.build(&modules).await?.start(modules).await - } - /// Creates the [`CorsLayer`] if any fn maybe_cors_layer(cors: Option) -> Result, CorsDomainError> { cors.as_deref().map(cors::create_cors_layer).transpose() } /// Creates the [`AuthLayer`] if any - fn maybe_jwt_layer(&self) -> Option> { - self.jwt_secret.map(|secret| AuthLayer::new(JwtAuthValidator::new(secret))) + fn maybe_jwt_layer(jwt_secret: Option) -> Option> { + jwt_secret.map(|secret| AuthLayer::new(JwtAuthValidator::new(secret))) } - /// Builds the ws and http server(s). + /// Builds and starts the configured server(s): http, ws, ipc. /// - /// If both are on the same port, they are combined into one server. - async fn build_ws_http( - &mut self, - modules: &TransportRpcModules, - ) -> Result { + /// If both http and ws are on the same port, they are combined into one server. + /// + /// Returns the [`RpcServerHandle`] with the handle to the started servers. + pub async fn start(self, modules: &TransportRpcModules) -> Result { + let mut http_handle = None; + let mut ws_handle = None; + let mut ipc_handle = None; + let http_socket_addr = self.http_addr.unwrap_or(SocketAddr::V4(SocketAddrV4::new( Ipv4Addr::LOCALHOST, constants::DEFAULT_HTTP_RPC_PORT, @@ -1315,6 +1307,17 @@ impl RpcServerConfig { constants::DEFAULT_WS_RPC_PORT, ))); + let metrics = modules.ipc.as_ref().map(RpcRequestMetrics::ipc).unwrap_or_default(); + let ipc_path = + self.ipc_endpoint.clone().unwrap_or_else(|| constants::DEFAULT_IPC_ENDPOINT.into()); + + if let Some(builder) = self.ipc_server_config { + let ipc = builder + .set_rpc_middleware(IpcRpcServiceBuilder::new().layer(metrics)) + .build(ipc_path); + ipc_handle = Some(ipc.start(modules.ipc.clone().expect("ipc server error")).await?); + } + // If both are configured on the same port, we combine them into one server. if self.http_addr == self.ws_addr && self.http_server_config.is_some() && @@ -1327,7 +1330,7 @@ impl RpcServerConfig { http_cors_domains: Some(http_cors.clone()), ws_cors_domains: Some(ws_cors.clone()), } - .into()) + .into()); } Some(ws_cors) } @@ -1336,53 +1339,62 @@ impl RpcServerConfig { .cloned(); // we merge this into one server using the http setup - self.ws_server_config.take(); - modules.config.ensure_ws_http_identical()?; - let builder = self.http_server_config.take().expect("http_server_config is Some"); - let server = builder - .set_http_middleware( - tower::ServiceBuilder::new() - .option_layer(Self::maybe_cors_layer(cors)?) - .option_layer(self.maybe_jwt_layer()), - ) - .set_rpc_middleware( - RpcServiceBuilder::new().layer( - modules - .http - .as_ref() - .or(modules.ws.as_ref()) - .map(RpcRequestMetrics::same_port) - .unwrap_or_default(), - ), - ) - .build(http_socket_addr) - .await - .map_err(|err| RpcError::server_error(err, ServerKind::WsHttp(http_socket_addr)))?; - let addr = server - .local_addr() - .map_err(|err| RpcError::server_error(err, ServerKind::WsHttp(http_socket_addr)))?; - return Ok(WsHttpServer { - http_local_addr: Some(addr), - ws_local_addr: Some(addr), - server: WsHttpServers::SamePort(server), - jwt_secret: self.jwt_secret, - }) + if let Some(builder) = self.http_server_config { + let server = builder + .set_http_middleware( + tower::ServiceBuilder::new() + .option_layer(Self::maybe_cors_layer(cors)?) + .option_layer(Self::maybe_jwt_layer(self.jwt_secret)), + ) + .set_rpc_middleware( + RpcServiceBuilder::new().layer( + modules + .http + .as_ref() + .or(modules.ws.as_ref()) + .map(RpcRequestMetrics::same_port) + .unwrap_or_default(), + ), + ) + .build(http_socket_addr) + .await + .map_err(|err| { + RpcError::server_error(err, ServerKind::WsHttp(http_socket_addr)) + })?; + let addr = server.local_addr().map_err(|err| { + RpcError::server_error(err, ServerKind::WsHttp(http_socket_addr)) + })?; + if let Some(module) = modules.http.as_ref().or(modules.ws.as_ref()) { + let handle = server.start(module.clone()); + http_handle = Some(handle.clone()); + ws_handle = Some(handle); + } + return Ok(RpcServerHandle { + http_local_addr: Some(addr), + ws_local_addr: Some(addr), + http: http_handle, + ws: ws_handle, + ipc_endpoint: self.ipc_endpoint.clone(), + ipc: ipc_handle, + jwt_secret: self.jwt_secret, + }); + } } + let mut ws_local_addr = None; + let mut ws_server = None; let mut http_local_addr = None; let mut http_server = None; - let mut ws_local_addr = None; - let mut ws_server = None; - if let Some(builder) = self.ws_server_config.take() { + if let Some(builder) = self.ws_server_config { let server = builder .ws_only() .set_http_middleware( tower::ServiceBuilder::new() .option_layer(Self::maybe_cors_layer(self.ws_cors_domains.clone())?) - .option_layer(self.maybe_jwt_layer()), + .option_layer(Self::maybe_jwt_layer(self.jwt_secret)), ) .set_rpc_middleware( RpcServiceBuilder::new() @@ -1391,6 +1403,7 @@ impl RpcServerConfig { .build(ws_socket_addr) .await .map_err(|err| RpcError::server_error(err, ServerKind::WS(ws_socket_addr)))?; + let addr = server .local_addr() .map_err(|err| RpcError::server_error(err, ServerKind::WS(ws_socket_addr)))?; @@ -1399,13 +1412,13 @@ impl RpcServerConfig { ws_server = Some(server); } - if let Some(builder) = self.http_server_config.take() { + if let Some(builder) = self.http_server_config { let server = builder .http_only() .set_http_middleware( tower::ServiceBuilder::new() .option_layer(Self::maybe_cors_layer(self.http_cors_domains.clone())?) - .option_layer(self.maybe_jwt_layer()), + .option_layer(Self::maybe_jwt_layer(self.jwt_secret)), ) .set_rpc_middleware( RpcServiceBuilder::new().layer( @@ -1422,36 +1435,20 @@ impl RpcServerConfig { http_server = Some(server); } - Ok(WsHttpServer { + http_handle = http_server + .map(|http_server| http_server.start(modules.http.clone().expect("http server error"))); + ws_handle = ws_server + .map(|ws_server| ws_server.start(modules.ws.clone().expect("ws server error"))); + Ok(RpcServerHandle { http_local_addr, ws_local_addr, - server: WsHttpServers::DifferentPort { http: http_server, ws: ws_server }, + http: http_handle, + ws: ws_handle, + ipc_endpoint: self.ipc_endpoint.clone(), + ipc: ipc_handle, jwt_secret: self.jwt_secret, }) } - - /// Finalize the configuration of the server(s). - /// - /// This consumes the builder and returns a server. - /// - /// Note: The server is not started and does nothing unless polled, See also - /// [`RpcServer::start`] - pub async fn build(mut self, modules: &TransportRpcModules) -> Result { - let mut server = RpcServer::empty(); - server.ws_http = self.build_ws_http(modules).await?; - - if let Some(builder) = self.ipc_server_config { - let metrics = modules.ipc.as_ref().map(RpcRequestMetrics::ipc).unwrap_or_default(); - let ipc_path = - self.ipc_endpoint.unwrap_or_else(|| constants::DEFAULT_IPC_ENDPOINT.into()); - let ipc = builder - .set_rpc_middleware(IpcRpcServiceBuilder::new().layer(metrics)) - .build(ipc_path); - server.ipc = Some(ipc); - } - - Ok(server) - } } /// Holds modules to be installed per transport type @@ -1658,167 +1655,6 @@ impl TransportRpcModules { self.merge_ipc(other)?; Ok(()) } - - /// Convenience function for starting a server - pub async fn start_server(self, builder: RpcServerConfig) -> Result { - builder.start(self).await - } -} - -/// Container type for ws and http servers in all possible combinations. -#[derive(Default)] -struct WsHttpServer { - /// The address of the http server - http_local_addr: Option, - /// The address of the ws server - ws_local_addr: Option, - /// Configured ws,http servers - server: WsHttpServers, - /// The jwt secret. - jwt_secret: Option, -} - -// Define the type alias with detailed type complexity -type WsHttpServerKind = Server< - Stack< - tower::util::Either, Identity>, - Stack, Identity>, - >, - Stack, ->; - -/// Enum for holding the http and ws servers in all possible combinations. -enum WsHttpServers { - /// Both servers are on the same port - SamePort(WsHttpServerKind), - /// Servers are on different ports - DifferentPort { http: Option, ws: Option }, -} - -// === impl WsHttpServers === - -impl WsHttpServers { - /// Starts the servers and returns the handles (http, ws) - fn start( - self, - http_module: Option>, - ws_module: Option>, - config: &TransportRpcModuleConfig, - ) -> Result<(Option, Option), RpcError> { - let mut http_handle = None; - let mut ws_handle = None; - match self { - Self::SamePort(server) => { - // Make sure http and ws modules are identical, since we currently can't run - // different modules on same server - config.ensure_ws_http_identical()?; - - if let Some(module) = http_module.or(ws_module) { - let handle = server.start(module); - http_handle = Some(handle.clone()); - ws_handle = Some(handle); - } - } - Self::DifferentPort { http, ws } => { - if let Some((server, module)) = - http.and_then(|server| http_module.map(|module| (server, module))) - { - http_handle = Some(server.start(module)); - } - if let Some((server, module)) = - ws.and_then(|server| ws_module.map(|module| (server, module))) - { - ws_handle = Some(server.start(module)); - } - } - } - - Ok((http_handle, ws_handle)) - } -} - -impl Default for WsHttpServers { - fn default() -> Self { - Self::DifferentPort { http: None, ws: None } - } -} - -/// Container type for each transport ie. http, ws, and ipc server -pub struct RpcServer { - /// Configured ws,http servers - ws_http: WsHttpServer, - /// ipc server - ipc: Option>>, -} - -// === impl RpcServer === - -impl RpcServer { - fn empty() -> Self { - Self { ws_http: Default::default(), ipc: None } - } - - /// Returns the [`SocketAddr`] of the http server if started. - pub const fn http_local_addr(&self) -> Option { - self.ws_http.http_local_addr - } - /// Return the `JwtSecret` of the server - pub const fn jwt(&self) -> Option { - self.ws_http.jwt_secret - } - - /// Returns the [`SocketAddr`] of the ws server if started. - pub const fn ws_local_addr(&self) -> Option { - self.ws_http.ws_local_addr - } - - /// Returns the endpoint of the ipc server if started. - pub fn ipc_endpoint(&self) -> Option { - self.ipc.as_ref().map(|ipc| ipc.endpoint()) - } - - /// Starts the configured server by spawning the servers on the tokio runtime. - /// - /// This returns an [RpcServerHandle] that's connected to the server task(s) until the server is - /// stopped or the [RpcServerHandle] is dropped. - #[instrument(name = "start", skip_all, fields(http = ?self.http_local_addr(), ws = ?self.ws_local_addr(), ipc = ?self.ipc_endpoint()), target = "rpc", level = "TRACE")] - pub async fn start(self, modules: TransportRpcModules) -> Result { - trace!(target: "rpc", "staring RPC server"); - let Self { ws_http, ipc: ipc_server } = self; - let TransportRpcModules { config, http, ws, ipc } = modules; - let mut handle = RpcServerHandle { - http_local_addr: ws_http.http_local_addr, - ws_local_addr: ws_http.ws_local_addr, - http: None, - ws: None, - ipc_endpoint: None, - ipc: None, - jwt_secret: None, - }; - - let (http, ws) = ws_http.server.start(http, ws, &config)?; - handle.http = http; - handle.ws = ws; - - if let Some((server, module)) = - ipc_server.and_then(|server| ipc.map(|module| (server, module))) - { - handle.ipc_endpoint = Some(server.endpoint()); - handle.ipc = Some(server.start(module).await?); - } - - Ok(handle) - } -} - -impl fmt::Debug for RpcServer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RpcServer") - .field("http", &self.ws_http.http_local_addr.is_some()) - .field("ws", &self.ws_http.ws_local_addr.is_some()) - .field("ipc", &self.ipc.is_some()) - .finish() - } } /// A handle to the spawned servers. diff --git a/crates/rpc/rpc-builder/tests/it/startup.rs b/crates/rpc/rpc-builder/tests/it/startup.rs index 4c873f2b38c9..5680d03a5307 100644 --- a/crates/rpc/rpc-builder/tests/it/startup.rs +++ b/crates/rpc/rpc-builder/tests/it/startup.rs @@ -28,9 +28,8 @@ async fn test_http_addr_in_use() { let builder = test_rpc_builder(); let server = builder .build(TransportRpcModuleConfig::set_http(vec![RethRpcModule::Admin]), EthApiBuild::build); - let result = server - .start_server(RpcServerConfig::http(Default::default()).with_http_address(addr)) - .await; + let result = + RpcServerConfig::http(Default::default()).with_http_address(addr).start(&server).await; let err = result.unwrap_err(); assert!(is_addr_in_use_kind(&err, ServerKind::Http(addr)), "{err}"); } @@ -42,8 +41,7 @@ async fn test_ws_addr_in_use() { let builder = test_rpc_builder(); let server = builder .build(TransportRpcModuleConfig::set_ws(vec![RethRpcModule::Admin]), EthApiBuild::build); - let result = - server.start_server(RpcServerConfig::ws(Default::default()).with_ws_address(addr)).await; + let result = RpcServerConfig::ws(Default::default()).with_ws_address(addr).start(&server).await; let err = result.unwrap_err(); assert!(is_addr_in_use_kind(&err, ServerKind::WS(addr)), "{err}"); } @@ -65,13 +63,11 @@ async fn test_launch_same_port_different_modules() { EthApiBuild::build, ); let addr = test_address(); - let res = server - .start_server( - RpcServerConfig::ws(Default::default()) - .with_ws_address(addr) - .with_http(Default::default()) - .with_http_address(addr), - ) + let res = RpcServerConfig::ws(Default::default()) + .with_ws_address(addr) + .with_http(Default::default()) + .with_http_address(addr) + .start(&server) .await; let err = res.unwrap_err(); assert!(matches!( @@ -89,15 +85,13 @@ async fn test_launch_same_port_same_cors() { EthApiBuild::build, ); let addr = test_address(); - let res = server - .start_server( - RpcServerConfig::ws(Default::default()) - .with_ws_address(addr) - .with_http(Default::default()) - .with_cors(Some("*".to_string())) - .with_http_cors(Some("*".to_string())) - .with_http_address(addr), - ) + let res = RpcServerConfig::ws(Default::default()) + .with_ws_address(addr) + .with_http(Default::default()) + .with_cors(Some("*".to_string())) + .with_http_cors(Some("*".to_string())) + .with_http_address(addr) + .start(&server) .await; assert!(res.is_ok()); } @@ -111,15 +105,13 @@ async fn test_launch_same_port_different_cors() { EthApiBuild::build, ); let addr = test_address(); - let res = server - .start_server( - RpcServerConfig::ws(Default::default()) - .with_ws_address(addr) - .with_http(Default::default()) - .with_cors(Some("*".to_string())) - .with_http_cors(Some("example".to_string())) - .with_http_address(addr), - ) + let res = RpcServerConfig::ws(Default::default()) + .with_ws_address(addr) + .with_http(Default::default()) + .with_cors(Some("*".to_string())) + .with_http_cors(Some("example".to_string())) + .with_http_address(addr) + .start(&server) .await; let err = res.unwrap_err(); assert!(matches!( diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index 85c9dbeac3f2..ea9954f23c10 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -53,8 +53,9 @@ pub async fn launch_auth(secret: JwtSecret) -> AuthServerHandle { pub async fn launch_http(modules: impl Into) -> RpcServerHandle { let builder = test_rpc_builder(); let server = builder.build(TransportRpcModuleConfig::set_http(modules), EthApiBuild::build); - server - .start_server(RpcServerConfig::http(Default::default()).with_http_address(test_address())) + RpcServerConfig::http(Default::default()) + .with_http_address(test_address()) + .start(&server) .await .unwrap() } @@ -63,8 +64,9 @@ pub async fn launch_http(modules: impl Into) -> RpcServerHan pub async fn launch_ws(modules: impl Into) -> RpcServerHandle { let builder = test_rpc_builder(); let server = builder.build(TransportRpcModuleConfig::set_ws(modules), EthApiBuild::build); - server - .start_server(RpcServerConfig::ws(Default::default()).with_ws_address(test_address())) + RpcServerConfig::ws(Default::default()) + .with_http_address(test_address()) + .start(&server) .await .unwrap() } @@ -77,13 +79,11 @@ pub async fn launch_http_ws(modules: impl Into) -> RpcServer TransportRpcModuleConfig::set_ws(modules.clone()).with_http(modules), EthApiBuild::build, ); - server - .start_server( - RpcServerConfig::ws(Default::default()) - .with_ws_address(test_address()) - .with_http(Default::default()) - .with_http_address(test_address()), - ) + RpcServerConfig::ws(Default::default()) + .with_ws_address(test_address()) + .with_http(Default::default()) + .with_http_address(test_address()) + .start(&server) .await .unwrap() } @@ -97,13 +97,11 @@ pub async fn launch_http_ws_same_port(modules: impl Into) -> EthApiBuild::build, ); let addr = test_address(); - server - .start_server( - RpcServerConfig::ws(Default::default()) - .with_ws_address(addr) - .with_http(Default::default()) - .with_http_address(addr), - ) + RpcServerConfig::ws(Default::default()) + .with_ws_address(addr) + .with_http(Default::default()) + .with_http_address(addr) + .start(&server) .await .unwrap() } diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index 85ad28d6a051..30c0479549fc 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -79,7 +79,7 @@ async fn main() -> eyre::Result<()> { // Start the server & keep it alive let server_args = RpcServerConfig::http(Default::default()).with_http_address("0.0.0.0:8545".parse()?); - let _handle = server_args.start(server).await?; + let _handle = server_args.start(&server).await?; futures::future::pending::<()>().await; Ok(()) From 38f4c6118ca7856acda2535c4361c67bfdac4134 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Tue, 9 Jul 2024 11:47:14 +0200 Subject: [PATCH 397/405] chore: fix clippy warnings for needless_borrows_for_generic_args (#9387) --- crates/e2e-test-utils/src/wallet.rs | 2 +- crates/rpc/rpc-layer/src/auth_layer.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/e2e-test-utils/src/wallet.rs b/crates/e2e-test-utils/src/wallet.rs index f8a4230ee940..d24ee2d3f0e4 100644 --- a/crates/e2e-test-utils/src/wallet.rs +++ b/crates/e2e-test-utils/src/wallet.rs @@ -36,7 +36,7 @@ impl Wallet { let mut wallets = Vec::with_capacity(self.amount); for idx in 0..self.amount { let builder = - builder.clone().derivation_path(&format!("{derivation_path}{idx}")).unwrap(); + builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap(); let wallet = builder.build().unwrap().with_chain_id(Some(self.chain_id)); wallets.push(wallet) } diff --git a/crates/rpc/rpc-layer/src/auth_layer.rs b/crates/rpc/rpc-layer/src/auth_layer.rs index 0a11ae8024f6..255273194a37 100644 --- a/crates/rpc/rpc-layer/src/auth_layer.rs +++ b/crates/rpc/rpc-layer/src/auth_layer.rs @@ -232,7 +232,7 @@ mod tests { let body = r#"{"jsonrpc": "2.0", "method": "greet_melkor", "params": [], "id": 1}"#; let response = client - .post(&format!("http://{AUTH_ADDR}:{AUTH_PORT}")) + .post(format!("http://{AUTH_ADDR}:{AUTH_PORT}")) .bearer_auth(jwt.unwrap_or_default()) .body(body) .header(header::CONTENT_TYPE, "application/json") From 67478b7a587cba96fce7d330b89430f541edd36c Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 9 Jul 2024 17:55:55 +0800 Subject: [PATCH 398/405] feat(rpc/ots): implement ots_traceTransaction RPC (#9246) Signed-off-by: jsvisa --- crates/rpc/rpc-api/src/otterscan.rs | 2 +- crates/rpc/rpc-builder/tests/it/http.rs | 4 +--- crates/rpc/rpc/src/otterscan.rs | 32 ++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/crates/rpc/rpc-api/src/otterscan.rs b/crates/rpc/rpc-api/src/otterscan.rs index 4d45cba358e1..c87a128bfbe0 100644 --- a/crates/rpc/rpc-api/src/otterscan.rs +++ b/crates/rpc/rpc-api/src/otterscan.rs @@ -43,7 +43,7 @@ pub trait Otterscan { /// Extract all variations of calls, contract creation and self-destructs and returns a call /// tree. #[method(name = "traceTransaction")] - async fn trace_transaction(&self, tx_hash: TxHash) -> RpcResult; + async fn trace_transaction(&self, tx_hash: TxHash) -> RpcResult>>; /// Tailor-made and expanded version of eth_getBlockByNumber for block details page in /// Otterscan. diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 968280296b06..37c33551f597 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -311,9 +311,7 @@ where OtterscanClient::get_transaction_error(client, tx_hash).await.unwrap(); - assert!(is_unimplemented( - OtterscanClient::trace_transaction(client, tx_hash).await.err().unwrap() - )); + OtterscanClient::trace_transaction(client, tx_hash).await.unwrap(); OtterscanClient::get_block_details(client, block_number).await.unwrap(); diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index 214315f8aa3a..a309b141ea54 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -17,7 +17,7 @@ use reth_rpc_types::{ BlockTransactions, Header, Transaction, }; use revm_inspectors::{ - tracing::TracingInspectorConfig, + tracing::{types::CallTraceNode, TracingInspectorConfig}, transfer::{TransferInspector, TransferKind}, }; use revm_primitives::ExecutionResult; @@ -101,8 +101,34 @@ where } /// Handler for `ots_traceTransaction` - async fn trace_transaction(&self, _tx_hash: TxHash) -> RpcResult { - Err(internal_rpc_err("unimplemented")) + async fn trace_transaction(&self, tx_hash: TxHash) -> RpcResult>> { + let traces = self + .eth + .spawn_trace_transaction_in_block( + tx_hash, + TracingInspectorConfig::default_parity(), + move |_tx_info, inspector, _, _| Ok(inspector.into_traces().into_nodes()), + ) + .await? + .map(|traces| { + traces + .into_iter() + .map(|CallTraceNode { trace, .. }| TraceEntry { + r#type: if trace.is_selfdestruct() { + "SELFDESTRUCT".to_string() + } else { + trace.kind.to_string() + }, + depth: trace.depth as u32, + from: trace.caller, + to: trace.address, + value: trace.value, + input: trace.data, + output: trace.output, + }) + .collect::>() + }); + Ok(traces) } /// Handler for `ots_getBlockDetails` From e4d16eb1ab473cffa738d4836239c904a5156f44 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 9 Jul 2024 12:19:27 +0200 Subject: [PATCH 399/405] chore: update private-testnet.md (#9389) --- book/run/private-testnet.md | 115 +++++++++++++++++------------------- 1 file changed, 53 insertions(+), 62 deletions(-) diff --git a/book/run/private-testnet.md b/book/run/private-testnet.md index 3b24e94449dc..3a987e52c73a 100644 --- a/book/run/private-testnet.md +++ b/book/run/private-testnet.md @@ -13,85 +13,76 @@ To see all possible configurations and flags you can use, including metrics and Genesis data will be generated using this [genesis-generator](https://github.com/ethpandaops/ethereum-genesis-generator) to be used to bootstrap the EL and CL clients for each node. The end result will be a private testnet with nodes deployed as Docker containers in an ephemeral, isolated environment on your machine called an [enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/). Read more about how the `ethereum-package` works by going [here](https://github.com/ethpandaops/ethereum-package/). ### Step 1: Define the parameters and shape of your private network -First, in your home directory, create a file with the name `network_params.json` with the following contents: -```json -{ - "participants": [ - { - "el_type": "reth", - "el_image": "ghcr.io/paradigmxyz/reth", - "cl_type": "lighthouse", - "cl_image": "sigp/lighthouse:latest", - "count": 1 - }, - { - "el_type": "reth", - "el_image": "ghcr.io/paradigmxyz/reth", - "cl_type": "teku", - "cl_image": "consensys/teku:latest", - "count": 1 - } - ], - "launch_additional_services": false -} +First, in your home directory, create a file with the name `network_params.yaml` with the following contents: +```yaml +participants: + - el_type: reth + el_image: ghcr.io/paradigmxyz/reth + cl_type: lighthouse + cl_image: sigp/lighthouse:latest + - el_type: reth + el_image: ghcr.io/paradigmxyz/reth + cl_type: teku + cl_image: consensys/teku:latest ``` > [!TIP] -> If you would like to use a modified reth node, you can build an image locally with a custom tag. The tag can then be used in the `el_image` field in the `network_params.json` file. +> If you would like to use a modified reth node, you can build an image locally with a custom tag. The tag can then be used in the `el_image` field in the `network_params.yaml` file. ### Step 2: Spin up your network Next, run the following command from your command line: ```bash -kurtosis run github.com/ethpandaops/ethereum-package --args-file ~/network_params.json +kurtosis run github.com/ethpandaops/ethereum-package --args-file ~/network_params.yaml --image-download always ``` Kurtosis will spin up an [enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/) (i.e an ephemeral, isolated environment) and begin to configure and instantiate the nodes in your network. In the end, Kurtosis will print the services running in your enclave that form your private testnet alongside all the container ports and files that were generated & used to start up the private testnet. Here is a sample output: ```console -INFO[2023-08-21T18:22:18-04:00] ==================================================== -INFO[2023-08-21T18:22:18-04:00] || Created enclave: silky-swamp || -INFO[2023-08-21T18:22:18-04:00] ==================================================== -Name: silky-swamp -UUID: 3df730c66123 +INFO[2024-07-09T12:01:35+02:00] ======================================================== +INFO[2024-07-09T12:01:35+02:00] || Created enclave: silent-mountain || +INFO[2024-07-09T12:01:35+02:00] ======================================================== +Name: silent-mountain +UUID: cb5d0a7d0e7c Status: RUNNING -Creation Time: Mon, 21 Aug 2023 18:21:32 EDT +Creation Time: Tue, 09 Jul 2024 12:00:03 CEST +Flags: ========================================= Files Artifacts ========================================= UUID Name -c168ec4468f6 1-lighthouse-reth-0-63 -61f821e2cfd5 2-teku-reth-64-127 -e6f94fdac1b8 cl-genesis-data -e6b57828d099 el-genesis-data -1fb632573a2e genesis-generation-config-cl -b8917e497980 genesis-generation-config-el -6fd8c5be336a geth-prefunded-keys -6ab83723b4bd prysm-password +414a075a37aa 1-lighthouse-reth-0-63-0 +34d0b9ff906b 2-teku-reth-64-127-0 +dffa1bcd1da1 el_cl_genesis_data +fdb202429b26 final-genesis-timestamp +da0d9d24b340 genesis-el-cl-env-file +55c46a6555ad genesis_validators_root +ba79dbd109dd jwt_file +04948fd8b1e3 keymanager_file +538211b6b7d7 prysm-password +ed75fe7d5293 validator-ranges ========================================== User Services ========================================== -UUID Name Ports Status -95386198d3f9 cl-1-lighthouse-reth http: 4000/tcp -> http://127.0.0.1:64947 RUNNING - metrics: 5054/tcp -> http://127.0.0.1:64948 - tcp-discovery: 9000/tcp -> 127.0.0.1:64949 - udp-discovery: 9000/udp -> 127.0.0.1:60303 -5f5cc4cf639a cl-1-lighthouse-reth-validator http: 5042/tcp -> 127.0.0.1:64950 RUNNING - metrics: 5064/tcp -> http://127.0.0.1:64951 -27e1cfaddc72 cl-2-teku-reth http: 4000/tcp -> 127.0.0.1:64954 RUNNING - metrics: 8008/tcp -> 127.0.0.1:64952 - tcp-discovery: 9000/tcp -> 127.0.0.1:64953 - udp-discovery: 9000/udp -> 127.0.0.1:53749 -b454497fbec8 el-1-reth-lighthouse engine-rpc: 8551/tcp -> 127.0.0.1:64941 RUNNING - metrics: 9001/tcp -> 127.0.0.1:64937 - rpc: 8545/tcp -> 127.0.0.1:64939 - tcp-discovery: 30303/tcp -> 127.0.0.1:64938 - udp-discovery: 30303/udp -> 127.0.0.1:55861 - ws: 8546/tcp -> 127.0.0.1:64940 -03a2ef13c99b el-2-reth-teku engine-rpc: 8551/tcp -> 127.0.0.1:64945 RUNNING - metrics: 9001/tcp -> 127.0.0.1:64946 - rpc: 8545/tcp -> 127.0.0.1:64943 - tcp-discovery: 30303/tcp -> 127.0.0.1:64942 - udp-discovery: 30303/udp -> 127.0.0.1:64186 - ws: 8546/tcp -> 127.0.0.1:64944 -5c199b334236 prelaunch-data-generator-cl-genesis-data RUNNING -46829c4bd8b0 prelaunch-data-generator-el-genesis-data RUNNING +UUID Name Ports Status +0853f809c300 cl-1-lighthouse-reth http: 4000/tcp -> http://127.0.0.1:32811 RUNNING + metrics: 5054/tcp -> http://127.0.0.1:32812 + tcp-discovery: 9000/tcp -> 127.0.0.1:32813 + udp-discovery: 9000/udp -> 127.0.0.1:32776 +f81cd467efe3 cl-2-teku-reth http: 4000/tcp -> http://127.0.0.1:32814 RUNNING + metrics: 8008/tcp -> http://127.0.0.1:32815 + tcp-discovery: 9000/tcp -> 127.0.0.1:32816 + udp-discovery: 9000/udp -> 127.0.0.1:32777 +f21d5ca3061f el-1-reth-lighthouse engine-rpc: 8551/tcp -> 127.0.0.1:32803 RUNNING + metrics: 9001/tcp -> http://127.0.0.1:32804 + rpc: 8545/tcp -> 127.0.0.1:32801 + tcp-discovery: 30303/tcp -> 127.0.0.1:32805 + udp-discovery: 30303/udp -> 127.0.0.1:32774 + ws: 8546/tcp -> 127.0.0.1:32802 +e234b3b4a440 el-2-reth-teku engine-rpc: 8551/tcp -> 127.0.0.1:32808 RUNNING + metrics: 9001/tcp -> http://127.0.0.1:32809 + rpc: 8545/tcp -> 127.0.0.1:32806 + tcp-discovery: 30303/tcp -> 127.0.0.1:32810 + udp-discovery: 30303/udp -> 127.0.0.1:32775 + ws: 8546/tcp -> 127.0.0.1:32807 +92dd5a0599dc validator-key-generation-cl-validator-keystore RUNNING +f0a7d5343346 vc-1-reth-lighthouse metrics: 8080/tcp -> http://127.0.0.1:32817 RUNNING ``` Great! You now have a private network with 2 full Ethereum nodes on your local machine over Docker - one that is a Reth/Lighthouse pair and another that is Reth/Teku. Check out the [Kurtosis docs](https://docs.kurtosis.com/cli) to learn about the various ways you can interact with and inspect your network. From 5272a53091769afb93cfb0facdc7836a35774413 Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 9 Jul 2024 18:23:14 +0800 Subject: [PATCH 400/405] feat(rpc/ots): implement ots_getTransactionBySenderAndNonce (#9263) Signed-off-by: jsvisa Co-authored-by: Emilia Hane Co-authored-by: Matthias Seitz --- crates/rpc/rpc-api/src/otterscan.rs | 4 +- crates/rpc/rpc-builder/tests/it/http.rs | 12 +-- crates/rpc/rpc/src/otterscan.rs | 134 ++++++++++++++++++++---- 3 files changed, 119 insertions(+), 31 deletions(-) diff --git a/crates/rpc/rpc-api/src/otterscan.rs b/crates/rpc/rpc-api/src/otterscan.rs index c87a128bfbe0..a06fa1a4ddaf 100644 --- a/crates/rpc/rpc-api/src/otterscan.rs +++ b/crates/rpc/rpc-api/src/otterscan.rs @@ -5,7 +5,7 @@ use reth_rpc_types::{ BlockDetails, ContractCreator, InternalOperation, OtsBlockTransactions, TraceEntry, TransactionsWithReceipts, }, - Header, Transaction, + Header, }; /// Otterscan rpc interface. @@ -87,7 +87,7 @@ pub trait Otterscan { &self, sender: Address, nonce: u64, - ) -> RpcResult>; + ) -> RpcResult>; /// Gets the transaction hash and the address who created a contract. #[method(name = "getContractCreator")] diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 37c33551f597..14143d229cca 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -334,12 +334,10 @@ where .err() .unwrap() )); - assert!(is_unimplemented( - OtterscanClient::get_transaction_by_sender_and_nonce(client, sender, nonce,) - .await - .err() - .unwrap() - )); + assert!(OtterscanClient::get_transaction_by_sender_and_nonce(client, sender, nonce) + .await + .err() + .is_none()); assert!(OtterscanClient::get_contract_creator(client, address).await.unwrap().is_none()); } @@ -550,7 +548,7 @@ async fn test_eth_logs_args() { let client = handle.http_client().unwrap(); let mut params = ArrayParams::default(); - params.insert( serde_json::json!({"blockHash":"0x58dc57ab582b282c143424bd01e8d923cddfdcda9455bad02a29522f6274a948"})).unwrap(); + params.insert(serde_json::json!({"blockHash":"0x58dc57ab582b282c143424bd01e8d923cddfdcda9455bad02a29522f6274a948"})).unwrap(); let resp = client.request::, _>("eth_getLogs", params).await; // block does not exist diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index a309b141ea54..14492f957128 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -1,5 +1,6 @@ use alloy_primitives::Bytes; use async_trait::async_trait; +use futures::future::BoxFuture; use jsonrpsee::core::RpcResult; use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256}; use reth_rpc_api::{EthApiServer, OtterscanServer}; @@ -14,7 +15,7 @@ use reth_rpc_types::{ }, parity::{Action, CreateAction, CreateOutput, TraceOutput}, }, - BlockTransactions, Header, Transaction, + BlockTransactions, Header, }; use revm_inspectors::{ tracing::{types::CallTraceNode, TracingInspectorConfig}, @@ -30,6 +31,41 @@ pub struct OtterscanApi { eth: Eth, } +/// Performs a binary search within a given block range to find the desired block number. +/// +/// The binary search is performed by calling the provided asynchronous `check` closure on the +/// blocks of the range. The closure should return a future representing the result of performing +/// the desired logic at a given block. The future resolves to an `bool` where: +/// - `true` indicates that the condition has been matched, but we can try to find a lower block to +/// make the condition more matchable. +/// - `false` indicates that the condition not matched, so the target is not present in the current +/// block and should continue searching in a higher range. +/// +/// Args: +/// - `low`: The lower bound of the block range (inclusive). +/// - `high`: The upper bound of the block range (inclusive). +/// - `check`: A closure that performs the desired logic at a given block. +async fn binary_search<'a, F>(low: u64, high: u64, check: F) -> RpcResult +where + F: Fn(u64) -> BoxFuture<'a, RpcResult>, +{ + let mut low = low; + let mut high = high; + let mut num = high; + + while low <= high { + let mid = (low + high) / 2; + if check(mid).await? { + high = mid - 1; + num = mid; + } else { + low = mid + 1 + } + } + + Ok(num) +} + impl OtterscanApi { /// Creates a new instance of `Otterscan`. pub const fn new(eth: Eth) -> Self { @@ -234,10 +270,51 @@ where /// Handler for `getTransactionBySenderAndNonce` async fn get_transaction_by_sender_and_nonce( &self, - _sender: Address, - _nonce: u64, - ) -> RpcResult> { - Err(internal_rpc_err("unimplemented")) + sender: Address, + nonce: u64, + ) -> RpcResult> { + // Check if the sender is a contract + if self.has_code(sender, None).await? { + return Ok(None) + } + + let highest = + EthApiServer::transaction_count(&self.eth, sender, None).await?.saturating_to::(); + + // If the nonce is higher or equal to the highest nonce, the transaction is pending or not + // exists. + if nonce >= highest { + return Ok(None) + } + + // perform a binary search over the block range to find the block in which the sender's + // nonce reached the requested nonce. + let num = binary_search(1, self.eth.block_number()?.saturating_to(), |mid| { + Box::pin(async move { + let mid_nonce = + EthApiServer::transaction_count(&self.eth, sender, Some(mid.into())) + .await? + .saturating_to::(); + + // The `transaction_count` returns the `nonce` after the transaction was + // executed, which is the state of the account after the block, and we need to find + // the transaction whose nonce is the pre-state, so need to compare with `nonce`(no + // equal). + Ok(mid_nonce > nonce) + }) + }) + .await?; + + let Some(BlockTransactions::Full(transactions)) = + self.eth.block_by_number(num.into(), true).await?.map(|block| block.inner.transactions) + else { + return Err(EthApiError::UnknownBlockNumber.into()); + }; + + Ok(transactions + .into_iter() + .find(|tx| tx.from == sender && tx.nonce == nonce) + .map(|tx| tx.hash)) } /// Handler for `getContractCreator` @@ -246,23 +323,12 @@ where return Ok(None); } - // use binary search from block [1, latest block number] to find the first block where the - // contract was deployed - let mut low = 1; - let mut high = self.eth.block_number()?.saturating_to::(); - let mut num = high; - - while low <= high { - let mid = (low + high) / 2; - if self.eth.get_code(address, Some(mid.into())).await?.is_empty() { - // not found in current block, need to search in the later blocks - low = mid + 1; - } else { - // found in current block, try to find a lower block - high = mid - 1; - num = mid; - } - } + let num = binary_search(1, self.eth.block_number()?.saturating_to(), |mid| { + Box::pin( + async move { Ok(!self.eth.get_code(address, Some(mid.into())).await?.is_empty()) }, + ) + }) + .await?; let traces = self .eth @@ -307,3 +373,27 @@ where Ok(found) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_binary_search() { + // in the middle + let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 5) })).await; + assert_eq!(num, Ok(5)); + + // in the upper + let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 7) })).await; + assert_eq!(num, Ok(7)); + + // in the lower + let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 1) })).await; + assert_eq!(num, Ok(1)); + + // high than the upper + let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 11) })).await; + assert_eq!(num, Ok(10)); + } +} From 41382557b6b347d793cf65c54580f6df0706fab9 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 9 Jul 2024 12:34:09 +0200 Subject: [PATCH 401/405] chore: release 1.0.1 (#9388) --- Cargo.lock | 218 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 110 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5236fb38ee33..0085e71a7a23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2542,7 +2542,7 @@ dependencies = [ [[package]] name = "ef-tests" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "rayon", @@ -6307,7 +6307,7 @@ dependencies = [ [[package]] name = "reth" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "aquamarine", @@ -6388,7 +6388,7 @@ dependencies = [ [[package]] name = "reth-auto-seal-consensus" -version = "1.0.0" +version = "1.0.1" dependencies = [ "futures-util", "reth-beacon-consensus", @@ -6414,7 +6414,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "futures-core", @@ -6436,7 +6436,7 @@ dependencies = [ [[package]] name = "reth-beacon-consensus" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-genesis", "assert_matches", @@ -6487,7 +6487,7 @@ dependencies = [ [[package]] name = "reth-bench" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6528,7 +6528,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-genesis", "aquamarine", @@ -6562,7 +6562,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -6573,7 +6573,7 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-chains", "alloy-eips", @@ -6597,7 +6597,7 @@ dependencies = [ [[package]] name = "reth-cli" -version = "1.0.0" +version = "1.0.1" dependencies = [ "clap", "eyre", @@ -6607,7 +6607,7 @@ dependencies = [ [[package]] name = "reth-cli-commands" -version = "1.0.0" +version = "1.0.1" dependencies = [ "ahash", "arbitrary", @@ -6656,7 +6656,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-tasks", "tokio", @@ -6665,7 +6665,7 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "1.0.0" +version = "1.0.1" dependencies = [ "eyre", "proptest", @@ -6678,7 +6678,7 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6698,7 +6698,7 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.0.0" +version = "1.0.1" dependencies = [ "convert_case 0.6.0", "proc-macro2", @@ -6709,7 +6709,7 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.0.0" +version = "1.0.1" dependencies = [ "confy", "humantime-serde", @@ -6723,7 +6723,7 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.0.0" +version = "1.0.1" dependencies = [ "auto_impl", "reth-primitives", @@ -6732,7 +6732,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.0.0" +version = "1.0.1" dependencies = [ "mockall", "rand 0.8.5", @@ -6744,7 +6744,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6766,7 +6766,7 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.0.0" +version = "1.0.1" dependencies = [ "arbitrary", "assert_matches", @@ -6805,7 +6805,7 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "arbitrary", "assert_matches", @@ -6836,7 +6836,7 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-genesis", "boyer-moore-magiclen", @@ -6861,7 +6861,7 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6888,7 +6888,7 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6914,7 +6914,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -6942,7 +6942,7 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "assert_matches", @@ -6977,7 +6977,7 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-consensus", "alloy-network", @@ -7008,7 +7008,7 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.0.0" +version = "1.0.1" dependencies = [ "aes 0.8.4", "alloy-primitives", @@ -7038,7 +7038,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-chainspec", "reth-payload-primitives", @@ -7047,7 +7047,7 @@ dependencies = [ [[package]] name = "reth-engine-tree" -version = "1.0.0" +version = "1.0.1" dependencies = [ "aquamarine", "assert_matches", @@ -7091,7 +7091,7 @@ dependencies = [ [[package]] name = "reth-engine-util" -version = "1.0.0" +version = "1.0.1" dependencies = [ "eyre", "futures", @@ -7109,7 +7109,7 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-blockchain-tree-api", "reth-consensus", @@ -7121,7 +7121,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "arbitrary", @@ -7155,7 +7155,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-chains", "alloy-genesis", @@ -7177,11 +7177,11 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" -version = "1.0.0" +version = "1.0.1" [[package]] name = "reth-ethereum-consensus" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-chainspec", "reth-consensus", @@ -7192,7 +7192,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine" -version = "1.0.0" +version = "1.0.1" dependencies = [ "futures", "pin-project", @@ -7210,7 +7210,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "reth-chainspec", @@ -7228,7 +7228,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7247,7 +7247,7 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-basic-payload-builder", "reth-errors", @@ -7265,7 +7265,7 @@ dependencies = [ [[package]] name = "reth-etl" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "rayon", @@ -7275,7 +7275,7 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-eips", "auto_impl", @@ -7293,7 +7293,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-eips", "alloy-sol-types", @@ -7313,7 +7313,7 @@ dependencies = [ [[package]] name = "reth-evm-optimism" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-chainspec", "reth-consensus-common", @@ -7333,7 +7333,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7346,7 +7346,7 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7360,7 +7360,7 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.0.0" +version = "1.0.1" dependencies = [ "eyre", "metrics", @@ -7394,7 +7394,7 @@ dependencies = [ [[package]] name = "reth-exex-test-utils" -version = "1.0.0" +version = "1.0.1" dependencies = [ "eyre", "futures-util", @@ -7424,14 +7424,14 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-fs-util" -version = "1.0.0" +version = "1.0.1" dependencies = [ "serde_json", "thiserror", @@ -7439,7 +7439,7 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.0.0" +version = "1.0.1" dependencies = [ "async-trait", "bytes", @@ -7461,7 +7461,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.0.0" +version = "1.0.1" dependencies = [ "bitflags 2.6.0", "byteorder", @@ -7481,7 +7481,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.0.0" +version = "1.0.1" dependencies = [ "bindgen", "cc", @@ -7489,7 +7489,7 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "1.0.0" +version = "1.0.1" dependencies = [ "futures", "metrics", @@ -7500,7 +7500,7 @@ dependencies = [ [[package]] name = "reth-metrics-derive" -version = "1.0.0" +version = "1.0.1" dependencies = [ "metrics", "once_cell", @@ -7514,14 +7514,14 @@ dependencies = [ [[package]] name = "reth-net-banlist" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-net-nat" -version = "1.0.0" +version = "1.0.1" dependencies = [ "futures-util", "reqwest", @@ -7533,7 +7533,7 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-node-bindings", "alloy-provider", @@ -7591,7 +7591,7 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "alloy-rpc-types-admin", @@ -7605,7 +7605,7 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.0.0" +version = "1.0.1" dependencies = [ "auto_impl", "futures", @@ -7623,7 +7623,7 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7639,7 +7639,7 @@ dependencies = [ [[package]] name = "reth-network-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "humantime-serde", "reth-net-banlist", @@ -7652,7 +7652,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.0.0" +version = "1.0.1" dependencies = [ "anyhow", "bincode", @@ -7673,7 +7673,7 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-db-api", "reth-engine-primitives", @@ -7688,7 +7688,7 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.0.0" +version = "1.0.1" dependencies = [ "aquamarine", "backon", @@ -7740,7 +7740,7 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-genesis", "alloy-rpc-types-engine", @@ -7801,7 +7801,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -7834,7 +7834,7 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rpc-types-engine", "futures", @@ -7856,7 +7856,7 @@ dependencies = [ [[package]] name = "reth-node-optimism" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -7904,7 +7904,7 @@ dependencies = [ [[package]] name = "reth-optimism-cli" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "clap", @@ -7936,7 +7936,7 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-chainspec", "reth-consensus", @@ -7947,7 +7947,7 @@ dependencies = [ [[package]] name = "reth-optimism-payload-builder" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "reth-basic-payload-builder", @@ -7971,11 +7971,11 @@ dependencies = [ [[package]] name = "reth-optimism-primitives" -version = "1.0.0" +version = "1.0.1" [[package]] name = "reth-optimism-rpc" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "parking_lot 0.12.3", @@ -7993,7 +7993,7 @@ dependencies = [ [[package]] name = "reth-payload-builder" -version = "1.0.0" +version = "1.0.1" dependencies = [ "futures-util", "metrics", @@ -8015,7 +8015,7 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-chainspec", "reth-errors", @@ -8029,7 +8029,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-chainspec", "reth-primitives", @@ -8039,7 +8039,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-eips", "alloy-genesis", @@ -8083,7 +8083,7 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8111,7 +8111,7 @@ dependencies = [ [[package]] name = "reth-provider" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "alloy-rpc-types-engine", @@ -8153,7 +8153,7 @@ dependencies = [ [[package]] name = "reth-prune" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "assert_matches", @@ -8182,7 +8182,7 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "arbitrary", @@ -8203,7 +8203,7 @@ dependencies = [ [[package]] name = "reth-revm" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-eips", "reth-chainspec", @@ -8220,7 +8220,7 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-dyn-abi", "alloy-genesis", @@ -8277,7 +8277,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "jsonrpsee", "reth-engine-primitives", @@ -8291,7 +8291,7 @@ dependencies = [ [[package]] name = "reth-rpc-api-testing-util" -version = "1.0.0" +version = "1.0.1" dependencies = [ "futures", "jsonrpsee", @@ -8306,7 +8306,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.0.0" +version = "1.0.1" dependencies = [ "clap", "http 1.1.0", @@ -8351,7 +8351,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "assert_matches", @@ -8384,7 +8384,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-dyn-abi", "async-trait", @@ -8415,7 +8415,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-sol-types", "derive_more", @@ -8452,7 +8452,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rpc-types-engine", "assert_matches", @@ -8469,7 +8469,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "jsonrpsee-core", @@ -8484,7 +8484,7 @@ dependencies = [ [[package]] name = "reth-rpc-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -8509,7 +8509,7 @@ dependencies = [ [[package]] name = "reth-rpc-types-compat" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "alloy-rpc-types", @@ -8521,7 +8521,7 @@ dependencies = [ [[package]] name = "reth-stages" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "assert_matches", @@ -8567,7 +8567,7 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "aquamarine", @@ -8596,7 +8596,7 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "arbitrary", @@ -8614,7 +8614,7 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "assert_matches", @@ -8637,7 +8637,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-primitives", "clap", @@ -8648,7 +8648,7 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.0.0" +version = "1.0.1" dependencies = [ "auto_impl", "reth-chainspec", @@ -8664,7 +8664,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.0.0" +version = "1.0.1" dependencies = [ "reth-fs-util", "reth-primitives", @@ -8673,7 +8673,7 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "1.0.0" +version = "1.0.1" dependencies = [ "auto_impl", "dyn-clone", @@ -8690,7 +8690,7 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-genesis", "rand 0.8.5", @@ -8700,7 +8700,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.0.0" +version = "1.0.1" dependencies = [ "tokio", "tokio-stream", @@ -8709,7 +8709,7 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.0.0" +version = "1.0.1" dependencies = [ "clap", "eyre", @@ -8723,7 +8723,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "aquamarine", @@ -8763,7 +8763,7 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "auto_impl", @@ -8796,7 +8796,7 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -8825,7 +8825,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "1.0.0" +version = "1.0.1" dependencies = [ "alloy-rlp", "criterion", diff --git a/Cargo.toml b/Cargo.toml index ae22ca07874a..96f8370548fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.0.0" +version = "1.0.1" edition = "2021" rust-version = "1.79" license = "MIT OR Apache-2.0" From 05ad783763f263675cbef4796af96210c951fea1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 9 Jul 2024 12:38:16 +0200 Subject: [PATCH 402/405] fix: support additional eth call bundle args (#9383) --- crates/rpc/rpc/src/eth/bundle.rs | 39 ++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index aab706b28b23..d28013822ee1 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -16,8 +16,9 @@ use revm::{ db::CacheDB, primitives::{ResultAndState, TxEnv}, }; -use revm_primitives::{EnvKzgSettings, EnvWithHandlerCfg, MAX_BLOB_GAS_PER_BLOCK}; +use revm_primitives::{EnvKzgSettings, EnvWithHandlerCfg, SpecId, MAX_BLOB_GAS_PER_BLOCK}; +use reth_provider::{ChainSpecProvider, HeaderProvider}; use reth_rpc_eth_api::{ helpers::{Call, EthTransactions, LoadPendingBlock}, EthCallBundleApiServer, @@ -48,7 +49,15 @@ where /// state, or it can be used to simulate a past block. The sender is responsible for signing the /// transactions and using the correct nonce and ensuring validity pub async fn call_bundle(&self, bundle: EthCallBundle) -> EthResult { - let EthCallBundle { txs, block_number, state_block_number, timestamp, .. } = bundle; + let EthCallBundle { + txs, + block_number, + state_block_number, + timestamp, + gas_limit, + difficulty, + base_fee, + } = bundle; if txs.is_empty() { return Err(EthApiError::InvalidParams( EthBundleError::EmptyBundleTransactions.to_string(), @@ -88,6 +97,7 @@ where } let block_id: reth_rpc_types::BlockId = state_block_number.into(); + // Note: the block number is considered the `parent` block: let (cfg, mut block_env, at) = self.inner.eth_api.evm_env_at(block_id).await?; // need to adjust the timestamp for the next block @@ -97,6 +107,31 @@ where block_env.timestamp += U256::from(12); } + if let Some(difficulty) = difficulty { + block_env.difficulty = U256::from(difficulty); + } + + if let Some(gas_limit) = gas_limit { + block_env.gas_limit = U256::from(gas_limit); + } + + if let Some(base_fee) = base_fee { + block_env.basefee = U256::from(base_fee); + } else if cfg.handler_cfg.spec_id.is_enabled_in(SpecId::LONDON) { + let parent_block = block_env.number.saturating_to::(); + // here we need to fetch the _next_ block's basefee based on the parent block + let parent = LoadPendingBlock::provider(&self.inner.eth_api) + .header_by_number(parent_block)? + .ok_or_else(|| EthApiError::UnknownBlockNumber)?; + if let Some(base_fee) = parent.next_block_base_fee( + LoadPendingBlock::provider(&self.inner.eth_api) + .chain_spec() + .base_fee_params_at_block(parent_block), + ) { + block_env.basefee = U256::from(base_fee); + } + } + let state_block_number = block_env.number; // use the block number of the request block_env.number = U256::from(block_number); From b97ace200f6baecf5ae7eddebb3c83ad39940cd3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 9 Jul 2024 14:28:44 +0200 Subject: [PATCH 403/405] chore(deps): bump revm 11 (#9391) --- Cargo.lock | 24 ++++++++------- Cargo.toml | 6 ++-- crates/optimism/evm/src/lib.rs | 1 + crates/payload/builder/src/database.rs | 6 ++-- crates/primitives/src/transaction/compat.rs | 33 ++------------------- crates/revm/src/database.rs | 9 ++---- crates/rpc/rpc-eth-api/src/helpers/call.rs | 2 +- crates/rpc/rpc-eth-types/src/cache/db.rs | 16 +++++----- crates/rpc/rpc-eth-types/src/error.rs | 18 +++++++++-- crates/rpc/rpc-eth-types/src/revm_utils.rs | 5 ++-- crates/transaction-pool/src/validate/eth.rs | 13 ++++---- examples/exex/rollup/src/db.rs | 2 +- 12 files changed, 60 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0085e71a7a23..cdb35c8f9f71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4561,7 +4561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -8851,9 +8851,9 @@ dependencies = [ [[package]] name = "revm" -version = "10.0.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355bde4e21578c241f9379fbb344a73d254969b5007239115e094dda1511cd34" +checksum = "44102920a77b38b0144f4b84dcaa31fe44746e78f53685c2ca0149af5312e048" dependencies = [ "auto_impl", "cfg-if", @@ -8866,9 +8866,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.1.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0971cad2f8f1ecb10e270d80646e63bf19daef0dc0a17a45680d24bb346b7c" +checksum = "083fe9c20db39ab4d371e9c4d10367408fa3565ad277a4fa1770f7d9314e1b92" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -8884,9 +8884,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dfd24faa3cbbd96e0976103d1e174d6559b8036730f70415488ee21870d578" +checksum = "b2b319602039af3d130f792beba76592e7744bb3c4f2db5179758be33985a16b" dependencies = [ "revm-primitives", "serde", @@ -8894,13 +8894,14 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "8.0.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c669c9b105dbb41133c17bf7f34d29368e358a7fee8fcc289e90dbfb024dfc4" +checksum = "86b441000a0d30e06269f822f42a13fa6bec922e951a84b643818651472c4fe6" dependencies = [ "aurora-engine-modexp", "blst", "c-kzg", + "cfg-if", "k256", "once_cell", "p256", @@ -8913,10 +8914,11 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "902184a7a781550858d4b96707098da357429f1e4545806fd5b589f455555cf2" +checksum = "b518f536bacee396eb28a43f0984b25b2cd80f052ba4f2e794d554d711c13f33" dependencies = [ + "alloy-eips", "alloy-primitives", "auto_impl", "bitflags 2.6.0", diff --git a/Cargo.toml b/Cargo.toml index 96f8370548fd..64cb72a53a33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -375,15 +375,15 @@ reth-trie-common = { path = "crates/trie/common" } reth-trie-parallel = { path = "crates/trie/parallel" } # revm -revm = { version = "10.0.0", features = [ +revm = { version = "11.0.0", features = [ "std", "secp256k1", "blst", ], default-features = false } -revm-primitives = { version = "5.0.0", features = [ +revm-primitives = { version = "6.0.0", features = [ "std", ], default-features = false } -revm-inspectors = "0.1" +revm-inspectors = "0.4" # eth alloy-chains = "0.1.15" diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index f8e2d52ff679..8a56014c5688 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -66,6 +66,7 @@ impl ConfigureEvmEnv for OptimismEvmConfig { // blob fields can be None for this tx blob_hashes: Vec::new(), max_fee_per_blob_gas: None, + authorization_list: None, optimism: OptimismFields { source_hash: None, mint: None, diff --git a/crates/payload/builder/src/database.rs b/crates/payload/builder/src/database.rs index 340a8510a105..03ca5084392d 100644 --- a/crates/payload/builder/src/database.rs +++ b/crates/payload/builder/src/database.rs @@ -35,7 +35,7 @@ use std::{ pub struct CachedReads { accounts: HashMap, contracts: HashMap, - block_hashes: HashMap, + block_hashes: HashMap, } // === impl CachedReads === @@ -114,7 +114,7 @@ impl<'a, DB: DatabaseRef> Database for CachedReadsDbMut<'a, DB> { } } - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { let code = match self.cached.block_hashes.entry(number) { Entry::Occupied(entry) => *entry.get(), Entry::Vacant(entry) => *entry.insert(self.db.block_hash_ref(number)?), @@ -148,7 +148,7 @@ impl<'a, DB: DatabaseRef> DatabaseRef for CachedReadsDBRef<'a, DB> { self.inner.borrow_mut().storage(address, index) } - fn block_hash_ref(&self, number: U256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { self.inner.borrow_mut().block_hash(number) } } diff --git a/crates/primitives/src/transaction/compat.rs b/crates/primitives/src/transaction/compat.rs index ec4f7ad8255d..3dd8acf8683f 100644 --- a/crates/primitives/src/transaction/compat.rs +++ b/crates/primitives/src/transaction/compat.rs @@ -40,16 +40,7 @@ impl FillTxEnv for TransactionSigned { tx_env.data = tx.input.clone(); tx_env.chain_id = Some(tx.chain_id); tx_env.nonce = Some(tx.nonce); - tx_env.access_list = tx - .access_list - .iter() - .map(|l| { - ( - l.address, - l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect(), - ) - }) - .collect(); + tx_env.access_list = tx.access_list.0.clone(); tx_env.blob_hashes.clear(); tx_env.max_fee_per_blob_gas.take(); } @@ -62,16 +53,7 @@ impl FillTxEnv for TransactionSigned { tx_env.data = tx.input.clone(); tx_env.chain_id = Some(tx.chain_id); tx_env.nonce = Some(tx.nonce); - tx_env.access_list = tx - .access_list - .iter() - .map(|l| { - ( - l.address, - l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect(), - ) - }) - .collect(); + tx_env.access_list = tx.access_list.0.clone(); tx_env.blob_hashes.clear(); tx_env.max_fee_per_blob_gas.take(); } @@ -84,16 +66,7 @@ impl FillTxEnv for TransactionSigned { tx_env.data = tx.input.clone(); tx_env.chain_id = Some(tx.chain_id); tx_env.nonce = Some(tx.nonce); - tx_env.access_list = tx - .access_list - .iter() - .map(|l| { - ( - l.address, - l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect(), - ) - }) - .collect(); + tx_env.access_list = tx.access_list.0.clone(); tx_env.blob_hashes.clone_from(&tx.blob_versioned_hashes); tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas)); } diff --git a/crates/revm/src/database.rs b/crates/revm/src/database.rs index a1296ae39c24..5edd76bea4da 100644 --- a/crates/revm/src/database.rs +++ b/crates/revm/src/database.rs @@ -121,7 +121,7 @@ impl Database for StateProviderDatabase { /// /// Returns `Ok` with the block hash if found, or the default hash otherwise. /// Note: It safely casts the `number` to `u64`. - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { DatabaseRef::block_hash_ref(self, number) } } @@ -154,11 +154,8 @@ impl DatabaseRef for StateProviderDatabase { /// Retrieves the block hash for a given block number. /// /// Returns `Ok` with the block hash if found, or the default hash otherwise. - fn block_hash_ref(&self, number: U256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { // Get the block hash or default hash with an attempt to convert U256 block number to u64 - Ok(self - .0 - .block_hash(number.try_into().map_err(|_| Self::Error::BlockNumberOverflow(number))?)? - .unwrap_or_default()) + Ok(self.0.block_hash(number)?.unwrap_or_default()) } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 5f6aebaa85b3..0379292894cf 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -718,7 +718,7 @@ pub trait Call: LoadState + SpawnBlocking { } ExecutionResult::Halt { reason, .. } => { match reason { - HaltReason::OutOfGas(_) | HaltReason::InvalidEFOpcode => { + HaltReason::OutOfGas(_) | HaltReason::InvalidFEOpcode => { // Both `OutOfGas` and `InvalidEFOpcode` can occur dynamically if the gas // left is too low. Treat this as an out of gas // condition, knowing that the call succeeds with a diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index 2bd93daf748b..0370f5e600da 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -127,10 +127,6 @@ impl<'a, 'b> Database for StateCacheDbRefMutWrapper<'a, 'b> { self.0.basic(address) } - fn block_hash(&mut self, number: U256) -> Result { - self.0.block_hash(number) - } - fn code_by_hash(&mut self, code_hash: B256) -> Result { self.0.code_by_hash(code_hash) } @@ -142,6 +138,10 @@ impl<'a, 'b> Database for StateCacheDbRefMutWrapper<'a, 'b> { ) -> Result { self.0.storage(address, index) } + + fn block_hash(&mut self, number: u64) -> Result { + self.0.block_hash(number) + } } impl<'a, 'b> DatabaseRef for StateCacheDbRefMutWrapper<'a, 'b> { @@ -154,10 +154,6 @@ impl<'a, 'b> DatabaseRef for StateCacheDbRefMutWrapper<'a, 'b> { self.0.basic_ref(address) } - fn block_hash_ref(&self, number: U256) -> Result { - self.0.block_hash_ref(number) - } - fn code_by_hash_ref(&self, code_hash: B256) -> Result { self.0.code_by_hash_ref(code_hash) } @@ -169,4 +165,8 @@ impl<'a, 'b> DatabaseRef for StateCacheDbRefMutWrapper<'a, 'b> { ) -> Result { self.0.storage_ref(address, index) } + + fn block_hash_ref(&self, number: u64) -> Result { + self.0.block_hash_ref(number) + } } diff --git a/crates/rpc/rpc-eth-types/src/error.rs b/crates/rpc/rpc-eth-types/src/error.rs index 7cb302a53bb2..95d989a19a81 100644 --- a/crates/rpc/rpc-eth-types/src/error.rs +++ b/crates/rpc/rpc-eth-types/src/error.rs @@ -360,6 +360,15 @@ pub enum RpcInvalidTransactionError { /// Blob transaction is a create transaction #[error("blob transaction is a create transaction")] BlobTransactionIsCreate, + /// EOF crate should have `to` address + #[error("EOF crate should have `to` address")] + EofCrateShouldHaveToAddress, + /// EIP-7702 is not enabled. + #[error("EIP-7702 authorization list not supported")] + AuthorizationListNotSupported, + /// EIP-7702 transaction has invalid fields set. + #[error("EIP-7702 authorization list has invalid fields")] + AuthorizationListInvalidFields, /// Optimism related error #[error(transparent)] #[cfg(feature = "optimism")] @@ -454,6 +463,13 @@ impl From for RpcInvalidTransactionError { InvalidTransaction::BlobVersionNotSupported => Self::BlobHashVersionMismatch, InvalidTransaction::TooManyBlobs { max, have } => Self::TooManyBlobs { max, have }, InvalidTransaction::BlobCreateTransaction => Self::BlobTransactionIsCreate, + InvalidTransaction::EofCrateShouldHaveToAddress => Self::EofCrateShouldHaveToAddress, + InvalidTransaction::AuthorizationListNotSupported => { + Self::AuthorizationListNotSupported + } + InvalidTransaction::AuthorizationListInvalidFields => { + Self::AuthorizationListInvalidFields + } #[cfg(feature = "optimism")] InvalidTransaction::DepositSystemTxPostRegolith => { Self::Optimism(OptimismInvalidTransactionError::DepositSystemTxPostRegolith) @@ -462,8 +478,6 @@ impl From for RpcInvalidTransactionError { InvalidTransaction::HaltedDepositPostRegolith => { Self::Optimism(OptimismInvalidTransactionError::HaltedDepositPostRegolith) } - // TODO(EOF) - InvalidTransaction::EofCrateShouldHaveToAddress => todo!("EOF"), } } } diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 6b30de26c4da..0903d8056b76 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -165,12 +165,11 @@ pub fn create_txn_env(block_env: &BlockEnv, request: TransactionRequest) -> EthR value: value.unwrap_or_default(), data: input.try_into_unique_input()?.unwrap_or_default(), chain_id, - access_list: access_list - .map(reth_rpc_types::AccessList::into_flattened) - .unwrap_or_default(), + access_list: access_list.unwrap_or_default().into(), // EIP-4844 fields blob_hashes: blob_versioned_hashes.unwrap_or_default(), max_fee_per_blob_gas, + authorization_list: None, #[cfg(feature = "optimism")] optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, }; diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 36413709f838..eef090bcddb0 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -12,14 +12,14 @@ use crate::{ use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_primitives::{ constants::{eip4844::MAX_BLOBS_PER_BLOCK, ETHEREUM_BLOCK_GAS_LIMIT}, - Address, GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID, - EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, + GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID, + EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, }; use reth_provider::{AccountReader, BlockReaderIdExt, StateProviderFactory}; use reth_tasks::TaskSpawner; use revm::{ interpreter::gas::validate_initial_tx_gas, - primitives::{EnvKzgSettings, SpecId}, + primitives::{AccessListItem, EnvKzgSettings, SpecId}, }; use std::{ marker::PhantomData, @@ -712,12 +712,11 @@ pub fn ensure_intrinsic_gas( transaction: &T, is_shanghai: bool, ) -> Result<(), InvalidPoolTransactionError> { - let access_list = transaction.access_list().map(|list| list.flattened()).unwrap_or_default(); if transaction.gas_limit() < calculate_intrinsic_gas_after_merge( transaction.input(), &transaction.kind(), - &access_list, + transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]), is_shanghai, ) { @@ -734,11 +733,11 @@ pub fn ensure_intrinsic_gas( pub fn calculate_intrinsic_gas_after_merge( input: &[u8], kind: &TxKind, - access_list: &[(Address, Vec)], + access_list: &[AccessListItem], is_shanghai: bool, ) -> u64 { let spec_id = if is_shanghai { SpecId::SHANGHAI } else { SpecId::MERGE }; - validate_initial_tx_gas(spec_id, input, kind.is_create(), access_list) + validate_initial_tx_gas(spec_id, input, kind.is_create(), access_list, 0) } #[cfg(test)] diff --git a/examples/exex/rollup/src/db.rs b/examples/exex/rollup/src/db.rs index 2c42beafb93c..dcc8b435ebc5 100644 --- a/examples/exex/rollup/src/db.rs +++ b/examples/exex/rollup/src/db.rs @@ -443,7 +443,7 @@ impl reth_revm::Database for Database { get_storage(&self.connection(), address, index.into()).map(|data| data.unwrap_or_default()) } - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { let block_hash = self.connection().query_row::( "SELECT hash FROM block WHERE number = ?", (number.to_string(),), From d599393771f9d7d137ea4abf271e1bd118184c73 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 9 Jul 2024 14:30:04 +0200 Subject: [PATCH 404/405] chore(deps): rm reth-codecs dep (#9390) --- Cargo.lock | 1 - crates/net/eth-wire-types/Cargo.toml | 1 - crates/net/eth-wire-types/src/header.rs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdb35c8f9f71..d6ab20f85067 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7168,7 +7168,6 @@ dependencies = [ "proptest-derive 0.5.0", "rand 0.8.5", "reth-chainspec", - "reth-codecs", "reth-codecs-derive", "reth-primitives", "serde", diff --git a/crates/net/eth-wire-types/Cargo.toml b/crates/net/eth-wire-types/Cargo.toml index e9d502850ab3..671883dae68e 100644 --- a/crates/net/eth-wire-types/Cargo.toml +++ b/crates/net/eth-wire-types/Cargo.toml @@ -14,7 +14,6 @@ workspace = true [dependencies] # reth reth-chainspec.workspace = true -reth-codecs.workspace = true reth-codecs-derive.workspace = true reth-primitives.workspace = true diff --git a/crates/net/eth-wire-types/src/header.rs b/crates/net/eth-wire-types/src/header.rs index f6b3b8ac4cdb..607d6ba3e2cb 100644 --- a/crates/net/eth-wire-types/src/header.rs +++ b/crates/net/eth-wire-types/src/header.rs @@ -2,7 +2,7 @@ use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; -use reth_codecs::derive_arbitrary; +use reth_codecs_derive::derive_arbitrary; /// Represents the direction for a headers request depending on the `reverse` field of the request. /// > The response must contain a number of block headers, of rising number when reverse is 0, From 03611f12bd6a0fa6bf22e73ca7699b5217d7194e Mon Sep 17 00:00:00 2001 From: allnil Date: Mon, 15 Jul 2024 14:46:34 +0100 Subject: [PATCH 405/405] chore: fmt, clean, refactor --- Cargo.lock | 6 +- Dockerfile | 2 +- bin/reth/src/commands/debug_cmd/merkle.rs | 16 +- bin/reth/src/commands/import.rs | 4 +- crates/blockchain-tree-api/src/error.rs | 42 +- crates/blockchain-tree/src/blockchain_tree.rs | 8 +- crates/chainspec/src/spec.rs | 24 +- crates/cli/commands/src/db/stats.rs | 8 +- crates/cli/commands/src/stage/run.rs | 4 +- .../beacon/src/engine/hooks/controller.rs | 26 +- crates/consensus/beacon/src/engine/mod.rs | 16 +- crates/ethereum-forks/src/forkid.rs | 8 +- crates/ethereum-forks/src/hardfork.rs | 695 ------------------ crates/evm/execution-errors/src/trie.rs | 4 +- crates/net/discv4/src/lib.rs | 12 +- crates/net/discv5/src/config.rs | 14 +- crates/net/downloaders/src/bodies/bodies.rs | 32 +- .../src/headers/reverse_headers.rs | 4 +- crates/net/eth-wire/src/errors/p2p.rs | 4 +- crates/net/network/src/error.rs | 88 +-- crates/net/network/src/fetch/mod.rs | 4 +- crates/net/network/src/peers.rs | 16 +- crates/net/network/src/session/active.rs | 4 +- crates/net/network/src/state.rs | 4 +- .../net/network/src/transactions/constants.rs | 16 +- .../net/network/src/transactions/fetcher.rs | 20 +- crates/net/network/src/transactions/mod.rs | 20 +- crates/net/network/tests/it/multiplex.rs | 4 +- crates/net/p2p/src/error.rs | 8 +- crates/node/events/src/node.rs | 8 +- crates/optimism/cli/src/commands/import.rs | 4 +- crates/optimism/evm/src/execute.rs | 4 +- crates/primitives-traits/src/account.rs | 6 +- crates/primitives-traits/src/withdrawal.rs | 8 +- crates/primitives/src/alloy_compat.rs | 4 +- crates/primitives/src/transaction/eip1559.rs | 18 +- crates/primitives/src/transaction/eip2930.rs | 16 +- crates/primitives/src/transaction/eip4844.rs | 22 +- crates/primitives/src/transaction/legacy.rs | 12 +- crates/primitives/src/transaction/mod.rs | 72 +- crates/primitives/src/transaction/optimism.rs | 16 +- crates/primitives/src/transaction/pooled.rs | 6 +- crates/prune/prune/src/pruner.rs | 4 +- .../prune/src/segments/account_history.rs | 4 +- crates/prune/prune/src/segments/headers.rs | 4 +- crates/prune/prune/src/segments/receipts.rs | 4 +- .../prune/src/segments/receipts_by_logs.rs | 17 +- .../prune/src/segments/sender_recovery.rs | 4 +- .../prune/src/segments/storage_history.rs | 4 +- .../prune/src/segments/transaction_lookup.rs | 8 +- .../prune/prune/src/segments/transactions.rs | 4 +- crates/prune/types/src/lib.rs | 4 +- crates/revm/src/batch.rs | 4 +- crates/rpc/rpc-builder/src/lib.rs | 16 +- crates/rpc/rpc-builder/tests/it/http.rs | 4 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 8 +- crates/rpc/rpc-engine-api/src/error.rs | 20 +- crates/rpc/rpc-eth-types/src/error.rs | 20 +- crates/rpc/rpc-eth-types/src/fee_history.rs | 4 +- crates/rpc/rpc-eth-types/src/logs_utils.rs | 10 +- crates/rpc/rpc-layer/src/auth_client_layer.rs | 4 +- crates/rpc/rpc/src/eth/bundle.rs | 4 +- crates/rpc/rpc/src/eth/filter.rs | 4 +- crates/stages/api/src/error.rs | 22 +- crates/stages/api/src/pipeline/mod.rs | 5 +- crates/stages/stages/src/stages/bodies.rs | 4 +- crates/stages/stages/src/stages/execution.rs | 12 +- crates/stages/stages/src/stages/headers.rs | 6 +- crates/stages/stages/src/stages/merkle.rs | 12 +- crates/stages/stages/src/stages/tx_lookup.rs | 4 +- .../stages/stages/src/test_utils/test_db.rs | 4 +- crates/stages/types/src/checkpoints.rs | 22 +- .../static-file/src/static_file_producer.rs | 11 +- .../storage/codecs/derive/src/compact/mod.rs | 4 +- crates/storage/db-common/src/db_tool/mod.rs | 4 +- crates/storage/db-common/src/init.rs | 4 +- .../storage/db/src/implementation/mdbx/tx.rs | 6 +- crates/storage/libmdbx-rs/src/codec.rs | 4 +- crates/storage/libmdbx-rs/src/error.rs | 6 +- crates/storage/nippy-jar/src/phf/fmph.rs | 6 +- crates/storage/nippy-jar/src/phf/go_fmph.rs | 6 +- crates/storage/nippy-jar/src/writer.rs | 8 +- .../src/providers/database/provider.rs | 8 +- .../src/providers/state/historical.rs | 10 +- .../src/providers/static_file/manager.rs | 14 +- crates/transaction-pool/src/blobstore/mod.rs | 4 +- crates/transaction-pool/src/config.rs | 8 +- crates/transaction-pool/src/error.rs | 30 +- crates/transaction-pool/src/maintain.rs | 4 +- crates/transaction-pool/src/pool/blob.rs | 12 +- crates/transaction-pool/src/pool/pending.rs | 4 +- crates/transaction-pool/src/pool/state.rs | 8 +- crates/transaction-pool/src/pool/txpool.rs | 20 +- .../transaction-pool/src/test_utils/mock.rs | 104 +-- crates/transaction-pool/src/validate/eth.rs | 6 +- examples/db-access/src/main.rs | 8 +- testing/ef-tests/src/cases/blockchain_test.rs | 14 +- testing/ef-tests/src/models.rs | 16 +- wvm-apps/wvm-exexed/Cargo.toml | 12 +- .../wvm-exexed/crates/bigquery/src/client.rs | 2 +- .../wvm-exexed/crates/lambda/src/lambda.rs | 13 +- .../wvm-exexed/crates/reth-exexed/Cargo.toml | 6 +- .../wvm-exexed/crates/reth-exexed/src/main.rs | 13 +- wvm-apps/wvm-exexed/crates/types/src/types.rs | 2 +- 104 files changed, 610 insertions(+), 1322 deletions(-) delete mode 100644 crates/ethereum-forks/src/hardfork.rs diff --git a/Cargo.lock b/Cargo.lock index 09b1d6fb47a9..f38e87acff77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8278,17 +8278,13 @@ dependencies = [ "bigquery", "exex-etl", "eyre", - "futures", "lambda", "repository", "reth", "reth-exex", - "reth-node-api", "reth-node-ethereum", "reth-tracing", - "serde", "serde_json", - "tokio", "types", ] @@ -12399,7 +12395,7 @@ dependencies = [ [[package]] name = "wvm-exexed" -version = "0.1.0" +version = "1.0.0" dependencies = [ "alloy-primitives", "bigquery", diff --git a/Dockerfile b/Dockerfile index d2b71d23ac74..17518d8f011c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef WORKDIR /app -LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth +LABEL org.opencontainers.image.source=https://github.com/weaveVM/wvm-reth LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0" # Install system dependencies diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 73c333e398c5..8ff0b611b38b 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -221,14 +221,14 @@ impl Command { let mut clean_account_mismatched = Vec::new(); let mut incremental_account_trie_iter = incremental_account_trie.into_iter().peekable(); let mut clean_account_trie_iter = clean_account_trie.into_iter().peekable(); - while incremental_account_trie_iter.peek().is_some() - || clean_account_trie_iter.peek().is_some() + while incremental_account_trie_iter.peek().is_some() || + clean_account_trie_iter.peek().is_some() { match (incremental_account_trie_iter.next(), clean_account_trie_iter.next()) { (Some(incremental), Some(clean)) => { similar_asserts::assert_eq!(incremental.0, clean.0, "Nibbles don't match"); - if incremental.1 != clean.1 - && clean.0 .0.len() > self.skip_node_depth.unwrap_or_default() + if incremental.1 != clean.1 && + clean.0 .0.len() > self.skip_node_depth.unwrap_or_default() { incremental_account_mismatched.push(incremental); clean_account_mismatched.push(clean); @@ -250,13 +250,13 @@ impl Command { let mut first_mismatched_storage = None; let mut incremental_storage_trie_iter = incremental_storage_trie.into_iter().peekable(); let mut clean_storage_trie_iter = clean_storage_trie.into_iter().peekable(); - while incremental_storage_trie_iter.peek().is_some() - || clean_storage_trie_iter.peek().is_some() + while incremental_storage_trie_iter.peek().is_some() || + clean_storage_trie_iter.peek().is_some() { match (incremental_storage_trie_iter.next(), clean_storage_trie_iter.next()) { (Some(incremental), Some(clean)) => { - if incremental != clean - && clean.1.nibbles.len() > self.skip_node_depth.unwrap_or_default() + if incremental != clean && + clean.1.nibbles.len() > self.skip_node_depth.unwrap_or_default() { first_mismatched_storage = Some((incremental, clean)); break; diff --git a/bin/reth/src/commands/import.rs b/bin/reth/src/commands/import.rs index 00fe8e8f3dc3..f4810f05148b 100644 --- a/bin/reth/src/commands/import.rs +++ b/bin/reth/src/commands/import.rs @@ -126,8 +126,8 @@ impl ImportCommand { let total_imported_blocks = provider.tx_ref().entries::()?; let total_imported_txns = provider.tx_ref().entries::()?; - if total_decoded_blocks != total_imported_blocks - || total_decoded_txns != total_imported_txns + if total_decoded_blocks != total_imported_blocks || + total_decoded_txns != total_imported_txns { error!(target: "reth::cli", total_decoded_blocks, diff --git a/crates/blockchain-tree-api/src/error.rs b/crates/blockchain-tree-api/src/error.rs index d1308ec6b1fc..8e0cef4254d7 100644 --- a/crates/blockchain-tree-api/src/error.rs +++ b/crates/blockchain-tree-api/src/error.rs @@ -260,10 +260,10 @@ impl InsertBlockErrorKind { Self::Canonical(err) => { matches!( err, - CanonicalError::Validation(BlockValidationError::StateRoot { .. }) - | CanonicalError::Provider( - ProviderError::StateRootMismatch(_) - | ProviderError::UnwindStateRootMismatch(_) + CanonicalError::Validation(BlockValidationError::StateRoot { .. }) | + CanonicalError::Provider( + ProviderError::StateRootMismatch(_) | + ProviderError::UnwindStateRootMismatch(_) ) ) } @@ -292,12 +292,12 @@ impl InsertBlockErrorKind { true } // these are internal errors, not caused by an invalid block - BlockExecutionError::LatestBlock(_) - | BlockExecutionError::Pruning(_) - | BlockExecutionError::CanonicalRevert { .. } - | BlockExecutionError::CanonicalCommit { .. } - | BlockExecutionError::AppendChainDoesntConnect { .. } - | BlockExecutionError::Other(_) => false, + BlockExecutionError::LatestBlock(_) | + BlockExecutionError::Pruning(_) | + BlockExecutionError::CanonicalRevert { .. } | + BlockExecutionError::CanonicalCommit { .. } | + BlockExecutionError::AppendChainDoesntConnect { .. } | + BlockExecutionError::Other(_) => false, } } Self::Tree(err) => { @@ -306,12 +306,12 @@ impl InsertBlockErrorKind { // the block's number is lower than the finalized block's number true } - BlockchainTreeError::BlockSideChainIdConsistency { .. } - | BlockchainTreeError::CanonicalChain { .. } - | BlockchainTreeError::BlockNumberNotFoundInChain { .. } - | BlockchainTreeError::BlockHashNotFoundInChain { .. } - | BlockchainTreeError::BlockBufferingFailed { .. } - | BlockchainTreeError::GenesisBlockHasNoParent => false, + BlockchainTreeError::BlockSideChainIdConsistency { .. } | + BlockchainTreeError::CanonicalChain { .. } | + BlockchainTreeError::BlockNumberNotFoundInChain { .. } | + BlockchainTreeError::BlockHashNotFoundInChain { .. } | + BlockchainTreeError::BlockBufferingFailed { .. } | + BlockchainTreeError::GenesisBlockHasNoParent => false, } } Self::Provider(_) | Self::Internal(_) => { @@ -319,11 +319,11 @@ impl InsertBlockErrorKind { false } Self::Canonical(err) => match err { - CanonicalError::BlockchainTree(_) - | CanonicalError::CanonicalCommit(_) - | CanonicalError::CanonicalRevert(_) - | CanonicalError::OptimisticTargetRevert(_) - | CanonicalError::Provider(_) => false, + CanonicalError::BlockchainTree(_) | + CanonicalError::CanonicalCommit(_) | + CanonicalError::CanonicalRevert(_) | + CanonicalError::OptimisticTargetRevert(_) | + CanonicalError::Provider(_) => false, CanonicalError::Validation(_) => true, }, Self::BlockchainTree(_) => false, diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 6c6e229e2510..73bdbf2906ca 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1305,8 +1305,8 @@ where .provider_factory .static_file_provider() .get_highest_static_file_block(StaticFileSegment::Headers) - .unwrap_or_default() - > revert_until + .unwrap_or_default() > + revert_until { trace!( target: "blockchain_tree", @@ -1608,8 +1608,8 @@ mod tests { signer, ( AccountInfo { - balance: initial_signer_balance - - (single_tx_cost * U256::from(num_of_signer_txs)), + balance: initial_signer_balance - + (single_tx_cost * U256::from(num_of_signer_txs)), nonce: num_of_signer_txs, ..Default::default() }, diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 57d22975b28a..38521cf93a2b 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -333,14 +333,14 @@ impl ChainSpec { matches!( self.chain.kind(), ChainKind::Named( - NamedChain::Mainnet - | NamedChain::Morden - | NamedChain::Ropsten - | NamedChain::Rinkeby - | NamedChain::Goerli - | NamedChain::Kovan - | NamedChain::Holesky - | NamedChain::Sepolia + NamedChain::Mainnet | + NamedChain::Morden | + NamedChain::Ropsten | + NamedChain::Rinkeby | + NamedChain::Goerli | + NamedChain::Kovan | + NamedChain::Holesky | + NamedChain::Sepolia ) ) } @@ -555,8 +555,8 @@ impl ChainSpec { // We filter out TTD-based forks w/o a pre-known block since those do not show up in the // fork filter. Some(match condition { - ForkCondition::Block(block) - | ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block), + ForkCondition::Block(block) | + ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block), ForkCondition::Timestamp(time) => ForkFilterKey::Time(time), _ => return None, }) @@ -574,8 +574,8 @@ impl ChainSpec { for (_, cond) in self.hardforks.forks_iter() { // handle block based forks and the sepolia merge netsplit block edge case (TTD // ForkCondition with Some(block)) - if let ForkCondition::Block(block) - | ForkCondition::TTD { fork_block: Some(block), .. } = cond + if let ForkCondition::Block(block) | + ForkCondition::TTD { fork_block: Some(block), .. } = cond { if cond.active_at_head(head) { if block != current_applied { diff --git a/crates/cli/commands/src/db/stats.rs b/crates/cli/commands/src/db/stats.rs index 9252b06ae5a6..37f7d617ba47 100644 --- a/crates/cli/commands/src/db/stats.rs +++ b/crates/cli/commands/src/db/stats.rs @@ -271,10 +271,10 @@ impl Command { .add_cell(Cell::new(human_bytes(segment_config_size as f64))); } row.add_cell(Cell::new(human_bytes( - (segment_data_size - + segment_index_size - + segment_offsets_size - + segment_config_size) as f64, + (segment_data_size + + segment_index_size + + segment_offsets_size + + segment_config_size) as f64, ))); table.add_row(row); } diff --git a/crates/cli/commands/src/stage/run.rs b/crates/cli/commands/src/stage/run.rs index 86302f6b61cd..9b6416cf4fe0 100644 --- a/crates/cli/commands/src/stage/run.rs +++ b/crates/cli/commands/src/stage/run.rs @@ -155,8 +155,8 @@ impl Command { config.stages.bodies.downloader_max_buffered_blocks_size_bytes, ) .with_concurrent_requests_range( - config.stages.bodies.downloader_min_concurrent_requests - ..=config.stages.bodies.downloader_max_concurrent_requests, + config.stages.bodies.downloader_min_concurrent_requests..= + config.stages.bodies.downloader_max_concurrent_requests, ) .build(fetch_client, consensus.clone(), provider_factory.clone()), ); diff --git a/crates/consensus/beacon/src/engine/hooks/controller.rs b/crates/consensus/beacon/src/engine/hooks/controller.rs index f24348496607..55c1b2aac6e2 100644 --- a/crates/consensus/beacon/src/engine/hooks/controller.rs +++ b/crates/consensus/beacon/src/engine/hooks/controller.rs @@ -136,10 +136,10 @@ impl EngineHooksController { // - Active DB write according to passed argument // - Missing a finalized block number. We might be on an optimistic sync scenario where we // cannot skip the FCU with the finalized hash, otherwise CL might misbehave. - if hook.db_access_level().is_read_write() - && (self.active_db_write_hook.is_some() - || db_write_active - || args.finalized_block_number.is_none()) + if hook.db_access_level().is_read_write() && + (self.active_db_write_hook.is_some() || + db_write_active || + args.finalized_block_number.is_none()) { return Poll::Pending; } @@ -307,9 +307,9 @@ mod tests { assert_eq!( result.map(|result| { let polled_hook = result.unwrap(); - polled_hook.name == hook_ro_name - && polled_hook.event.is_started() - && polled_hook.db_access_level.is_read_only() + polled_hook.name == hook_ro_name && + polled_hook.event.is_started() && + polled_hook.db_access_level.is_read_only() }), Poll::Ready(true) ); @@ -346,9 +346,9 @@ mod tests { assert_eq!( result.map(|result| { let polled_hook = result.unwrap(); - polled_hook.name == hook_rw_1_name - && polled_hook.event.is_started() - && polled_hook.db_access_level.is_read_write() + polled_hook.name == hook_rw_1_name && + polled_hook.event.is_started() && + polled_hook.db_access_level.is_read_write() }), Poll::Ready(true) ); @@ -368,9 +368,9 @@ mod tests { assert_eq!( result.map(|result| { let polled_hook = result.unwrap(); - polled_hook.name == hook_ro_name - && polled_hook.event.is_started() - && polled_hook.db_access_level.is_read_only() + polled_hook.name == hook_ro_name && + polled_hook.event.is_started() && + polled_hook.db_access_level.is_read_only() }), Poll::Ready(true) ); diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 0815d1c91236..2179618481ad 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -757,8 +757,8 @@ where // If current_header is None, then the current_hash does not have an invalid // ancestor in the cache, check its presence in blockchain tree - if current_header.is_none() - && self.blockchain.find_block_by_hash(current_hash, BlockSource::Any)?.is_some() + if current_header.is_none() && + self.blockchain.find_block_by_hash(current_hash, BlockSource::Any)?.is_some() { return Ok(Some(current_hash)); } @@ -857,8 +857,8 @@ where // // This ensures that the finalized block is consistent with the head block, i.e. the // finalized block is an ancestor of the head block. - if !state.finalized_block_hash.is_zero() - && !self.blockchain.is_canonical(state.finalized_block_hash)? + if !state.finalized_block_hash.is_zero() && + !self.blockchain.is_canonical(state.finalized_block_hash)? { return Ok(Some(OnForkChoiceUpdated::invalid_state())); } @@ -871,8 +871,8 @@ where // // This ensures that the safe block is consistent with the head block, i.e. the safe // block is an ancestor of the head block. - if !state.safe_block_hash.is_zero() - && !self.blockchain.is_canonical(state.safe_block_hash)? + if !state.safe_block_hash.is_zero() && + !self.blockchain.is_canonical(state.safe_block_hash)? { return Ok(Some(OnForkChoiceUpdated::invalid_state())); } @@ -1255,8 +1255,8 @@ where latest_valid_hash = Some(block_hash); PayloadStatusEnum::Valid } - InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) - | InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => { + InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) | + InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => { // check if the block's parent is already marked as invalid if let Some(status) = self.check_invalid_ancestor_with_head(block.parent_hash, block.hash()).map_err( diff --git a/crates/ethereum-forks/src/forkid.rs b/crates/ethereum-forks/src/forkid.rs index 92be9cc140b0..0a4d752b9b0d 100644 --- a/crates/ethereum-forks/src/forkid.rs +++ b/crates/ethereum-forks/src/forkid.rs @@ -332,10 +332,10 @@ impl ForkFilter { // we check if this fork is time-based or block number-based by estimating that, // if fork_id.next is bigger than the old timestamp, we are dealing with a // timestamp, otherwise with a block. - (fork_id.next > TIMESTAMP_BEFORE_ETHEREUM_MAINNET - && self.head.timestamp >= fork_id.next) - || (fork_id.next <= TIMESTAMP_BEFORE_ETHEREUM_MAINNET - && self.head.number >= fork_id.next) + (fork_id.next > TIMESTAMP_BEFORE_ETHEREUM_MAINNET && + self.head.timestamp >= fork_id.next) || + (fork_id.next <= TIMESTAMP_BEFORE_ETHEREUM_MAINNET && + self.head.number >= fork_id.next) } else { // Extra safety check to future-proof for when Ethereum has over a billion blocks. let head_block_or_time = match self.cache.epoch_start { diff --git a/crates/ethereum-forks/src/hardfork.rs b/crates/ethereum-forks/src/hardfork.rs deleted file mode 100644 index 524727b0fc4f..000000000000 --- a/crates/ethereum-forks/src/hardfork.rs +++ /dev/null @@ -1,695 +0,0 @@ -use alloy_chains::Chain; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -use std::{fmt::Display, str::FromStr}; - -/// Represents the consensus type of a blockchain fork. -/// -/// This enum defines two variants: `ProofOfWork` for hardforks that use a proof-of-work consensus -/// mechanism, and `ProofOfStake` for hardforks that use a proof-of-stake consensus mechanism. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum ConsensusType { - /// Indicates a proof-of-work consensus mechanism. - ProofOfWork, - /// Indicates a proof-of-stake consensus mechanism. - ProofOfStake, -} - -/// The name of an Ethereum hardfork. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -#[non_exhaustive] -pub enum Hardfork { - /// Frontier: . - Frontier, - /// Homestead: . - Homestead, - /// The DAO fork: . - Dao, - /// Tangerine: . - Tangerine, - /// Spurious Dragon: . - SpuriousDragon, - /// Byzantium: . - Byzantium, - /// Constantinople: . - Constantinople, - /// Petersburg: . - Petersburg, - /// Istanbul: . - Istanbul, - /// Muir Glacier: . - MuirGlacier, - /// Berlin: . - Berlin, - /// London: . - London, - /// Arrow Glacier: . - ArrowGlacier, - /// Gray Glacier: . - GrayGlacier, - /// Paris: . - Paris, - /// Bedrock: . - #[cfg(feature = "optimism")] - Bedrock, - /// Regolith: . - #[cfg(feature = "optimism")] - Regolith, - /// Shanghai: . - Shanghai, - /// Canyon: - /// . - #[cfg(feature = "optimism")] - Canyon, - // ArbOS11, - /// Cancun. - Cancun, - /// Ecotone: . - #[cfg(feature = "optimism")] - Ecotone, - // ArbOS20Atlas, - - // Upcoming - /// Prague: - Prague, - /// Fjord: - #[cfg(feature = "optimism")] - Fjord, -} - -impl Hardfork { - /// Retrieves the consensus type for the specified hardfork. - pub fn consensus_type(&self) -> ConsensusType { - if *self >= Self::Paris { - ConsensusType::ProofOfStake - } else { - ConsensusType::ProofOfWork - } - } - - /// Checks if the hardfork uses Proof of Stake consensus. - pub fn is_proof_of_stake(&self) -> bool { - matches!(self.consensus_type(), ConsensusType::ProofOfStake) - } - - /// Checks if the hardfork uses Proof of Work consensus. - pub fn is_proof_of_work(&self) -> bool { - matches!(self.consensus_type(), ConsensusType::ProofOfWork) - } - - /// Retrieves the activation block for the specified hardfork on the given chain. - pub fn activation_block(&self, chain: Chain) -> Option { - if chain == Chain::mainnet() { - return self.mainnet_activation_block(); - } - if chain == Chain::sepolia() { - return self.sepolia_activation_block(); - } - if chain == Chain::holesky() { - return self.holesky_activation_block(); - } - - #[cfg(feature = "optimism")] - { - if chain == Chain::base_sepolia() { - return self.base_sepolia_activation_block(); - } - if chain == Chain::base_mainnet() { - return self.base_mainnet_activation_block(); - } - } - - None - } - - /// Retrieves the activation block for the specified hardfork on the Ethereum mainnet. - pub const fn mainnet_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier => Some(0), - Self::Homestead => Some(1150000), - Self::Dao => Some(1920000), - Self::Tangerine => Some(2463000), - Self::SpuriousDragon => Some(2675000), - Self::Byzantium => Some(4370000), - Self::Constantinople | Self::Petersburg => Some(7280000), - Self::Istanbul => Some(9069000), - Self::MuirGlacier => Some(9200000), - Self::Berlin => Some(12244000), - Self::London => Some(12965000), - Self::ArrowGlacier => Some(13773000), - Self::GrayGlacier => Some(15050000), - Self::Paris => Some(15537394), - Self::Shanghai => Some(17034870), - Self::Cancun => Some(19426587), - - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Sepolia testnet. - pub const fn sepolia_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Paris => Some(1735371), - Self::Shanghai => Some(2990908), - Self::Cancun => Some(5187023), - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier => Some(0), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Arbitrum Sepolia testnet. - pub const fn arbitrum_sepolia_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris => Some(0), - Self::Shanghai => Some(10653737), - // Hardfork::ArbOS11 => Some(10653737), - Self::Cancun => Some(18683405), - // Hardfork::ArbOS20Atlas => Some(18683405), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Arbitrum One mainnet. - pub const fn arbitrum_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris => Some(0), - Self::Shanghai => Some(184097479), - // Hardfork::ArbOS11 => Some(184097479), - Self::Cancun => Some(190301729), - // Hardfork::ArbOS20Atlas => Some(190301729), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Base Sepolia testnet. - #[cfg(feature = "optimism")] - pub const fn base_sepolia_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris - | Self::Bedrock - | Self::Regolith => Some(0), - Self::Shanghai | Self::Canyon => Some(2106456), - Self::Cancun | Self::Ecotone => Some(6383256), - Self::Fjord => Some(10615056), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the Base mainnet. - #[cfg(feature = "optimism")] - pub const fn base_mainnet_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris - | Self::Bedrock - | Self::Regolith => Some(0), - Self::Shanghai | Self::Canyon => Some(9101527), - Self::Cancun | Self::Ecotone => Some(11188936), - _ => None, - } - } - - /// Retrieves the activation block for the specified hardfork on the holesky testnet. - const fn holesky_activation_block(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris => Some(0), - Self::Shanghai => Some(6698), - Self::Cancun => Some(894733), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the given chain. - pub fn activation_timestamp(&self, chain: Chain) -> Option { - if chain == Chain::mainnet() { - return self.mainnet_activation_timestamp(); - } - if chain == Chain::sepolia() { - return self.sepolia_activation_timestamp(); - } - if chain == Chain::holesky() { - return self.holesky_activation_timestamp(); - } - #[cfg(feature = "optimism")] - { - if chain == Chain::base_sepolia() { - return self.base_sepolia_activation_timestamp(); - } - if chain == Chain::base_mainnet() { - return self.base_mainnet_activation_timestamp(); - } - } - - None - } - - /// Retrieves the activation timestamp for the specified hardfork on the Ethereum mainnet. - pub const fn mainnet_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier => Some(1438226773), - Self::Homestead => Some(1457938193), - Self::Dao => Some(1468977640), - Self::Tangerine => Some(1476753571), - Self::SpuriousDragon => Some(1479788144), - Self::Byzantium => Some(1508131331), - Self::Constantinople | Self::Petersburg => Some(1551340324), - Self::Istanbul => Some(1575807909), - Self::MuirGlacier => Some(1577953849), - Self::Berlin => Some(1618481223), - Self::London => Some(1628166822), - Self::ArrowGlacier => Some(1639036523), - Self::GrayGlacier => Some(1656586444), - Self::Paris => Some(1663224162), - Self::Shanghai => Some(1681338455), - Self::Cancun => Some(1710338135), - - // upcoming hardforks - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Sepolia testnet. - pub const fn sepolia_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris => Some(1633267481), - Self::Shanghai => Some(1677557088), - Self::Cancun => Some(1706655072), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Holesky testnet. - pub const fn holesky_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Shanghai => Some(1696000704), - Self::Cancun => Some(1707305664), - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris => Some(1695902100), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum Sepolia - /// testnet. - pub const fn arbitrum_sepolia_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris => Some(1692726996), - Self::Shanghai => Some(1706634000), - // Hardfork::ArbOS11 => Some(1706634000), - Self::Cancun => Some(1709229600), - // Hardfork::ArbOS20Atlas => Some(1709229600), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum One mainnet. - pub const fn arbitrum_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris => Some(1622240000), - Self::Shanghai => Some(1708804873), - // Hardfork::ArbOS11 => Some(1708804873), - Self::Cancun => Some(1710424089), - // Hardfork::ArbOS20Atlas => Some(1710424089), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Base Sepolia testnet. - #[cfg(feature = "optimism")] - pub const fn base_sepolia_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris - | Self::Bedrock - | Self::Regolith => Some(1695768288), - Self::Shanghai | Self::Canyon => Some(1699981200), - Self::Cancun | Self::Ecotone => Some(1708534800), - Self::Fjord => Some(1716998400), - _ => None, - } - } - - /// Retrieves the activation timestamp for the specified hardfork on the Base mainnet. - #[cfg(feature = "optimism")] - pub const fn base_mainnet_activation_timestamp(&self) -> Option { - #[allow(unreachable_patterns)] - match self { - Self::Frontier - | Self::Homestead - | Self::Dao - | Self::Tangerine - | Self::SpuriousDragon - | Self::Byzantium - | Self::Constantinople - | Self::Petersburg - | Self::Istanbul - | Self::MuirGlacier - | Self::Berlin - | Self::London - | Self::ArrowGlacier - | Self::GrayGlacier - | Self::Paris - | Self::Bedrock - | Self::Regolith => Some(1686789347), - Self::Shanghai | Self::Canyon => Some(1704992401), - Self::Cancun | Self::Ecotone => Some(1710374401), - Self::Fjord => Some(1720627201), - _ => None, - } - } -} - -impl FromStr for Hardfork { - type Err = String; - - fn from_str(s: &str) -> Result { - Ok(match s.to_lowercase().as_str() { - "frontier" => Self::Frontier, - "homestead" => Self::Homestead, - "dao" => Self::Dao, - "tangerine" => Self::Tangerine, - "spuriousdragon" => Self::SpuriousDragon, - "byzantium" => Self::Byzantium, - "constantinople" => Self::Constantinople, - "petersburg" => Self::Petersburg, - "istanbul" => Self::Istanbul, - "muirglacier" => Self::MuirGlacier, - "berlin" => Self::Berlin, - "london" => Self::London, - "arrowglacier" => Self::ArrowGlacier, - "grayglacier" => Self::GrayGlacier, - "paris" => Self::Paris, - "shanghai" => Self::Shanghai, - "cancun" => Self::Cancun, - #[cfg(feature = "optimism")] - "bedrock" => Self::Bedrock, - #[cfg(feature = "optimism")] - "regolith" => Self::Regolith, - #[cfg(feature = "optimism")] - "canyon" => Self::Canyon, - #[cfg(feature = "optimism")] - "ecotone" => Self::Ecotone, - #[cfg(feature = "optimism")] - "fjord" => Self::Fjord, - "prague" => Self::Prague, - // "arbos11" => Hardfork::ArbOS11, - // "arbos20atlas" => Hardfork::ArbOS20Atlas, - _ => return Err(format!("Unknown hardfork: {s}")), - }) - } -} - -impl Display for Hardfork { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_hardfork_from_str() { - let hardfork_str = [ - "frOntier", - "homEstead", - "dao", - "tAngerIne", - "spurIousdrAgon", - "byzAntium", - "constantinople", - "petersburg", - "istanbul", - "muirglacier", - "bErlin", - "lonDon", - "arrowglacier", - "grayglacier", - "PARIS", - "ShAnGhAI", - "CaNcUn", - "PrAguE", - ]; - let expected_hardforks = [ - Hardfork::Frontier, - Hardfork::Homestead, - Hardfork::Dao, - Hardfork::Tangerine, - Hardfork::SpuriousDragon, - Hardfork::Byzantium, - Hardfork::Constantinople, - Hardfork::Petersburg, - Hardfork::Istanbul, - Hardfork::MuirGlacier, - Hardfork::Berlin, - Hardfork::London, - Hardfork::ArrowGlacier, - Hardfork::GrayGlacier, - Hardfork::Paris, - Hardfork::Shanghai, - Hardfork::Cancun, - Hardfork::Prague, - ]; - - let hardforks: Vec = - hardfork_str.iter().map(|h| Hardfork::from_str(h).unwrap()).collect(); - - assert_eq!(hardforks, expected_hardforks); - } - - #[test] - #[cfg(feature = "optimism")] - fn check_op_hardfork_from_str() { - let hardfork_str = ["beDrOck", "rEgOlITH", "cAnYoN", "eCoToNe", "FJorD"]; - let expected_hardforks = [ - Hardfork::Bedrock, - Hardfork::Regolith, - Hardfork::Canyon, - Hardfork::Ecotone, - Hardfork::Fjord, - ]; - - let hardforks: Vec = - hardfork_str.iter().map(|h| Hardfork::from_str(h).unwrap()).collect(); - - assert_eq!(hardforks, expected_hardforks); - } - - #[test] - fn check_nonexistent_hardfork_from_str() { - assert!(Hardfork::from_str("not a hardfork").is_err()); - } - - #[test] - fn check_consensus_type() { - let pow_hardforks = [ - Hardfork::Frontier, - Hardfork::Homestead, - Hardfork::Dao, - Hardfork::Tangerine, - Hardfork::SpuriousDragon, - Hardfork::Byzantium, - Hardfork::Constantinople, - Hardfork::Petersburg, - Hardfork::Istanbul, - Hardfork::MuirGlacier, - Hardfork::Berlin, - Hardfork::London, - Hardfork::ArrowGlacier, - Hardfork::GrayGlacier, - ]; - - let pos_hardforks = [Hardfork::Paris, Hardfork::Shanghai, Hardfork::Cancun]; - - #[cfg(feature = "optimism")] - let op_hardforks = [ - Hardfork::Bedrock, - Hardfork::Regolith, - Hardfork::Canyon, - Hardfork::Ecotone, - Hardfork::Fjord, - ]; - - for hardfork in &pow_hardforks { - assert_eq!(hardfork.consensus_type(), ConsensusType::ProofOfWork); - assert!(!hardfork.is_proof_of_stake()); - assert!(hardfork.is_proof_of_work()); - } - - for hardfork in &pos_hardforks { - assert_eq!(hardfork.consensus_type(), ConsensusType::ProofOfStake); - assert!(hardfork.is_proof_of_stake()); - assert!(!hardfork.is_proof_of_work()); - } - - #[cfg(feature = "optimism")] - for hardfork in &op_hardforks { - assert_eq!(hardfork.consensus_type(), ConsensusType::ProofOfStake); - assert!(hardfork.is_proof_of_stake()); - assert!(!hardfork.is_proof_of_work()); - } - } -} diff --git a/crates/evm/execution-errors/src/trie.rs b/crates/evm/execution-errors/src/trie.rs index 38e3f60e4646..fd3533977ab2 100644 --- a/crates/evm/execution-errors/src/trie.rs +++ b/crates/evm/execution-errors/src/trie.rs @@ -17,8 +17,8 @@ pub enum StateRootError { impl From for DatabaseError { fn from(err: StateRootError) -> Self { match err { - StateRootError::DB(err) - | StateRootError::StorageRootError(StorageRootError::DB(err)) => err, + StateRootError::DB(err) | + StateRootError::StorageRootError(StorageRootError::DB(err)) => err, } } } diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 099092eb8b97..d6717f834f47 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -749,8 +749,8 @@ impl Discv4Service { self.kbuckets .closest_values(&target_key) .filter(|node| { - node.value.has_endpoint_proof - && !self.pending_find_nodes.contains_key(&node.key.preimage().0) + node.value.has_endpoint_proof && + !self.pending_find_nodes.contains_key(&node.key.preimage().0) }) .take(MAX_NODES_PER_BUCKET) .map(|n| (target_key.distance(&n.key), n.value.record)), @@ -1123,8 +1123,8 @@ impl Discv4Service { return; } - if self.pending_pings.contains_key(&node.id) - || self.pending_find_nodes.contains_key(&node.id) + if self.pending_pings.contains_key(&node.id) || + self.pending_find_nodes.contains_key(&node.id) { return; } @@ -2543,8 +2543,8 @@ mod tests { }) .await; - let expiry = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() - + 10000000000000; + let expiry = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() + + 10000000000000; let msg = Neighbours { nodes: vec![service2.local_node_record], expire: expiry }; service.on_neighbours(msg, record.tcp_addr(), id); // wait for the processed ping diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index 81f785be5e2e..669e7d04fe04 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -320,8 +320,8 @@ impl Config { /// Returns the IPv4 discovery socket if one is configured. pub const fn ipv4(listen_config: &ListenConfig) -> Option { match listen_config { - ListenConfig::Ipv4 { ip, port } - | ListenConfig::DualStack { ipv4: ip, ipv4_port: port, .. } => { + ListenConfig::Ipv4 { ip, port } | + ListenConfig::DualStack { ipv4: ip, ipv4_port: port, .. } => { Some(SocketAddrV4::new(*ip, *port)) } ListenConfig::Ipv6 { .. } => None, @@ -332,8 +332,8 @@ pub const fn ipv4(listen_config: &ListenConfig) -> Option { pub const fn ipv6(listen_config: &ListenConfig) -> Option { match listen_config { ListenConfig::Ipv4 { .. } => None, - ListenConfig::Ipv6 { ip, port } - | ListenConfig::DualStack { ipv6: ip, ipv6_port: port, .. } => { + ListenConfig::Ipv6 { ip, port } | + ListenConfig::DualStack { ipv6: ip, ipv6_port: port, .. } => { Some(SocketAddrV6::new(*ip, *port, 0, 0)) } } @@ -479,9 +479,9 @@ mod test { for node in config.bootstrap_nodes { let BootNode::Enr(node) = node else { panic!() }; assert!( - socket_1 == node.udp4_socket().unwrap() && socket_1 == node.tcp4_socket().unwrap() - || socket_2 == node.udp4_socket().unwrap() - && socket_2 == node.tcp4_socket().unwrap() + socket_1 == node.udp4_socket().unwrap() && socket_1 == node.tcp4_socket().unwrap() || + socket_2 == node.udp4_socket().unwrap() && + socket_2 == node.tcp4_socket().unwrap() ); assert_eq!("84b4940500", hex::encode(node.get_raw_rlp("opstack").unwrap())); } diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index 320228d53471..b4937eb417b9 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -106,9 +106,9 @@ where let mut collected = 0; let mut non_empty_headers = 0; let headers = self.provider.sealed_headers_while(range.clone(), |header| { - let should_take = range.contains(&header.number) - && non_empty_headers < max_non_empty - && collected < self.stream_batch_size; + let should_take = range.contains(&header.number) && + non_empty_headers < max_non_empty && + collected < self.stream_batch_size; if should_take { collected += 1; @@ -165,10 +165,10 @@ where .map(|last| last == *self.download_range.end()) .unwrap_or_default(); - nothing_to_request - && self.in_progress_queue.is_empty() - && self.buffered_responses.is_empty() - && self.queued_bodies.is_empty() + nothing_to_request && + self.in_progress_queue.is_empty() && + self.buffered_responses.is_empty() && + self.queued_bodies.is_empty() } /// Clear all download related data. @@ -210,8 +210,8 @@ where /// Adds a new response to the internal buffer fn buffer_bodies_response(&mut self, response: Vec) { // take into account capacity - let size = response.iter().map(BlockResponse::size).sum::() - + response.capacity() * mem::size_of::(); + let size = response.iter().map(BlockResponse::size).sum::() + + response.capacity() * mem::size_of::(); let response = OrderedBodiesResponse { resp: response, size }; let response_len = response.len(); @@ -270,9 +270,9 @@ where // requests are issued in order but not necessarily finished in order, so the queued bodies // can grow large if a certain request is slow, so we limit the followup requests if the // queued bodies grew too large - self.queued_bodies.len() < 4 * self.stream_batch_size - && self.has_buffer_capacity() - && self.in_progress_queue.len() < self.concurrent_request_limit() + self.queued_bodies.len() < 4 * self.stream_batch_size && + self.has_buffer_capacity() && + self.in_progress_queue.len() < self.concurrent_request_limit() } } @@ -315,8 +315,8 @@ where } // Check if the provided range is the subset of the existing range. - let is_current_range_subset = self.download_range.contains(range.start()) - && *range.end() == *self.download_range.end(); + let is_current_range_subset = self.download_range.contains(range.start()) && + *range.end() == *self.download_range.end(); if is_current_range_subset { tracing::trace!(target: "downloaders::bodies", ?range, "Download range already in progress"); // The current range already includes requested. @@ -510,8 +510,8 @@ impl BodiesDownloaderBuilder { .with_request_limit(config.downloader_request_limit) .with_max_buffered_blocks_size_bytes(config.downloader_max_buffered_blocks_size_bytes) .with_concurrent_requests_range( - config.downloader_min_concurrent_requests - ..=config.downloader_max_concurrent_requests, + config.downloader_min_concurrent_requests..= + config.downloader_max_concurrent_requests, ) } } diff --git a/crates/net/downloaders/src/headers/reverse_headers.rs b/crates/net/downloaders/src/headers/reverse_headers.rs index 10298dfbd472..9f98fedcd23f 100644 --- a/crates/net/downloaders/src/headers/reverse_headers.rs +++ b/crates/net/downloaders/src/headers/reverse_headers.rs @@ -824,8 +824,8 @@ where let concurrent_request_limit = this.concurrent_request_limit(); // populate requests - while this.in_progress_queue.len() < concurrent_request_limit - && this.buffered_responses.len() < this.max_buffered_responses + while this.in_progress_queue.len() < concurrent_request_limit && + this.buffered_responses.len() < this.max_buffered_responses { if let Some(request) = this.next_request() { trace!( diff --git a/crates/net/eth-wire/src/errors/p2p.rs b/crates/net/eth-wire/src/errors/p2p.rs index 6ef856210fcc..a64385fe2b3c 100644 --- a/crates/net/eth-wire/src/errors/p2p.rs +++ b/crates/net/eth-wire/src/errors/p2p.rs @@ -86,8 +86,8 @@ impl P2PStreamError { /// Returns the [`DisconnectReason`] if it is the `Disconnected` variant. pub const fn as_disconnected(&self) -> Option { let reason = match self { - Self::HandshakeError(P2PHandshakeError::Disconnected(reason)) - | Self::Disconnected(reason) => reason, + Self::HandshakeError(P2PHandshakeError::Disconnected(reason)) | + Self::Disconnected(reason) => reason, _ => return None, }; diff --git a/crates/net/network/src/error.rs b/crates/net/network/src/error.rs index 13b27fdb88b7..66f7bd5748ad 100644 --- a/crates/net/network/src/error.rs +++ b/crates/net/network/src/error.rs @@ -110,8 +110,8 @@ impl SessionError for EthStreamError { match self { Self::P2PStreamError(P2PStreamError::HandshakeError( P2PHandshakeError::HelloNotInHandshake, - )) - | Self::P2PStreamError(P2PStreamError::HandshakeError( + )) | + Self::P2PStreamError(P2PStreamError::HandshakeError( P2PHandshakeError::NonHelloMessageInHandshake, )) => true, Self::EthHandshakeError(err) => !matches!(err, EthHandshakeError::NoResponse), @@ -124,30 +124,30 @@ impl SessionError for EthStreamError { Self::P2PStreamError(err) => { matches!( err, - P2PStreamError::HandshakeError(P2PHandshakeError::NoSharedCapabilities) - | P2PStreamError::HandshakeError(P2PHandshakeError::HelloNotInHandshake) - | P2PStreamError::HandshakeError( + P2PStreamError::HandshakeError(P2PHandshakeError::NoSharedCapabilities) | + P2PStreamError::HandshakeError(P2PHandshakeError::HelloNotInHandshake) | + P2PStreamError::HandshakeError( P2PHandshakeError::NonHelloMessageInHandshake - ) - | P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected( + ) | + P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected( DisconnectReason::UselessPeer - )) - | P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected( + )) | + P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected( DisconnectReason::IncompatibleP2PProtocolVersion - )) - | P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected( + )) | + P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected( DisconnectReason::ProtocolBreach - )) - | P2PStreamError::UnknownReservedMessageId(_) - | P2PStreamError::EmptyProtocolMessage - | P2PStreamError::ParseSharedCapability(_) - | P2PStreamError::CapabilityNotShared - | P2PStreamError::Disconnected(DisconnectReason::UselessPeer) - | P2PStreamError::Disconnected( + )) | + P2PStreamError::UnknownReservedMessageId(_) | + P2PStreamError::EmptyProtocolMessage | + P2PStreamError::ParseSharedCapability(_) | + P2PStreamError::CapabilityNotShared | + P2PStreamError::Disconnected(DisconnectReason::UselessPeer) | + P2PStreamError::Disconnected( DisconnectReason::IncompatibleP2PProtocolVersion - ) - | P2PStreamError::Disconnected(DisconnectReason::ProtocolBreach) - | P2PStreamError::MismatchedProtocolVersion { .. } + ) | + P2PStreamError::Disconnected(DisconnectReason::ProtocolBreach) | + P2PStreamError::MismatchedProtocolVersion { .. } ) } Self::EthHandshakeError(err) => !matches!(err, EthHandshakeError::NoResponse), @@ -162,20 +162,20 @@ impl SessionError for EthStreamError { if let Some(err) = self.as_disconnected() { return match err { - DisconnectReason::TooManyPeers - | DisconnectReason::AlreadyConnected - | DisconnectReason::PingTimeout - | DisconnectReason::DisconnectRequested - | DisconnectReason::TcpSubsystemError => Some(BackoffKind::Low), + DisconnectReason::TooManyPeers | + DisconnectReason::AlreadyConnected | + DisconnectReason::PingTimeout | + DisconnectReason::DisconnectRequested | + DisconnectReason::TcpSubsystemError => Some(BackoffKind::Low), - DisconnectReason::ProtocolBreach - | DisconnectReason::UselessPeer - | DisconnectReason::IncompatibleP2PProtocolVersion - | DisconnectReason::NullNodeIdentity - | DisconnectReason::ClientQuitting - | DisconnectReason::UnexpectedHandshakeIdentity - | DisconnectReason::ConnectedToSelf - | DisconnectReason::SubprotocolSpecific => { + DisconnectReason::ProtocolBreach | + DisconnectReason::UselessPeer | + DisconnectReason::IncompatibleP2PProtocolVersion | + DisconnectReason::NullNodeIdentity | + DisconnectReason::ClientQuitting | + DisconnectReason::UnexpectedHandshakeIdentity | + DisconnectReason::ConnectedToSelf | + DisconnectReason::SubprotocolSpecific => { // These are considered fatal, and are handled by the // [`SessionError::is_fatal_protocol_error`] Some(BackoffKind::High) @@ -187,17 +187,17 @@ impl SessionError for EthStreamError { // [`SessionError::is_fatal_protocol_error`] match self { // timeouts - Self::EthHandshakeError(EthHandshakeError::NoResponse) - | Self::P2PStreamError(P2PStreamError::HandshakeError(P2PHandshakeError::NoResponse)) - | Self::P2PStreamError(P2PStreamError::PingTimeout) => Some(BackoffKind::Low), + Self::EthHandshakeError(EthHandshakeError::NoResponse) | + Self::P2PStreamError(P2PStreamError::HandshakeError(P2PHandshakeError::NoResponse)) | + Self::P2PStreamError(P2PStreamError::PingTimeout) => Some(BackoffKind::Low), // malformed messages - Self::P2PStreamError(P2PStreamError::Rlp(_)) - | Self::P2PStreamError(P2PStreamError::UnknownReservedMessageId(_)) - | Self::P2PStreamError(P2PStreamError::UnknownDisconnectReason(_)) - | Self::P2PStreamError(P2PStreamError::MessageTooBig { .. }) - | Self::P2PStreamError(P2PStreamError::EmptyProtocolMessage) - | Self::P2PStreamError(P2PStreamError::PingerError(_)) - | Self::P2PStreamError(P2PStreamError::Snap(_)) => Some(BackoffKind::Medium), + Self::P2PStreamError(P2PStreamError::Rlp(_)) | + Self::P2PStreamError(P2PStreamError::UnknownReservedMessageId(_)) | + Self::P2PStreamError(P2PStreamError::UnknownDisconnectReason(_)) | + Self::P2PStreamError(P2PStreamError::MessageTooBig { .. }) | + Self::P2PStreamError(P2PStreamError::EmptyProtocolMessage) | + Self::P2PStreamError(P2PStreamError::PingerError(_)) | + Self::P2PStreamError(P2PStreamError::Snap(_)) => Some(BackoffKind::Medium), _ => None, } } diff --git a/crates/net/network/src/fetch/mod.rs b/crates/net/network/src/fetch/mod.rs index 40b13ebe61e7..feb7e2a44afb 100644 --- a/crates/net/network/src/fetch/mod.rs +++ b/crates/net/network/src/fetch/mod.rs @@ -143,8 +143,8 @@ impl StateFetcher { } // replace best peer if this peer has better rtt - if maybe_better.1.timeout() < best_peer.1.timeout() - && !maybe_better.1.last_response_likely_bad + if maybe_better.1.timeout() < best_peer.1.timeout() && + !maybe_better.1.last_response_likely_bad { best_peer = maybe_better; } diff --git a/crates/net/network/src/peers.rs b/crates/net/network/src/peers.rs index 6d80d058bca8..8d5ad9dfd2b5 100644 --- a/crates/net/network/src/peers.rs +++ b/crates/net/network/src/peers.rs @@ -450,10 +450,10 @@ impl PeersManager { // exempt trusted peers from reputation slashing for if matches!( rep, - ReputationChangeKind::Dropped - | ReputationChangeKind::BadAnnouncement - | ReputationChangeKind::Timeout - | ReputationChangeKind::AlreadySeenTransaction + ReputationChangeKind::Dropped | + ReputationChangeKind::BadAnnouncement | + ReputationChangeKind::Timeout | + ReputationChangeKind::AlreadySeenTransaction ) { return; } @@ -790,10 +790,10 @@ impl PeersManager { /// Returns `None` if no peer is available. fn best_unconnected(&mut self) -> Option<(PeerId, &mut Peer)> { let mut unconnected = self.peers.iter_mut().filter(|(_, peer)| { - !peer.is_backed_off() - && !peer.is_banned() - && peer.state.is_unconnected() - && (!self.trusted_nodes_only || peer.is_trusted()) + !peer.is_backed_off() && + !peer.is_banned() && + peer.state.is_unconnected() && + (!self.trusted_nodes_only || peer.is_trusted()) }); // keep track of the best peer, if there's one diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index c8b69655653b..03ff50414831 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -279,8 +279,8 @@ impl ActiveSession { /// Returns the deadline timestamp at which the request times out fn request_deadline(&self) -> Instant { - Instant::now() - + Duration::from_millis(self.internal_request_timeout.load(Ordering::Relaxed)) + Instant::now() + + Duration::from_millis(self.internal_request_timeout.load(Ordering::Relaxed)) } /// Handle a Response to the peer diff --git a/crates/net/network/src/state.rs b/crates/net/network/src/state.rs index ba4ae450adb3..1087e781439c 100644 --- a/crates/net/network/src/state.rs +++ b/crates/net/network/src/state.rs @@ -313,8 +313,8 @@ where self.state_fetcher.on_pending_disconnect(&peer_id); self.queued_messages.push_back(StateAction::Disconnect { peer_id, reason }); } - PeerAction::DisconnectBannedIncoming { peer_id } - | PeerAction::DisconnectUntrustedIncoming { peer_id } => { + PeerAction::DisconnectBannedIncoming { peer_id } | + PeerAction::DisconnectUntrustedIncoming { peer_id } => { self.state_fetcher.on_pending_disconnect(&peer_id); self.queued_messages.push_back(StateAction::Disconnect { peer_id, reason: None }); } diff --git a/crates/net/network/src/transactions/constants.rs b/crates/net/network/src/transactions/constants.rs index 7ee646d7ae34..48fb8857cc35 100644 --- a/crates/net/network/src/transactions/constants.rs +++ b/crates/net/network/src/transactions/constants.rs @@ -138,8 +138,8 @@ pub mod tx_fetcher { /// [`DEFAULT_MAX_COUNT_INFLIGHT_REQUESTS_ON_FETCH_PENDING_HASHES`], which is 25600 hashes and /// 65 requests, so it is 25665 hashes. pub const DEFAULT_MAX_CAPACITY_CACHE_INFLIGHT_AND_PENDING_FETCH: u32 = - DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH - + DEFAULT_MAX_COUNT_INFLIGHT_REQUESTS_ON_FETCH_PENDING_HASHES as u32; + DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH + + DEFAULT_MAX_COUNT_INFLIGHT_REQUESTS_ON_FETCH_PENDING_HASHES as u32; /// Default maximum number of hashes pending fetch to tolerate at any time. /// @@ -190,8 +190,8 @@ pub mod tx_fetcher { /// which defaults to 128 KiB, so 64 KiB. pub const DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE_ON_FETCH_PENDING_HASHES: usize = - DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ - / 2; + DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ / + 2; /// Default max inflight request when fetching pending hashes. /// @@ -238,8 +238,8 @@ pub mod tx_fetcher { /// divided by [`SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE`], which /// is spec'd at 4096 hashes, so 521 bytes. pub const AVERAGE_BYTE_SIZE_TX_ENCODED: usize = - SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE - / SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE; + SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE / + SOFT_LIMIT_COUNT_HASHES_IN_NEW_POOLED_TRANSACTIONS_BROADCAST_MESSAGE; /// Median observed size in bytes of a small encoded legacy transaction. /// @@ -267,8 +267,8 @@ pub mod tx_fetcher { let max_size_expected_response = info.soft_limit_byte_size_pooled_transactions_response_on_pack_request; - max_size_expected_response / MEDIAN_BYTE_SIZE_SMALL_LEGACY_TX_ENCODED - + DEFAULT_MARGINAL_COUNT_HASHES_GET_POOLED_TRANSACTIONS_REQUEST + max_size_expected_response / MEDIAN_BYTE_SIZE_SMALL_LEGACY_TX_ENCODED + + DEFAULT_MARGINAL_COUNT_HASHES_GET_POOLED_TRANSACTIONS_REQUEST } /// Returns the approx number of transactions that a diff --git a/crates/net/network/src/transactions/fetcher.rs b/crates/net/network/src/transactions/fetcher.rs index 992f0829c46d..04d9b60238b4 100644 --- a/crates/net/network/src/transactions/fetcher.rs +++ b/crates/net/network/src/transactions/fetcher.rs @@ -293,8 +293,8 @@ impl TransactionFetcher { let next_acc_size = acc_size_response + size; - if next_acc_size - <= self.info.soft_limit_byte_size_pooled_transactions_response_on_pack_request + if next_acc_size <= + self.info.soft_limit_byte_size_pooled_transactions_response_on_pack_request { // only update accumulated size of tx response if tx will fit in without exceeding // soft limit @@ -305,8 +305,8 @@ impl TransactionFetcher { } let free_space = - self.info.soft_limit_byte_size_pooled_transactions_response_on_pack_request - - acc_size_response; + self.info.soft_limit_byte_size_pooled_transactions_response_on_pack_request - + acc_size_response; if free_space < MEDIAN_BYTE_SIZE_SMALL_LEGACY_TX_ENCODED { break; @@ -738,8 +738,8 @@ impl TransactionFetcher { .unwrap_or(AVERAGE_BYTE_SIZE_TX_ENCODED); // if request full enough already, we're satisfied, send request for single tx - if acc_size_response - >= DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE_ON_FETCH_PENDING_HASHES + if acc_size_response >= + DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE_ON_FETCH_PENDING_HASHES { return; } @@ -814,8 +814,8 @@ impl TransactionFetcher { let info = &self.info; let tx_fetcher_has_capacity = self.has_capacity( - info.max_inflight_requests - / DEFAULT_DIVISOR_MAX_COUNT_INFLIGHT_REQUESTS_ON_FIND_IDLE_PEER, + info.max_inflight_requests / + DEFAULT_DIVISOR_MAX_COUNT_INFLIGHT_REQUESTS_ON_FIND_IDLE_PEER, ); let tx_pool_has_capacity = has_capacity_wrt_pending_pool_imports( DEFAULT_DIVISOR_MAX_COUNT_PENDING_POOL_IMPORTS_ON_FIND_IDLE_PEER, @@ -853,8 +853,8 @@ impl TransactionFetcher { let info = &self.info; let tx_fetcher_has_capacity = self.has_capacity( - info.max_inflight_requests - / DEFAULT_DIVISOR_MAX_COUNT_INFLIGHT_REQUESTS_ON_FIND_INTERSECTION, + info.max_inflight_requests / + DEFAULT_DIVISOR_MAX_COUNT_INFLIGHT_REQUESTS_ON_FIND_INTERSECTION, ); let tx_pool_has_capacity = has_capacity_wrt_pending_pool_imports( DEFAULT_DIVISOR_MAX_COUNT_PENDING_POOL_IMPORTS_ON_FIND_INTERSECTION, diff --git a/crates/net/network/src/transactions/mod.rs b/crates/net/network/src/transactions/mod.rs index 681defcfa186..cf4f8af78e0b 100644 --- a/crates/net/network/src/transactions/mod.rs +++ b/crates/net/network/src/transactions/mod.rs @@ -1189,8 +1189,8 @@ where /// `false` if [`TransactionsManager`] is operating close to full capacity. fn has_capacity_for_fetching_pending_hashes(&self) -> bool { self.pending_pool_imports_info - .has_capacity(self.pending_pool_imports_info.max_pending_pool_imports) - && self.transaction_fetcher.has_capacity_for_fetching_pending_hashes() + .has_capacity(self.pending_pool_imports_info.max_pending_pool_imports) && + self.transaction_fetcher.has_capacity_for_fetching_pending_hashes() } } @@ -1339,12 +1339,12 @@ where this.transaction_fetcher.update_metrics(); // all channels are fully drained and import futures pending - if maybe_more_network_events - || maybe_more_commands - || maybe_more_tx_events - || maybe_more_tx_fetch_events - || maybe_more_pool_imports - || maybe_more_pending_txns + if maybe_more_network_events || + maybe_more_commands || + maybe_more_tx_events || + maybe_more_tx_fetch_events || + maybe_more_pool_imports || + maybe_more_pending_txns { // make sure we're woken up again cx.waker().wake_by_ref(); @@ -1396,8 +1396,8 @@ impl FullTransactionsBuilder { /// [`TransactionFetcher::fill_request_from_hashes_pending_fetch`]. fn push(&mut self, transaction: &PropagateTransaction) { let new_size = self.total_size + transaction.size; - if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE - && self.total_size > 0 + if new_size > DEFAULT_SOFT_LIMIT_BYTE_SIZE_TRANSACTIONS_BROADCAST_MESSAGE && + self.total_size > 0 { return; } diff --git a/crates/net/network/tests/it/multiplex.rs b/crates/net/network/tests/it/multiplex.rs index f2f70c59577c..800dd370c6c6 100644 --- a/crates/net/network/tests/it/multiplex.rs +++ b/crates/net/network/tests/it/multiplex.rs @@ -99,8 +99,8 @@ mod proto { buf.put_u8(self.message_type as u8); match &self.message { PingPongProtoMessageKind::Ping | PingPongProtoMessageKind::Pong => {} - PingPongProtoMessageKind::PingMessage(msg) - | PingPongProtoMessageKind::PongMessage(msg) => { + PingPongProtoMessageKind::PingMessage(msg) | + PingPongProtoMessageKind::PongMessage(msg) => { buf.put(msg.as_bytes()); } } diff --git a/crates/net/p2p/src/error.rs b/crates/net/p2p/src/error.rs index de778707dce6..df1b38142ace 100644 --- a/crates/net/p2p/src/error.rs +++ b/crates/net/p2p/src/error.rs @@ -61,10 +61,10 @@ impl EthResponseValidator for RequestResult> { fn reputation_change_err(&self) -> Option { if let Err(err) = self { match err { - RequestError::ChannelClosed - | RequestError::ConnectionDropped - | RequestError::UnsupportedCapability - | RequestError::BadResponse => None, + RequestError::ChannelClosed | + RequestError::ConnectionDropped | + RequestError::UnsupportedCapability | + RequestError::BadResponse => None, RequestError::Timeout => Some(ReputationChangeKind::Timeout), } } else { diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index 5bc34a63ac4e..e7d33a7bcb0a 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -244,8 +244,8 @@ impl NodeState { BeaconConsensusEngineEvent::ForkchoiceUpdated(state, status) => { let ForkchoiceState { head_block_hash, safe_block_hash, finalized_block_hash } = state; - if self.safe_block_hash != Some(safe_block_hash) - && self.finalized_block_hash != Some(finalized_block_hash) + if self.safe_block_hash != Some(safe_block_hash) && + self.finalized_block_hash != Some(finalized_block_hash) { let msg = match status { ForkchoiceStatus::Valid => "Forkchoice updated", @@ -644,8 +644,8 @@ impl Eta { /// It's not the case for network-dependent ([`StageId::Headers`] and [`StageId::Bodies`]) and /// [`StageId::Execution`] stages. fn fmt_for_stage(&self, stage: StageId) -> Option { - if !self.is_available() - || matches!(stage, StageId::Headers | StageId::Bodies | StageId::Execution) + if !self.is_available() || + matches!(stage, StageId::Headers | StageId::Bodies | StageId::Execution) { None } else { diff --git a/crates/optimism/cli/src/commands/import.rs b/crates/optimism/cli/src/commands/import.rs index ec397f22b98d..b1096dda2154 100644 --- a/crates/optimism/cli/src/commands/import.rs +++ b/crates/optimism/cli/src/commands/import.rs @@ -123,8 +123,8 @@ impl ImportOpCommand { let total_imported_blocks = provider.tx_ref().entries::()?; let total_imported_txns = provider.tx_ref().entries::()?; - if total_decoded_blocks != total_imported_blocks - || total_decoded_txns != total_imported_txns + total_filtered_out_dup_txns + if total_decoded_blocks != total_imported_blocks || + total_decoded_txns != total_imported_txns + total_filtered_out_dup_txns { error!(target: "reth::cli", total_decoded_blocks, diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 97d1c8d40a7e..8f37f2554053 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -148,8 +148,8 @@ where // The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior, // must be no greater than the block’s gasLimit. let block_available_gas = block.header.gas_limit - cumulative_gas_used; - if transaction.gas_limit() > block_available_gas - && (is_regolith || !transaction.is_system_transaction()) + if transaction.gas_limit() > block_available_gas && + (is_regolith || !transaction.is_system_transaction()) { return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { transaction_gas_limit: transaction.gas_limit(), diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index 268291dbce69..21a8d199b3a9 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -29,9 +29,9 @@ impl Account { /// After `SpuriousDragon` empty account is defined as account with nonce == 0 && balance == 0 /// && bytecode = None (or hash is [`KECCAK_EMPTY`]). pub fn is_empty(&self) -> bool { - self.nonce == 0 - && self.balance.is_zero() - && self.bytecode_hash.map_or(true, |hash| hash == KECCAK_EMPTY) + self.nonce == 0 && + self.balance.is_zero() && + self.bytecode_hash.map_or(true, |hash| hash == KECCAK_EMPTY) } /// Makes an [Account] from [`GenesisAccount`] type diff --git a/crates/primitives-traits/src/withdrawal.rs b/crates/primitives-traits/src/withdrawal.rs index 7d398f9f2c8e..49d4e5e31269 100644 --- a/crates/primitives-traits/src/withdrawal.rs +++ b/crates/primitives-traits/src/withdrawal.rs @@ -107,10 +107,10 @@ mod tests { impl PartialEq for RethWithdrawal { fn eq(&self, other: &Withdrawal) -> bool { - self.index == other.index - && self.validator_index == other.validator_index - && self.address == other.address - && self.amount == other.amount + self.index == other.index && + self.validator_index == other.validator_index && + self.address == other.address && + self.amount == other.amount } } diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index 9a974a37ef98..d93e0b7b108d 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -38,8 +38,8 @@ impl TryFrom for Block { )) }) .collect(), - alloy_rpc_types::BlockTransactions::Hashes(_) - | alloy_rpc_types::BlockTransactions::Uncle => { + alloy_rpc_types::BlockTransactions::Hashes(_) | + alloy_rpc_types::BlockTransactions::Uncle => { // alloy deserializes empty blocks into `BlockTransactions::Hashes`, if the tx // root is the empty root then we can just return an empty vec. if block.header.transactions_root == EMPTY_TRANSACTIONS { diff --git a/crates/primitives/src/transaction/eip1559.rs b/crates/primitives/src/transaction/eip1559.rs index be922e25cc9b..cce6f0ca22fc 100644 --- a/crates/primitives/src/transaction/eip1559.rs +++ b/crates/primitives/src/transaction/eip1559.rs @@ -109,15 +109,15 @@ impl TxEip1559 { /// Encodes only the transaction's fields into the desired buffer, without a RLP header. pub(crate) fn fields_len(&self) -> usize { - self.chain_id.length() - + self.nonce.length() - + self.max_priority_fee_per_gas.length() - + self.max_fee_per_gas.length() - + self.gas_limit.length() - + self.to.length() - + self.value.length() - + self.input.0.length() - + self.access_list.length() + self.chain_id.length() + + self.nonce.length() + + self.max_priority_fee_per_gas.length() + + self.max_fee_per_gas.length() + + self.gas_limit.length() + + self.to.length() + + self.value.length() + + self.input.0.length() + + self.access_list.length() } /// Encodes only the transaction's fields into the desired buffer, without a RLP header. diff --git a/crates/primitives/src/transaction/eip2930.rs b/crates/primitives/src/transaction/eip2930.rs index e09d66d9e1bf..ebaa12785c1d 100644 --- a/crates/primitives/src/transaction/eip2930.rs +++ b/crates/primitives/src/transaction/eip2930.rs @@ -90,14 +90,14 @@ impl TxEip2930 { /// Outputs the length of the transaction's fields, without a RLP header. pub(crate) fn fields_len(&self) -> usize { - self.chain_id.length() - + self.nonce.length() - + self.gas_price.length() - + self.gas_limit.length() - + self.to.length() - + self.value.length() - + self.input.0.length() - + self.access_list.length() + self.chain_id.length() + + self.nonce.length() + + self.gas_price.length() + + self.gas_limit.length() + + self.to.length() + + self.value.length() + + self.input.0.length() + + self.access_list.length() } /// Encodes only the transaction's fields into the desired buffer, without a RLP header. diff --git a/crates/primitives/src/transaction/eip4844.rs b/crates/primitives/src/transaction/eip4844.rs index 9a46e135fbcb..f792d787afdd 100644 --- a/crates/primitives/src/transaction/eip4844.rs +++ b/crates/primitives/src/transaction/eip4844.rs @@ -174,17 +174,17 @@ impl TxEip4844 { /// Outputs the length of the transaction's fields, without a RLP header. pub(crate) fn fields_len(&self) -> usize { - self.chain_id.length() - + self.nonce.length() - + self.gas_limit.length() - + self.max_fee_per_gas.length() - + self.max_priority_fee_per_gas.length() - + self.to.length() - + self.value.length() - + self.access_list.length() - + self.blob_versioned_hashes.length() - + self.max_fee_per_blob_gas.length() - + self.input.0.length() + self.chain_id.length() + + self.nonce.length() + + self.gas_limit.length() + + self.max_fee_per_gas.length() + + self.max_priority_fee_per_gas.length() + + self.to.length() + + self.value.length() + + self.access_list.length() + + self.blob_versioned_hashes.length() + + self.max_fee_per_blob_gas.length() + + self.input.0.length() } /// Encodes only the transaction's fields into the desired buffer, without a RLP header. diff --git a/crates/primitives/src/transaction/legacy.rs b/crates/primitives/src/transaction/legacy.rs index 3fb127aba772..09b661cf7995 100644 --- a/crates/primitives/src/transaction/legacy.rs +++ b/crates/primitives/src/transaction/legacy.rs @@ -57,12 +57,12 @@ impl TxLegacy { /// Outputs the length of the transaction's fields, without a RLP header or length of the /// eip155 fields. pub(crate) fn fields_len(&self) -> usize { - self.nonce.length() - + self.gas_price.length() - + self.gas_limit.length() - + self.to.length() - + self.value.length() - + self.input.0.length() + self.nonce.length() + + self.gas_price.length() + + self.gas_limit.length() + + self.to.length() + + self.value.length() + + self.input.0.length() } /// Encodes only the transaction's fields into the desired buffer, without a RLP header or diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 0c38f016cd75..4e455014ff4f 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -147,9 +147,9 @@ impl Transaction { pub const fn chain_id(&self) -> Option { match self { Self::Legacy(TxLegacy { chain_id, .. }) => *chain_id, - Self::Eip2930(TxEip2930 { chain_id, .. }) - | Self::Eip1559(TxEip1559 { chain_id, .. }) - | Self::Eip4844(TxEip4844 { chain_id, .. }) => Some(*chain_id), + Self::Eip2930(TxEip2930 { chain_id, .. }) | + Self::Eip1559(TxEip1559 { chain_id, .. }) | + Self::Eip4844(TxEip4844 { chain_id, .. }) => Some(*chain_id), #[cfg(feature = "optimism")] Self::Deposit(_) => None, } @@ -159,9 +159,9 @@ impl Transaction { pub fn set_chain_id(&mut self, chain_id: u64) { match self { Self::Legacy(TxLegacy { chain_id: ref mut c, .. }) => *c = Some(chain_id), - Self::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) - | Self::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) - | Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) => *c = chain_id, + Self::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) | + Self::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) | + Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) => *c = chain_id, #[cfg(feature = "optimism")] Self::Deposit(_) => { /* noop */ } } @@ -171,9 +171,9 @@ impl Transaction { /// [`TxKind::Create`] if the transaction is a contract creation. pub const fn kind(&self) -> TxKind { match self { - Self::Legacy(TxLegacy { to, .. }) - | Self::Eip2930(TxEip2930 { to, .. }) - | Self::Eip1559(TxEip1559 { to, .. }) => *to, + Self::Legacy(TxLegacy { to, .. }) | + Self::Eip2930(TxEip2930 { to, .. }) | + Self::Eip1559(TxEip1559 { to, .. }) => *to, Self::Eip4844(TxEip4844 { to, .. }) => TxKind::Call(*to), #[cfg(feature = "optimism")] Self::Deposit(TxDeposit { to, .. }) => *to, @@ -203,10 +203,10 @@ impl Transaction { /// Gets the transaction's value field. pub const fn value(&self) -> U256 { *match self { - Self::Legacy(TxLegacy { value, .. }) - | Self::Eip2930(TxEip2930 { value, .. }) - | Self::Eip1559(TxEip1559 { value, .. }) - | Self::Eip4844(TxEip4844 { value, .. }) => value, + Self::Legacy(TxLegacy { value, .. }) | + Self::Eip2930(TxEip2930 { value, .. }) | + Self::Eip1559(TxEip1559 { value, .. }) | + Self::Eip4844(TxEip4844 { value, .. }) => value, #[cfg(feature = "optimism")] Self::Deposit(TxDeposit { value, .. }) => value, } @@ -215,10 +215,10 @@ impl Transaction { /// Get the transaction's nonce. pub const fn nonce(&self) -> u64 { match self { - Self::Legacy(TxLegacy { nonce, .. }) - | Self::Eip2930(TxEip2930 { nonce, .. }) - | Self::Eip1559(TxEip1559 { nonce, .. }) - | Self::Eip4844(TxEip4844 { nonce, .. }) => *nonce, + Self::Legacy(TxLegacy { nonce, .. }) | + Self::Eip2930(TxEip2930 { nonce, .. }) | + Self::Eip1559(TxEip1559 { nonce, .. }) | + Self::Eip4844(TxEip4844 { nonce, .. }) => *nonce, // Deposit transactions do not have nonces. #[cfg(feature = "optimism")] Self::Deposit(_) => 0, @@ -242,10 +242,10 @@ impl Transaction { /// Get the gas limit of the transaction. pub const fn gas_limit(&self) -> u64 { match self { - Self::Legacy(TxLegacy { gas_limit, .. }) - | Self::Eip2930(TxEip2930 { gas_limit, .. }) - | Self::Eip1559(TxEip1559 { gas_limit, .. }) - | Self::Eip4844(TxEip4844 { gas_limit, .. }) => *gas_limit, + Self::Legacy(TxLegacy { gas_limit, .. }) | + Self::Eip2930(TxEip2930 { gas_limit, .. }) | + Self::Eip1559(TxEip1559 { gas_limit, .. }) | + Self::Eip4844(TxEip4844 { gas_limit, .. }) => *gas_limit, #[cfg(feature = "optimism")] Self::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit, } @@ -266,10 +266,10 @@ impl Transaction { /// This is also commonly referred to as the "Gas Fee Cap" (`GasFeeCap`). pub const fn max_fee_per_gas(&self) -> u128 { match self { - Self::Legacy(TxLegacy { gas_price, .. }) - | Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price, - Self::Eip1559(TxEip1559 { max_fee_per_gas, .. }) - | Self::Eip4844(TxEip4844 { max_fee_per_gas, .. }) => *max_fee_per_gas, + Self::Legacy(TxLegacy { gas_price, .. }) | + Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price, + Self::Eip1559(TxEip1559 { max_fee_per_gas, .. }) | + Self::Eip4844(TxEip4844 { max_fee_per_gas, .. }) => *max_fee_per_gas, // Deposit transactions buy their L2 gas on L1 and, as such, the L2 gas is not // refundable. #[cfg(feature = "optimism")] @@ -284,8 +284,8 @@ impl Transaction { pub const fn max_priority_fee_per_gas(&self) -> Option { match self { Self::Legacy(_) | Self::Eip2930(_) => None, - Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) - | Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => { + Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) | + Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => { Some(*max_priority_fee_per_gas) } #[cfg(feature = "optimism")] @@ -338,12 +338,10 @@ impl Transaction { /// non-EIP-1559 transactions. pub const fn priority_fee_or_price(&self) -> u128 { match self { - Self::Legacy(TxLegacy { gas_price, .. }) - | Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price, - Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) - | Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => { - *max_priority_fee_per_gas - } + Self::Legacy(TxLegacy { gas_price, .. }) | + Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price, + Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) | + Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => *max_priority_fee_per_gas, #[cfg(feature = "optimism")] Self::Deposit(_) => 0, } @@ -397,10 +395,10 @@ impl Transaction { /// Get the transaction's input field. pub const fn input(&self) -> &Bytes { match self { - Self::Legacy(TxLegacy { input, .. }) - | Self::Eip2930(TxEip2930 { input, .. }) - | Self::Eip1559(TxEip1559 { input, .. }) - | Self::Eip4844(TxEip4844 { input, .. }) => input, + Self::Legacy(TxLegacy { input, .. }) | + Self::Eip2930(TxEip2930 { input, .. }) | + Self::Eip1559(TxEip1559 { input, .. }) | + Self::Eip4844(TxEip4844 { input, .. }) => input, #[cfg(feature = "optimism")] Self::Deposit(TxDeposit { input, .. }) => input, } diff --git a/crates/primitives/src/transaction/optimism.rs b/crates/primitives/src/transaction/optimism.rs index b28d85b65ebc..6bb8ec9b8c87 100644 --- a/crates/primitives/src/transaction/optimism.rs +++ b/crates/primitives/src/transaction/optimism.rs @@ -78,14 +78,14 @@ impl TxDeposit { /// Outputs the length of the transaction's fields, without a RLP header or length of the /// eip155 fields. pub(crate) fn fields_len(&self) -> usize { - self.source_hash.length() - + self.from.length() - + self.to.length() - + self.mint.map_or(1, |mint| mint.length()) - + self.value.length() - + self.gas_limit.length() - + self.is_system_transaction.length() - + self.input.0.length() + self.source_hash.length() + + self.from.length() + + self.to.length() + + self.mint.map_or(1, |mint| mint.length()) + + self.value.length() + + self.gas_limit.length() + + self.is_system_transaction.length() + + self.input.0.length() } /// Encodes only the transaction's fields into the desired buffer, without a RLP header. diff --git a/crates/primitives/src/transaction/pooled.rs b/crates/primitives/src/transaction/pooled.rs index ad2ba6b2a54b..87bed1d67415 100644 --- a/crates/primitives/src/transaction/pooled.rs +++ b/crates/primitives/src/transaction/pooled.rs @@ -122,9 +122,9 @@ impl PooledTransactionsElement { /// Returns the signature of the transaction. pub const fn signature(&self) -> &Signature { match self { - Self::Legacy { signature, .. } - | Self::Eip2930 { signature, .. } - | Self::Eip1559 { signature, .. } => signature, + Self::Legacy { signature, .. } | + Self::Eip2930 { signature, .. } | + Self::Eip1559 { signature, .. } => signature, Self::BlobTransaction(blob_tx) => &blob_tx.signature, } } diff --git a/crates/prune/prune/src/pruner.rs b/crates/prune/prune/src/pruner.rs index 3485ec34e3cc..65ab77b3c39e 100644 --- a/crates/prune/prune/src/pruner.rs +++ b/crates/prune/prune/src/pruner.rs @@ -290,8 +290,8 @@ impl Pruner { // Saturating subtraction is needed for the case when the chain was reverted, meaning // current block number might be less than the previous tip block number. // If that's the case, no pruning is needed as outdated data is also reverted. - if tip_block_number.saturating_sub(self.previous_tip_block_number.unwrap_or_default()) - >= self.min_block_interval as u64 + if tip_block_number.saturating_sub(self.previous_tip_block_number.unwrap_or_default()) >= + self.min_block_interval as u64 { debug!( target: "pruner", diff --git a/crates/prune/prune/src/segments/account_history.rs b/crates/prune/prune/src/segments/account_history.rs index 85f079e38ec8..90845e859a03 100644 --- a/crates/prune/prune/src/segments/account_history.rs +++ b/crates/prune/prune/src/segments/account_history.rs @@ -226,8 +226,8 @@ mod tests { .iter() .enumerate() .skip_while(|(i, (block_number, _))| { - *i < deleted_entries_limit / ACCOUNT_HISTORY_TABLES_TO_PRUNE * run - && *block_number <= to_block as usize + *i < deleted_entries_limit / ACCOUNT_HISTORY_TABLES_TO_PRUNE * run && + *block_number <= to_block as usize }) .next() .map(|(i, _)| i) diff --git a/crates/prune/prune/src/segments/headers.rs b/crates/prune/prune/src/segments/headers.rs index 94336efd7168..57294c556ca2 100644 --- a/crates/prune/prune/src/segments/headers.rs +++ b/crates/prune/prune/src/segments/headers.rs @@ -272,8 +272,8 @@ mod tests { provider.commit().expect("commit"); let last_pruned_block_number = to_block.min( - next_block_number_to_prune - + (input.limiter.deleted_entries_limit().unwrap() / HEADER_TABLES_TO_PRUNE - 1) + next_block_number_to_prune + + (input.limiter.deleted_entries_limit().unwrap() / HEADER_TABLES_TO_PRUNE - 1) as u64, ); diff --git a/crates/prune/prune/src/segments/receipts.rs b/crates/prune/prune/src/segments/receipts.rs index 2e22596d9574..de97a2aaf6f4 100644 --- a/crates/prune/prune/src/segments/receipts.rs +++ b/crates/prune/prune/src/segments/receipts.rs @@ -169,8 +169,8 @@ mod tests { .map(|block| block.body.len()) .sum::() .min( - next_tx_number_to_prune as usize - + input.limiter.deleted_entries_limit().unwrap(), + next_tx_number_to_prune as usize + + input.limiter.deleted_entries_limit().unwrap(), ) .sub(1); diff --git a/crates/prune/prune/src/segments/receipts_by_logs.rs b/crates/prune/prune/src/segments/receipts_by_logs.rs index 678f14301ec9..88c39beacaa1 100644 --- a/crates/prune/prune/src/segments/receipts_by_logs.rs +++ b/crates/prune/prune/src/segments/receipts_by_logs.rs @@ -143,11 +143,10 @@ impl Segment for ReceiptsByLogs { tx_range, &mut limiter, |(tx_num, receipt)| { - let skip = num_addresses > 0 - && receipt - .logs - .iter() - .any(|log| filtered_addresses[..num_addresses].contains(&&log.address)); + let skip = num_addresses > 0 && + receipt.logs.iter().any(|log| { + filtered_addresses[..num_addresses].contains(&&log.address) + }); if skip { last_skipped_transaction = *tx_num; @@ -313,8 +312,8 @@ mod tests { assert_eq!( db.table::().unwrap().len(), - blocks.iter().map(|block| block.body.len()).sum::() - - ((pruned_tx + 1) - unprunable) as usize + blocks.iter().map(|block| block.body.len()).sum::() - + ((pruned_tx + 1) - unprunable) as usize ); output.progress.is_finished() @@ -331,8 +330,8 @@ mod tests { // Either we only find our contract, or the receipt is part of the unprunable receipts // set by tip - 128 assert!( - receipt.logs.iter().any(|l| l.address == deposit_contract_addr) - || provider.transaction_block(tx_num).unwrap().unwrap() > tip - 128, + receipt.logs.iter().any(|l| l.address == deposit_contract_addr) || + provider.transaction_block(tx_num).unwrap().unwrap() > tip - 128, ); } } diff --git a/crates/prune/prune/src/segments/sender_recovery.rs b/crates/prune/prune/src/segments/sender_recovery.rs index 27a27bdd751d..aa045b76aa25 100644 --- a/crates/prune/prune/src/segments/sender_recovery.rs +++ b/crates/prune/prune/src/segments/sender_recovery.rs @@ -150,8 +150,8 @@ mod tests { .map(|block| block.body.len()) .sum::() .min( - next_tx_number_to_prune as usize - + input.limiter.deleted_entries_limit().unwrap(), + next_tx_number_to_prune as usize + + input.limiter.deleted_entries_limit().unwrap(), ) .sub(1); diff --git a/crates/prune/prune/src/segments/storage_history.rs b/crates/prune/prune/src/segments/storage_history.rs index d6a5919e9ddb..18a2a7af458c 100644 --- a/crates/prune/prune/src/segments/storage_history.rs +++ b/crates/prune/prune/src/segments/storage_history.rs @@ -235,8 +235,8 @@ mod tests { .iter() .enumerate() .skip_while(|(i, (block_number, _, _))| { - *i < deleted_entries_limit / STORAGE_HISTORY_TABLES_TO_PRUNE * run - && *block_number <= to_block as usize + *i < deleted_entries_limit / STORAGE_HISTORY_TABLES_TO_PRUNE * run && + *block_number <= to_block as usize }) .next() .map(|(i, _)| i) diff --git a/crates/prune/prune/src/segments/transaction_lookup.rs b/crates/prune/prune/src/segments/transaction_lookup.rs index ebdbdd09b63c..22b20c925c34 100644 --- a/crates/prune/prune/src/segments/transaction_lookup.rs +++ b/crates/prune/prune/src/segments/transaction_lookup.rs @@ -43,8 +43,8 @@ impl Segment for TransactionLookup { } } .into_inner(); - let tx_range = start - ..=Some(end) + let tx_range = start..= + Some(end) .min(input.limiter.deleted_entries_limit_left().map(|left| start + left as u64 - 1)) .unwrap(); let tx_range_end = *tx_range.end(); @@ -175,8 +175,8 @@ mod tests { .map(|block| block.body.len()) .sum::() .min( - next_tx_number_to_prune as usize - + input.limiter.deleted_entries_limit().unwrap(), + next_tx_number_to_prune as usize + + input.limiter.deleted_entries_limit().unwrap(), ) .sub(1); diff --git a/crates/prune/prune/src/segments/transactions.rs b/crates/prune/prune/src/segments/transactions.rs index 5119b1a54201..4a30808cbc50 100644 --- a/crates/prune/prune/src/segments/transactions.rs +++ b/crates/prune/prune/src/segments/transactions.rs @@ -152,8 +152,8 @@ mod tests { .map(|block| block.body.len()) .sum::() .min( - next_tx_number_to_prune as usize - + input.limiter.deleted_entries_limit().unwrap(), + next_tx_number_to_prune as usize + + input.limiter.deleted_entries_limit().unwrap(), ) .sub(1); diff --git a/crates/prune/types/src/lib.rs b/crates/prune/types/src/lib.rs index bb9f2e9f7151..82563010f165 100644 --- a/crates/prune/types/src/lib.rs +++ b/crates/prune/types/src/lib.rs @@ -68,8 +68,8 @@ impl ReceiptsLogPruneConfig { let block = (pruned_block + 1).max( mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)? .map(|(block, _)| block) - .unwrap_or_default() - + 1, + .unwrap_or_default() + + 1, ); map.entry(block).or_insert_with(Vec::new).push(address) diff --git a/crates/revm/src/batch.rs b/crates/revm/src/batch.rs index a9c6f1c17e0d..87d9898c803b 100644 --- a/crates/revm/src/batch.rs +++ b/crates/revm/src/batch.rs @@ -102,8 +102,8 @@ impl BlockBatchRecord { !self .prune_modes .account_history - .map_or(false, |mode| mode.should_prune(block_number, tip)) - && !self + .map_or(false, |mode| mode.should_prune(block_number, tip)) && + !self .prune_modes .storage_history .map_or(false, |mode| mode.should_prune(block_number, tip)) diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index a45bdc69986d..97c83dec3a00 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1257,9 +1257,9 @@ impl RpcServerConfig { /// /// If no server is configured, no server will be launched on [`RpcServerConfig::start`]. pub const fn has_server(&self) -> bool { - self.http_server_config.is_some() - || self.ws_server_config.is_some() - || self.ipc_server_config.is_some() + self.http_server_config.is_some() || + self.ws_server_config.is_some() || + self.ipc_server_config.is_some() } /// Returns the [`SocketAddr`] of the http server @@ -1319,9 +1319,9 @@ impl RpcServerConfig { } // If both are configured on the same port, we combine them into one server. - if self.http_addr == self.ws_addr - && self.http_server_config.is_some() - && self.ws_server_config.is_some() + if self.http_addr == self.ws_addr && + self.http_server_config.is_some() && + self.ws_server_config.is_some() { let cors = match (self.ws_cors_domains.as_ref(), self.http_cors_domains.as_ref()) { (Some(ws_cors), Some(http_cors)) => { @@ -1684,8 +1684,8 @@ impl RpcServerHandle { "Bearer {}", secret .encode(&Claims { - iat: (SystemTime::now().duration_since(UNIX_EPOCH).unwrap() - + Duration::from_secs(60)) + iat: (SystemTime::now().duration_since(UNIX_EPOCH).unwrap() + + Duration::from_secs(60)) .as_secs(), exp: None, }) diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 62e6fccedb75..14143d229cca 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -32,8 +32,8 @@ use std::collections::HashSet; fn is_unimplemented(err: jsonrpsee::core::client::Error) -> bool { match err { jsonrpsee::core::client::Error::Call(error_obj) => { - error_obj.code() == ErrorCode::InternalError.code() - && error_obj.message() == "unimplemented" + error_obj.code() == ErrorCode::InternalError.code() && + error_obj.message() == "unimplemented" } _ => false, } diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index c9629148f12d..55534083a582 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -1053,8 +1053,8 @@ mod tests { blocks .iter() .filter(|b| { - !first_missing_range.contains(&b.number) - && !second_missing_range.contains(&b.number) + !first_missing_range.contains(&b.number) && + !second_missing_range.contains(&b.number) }) .map(|b| (b.hash(), b.clone().unseal())), ); @@ -1083,8 +1083,8 @@ mod tests { // ensure we still return trailing `None`s here because by-hash will not be aware // of the missing block's number, and cannot compare it to the current best block .map(|b| { - if first_missing_range.contains(&b.number) - || second_missing_range.contains(&b.number) + if first_missing_range.contains(&b.number) || + second_missing_range.contains(&b.number) { None } else { diff --git a/crates/rpc/rpc-engine-api/src/error.rs b/crates/rpc/rpc-engine-api/src/error.rs index 540d6abf2b00..6fb7a197238d 100644 --- a/crates/rpc/rpc-engine-api/src/error.rs +++ b/crates/rpc/rpc-engine-api/src/error.rs @@ -116,11 +116,11 @@ impl ErrorData { impl From for jsonrpsee_types::error::ErrorObject<'static> { fn from(error: EngineApiError) -> Self { match error { - EngineApiError::InvalidBodiesRange { .. } - | EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload( + EngineApiError::InvalidBodiesRange { .. } | + EngineApiError::EngineObjectValidationError(EngineObjectValidationError::Payload( _, - )) - | EngineApiError::EngineObjectValidationError( + )) | + EngineApiError::EngineObjectValidationError( EngineObjectValidationError::InvalidParams(_), ) => { // Note: the data field is not required by the spec, but is also included by other @@ -164,8 +164,8 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { // Error responses from the consensus engine EngineApiError::ForkChoiceUpdate(ref err) => match err { BeaconForkChoiceUpdateError::ForkchoiceUpdateError(err) => (*err).into(), - BeaconForkChoiceUpdateError::EngineUnavailable - | BeaconForkChoiceUpdateError::Internal(_) => { + BeaconForkChoiceUpdateError::EngineUnavailable | + BeaconForkChoiceUpdateError::Internal(_) => { jsonrpsee_types::error::ErrorObject::owned( INTERNAL_ERROR_CODE, SERVER_ERROR_MSG, @@ -195,10 +195,10 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { } }, // Any other server error - EngineApiError::TerminalTD { .. } - | EngineApiError::TerminalBlockHash { .. } - | EngineApiError::Internal(_) - | EngineApiError::GetPayloadError(_) => jsonrpsee_types::error::ErrorObject::owned( + EngineApiError::TerminalTD { .. } | + EngineApiError::TerminalBlockHash { .. } | + EngineApiError::Internal(_) | + EngineApiError::GetPayloadError(_) => jsonrpsee_types::error::ErrorObject::owned( INTERNAL_ERROR_CODE, SERVER_ERROR_MSG, Some(ErrorData::new(error)), diff --git a/crates/rpc/rpc-eth-types/src/error.rs b/crates/rpc/rpc-eth-types/src/error.rs index d7c8915d0571..95d989a19a81 100644 --- a/crates/rpc/rpc-eth-types/src/error.rs +++ b/crates/rpc/rpc-eth-types/src/error.rs @@ -207,12 +207,12 @@ impl From for EthApiError { fn from(error: reth_errors::ProviderError) -> Self { use reth_errors::ProviderError; match error { - ProviderError::HeaderNotFound(_) - | ProviderError::BlockHashNotFound(_) - | ProviderError::BestBlockNotFound - | ProviderError::BlockNumberForTransactionIndexNotFound - | ProviderError::TotalDifficultyNotFound { .. } - | ProviderError::UnknownBlockHash(_) => Self::UnknownBlockNumber, + ProviderError::HeaderNotFound(_) | + ProviderError::BlockHashNotFound(_) | + ProviderError::BestBlockNotFound | + ProviderError::BlockNumberForTransactionIndexNotFound | + ProviderError::TotalDifficultyNotFound { .. } | + ProviderError::UnknownBlockHash(_) => Self::UnknownBlockNumber, ProviderError::FinalizedBlockNotFound | ProviderError::SafeBlockNotFound => { Self::UnknownSafeOrFinalizedBlock } @@ -495,10 +495,10 @@ impl From for RpcInvalidTransactionErr Self::OldLegacyChainId } InvalidTransactionError::ChainIdMismatch => Self::InvalidChainId, - InvalidTransactionError::Eip2930Disabled - | InvalidTransactionError::Eip1559Disabled - | InvalidTransactionError::Eip4844Disabled - | InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported, + InvalidTransactionError::Eip2930Disabled | + InvalidTransactionError::Eip1559Disabled | + InvalidTransactionError::Eip4844Disabled | + InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported, InvalidTransactionError::GasUintOverflow => Self::GasUintOverflow, InvalidTransactionError::GasTooLow => Self::GasTooLow, InvalidTransactionError::GasTooHigh => Self::GasTooHigh, diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index fae82976dfba..b0f36a83d17d 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -362,8 +362,8 @@ impl FeeHistoryEntry { base_fee_per_gas: block.base_fee_per_gas.unwrap_or_default(), gas_used_ratio: block.gas_used as f64 / block.gas_limit as f64, base_fee_per_blob_gas: block.blob_fee(), - blob_gas_used_ratio: block.blob_gas_used() as f64 - / reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64, + blob_gas_used_ratio: block.blob_gas_used() as f64 / + reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64, excess_blob_gas: block.excess_blob_gas, blob_gas_used: block.blob_gas_used, gas_used: block.gas_used, diff --git a/crates/rpc/rpc-eth-types/src/logs_utils.rs b/crates/rpc/rpc-eth-types/src/logs_utils.rs index 3cd177a5cdbb..2bedad16bb4f 100644 --- a/crates/rpc/rpc-eth-types/src/logs_utils.rs +++ b/crates/rpc/rpc-eth-types/src/logs_utils.rs @@ -169,11 +169,11 @@ pub fn log_matches_filter( log: &reth_primitives::Log, params: &FilteredParams, ) -> bool { - if params.filter.is_some() - && (!params.filter_block_range(block.number) - || !params.filter_block_hash(block.hash) - || !params.filter_address(&log.address) - || !params.filter_topics(log.topics())) + if params.filter.is_some() && + (!params.filter_block_range(block.number) || + !params.filter_block_hash(block.hash) || + !params.filter_address(&log.address) || + !params.filter_topics(log.topics())) { return false; } diff --git a/crates/rpc/rpc-layer/src/auth_client_layer.rs b/crates/rpc/rpc-layer/src/auth_client_layer.rs index c5899b0696d0..5eda04aa0f37 100644 --- a/crates/rpc/rpc-layer/src/auth_client_layer.rs +++ b/crates/rpc/rpc-layer/src/auth_client_layer.rs @@ -66,8 +66,8 @@ pub fn secret_to_bearer_header(secret: &JwtSecret) -> HeaderValue { "Bearer {}", secret .encode(&Claims { - iat: (SystemTime::now().duration_since(UNIX_EPOCH).unwrap() - + Duration::from_secs(60)) + iat: (SystemTime::now().duration_since(UNIX_EPOCH).unwrap() + + Duration::from_secs(60)) .as_secs(), exp: None, }) diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index d4b3e5701537..919c0c36f598 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -88,8 +88,8 @@ where None } }) - .sum::() - > MAX_BLOB_GAS_PER_BLOCK + .sum::() > + MAX_BLOB_GAS_PER_BLOCK { return Err(EthApiError::InvalidParams( EthBundleError::Eip4844BlobGasExceeded.to_string(), diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index b474b8d7a247..734749e50eb1 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -474,8 +474,8 @@ where for (idx, header) in headers.iter().enumerate() { // only if filter matches - if FilteredParams::matches_address(header.logs_bloom, &address_filter) - && FilteredParams::matches_topics(header.logs_bloom, &topics_filter) + if FilteredParams::matches_address(header.logs_bloom, &address_filter) && + FilteredParams::matches_topics(header.logs_bloom, &topics_filter) { // these are consecutive headers, so we can use the parent hash of the next // block to get the current header's hash diff --git a/crates/stages/api/src/error.rs b/crates/stages/api/src/error.rs index 7f1850e021c6..2f113f2fa813 100644 --- a/crates/stages/api/src/error.rs +++ b/crates/stages/api/src/error.rs @@ -139,17 +139,17 @@ impl StageError { pub const fn is_fatal(&self) -> bool { matches!( self, - Self::Database(_) - | Self::Download(_) - | Self::DatabaseIntegrity(_) - | Self::StageCheckpoint(_) - | Self::MissingDownloadBuffer - | Self::MissingSyncGap - | Self::ChannelClosed - | Self::InconsistentBlockNumber { .. } - | Self::InconsistentTxNumber { .. } - | Self::Internal(_) - | Self::Fatal(_) + Self::Database(_) | + Self::Download(_) | + Self::DatabaseIntegrity(_) | + Self::StageCheckpoint(_) | + Self::MissingDownloadBuffer | + Self::MissingSyncGap | + Self::ChannelClosed | + Self::InconsistentBlockNumber { .. } | + Self::InconsistentTxNumber { .. } | + Self::Internal(_) | + Self::Fatal(_) ) } } diff --git a/crates/stages/api/src/pipeline/mod.rs b/crates/stages/api/src/pipeline/mod.rs index 5288ec168a7d..6fa5cb9500c0 100644 --- a/crates/stages/api/src/pipeline/mod.rs +++ b/crates/stages/api/src/pipeline/mod.rs @@ -166,9 +166,8 @@ where // Terminate the loop early if it's reached the maximum user // configured block. - if next_action.should_continue() - && self - .progress + if next_action.should_continue() && + self.progress .minimum_block_number .zip(self.max_block) .map_or(false, |(progress, target)| progress >= target) diff --git a/crates/stages/stages/src/stages/bodies.rs b/crates/stages/stages/src/stages/bodies.rs index 71f255b75c38..72ea340e9d22 100644 --- a/crates/stages/stages/src/stages/bodies.rs +++ b/crates/stages/stages/src/stages/bodies.rs @@ -303,8 +303,8 @@ impl Stage for BodyStage { } // Delete all transaction to block values. - if !block_meta.is_empty() - && tx_block_cursor.seek_exact(block_meta.last_tx_num())?.is_some() + if !block_meta.is_empty() && + tx_block_cursor.seek_exact(block_meta.last_tx_num())?.is_some() { tx_block_cursor.delete_current()?; } diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index bf3ea41438b7..fd54ce931d5f 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -160,8 +160,8 @@ impl ExecutionStage { // If we're not executing MerkleStage from scratch (by threshold or first-sync), then erase // changeset related pruning configurations - if !(max_block - start_block > self.external_clean_threshold - || provider.count_entries::()?.is_zero()) + if !(max_block - start_block > self.external_clean_threshold || + provider.count_entries::()?.is_zero()) { prune_modes.account_history = None; prune_modes.storage_history = None; @@ -206,8 +206,8 @@ where let static_file_provider = provider.static_file_provider(); // We only use static files for Receipts, if there is no receipt pruning of any kind. - let static_file_producer = if self.prune_modes.receipts.is_none() - && self.prune_modes.receipts_log_filter.is_empty() + let static_file_producer = if self.prune_modes.receipts.is_none() && + self.prune_modes.receipts_log_filter.is_empty() { let mut producer = prepare_static_file_producer(provider, start_block)?; // Since there might be a database <-> static file inconsistency (read @@ -537,8 +537,8 @@ fn execution_checkpoint( block_range: CheckpointBlockRange { from: start_block, to: max_block }, progress: EntitiesCheckpoint { processed, - total: processed - + calculate_gas_used_from_headers(provider, start_block..=max_block)?, + total: processed + + calculate_gas_used_from_headers(provider, start_block..=max_block)?, }, } } diff --git a/crates/stages/stages/src/stages/headers.rs b/crates/stages/stages/src/stages/headers.rs index db4c34511870..9bf8c944518f 100644 --- a/crates/stages/stages/src/stages/headers.rs +++ b/crates/stages/stages/src/stages/headers.rs @@ -249,11 +249,7 @@ where } Some(Err(HeadersDownloaderError::DetachedHead { local_head, header, error })) => { error!(target: "sync::stages::headers", %error, "Cannot attach header to head"); - return Poll::Ready(Err(StageError::DetachedHead { - local_head, - header, - error, - })); + return Poll::Ready(Err(StageError::DetachedHead { local_head, header, error })); } None => return Poll::Ready(Err(StageError::ChannelClosed)), } diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 7742f2b8705f..d7b044497d61 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -202,8 +202,8 @@ impl Stage for MerkleStage { } .unwrap_or(EntitiesCheckpoint { processed: 0, - total: (provider.count_entries::()? - + provider.count_entries::()?) + total: (provider.count_entries::()? + + provider.count_entries::()?) as u64, }); @@ -254,8 +254,8 @@ impl Stage for MerkleStage { })?; updates.write_to_database(provider.tx_ref())?; - let total_hashed_entries = (provider.count_entries::()? - + provider.count_entries::()?) + let total_hashed_entries = (provider.count_entries::()? + + provider.count_entries::()?) as u64; let entities_checkpoint = EntitiesCheckpoint { @@ -297,8 +297,8 @@ impl Stage for MerkleStage { let mut entities_checkpoint = input.checkpoint.entities_stage_checkpoint().unwrap_or(EntitiesCheckpoint { processed: 0, - total: (tx.entries::()? - + tx.entries::()?) as u64, + total: (tx.entries::()? + + tx.entries::()?) as u64, }); if input.unwind_to == 0 { diff --git a/crates/stages/stages/src/stages/tx_lookup.rs b/crates/stages/stages/src/stages/tx_lookup.rs index 7dd7cbeb5a44..94d4683bbe9b 100644 --- a/crates/stages/stages/src/stages/tx_lookup.rs +++ b/crates/stages/stages/src/stages/tx_lookup.rs @@ -225,8 +225,8 @@ fn stage_checkpoint( // If `TransactionHashNumbers` table was pruned, we will have a number of entries in it not // matching the actual number of processed transactions. To fix that, we add the // number of pruned `TransactionHashNumbers` entries. - processed: provider.count_entries::()? as u64 - + pruned_entries, + processed: provider.count_entries::()? as u64 + + pruned_entries, // Count only static files entries. If we count the database entries too, we may have // duplicates. We're sure that the static files have all entries that database has, // because we run the `StaticFileProducer` before starting the pipeline. diff --git a/crates/stages/stages/src/test_utils/test_db.rs b/crates/stages/stages/src/test_utils/test_db.rs index 0ecc0565616a..f149720a83ef 100644 --- a/crates/stages/stages/src/test_utils/test_db.rs +++ b/crates/stages/stages/src/test_utils/test_db.rs @@ -278,8 +278,8 @@ impl TestStageDB { // Backfill: some tests start at a forward block number, but static files // require no gaps. let segment_header = txs_writer.user_header(); - if segment_header.block_end().is_none() - && segment_header.expected_block_start() == 0 + if segment_header.block_end().is_none() && + segment_header.expected_block_start() == 0 { for block in 0..block.number { txs_writer.increment_block(StaticFileSegment::Transactions, block)?; diff --git a/crates/stages/types/src/checkpoints.rs b/crates/stages/types/src/checkpoints.rs index 6445ecbfe5bc..ee830015486e 100644 --- a/crates/stages/types/src/checkpoints.rs +++ b/crates/stages/types/src/checkpoints.rs @@ -228,14 +228,14 @@ impl StageCheckpoint { match stage_checkpoint { StageUnitCheckpoint::Account(AccountHashingCheckpoint { progress: entities, .. - }) - | StageUnitCheckpoint::Storage(StorageHashingCheckpoint { + }) | + StageUnitCheckpoint::Storage(StorageHashingCheckpoint { progress: entities, .. - }) - | StageUnitCheckpoint::Entities(entities) - | StageUnitCheckpoint::Execution(ExecutionCheckpoint { progress: entities, .. }) - | StageUnitCheckpoint::Headers(HeadersCheckpoint { progress: entities, .. }) - | StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint { + }) | + StageUnitCheckpoint::Entities(entities) | + StageUnitCheckpoint::Execution(ExecutionCheckpoint { progress: entities, .. }) | + StageUnitCheckpoint::Headers(HeadersCheckpoint { progress: entities, .. }) | + StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint { progress: entities, .. }) => Some(entities), @@ -268,10 +268,10 @@ impl StageUnitCheckpoint { /// range. pub fn set_block_range(&mut self, from: u64, to: u64) -> Option { match self { - Self::Account(AccountHashingCheckpoint { ref mut block_range, .. }) - | Self::Storage(StorageHashingCheckpoint { ref mut block_range, .. }) - | Self::Execution(ExecutionCheckpoint { ref mut block_range, .. }) - | Self::IndexHistory(IndexHistoryCheckpoint { ref mut block_range, .. }) => { + Self::Account(AccountHashingCheckpoint { ref mut block_range, .. }) | + Self::Storage(StorageHashingCheckpoint { ref mut block_range, .. }) | + Self::Execution(ExecutionCheckpoint { ref mut block_range, .. }) | + Self::IndexHistory(IndexHistoryCheckpoint { ref mut block_range, .. }) => { let old_range = *block_range; *block_range = CheckpointBlockRange { from, to }; diff --git a/crates/static-file/static-file/src/static_file_producer.rs b/crates/static-file/static-file/src/static_file_producer.rs index b592a7a879f3..e93d6013e047 100644 --- a/crates/static-file/static-file/src/static_file_producer.rs +++ b/crates/static-file/static-file/src/static_file_producer.rs @@ -85,9 +85,10 @@ impl StaticFileTargets { .iter() .all(|(target_block_range, highest_static_fileted_block)| { target_block_range.map_or(true, |target_block_range| { - *target_block_range.start() - == highest_static_fileted_block - .map_or(0, |highest_static_fileted_block| highest_static_fileted_block + 1) + *target_block_range.start() == + highest_static_fileted_block.map_or(0, |highest_static_fileted_block| { + highest_static_fileted_block + 1 + }) }) }) } @@ -207,8 +208,8 @@ impl StaticFileProducerInner { self.get_static_file_target(highest_static_files.headers, finalized_block_number) }), // StaticFile receipts only if they're not pruned according to the user configuration - receipts: if self.prune_modes.receipts.is_none() - && self.prune_modes.receipts_log_filter.is_empty() + receipts: if self.prune_modes.receipts.is_none() && + self.prune_modes.receipts_log_filter.is_empty() { finalized_block_numbers.receipts.and_then(|finalized_block_number| { self.get_static_file_target( diff --git a/crates/storage/codecs/derive/src/compact/mod.rs b/crates/storage/codecs/derive/src/compact/mod.rs index 1811d2a1b778..40b7b2f31eff 100644 --- a/crates/storage/codecs/derive/src/compact/mod.rs +++ b/crates/storage/codecs/derive/src/compact/mod.rs @@ -118,8 +118,8 @@ fn load_field(field: &syn::Field, fields: &mut FieldList, is_enum: bool) { if is_enum { fields.push(FieldTypes::EnumUnnamedField((ftype.to_string(), use_alt_impl))); } else { - let should_compact = is_flag_type(&ftype) - || field.attrs.iter().any(|attr| { + let should_compact = is_flag_type(&ftype) || + field.attrs.iter().any(|attr| { attr.path().segments.iter().any(|path| path.ident == "maybe_zero") }); diff --git a/crates/storage/db-common/src/db_tool/mod.rs b/crates/storage/db-common/src/db_tool/mod.rs index c1a3695868c2..6da09900faf6 100644 --- a/crates/storage/db-common/src/db_tool/mod.rs +++ b/crates/storage/db-common/src/db_tool/mod.rs @@ -80,8 +80,8 @@ impl DbTool { match &*bmb { Some(searcher) => { - if searcher.find_first_in(&value).is_some() - || searcher.find_first_in(&key).is_some() + if searcher.find_first_in(&value).is_some() || + searcher.find_first_in(&key).is_some() { hits += 1; return result(); diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 5efae2869254..24c2af83b853 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -412,8 +412,8 @@ fn dump_state( accounts.push((address, account)); - if (index > 0 && index % AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP == 0) - || index == accounts_len - 1 + if (index > 0 && index % AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP == 0) || + index == accounts_len - 1 { total_inserted_accounts += accounts.len(); diff --git a/crates/storage/db/src/implementation/mdbx/tx.rs b/crates/storage/db/src/implementation/mdbx/tx.rs index 462b2b279f58..8feb6c90ab27 100644 --- a/crates/storage/db/src/implementation/mdbx/tx.rs +++ b/crates/storage/db/src/implementation/mdbx/tx.rs @@ -225,9 +225,9 @@ impl MetricsHandler { /// NOTE: Backtrace is recorded using [`Backtrace::force_capture`], so `RUST_BACKTRACE` env var /// is not needed. fn log_backtrace_on_long_read_transaction(&self) { - if self.record_backtrace - && !self.backtrace_recorded.load(Ordering::Relaxed) - && self.transaction_mode().is_read_only() + if self.record_backtrace && + !self.backtrace_recorded.load(Ordering::Relaxed) && + self.transaction_mode().is_read_only() { let open_duration = self.start.elapsed(); if open_duration >= self.long_transaction_duration { diff --git a/crates/storage/libmdbx-rs/src/codec.rs b/crates/storage/libmdbx-rs/src/codec.rs index 09886acfccda..26af0999045e 100644 --- a/crates/storage/libmdbx-rs/src/codec.rs +++ b/crates/storage/libmdbx-rs/src/codec.rs @@ -41,8 +41,8 @@ impl<'tx> TableObject for Cow<'tx, [u8]> { #[cfg(not(feature = "return-borrowed"))] { - let is_dirty = (!K::IS_READ_ONLY) - && crate::error::mdbx_result(ffi::mdbx_is_dirty(_txn, data_val.iov_base))?; + let is_dirty = (!K::IS_READ_ONLY) && + crate::error::mdbx_result(ffi::mdbx_is_dirty(_txn, data_val.iov_base))?; Ok(if is_dirty { Cow::Owned(s.to_vec()) } else { Cow::Borrowed(s) }) } diff --git a/crates/storage/libmdbx-rs/src/error.rs b/crates/storage/libmdbx-rs/src/error.rs index 0cb027bc3dfc..1df5a397b2de 100644 --- a/crates/storage/libmdbx-rs/src/error.rs +++ b/crates/storage/libmdbx-rs/src/error.rs @@ -192,9 +192,9 @@ impl Error { Self::DecodeErrorLenDiff | Self::DecodeError => ffi::MDBX_EINVAL, Self::TooLarge => ffi::MDBX_TOO_LARGE, Self::BadSignature => ffi::MDBX_EBADSIGN, - Self::Access - | Self::WriteTransactionUnsupportedInReadOnlyMode - | Self::NestedTransactionsUnsupportedWithWriteMap => ffi::MDBX_EACCESS, + Self::Access | + Self::WriteTransactionUnsupportedInReadOnlyMode | + Self::NestedTransactionsUnsupportedWithWriteMap => ffi::MDBX_EACCESS, Self::ReadTransactionTimeout => -96000, // Custom non-MDBX error code Self::Other(err_code) => *err_code, } diff --git a/crates/storage/nippy-jar/src/phf/fmph.rs b/crates/storage/nippy-jar/src/phf/fmph.rs index 86a6c583076d..20e1d9ac0c96 100644 --- a/crates/storage/nippy-jar/src/phf/fmph.rs +++ b/crates/storage/nippy-jar/src/phf/fmph.rs @@ -39,9 +39,9 @@ impl PartialEq for Fmph { fn eq(&self, _other: &Self) -> bool { match (&self.function, &_other.function) { (Some(func1), Some(func2)) => { - func1.level_sizes() == func2.level_sizes() - && func1.write_bytes() == func2.write_bytes() - && { + func1.level_sizes() == func2.level_sizes() && + func1.write_bytes() == func2.write_bytes() && + { let mut f1 = Vec::with_capacity(func1.write_bytes()); func1.write(&mut f1).expect("enough capacity"); diff --git a/crates/storage/nippy-jar/src/phf/go_fmph.rs b/crates/storage/nippy-jar/src/phf/go_fmph.rs index 19ac99aa703d..8898c8be1adf 100644 --- a/crates/storage/nippy-jar/src/phf/go_fmph.rs +++ b/crates/storage/nippy-jar/src/phf/go_fmph.rs @@ -39,9 +39,9 @@ impl PartialEq for GoFmph { fn eq(&self, other: &Self) -> bool { match (&self.function, &other.function) { (Some(func1), Some(func2)) => { - func1.level_sizes() == func2.level_sizes() - && func1.write_bytes() == func2.write_bytes() - && { + func1.level_sizes() == func2.level_sizes() && + func1.write_bytes() == func2.write_bytes() && + { let mut f1 = Vec::with_capacity(func1.write_bytes()); func1.write(&mut f1).expect("enough capacity"); diff --git a/crates/storage/nippy-jar/src/writer.rs b/crates/storage/nippy-jar/src/writer.rs index c4bdbc1347f2..44b3bd5ed152 100644 --- a/crates/storage/nippy-jar/src/writer.rs +++ b/crates/storage/nippy-jar/src/writer.rs @@ -171,8 +171,8 @@ impl NippyJarWriter { OFFSET_SIZE_BYTES as usize) as u64; // expected size of the data file let actual_offsets_file_size = self.offsets_file.get_ref().metadata()?.len(); - if check_mode.should_err() - && expected_offsets_file_size.cmp(&actual_offsets_file_size) != Ordering::Equal + if check_mode.should_err() && + expected_offsets_file_size.cmp(&actual_offsets_file_size) != Ordering::Equal { return Err(NippyJarError::InconsistentState); } @@ -191,8 +191,8 @@ impl NippyJarWriter { self.jar.rows = ((actual_offsets_file_size. saturating_sub(1). // first byte is the size of one offset saturating_sub(OFFSET_SIZE_BYTES as u64) / // expected size of the data file - (self.jar.columns as u64)) - / OFFSET_SIZE_BYTES as u64) as usize; + (self.jar.columns as u64)) / + OFFSET_SIZE_BYTES as u64) as usize; // Freeze row count changed self.jar.freeze_config()?; diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index ce73c47a372f..fe979d5a5ba0 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -133,8 +133,8 @@ impl DatabaseProvider { self, mut block_number: BlockNumber, ) -> ProviderResult { - if block_number == self.best_block_number().unwrap_or_default() - && block_number == self.last_block_number().unwrap_or_default() + if block_number == self.best_block_number().unwrap_or_default() && + block_number == self.last_block_number().unwrap_or_default() { return Ok(Box::new(LatestStateProvider::new(self.tx, self.static_file_provider))); } @@ -2484,8 +2484,8 @@ impl HistoryWriter for DatabaseProvider { StorageShardedKey::last(address, storage_key), rem_index, |storage_sharded_key| { - storage_sharded_key.address == address - && storage_sharded_key.sharded_key.key == storage_key + storage_sharded_key.address == address && + storage_sharded_key.sharded_key.key == storage_key }, )?; diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 2ea31e0336a6..94ae361aff6c 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -107,8 +107,8 @@ impl<'b, TX: DbTx> HistoricalStateProviderRef<'b, TX> { /// Retrieve revert hashed state for this history provider. fn revert_state(&self) -> ProviderResult { - if !self.lowest_available_blocks.is_account_history_available(self.block_number) - || !self.lowest_available_blocks.is_storage_history_available(self.block_number) + if !self.lowest_available_blocks.is_account_history_available(self.block_number) || + !self.lowest_available_blocks.is_storage_history_available(self.block_number) { return Err(ProviderError::StateAtBlockPruned(self.block_number)); } @@ -166,9 +166,9 @@ impl<'b, TX: DbTx> HistoricalStateProviderRef<'b, TX> { // This check is worth it, the `cursor.prev()` check is rarely triggered (the if will // short-circuit) and when it passes we save a full seek into the changeset/plain state // table. - if rank == 0 - && block_number != Some(self.block_number) - && !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) + if rank == 0 && + block_number != Some(self.block_number) && + !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) { if let (Some(_), Some(block_number)) = (lowest_available_block_number, block_number) { diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 641d1341137f..0844044d427b 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -443,8 +443,8 @@ impl StaticFileProvider { } else if tx_index.get(&segment).map(|index| index.len()) == Some(1) { // Only happens if we unwind all the txs/receipts from the first static file. // Should only happen in test scenarios. - if jar.user_header().expected_block_start() == 0 - && matches!( + if jar.user_header().expected_block_start() == 0 && + matches!( segment, StaticFileSegment::Receipts | StaticFileSegment::Transactions ) @@ -663,8 +663,8 @@ impl StaticFileProvider { // If there is a gap between the entry found in static file and // database, then we have most likely lost static file data and need to unwind so we can // load it again - if !(db_first_entry <= highest_static_file_entry - || highest_static_file_entry + 1 == db_first_entry) + if !(db_first_entry <= highest_static_file_entry || + highest_static_file_entry + 1 == db_first_entry) { info!( target: "reth::providers::static_file", @@ -1516,9 +1516,9 @@ impl RequestsProvider for StaticFileProvider { impl StatsReader for StaticFileProvider { fn count_entries(&self) -> ProviderResult { match T::NAME { - tables::CanonicalHeaders::NAME - | tables::Headers::NAME - | tables::HeaderTerminalDifficulties::NAME => Ok(self + tables::CanonicalHeaders::NAME | + tables::Headers::NAME | + tables::HeaderTerminalDifficulties::NAME => Ok(self .get_highest_static_file_block(StaticFileSegment::Headers) .map(|block| block + 1) .unwrap_or_default() diff --git a/crates/transaction-pool/src/blobstore/mod.rs b/crates/transaction-pool/src/blobstore/mod.rs index 20f9b2607489..bba4b85336d6 100644 --- a/crates/transaction-pool/src/blobstore/mod.rs +++ b/crates/transaction-pool/src/blobstore/mod.rs @@ -135,8 +135,8 @@ impl BlobStoreSize { impl PartialEq for BlobStoreSize { fn eq(&self, other: &Self) -> bool { - self.data_size.load(Ordering::Relaxed) == other.data_size.load(Ordering::Relaxed) - && self.num_blobs.load(Ordering::Relaxed) == other.num_blobs.load(Ordering::Relaxed) + self.data_size.load(Ordering::Relaxed) == other.data_size.load(Ordering::Relaxed) && + self.num_blobs.load(Ordering::Relaxed) == other.num_blobs.load(Ordering::Relaxed) } } diff --git a/crates/transaction-pool/src/config.rs b/crates/transaction-pool/src/config.rs index d35bfe5bc1d4..74f8c055c7a9 100644 --- a/crates/transaction-pool/src/config.rs +++ b/crates/transaction-pool/src/config.rs @@ -44,10 +44,10 @@ impl PoolConfig { /// Returns whether or not the size and amount constraints in any sub-pools are exceeded. #[inline] pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool { - self.blob_limit.is_exceeded(pool_size.blob, pool_size.blob_size) - || self.pending_limit.is_exceeded(pool_size.pending, pool_size.pending_size) - || self.basefee_limit.is_exceeded(pool_size.basefee, pool_size.basefee_size) - || self.queued_limit.is_exceeded(pool_size.queued, pool_size.queued_size) + self.blob_limit.is_exceeded(pool_size.blob, pool_size.blob_size) || + self.pending_limit.is_exceeded(pool_size.pending, pool_size.pending_size) || + self.basefee_limit.is_exceeded(pool_size.basefee, pool_size.basefee_size) || + self.queued_limit.is_exceeded(pool_size.queued, pool_size.queued_size) } } diff --git a/crates/transaction-pool/src/error.rs b/crates/transaction-pool/src/error.rs index 2bba98481b41..c6ed4b2e0700 100644 --- a/crates/transaction-pool/src/error.rs +++ b/crates/transaction-pool/src/error.rs @@ -224,14 +224,14 @@ impl InvalidPoolTransactionError { // depend on dynamic environmental conditions and should not be assumed to have been // intentionally caused by the sender match err { - InvalidTransactionError::InsufficientFunds { .. } - | InvalidTransactionError::NonceNotConsistent => { + InvalidTransactionError::InsufficientFunds { .. } | + InvalidTransactionError::NonceNotConsistent => { // transaction could just have arrived late/early false } - InvalidTransactionError::GasTooLow - | InvalidTransactionError::GasTooHigh - | InvalidTransactionError::TipAboveFeeCap => { + InvalidTransactionError::GasTooLow | + InvalidTransactionError::GasTooHigh | + InvalidTransactionError::TipAboveFeeCap => { // these are technically not invalid false } @@ -239,17 +239,17 @@ impl InvalidPoolTransactionError { // dynamic, but not used during validation false } - InvalidTransactionError::Eip2930Disabled - | InvalidTransactionError::Eip1559Disabled - | InvalidTransactionError::Eip4844Disabled => { + InvalidTransactionError::Eip2930Disabled | + InvalidTransactionError::Eip1559Disabled | + InvalidTransactionError::Eip4844Disabled => { // settings false } - InvalidTransactionError::OldLegacyChainId - | InvalidTransactionError::ChainIdMismatch - | InvalidTransactionError::GasUintOverflow - | InvalidTransactionError::TxTypeNotSupported - | InvalidTransactionError::SignerAccountHasBytecode => true, + InvalidTransactionError::OldLegacyChainId | + InvalidTransactionError::ChainIdMismatch | + InvalidTransactionError::GasUintOverflow | + InvalidTransactionError::TxTypeNotSupported | + InvalidTransactionError::SignerAccountHasBytecode => true, } } Self::ExceedsGasLimit(_, _) => true, @@ -293,7 +293,7 @@ impl InvalidPoolTransactionError { /// Returns `true` if an import failed due to nonce gap. pub const fn is_nonce_gap(&self) -> bool { - matches!(self, Self::Consensus(InvalidTransactionError::NonceNotConsistent)) - || matches!(self, Self::Eip4844(Eip4844PoolTransactionError::Eip4844NonceGap)) + matches!(self, Self::Consensus(InvalidTransactionError::NonceNotConsistent)) || + matches!(self, Self::Eip4844(Eip4844PoolTransactionError::Eip4844NonceGap)) } } diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 3f545fab6767..3acfae135ddf 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -256,8 +256,8 @@ pub async fn maintain_transaction_pool( let old_first = old_blocks.first(); // check if the reorg is not canonical with the pool's block - if !(old_first.parent_hash == pool_info.last_seen_block_hash - || new_first.parent_hash == pool_info.last_seen_block_hash) + if !(old_first.parent_hash == pool_info.last_seen_block_hash || + new_first.parent_hash == pool_info.last_seen_block_hash) { // the new block points to a higher block than the oldest block in the old chain maintained_state = MaintainedPoolState::Drifted; diff --git a/crates/transaction-pool/src/pool/blob.rs b/crates/transaction-pool/src/pool/blob.rs index 63dd39272241..52273e7bbde4 100644 --- a/crates/transaction-pool/src/pool/blob.rs +++ b/crates/transaction-pool/src/pool/blob.rs @@ -96,10 +96,10 @@ impl BlobTransactions { let mut iter = self.by_id.iter().peekable(); while let Some((id, tx)) = iter.next() { - if tx.transaction.max_fee_per_blob_gas().unwrap_or_default() - < blob_fee_to_satisfy - || tx.transaction.max_fee_per_gas() - < best_transactions_attributes.basefee as u128 + if tx.transaction.max_fee_per_blob_gas().unwrap_or_default() < + blob_fee_to_satisfy || + tx.transaction.max_fee_per_gas() < + best_transactions_attributes.basefee as u128 { // does not satisfy the blob fee or base fee // still parked in blob pool -> skip descendant transactions @@ -150,8 +150,8 @@ impl BlobTransactions { let mut iter = self.by_id.iter().peekable(); while let Some((id, tx)) = iter.next() { - if tx.transaction.max_fee_per_blob_gas() < Some(pending_fees.blob_fee) - || tx.transaction.max_fee_per_gas() < pending_fees.base_fee as u128 + if tx.transaction.max_fee_per_blob_gas() < Some(pending_fees.blob_fee) || + tx.transaction.max_fee_per_gas() < pending_fees.base_fee as u128 { // still parked in blob pool -> skip descendant transactions 'this: while let Some((peek, _)) = iter.peek() { diff --git a/crates/transaction-pool/src/pool/pending.rs b/crates/transaction-pool/src/pool/pending.rs index 2271c5e910b6..5cc4d4ff1ba6 100644 --- a/crates/transaction-pool/src/pool/pending.rs +++ b/crates/transaction-pool/src/pool/pending.rs @@ -405,8 +405,8 @@ impl PendingPool { // loop through the highest nonces set, removing transactions until we reach the limit for tx in &self.highest_nonces { // return early if the pool is under limits - if !limit.is_exceeded(original_length - total_removed, original_size - total_size) - || non_local_senders == 0 + if !limit.is_exceeded(original_length - total_removed, original_size - total_size) || + non_local_senders == 0 { // need to remove remaining transactions before exiting for id in &removed { diff --git a/crates/transaction-pool/src/pool/state.rs b/crates/transaction-pool/src/pool/state.rs index b506d8d3da84..f929245b2556 100644 --- a/crates/transaction-pool/src/pool/state.rs +++ b/crates/transaction-pool/src/pool/state.rs @@ -165,10 +165,10 @@ mod tests { let state = TxState::default(); assert_eq!(SubPool::Queued, state.into()); - let state = TxState::NO_PARKED_ANCESTORS - | TxState::NO_NONCE_GAPS - | TxState::NOT_TOO_MUCH_GAS - | TxState::ENOUGH_FEE_CAP_BLOCK; + let state = TxState::NO_PARKED_ANCESTORS | + TxState::NO_NONCE_GAPS | + TxState::NOT_TOO_MUCH_GAS | + TxState::ENOUGH_FEE_CAP_BLOCK; assert_eq!(SubPool::Queued, state.into()); } diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 7ee3d0ce4040..67be88d868f8 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -1372,9 +1372,7 @@ impl AllTransactions { cumulative_cost += tx.transaction.cost(); if tx.transaction.is_eip4844() && cumulative_cost > on_chain_balance { // the transaction would shift - return Err(InsertErr::Overdraft { - transaction: Arc::new(new_blob_tx), - }); + return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) }); } } } @@ -1397,8 +1395,8 @@ impl AllTransactions { ) -> bool { let price_bump = price_bumps.price_bump(existing_transaction.tx_type()); - if maybe_replacement.max_fee_per_gas() - <= existing_transaction.max_fee_per_gas() * (100 + price_bump) / 100 + if maybe_replacement.max_fee_per_gas() <= + existing_transaction.max_fee_per_gas() * (100 + price_bump) / 100 { return true; } @@ -1408,10 +1406,10 @@ impl AllTransactions { let replacement_max_priority_fee_per_gas = maybe_replacement.transaction.max_priority_fee_per_gas().unwrap_or(0); - if replacement_max_priority_fee_per_gas - <= existing_max_priority_fee_per_gas * (100 + price_bump) / 100 - && existing_max_priority_fee_per_gas != 0 - && replacement_max_priority_fee_per_gas != 0 + if replacement_max_priority_fee_per_gas <= + existing_max_priority_fee_per_gas * (100 + price_bump) / 100 && + existing_max_priority_fee_per_gas != 0 && + replacement_max_priority_fee_per_gas != 0 { return true; } @@ -1423,8 +1421,8 @@ impl AllTransactions { // this enforces that blob txs can only be replaced by blob txs let replacement_max_blob_fee_per_gas = maybe_replacement.transaction.max_fee_per_blob_gas().unwrap_or(0); - if replacement_max_blob_fee_per_gas - <= existing_max_blob_fee_per_gas * (100 + price_bump) / 100 + if replacement_max_blob_fee_per_gas <= + existing_max_blob_fee_per_gas * (100 + price_bump) / 100 { return true; } diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 8d167add766d..2e12c89bc035 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -44,10 +44,10 @@ macro_rules! set_value { ($this:ident => $field:ident) => { let new_value = $field; match $this { - MockTransaction::Legacy { ref mut $field, .. } - | MockTransaction::Eip1559 { ref mut $field, .. } - | MockTransaction::Eip4844 { ref mut $field, .. } - | MockTransaction::Eip2930 { ref mut $field, .. } => { + MockTransaction::Legacy { ref mut $field, .. } | + MockTransaction::Eip1559 { ref mut $field, .. } | + MockTransaction::Eip4844 { ref mut $field, .. } | + MockTransaction::Eip2930 { ref mut $field, .. } => { *$field = new_value; } } @@ -58,10 +58,10 @@ macro_rules! set_value { macro_rules! get_value { ($this:tt => $field:ident) => { match $this { - MockTransaction::Legacy { $field, .. } - | MockTransaction::Eip1559 { $field, .. } - | MockTransaction::Eip4844 { $field, .. } - | MockTransaction::Eip2930 { $field, .. } => $field.clone(), + MockTransaction::Legacy { $field, .. } | + MockTransaction::Eip1559 { $field, .. } | + MockTransaction::Eip4844 { $field, .. } | + MockTransaction::Eip2930 { $field, .. } => $field.clone(), } }; } @@ -333,8 +333,8 @@ impl MockTransaction { /// Sets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844) pub fn set_priority_fee(&mut self, val: u128) -> &mut Self { - if let Self::Eip1559 { max_priority_fee_per_gas, .. } - | Self::Eip4844 { max_priority_fee_per_gas, .. } = self + if let Self::Eip1559 { max_priority_fee_per_gas, .. } | + Self::Eip4844 { max_priority_fee_per_gas, .. } = self { *max_priority_fee_per_gas = val; } @@ -350,8 +350,8 @@ impl MockTransaction { /// Gets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844) pub const fn get_priority_fee(&self) -> Option { match self { - Self::Eip1559 { max_priority_fee_per_gas, .. } - | Self::Eip4844 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas), + Self::Eip1559 { max_priority_fee_per_gas, .. } | + Self::Eip4844 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas), _ => None, } } @@ -385,9 +385,9 @@ impl MockTransaction { pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self { match self { Self::Legacy { .. } => {} - Self::Eip1559 { access_list: accesslist, .. } - | Self::Eip4844 { access_list: accesslist, .. } - | Self::Eip2930 { access_list: accesslist, .. } => { + Self::Eip1559 { access_list: accesslist, .. } | + Self::Eip4844 { access_list: accesslist, .. } | + Self::Eip2930 { access_list: accesslist, .. } => { *accesslist = list; } } @@ -400,8 +400,8 @@ impl MockTransaction { Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => { *gas_price = val; } - Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } - | Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } => { + Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } | + Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } => { *max_fee_per_gas = val; *max_priority_fee_per_gas = val; } @@ -415,8 +415,8 @@ impl MockTransaction { Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => { *gas_price = val; } - Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } - | Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => { + Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } | + Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => { *max_fee_per_gas = val; *max_priority_fee_per_gas = val; } @@ -560,39 +560,39 @@ impl MockTransaction { impl PoolTransaction for MockTransaction { fn hash(&self) -> &TxHash { match self { - Self::Legacy { hash, .. } - | Self::Eip1559 { hash, .. } - | Self::Eip4844 { hash, .. } - | Self::Eip2930 { hash, .. } => hash, + Self::Legacy { hash, .. } | + Self::Eip1559 { hash, .. } | + Self::Eip4844 { hash, .. } | + Self::Eip2930 { hash, .. } => hash, } } fn sender(&self) -> Address { match self { - Self::Legacy { sender, .. } - | Self::Eip1559 { sender, .. } - | Self::Eip4844 { sender, .. } - | Self::Eip2930 { sender, .. } => *sender, + Self::Legacy { sender, .. } | + Self::Eip1559 { sender, .. } | + Self::Eip4844 { sender, .. } | + Self::Eip2930 { sender, .. } => *sender, } } fn nonce(&self) -> u64 { match self { - Self::Legacy { nonce, .. } - | Self::Eip1559 { nonce, .. } - | Self::Eip4844 { nonce, .. } - | Self::Eip2930 { nonce, .. } => *nonce, + Self::Legacy { nonce, .. } | + Self::Eip1559 { nonce, .. } | + Self::Eip4844 { nonce, .. } | + Self::Eip2930 { nonce, .. } => *nonce, } } fn cost(&self) -> U256 { match self { - Self::Legacy { gas_price, value, gas_limit, .. } - | Self::Eip2930 { gas_limit, gas_price, value, .. } => { + Self::Legacy { gas_price, value, gas_limit, .. } | + Self::Eip2930 { gas_limit, gas_price, value, .. } => { U256::from(*gas_limit) * U256::from(*gas_price) + *value } - Self::Eip1559 { max_fee_per_gas, value, gas_limit, .. } - | Self::Eip4844 { max_fee_per_gas, value, gas_limit, .. } => { + Self::Eip1559 { max_fee_per_gas, value, gas_limit, .. } | + Self::Eip4844 { max_fee_per_gas, value, gas_limit, .. } => { U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value } } @@ -614,17 +614,17 @@ impl PoolTransaction for MockTransaction { fn access_list(&self) -> Option<&AccessList> { match self { Self::Legacy { .. } => None, - Self::Eip1559 { access_list: accesslist, .. } - | Self::Eip4844 { access_list: accesslist, .. } - | Self::Eip2930 { access_list: accesslist, .. } => Some(accesslist), + Self::Eip1559 { access_list: accesslist, .. } | + Self::Eip4844 { access_list: accesslist, .. } | + Self::Eip2930 { access_list: accesslist, .. } => Some(accesslist), } } fn max_priority_fee_per_gas(&self) -> Option { match self { Self::Legacy { .. } | Self::Eip2930 { .. } => None, - Self::Eip1559 { max_priority_fee_per_gas, .. } - | Self::Eip4844 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas), + Self::Eip1559 { max_priority_fee_per_gas, .. } | + Self::Eip4844 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas), } } @@ -665,8 +665,8 @@ impl PoolTransaction for MockTransaction { fn priority_fee_or_price(&self) -> u128 { match self { Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price, - Self::Eip1559 { max_priority_fee_per_gas, .. } - | Self::Eip4844 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas, + Self::Eip1559 { max_priority_fee_per_gas, .. } | + Self::Eip4844 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas, } } @@ -682,19 +682,19 @@ impl PoolTransaction for MockTransaction { fn input(&self) -> &[u8] { match self { Self::Legacy { .. } => &[], - Self::Eip1559 { input, .. } - | Self::Eip4844 { input, .. } - | Self::Eip2930 { input, .. } => input, + Self::Eip1559 { input, .. } | + Self::Eip4844 { input, .. } | + Self::Eip2930 { input, .. } => input, } } /// Returns the size of the transaction. fn size(&self) -> usize { match self { - Self::Legacy { size, .. } - | Self::Eip1559 { size, .. } - | Self::Eip4844 { size, .. } - | Self::Eip2930 { size, .. } => *size, + Self::Legacy { size, .. } | + Self::Eip1559 { size, .. } | + Self::Eip4844 { size, .. } | + Self::Eip2930 { size, .. } => *size, } } @@ -717,9 +717,9 @@ impl PoolTransaction for MockTransaction { fn chain_id(&self) -> Option { match self { Self::Legacy { chain_id, .. } => *chain_id, - Self::Eip1559 { chain_id, .. } - | Self::Eip4844 { chain_id, .. } - | Self::Eip2930 { chain_id, .. } => Some(*chain_id), + Self::Eip1559 { chain_id, .. } | + Self::Eip4844 { chain_id, .. } | + Self::Eip2930 { chain_id, .. } => Some(*chain_id), } } } diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 3336a902eb80..3690513d9c1e 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -235,9 +235,9 @@ where // Drop non-local transactions with a fee lower than the configured fee for acceptance into // the pool. - if !self.local_transactions_config.is_local(origin, transaction.sender()) - && transaction.is_eip1559() - && transaction.max_priority_fee_per_gas() < self.minimum_priority_fee + if !self.local_transactions_config.is_local(origin, transaction.sender()) && + transaction.is_eip1559() && + transaction.max_priority_fee_per_gas() < self.minimum_priority_fee { return TransactionValidationOutcome::Invalid( transaction, diff --git a/examples/db-access/src/main.rs b/examples/db-access/src/main.rs index 74a233880b3b..27047fd3f8ec 100644 --- a/examples/db-access/src/main.rs +++ b/examples/db-access/src/main.rs @@ -200,13 +200,13 @@ fn receipts_provider_example for ChainSpec { spec_builder.tangerine_whistle_activated() } ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(), - ForkSpec::Byzantium - | ForkSpec::EIP158ToByzantiumAt5 - | ForkSpec::ConstantinopleFix - | ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(), + ForkSpec::Byzantium | + ForkSpec::EIP158ToByzantiumAt5 | + ForkSpec::ConstantinopleFix | + ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(), ForkSpec::Istanbul => spec_builder.istanbul_activated(), ForkSpec::Berlin => spec_builder.berlin_activated(), ForkSpec::London | ForkSpec::BerlinToLondonAt5 => spec_builder.london_activated(), - ForkSpec::Merge - | ForkSpec::MergeEOF - | ForkSpec::MergeMeterInitCode - | ForkSpec::MergePush0 => spec_builder.paris_activated(), + ForkSpec::Merge | + ForkSpec::MergeEOF | + ForkSpec::MergeMeterInitCode | + ForkSpec::MergePush0 => spec_builder.paris_activated(), ForkSpec::Shanghai => spec_builder.shanghai_activated(), ForkSpec::Cancun => spec_builder.cancun_activated(), ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => { diff --git a/wvm-apps/wvm-exexed/Cargo.toml b/wvm-apps/wvm-exexed/Cargo.toml index ea2392fa80b2..9f6debdc412b 100644 --- a/wvm-apps/wvm-exexed/Cargo.toml +++ b/wvm-apps/wvm-exexed/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "wvm-exexed" -version = "0.1.0" -rust-version = "1.76" -edition = "2021" +version = "1.0.0" +rust-version.workspace = true +edition.workspace = true [lints] workspace = true @@ -18,13 +18,12 @@ futures.workspace = true alloy-primitives.workspace = true tokio.workspace = true -## bigquery dependencies +# bigquery dependencies gcp-bigquery-client = "0.17.0" indexmap = "2.0.0" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -### repository = { path = "crates/repository" } bigquery = { path = "crates/bigquery" } lambda = { path = "crates/lambda" } @@ -35,9 +34,6 @@ types = { path = "crates/types" } reth-exex-test-utils.workspace = true reth-testing-utils.workspace = true -#[workspace] -#members = ["crates/*"] - [[bin]] name = "reth" path = "crates/reth-exexed/src/main.rs" diff --git a/wvm-apps/wvm-exexed/crates/bigquery/src/client.rs b/wvm-apps/wvm-exexed/crates/bigquery/src/client.rs index 7ecbeb3adbf0..1840291ea759 100644 --- a/wvm-apps/wvm-exexed/crates/bigquery/src/client.rs +++ b/wvm-apps/wvm-exexed/crates/bigquery/src/client.rs @@ -1,5 +1,5 @@ use indexmap::IndexMap; -use std::{any::Any, collections::HashMap}; +use std::{collections::HashMap}; use gcp_bigquery_client::{ error::BQError, diff --git a/wvm-apps/wvm-exexed/crates/lambda/src/lambda.rs b/wvm-apps/wvm-exexed/crates/lambda/src/lambda.rs index f1f7d085918b..9a0067b3cc71 100644 --- a/wvm-apps/wvm-exexed/crates/lambda/src/lambda.rs +++ b/wvm-apps/wvm-exexed/crates/lambda/src/lambda.rs @@ -1,12 +1,9 @@ -use reqwest::Client; -use reth::api::FullNodeComponents; -use reth::primitives::{address, Address, TransactionSigned}; +use reth::{ + api::FullNodeComponents, + primitives::{Address, TransactionSigned}, +}; use reth_exex::ExExContext; -use reth_node_ethereum::EthereumNode; -use reth_tracing::tracing::info; -use serde_json; -use serde_json::json; -use std::collections::HashMap; +use serde_json::{self, json}; pub const SEQ_ADDRESS: &str = "0x197f818c1313DC58b32D88078ecdfB40EA822614"; pub const LAMBDA_ENDPOINT: &str = "https://wvm-lambda-0755acbdae90.herokuapp.com"; diff --git a/wvm-apps/wvm-exexed/crates/reth-exexed/Cargo.toml b/wvm-apps/wvm-exexed/crates/reth-exexed/Cargo.toml index 97a7a0b462b3..04d3782d768f 100644 --- a/wvm-apps/wvm-exexed/crates/reth-exexed/Cargo.toml +++ b/wvm-apps/wvm-exexed/crates/reth-exexed/Cargo.toml @@ -9,14 +9,12 @@ rust-version.workspace = true [dependencies] reth.workspace = true reth-exex.workspace = true -reth-node-api.workspace = true reth-node-ethereum.workspace = true reth-tracing.workspace = true eyre.workspace = true -futures.workspace = true -tokio.workspace = true + + serde_json.workspace = true -serde.workspace = true repository = { path = "../repository" } bigquery = { path = "../bigquery" } diff --git a/wvm-apps/wvm-exexed/crates/reth-exexed/src/main.rs b/wvm-apps/wvm-exexed/crates/reth-exexed/src/main.rs index 5590550883a1..7ed0482ef2f3 100644 --- a/wvm-apps/wvm-exexed/crates/reth-exexed/src/main.rs +++ b/wvm-apps/wvm-exexed/crates/reth-exexed/src/main.rs @@ -1,3 +1,9 @@ +//! WVM node main + +#![doc( + issue_tracker_base_url = "https://github.com/weaveVM/wvm-reth/issues/" +)] + use bigquery::client::BigQueryConfig; use lambda::lambda::exex_lambda_processor; use repository::state_repository; @@ -6,14 +12,12 @@ use reth_exex::{ExExContext, ExExEvent, ExExNotification}; use reth_node_ethereum::EthereumNode; use reth_tracing::tracing::info; use serde_json; -use std::path::Path; -use tokio; use types::types::ExecutionTipState; async fn exex_etl_processor( mut ctx: ExExContext, - state_repository: repository::state_repository::StateRepository, - state_processor: exex_etl::state_processor::StateProcessor, + state_repository: state_repository::StateRepository, + _state_processor: exex_etl::state_processor::StateProcessor, ) -> eyre::Result<()> { while let Some(notification) = ctx.notifications.recv().await { match ¬ification { @@ -45,6 +49,7 @@ async fn exex_etl_processor( Ok(()) } +/// Main loop of the WVM node fn main() -> eyre::Result<()> { reth::cli::Cli::parse_args().run(|builder, _| async move { let handle = builder diff --git a/wvm-apps/wvm-exexed/crates/types/src/types.rs b/wvm-apps/wvm-exexed/crates/types/src/types.rs index d4b26f24636e..ad539d7d0eeb 100644 --- a/wvm-apps/wvm-exexed/crates/types/src/types.rs +++ b/wvm-apps/wvm-exexed/crates/types/src/types.rs @@ -1,5 +1,5 @@ use alloy_primitives; -use reth::{primitives::SealedBlockWithSenders, providers::ExecutionOutcome}; +use reth::{primitives::SealedBlockWithSenders}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)]

`jllY!)NNLQZA-<0Ir`%x^GVUn;ZSUc09B;IkCmm*rtkd{0WE!W$YQYCc$OJ}A0wYcB2pa)@A|R8djCk1Yef zn@|km<>ysb%5XKr$k} zd)dL@{piFmz2@nr49o21$*y(kwv3bJufm+0PD_b3X)$Y8shXvSn5K-e>8bo*nMmgF zlGxh3$mUZAr34Bu5V9w~88$-`J0)*JzSpqT%slV2V`!$0!J*?MVBI&mJ5`SJS{Umo zTdh*}igRCFN(v6q7Jk`Bg{ZTKKh6i; z$p)GaPg?tBKhzEO2{ofvUvbYAv^_UwpFek}@*pujDJN^4HR}_uiFlCsLTQV&rbcnq zL6U!J`Yje4_!rSV91A7_4^ic4x4W$!;GX<`@G%FWo0nBJ&!3NWEYdMdOP{lLADbPn zVC*HiF9lCFm)|!||Gk;0a`O5dCp*t(G&5_NpTX z#{HGYZW^pf<*qIo-F5hgVW4z98HZR#{0yYODE|s}Ib13tHQ4*EAzsB49DDmsIQm&y z+1B$g@xv!_EfX5(?|J8VF&RzYdZo3{Z-*bR?>`+H&%Eq;Z-A$x-T6JEg6!q}*yqA? zWCi|C1Q4ww%a0<=25zG3`Egj2;5)s-4eEhvi+F)c%G>QzF)Eby~u$b>Bex|tF}iATZ*iv+!$6JYQ2o5yKOJN_Kh(f zgs8n}Svn0`r){FgQi|xb`{RjtM|+>!4u3=4dr45K!<`E(dZokE#mkl$>+0AlO??oB zc&g;HvWgoK#fa&}wOh%rm~}n;Nx!Oc^#{B>>)9z?n8Ik+lAXq(W~|GTT! z1R`3ZUGtkHmvmZUK|GXVqkoQIq(1TuOP3G)_|?k_j7~@!djjt#C3CgWUK7*hY!Yjq zt0i^q2NoJ?8KJX@G5d^$eJ|U@uQnLd7(2i7WoMkrqu;5+n|cC!LOtK2H%PP#r6mh{ zUeI|f#e9gU;;4Th{G~!w#`I~)gLRfaUkhvpRK4wT|hbf=K*asSC15Ey%6_`8lo=%Dx9zOHRtj;LyE9Ky@CRHu5?VFeyJL zBY41lpF4+7Bw9Z51D+_PFyk^-_Mhj@`Uc!C57deeF5&YiX1S?{d5Yu5Raa6aVZgtPz8 zv-f}R-*${FAqH1%LR8)vN!5O8Fh5pEsgd}f(Wv%qMk9dSl$u7@98;^(ON#a^7a5mxOdbe%}n_SBff4;|EvCLKylVH)}NUT1NEcC6O_~5?>Gp zEvTr5IEWGYf>8JWeO`JI7fJIw&+KhN65~%0DAo87k{H$=dLM5hQIdVPaL1>aTa#Xf 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