diff --git a/Cargo.lock b/Cargo.lock index 1a9674584e14..1c6d731bcf52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7765,6 +7765,7 @@ dependencies = [ "reth-consensus", "reth-db", "reth-e2e-test-utils", + "reth-engine-tree", "reth-ethereum-engine", "reth-ethereum-engine-primitives", "reth-ethereum-payload-builder", diff --git a/crates/engine/tree/src/tree/config.rs b/crates/engine/tree/src/tree/config.rs new file mode 100644 index 000000000000..2d5a3b9020f3 --- /dev/null +++ b/crates/engine/tree/src/tree/config.rs @@ -0,0 +1,74 @@ +//! Engine tree configuration. + +const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 256; +const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = 256; +const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256; + +/// The configuration of the engine tree. +#[derive(Debug)] +pub struct TreeConfig { + /// Maximum number of blocks to be kept only in memory without triggering persistence. + persistence_threshold: u64, + /// Number of pending blocks that cannot be executed due to missing parent and + /// are kept in cache. + block_buffer_limit: u32, + /// Number of invalid headers to keep in cache. + max_invalid_header_cache_length: u32, +} + +impl Default for TreeConfig { + fn default() -> Self { + Self { + persistence_threshold: DEFAULT_PERSISTENCE_THRESHOLD, + block_buffer_limit: DEFAULT_BLOCK_BUFFER_LIMIT, + max_invalid_header_cache_length: DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH, + } + } +} + +impl TreeConfig { + /// Create engine tree configuration. + pub const fn new( + persistence_threshold: u64, + block_buffer_limit: u32, + max_invalid_header_cache_length: u32, + ) -> Self { + Self { persistence_threshold, block_buffer_limit, max_invalid_header_cache_length } + } + + /// Return the persistence threshold. + pub const fn persistence_threshold(&self) -> u64 { + self.persistence_threshold + } + + /// Return the block buffer limit. + pub const fn block_buffer_limit(&self) -> u32 { + self.block_buffer_limit + } + + /// Return the maximum invalid cache header length. + pub const fn max_invalid_header_cache_length(&self) -> u32 { + self.max_invalid_header_cache_length + } + + /// Setter for persistence threshold. + pub const fn with_persistence_threshold(mut self, persistence_threshold: u64) -> Self { + self.persistence_threshold = persistence_threshold; + self + } + + /// Setter for block buffer limit. + pub const fn with_block_buffer_limit(mut self, block_buffer_limit: u32) -> Self { + self.block_buffer_limit = block_buffer_limit; + self + } + + /// Setter for maximum invalid header cache length. + pub const fn with_max_invalid_header_cache_length( + mut self, + max_invalid_header_cache_length: u32, + ) -> Self { + self.max_invalid_header_cache_length = max_invalid_header_cache_length; + self + } +} diff --git a/crates/engine/tree/src/tree.rs b/crates/engine/tree/src/tree/mod.rs similarity index 98% rename from crates/engine/tree/src/tree.rs rename to crates/engine/tree/src/tree/mod.rs index 6f0856e29d97..98cf2590f779 100644 --- a/crates/engine/tree/src/tree.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -49,13 +49,8 @@ use tokio::sync::{ }; use tracing::*; -/// Maximum number of blocks to be kept only in memory without triggering persistence. -const PERSISTENCE_THRESHOLD: u64 = 256; -/// Number of pending blocks that cannot be executed due to missing parent and -/// are kept in cache. -const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = 256; -/// Number of invalid headers to keep in cache. -const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256; +mod config; +pub use config::TreeConfig; /// Keeps track of the state of the tree. /// @@ -387,6 +382,8 @@ pub struct EngineApiTreeHandlerImpl { /// Handle to the payload builder that will receive payload attributes for valid forkchoice /// updates payload_builder: PayloadBuilderHandle, + /// Configuration settings. + config: TreeConfig, } impl EngineApiTreeHandlerImpl @@ -407,6 +404,7 @@ where canonical_in_memory_state: CanonicalInMemoryState, persistence: PersistenceHandle, payload_builder: PayloadBuilderHandle, + config: TreeConfig, ) -> Self { Self { provider, @@ -421,6 +419,7 @@ where state, canonical_in_memory_state, payload_builder, + config, } } @@ -437,14 +436,15 @@ where persistence: PersistenceHandle, payload_builder: PayloadBuilderHandle, canonical_in_memory_state: CanonicalInMemoryState, + config: TreeConfig, ) -> UnboundedReceiver { let best_block_number = provider.best_block_number().unwrap_or(0); let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default(); let (tx, outgoing) = tokio::sync::mpsc::unbounded_channel(); let state = EngineApiTreeState::new( - DEFAULT_BLOCK_BUFFER_LIMIT, - DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH, + config.block_buffer_limit(), + config.max_invalid_header_cache_length(), header.num_hash(), ); @@ -459,6 +459,7 @@ where canonical_in_memory_state, persistence, payload_builder, + config, ); std::thread::Builder::new().name("Tree Task".to_string()).spawn(|| task.run()).unwrap(); outgoing @@ -657,12 +658,12 @@ where fn should_persist(&self) -> bool { self.state.tree_state.max_block_number() - self.persistence_state.last_persisted_block_number >= - PERSISTENCE_THRESHOLD + self.config.persistence_threshold() } fn get_blocks_to_persist(&self) -> Vec { let start = self.persistence_state.last_persisted_block_number; - let end = start + PERSISTENCE_THRESHOLD; + let end = start + self.config.persistence_threshold(); // NOTE: this is an exclusive range, to try to include exactly PERSISTENCE_THRESHOLD blocks self.state @@ -1607,6 +1608,7 @@ mod tests { canonical_in_memory_state, persistence_handle, payload_builder, + TreeConfig::default(), ); Self { tree, to_tree_tx, blocks: vec![], action_rx, payload_command_rx } @@ -1675,6 +1677,7 @@ mod tests { canonical_in_memory_state, persistence_handle, payload_builder, + TreeConfig::default(), ); let last_executed_block = blocks.last().unwrap().clone(); let pending = Some(BlockState::new(last_executed_block)); @@ -1688,8 +1691,10 @@ mod tests { async fn test_tree_persist_blocks() { // we need more than PERSISTENCE_THRESHOLD blocks to trigger the // persistence task. + let tree_config = TreeConfig::default(); + let TestHarness { tree, to_tree_tx, action_rx, mut blocks, payload_command_rx } = - get_default_test_harness(PERSISTENCE_THRESHOLD + 1); + get_default_test_harness(tree_config.persistence_threshold() + 1); std::thread::Builder::new().name("Tree Task".to_string()).spawn(|| tree.run()).unwrap(); // send a message to the tree to enter the main loop. @@ -1699,7 +1704,7 @@ mod tests { if let PersistenceAction::SaveBlocks((saved_blocks, _)) = received_action { // only PERSISTENCE_THRESHOLD will be persisted blocks.pop(); - assert_eq!(saved_blocks.len() as u64, PERSISTENCE_THRESHOLD); + assert_eq!(saved_blocks.len() as u64, tree_config.persistence_threshold()); assert_eq!(saved_blocks, blocks); } else { panic!("unexpected action received {received_action:?}"); @@ -1731,8 +1736,10 @@ mod tests { #[tokio::test] async fn test_engine_request_during_backfill() { + let tree_config = TreeConfig::default(); + let TestHarness { mut tree, to_tree_tx, action_rx, blocks, payload_command_rx } = - get_default_test_harness(PERSISTENCE_THRESHOLD); + get_default_test_harness(tree_config.persistence_threshold()); // set backfill active tree.backfill_sync_state = BackfillSyncState::Active; @@ -1754,7 +1761,7 @@ mod tests { #[tokio::test] async fn test_holesky_payload() { - let s = include_str!("../test-data/holesky/1.rlp"); + let s = include_str!("../../test-data/holesky/1.rlp"); let data = Bytes::from_str(s).unwrap(); let block = Block::decode(&mut data.as_ref()).unwrap(); let sealed = block.seal_slow(); diff --git a/crates/ethereum/engine/src/service.rs b/crates/ethereum/engine/src/service.rs index b4f63aa7254a..a258c26c9558 100644 --- a/crates/ethereum/engine/src/service.rs +++ b/crates/ethereum/engine/src/service.rs @@ -8,7 +8,7 @@ use reth_engine_tree::{ download::BasicBlockDownloader, engine::{EngineApiRequestHandler, EngineHandler}, persistence::PersistenceHandle, - tree::EngineApiTreeHandlerImpl, + tree::{EngineApiTreeHandlerImpl, TreeConfig}, }; pub use reth_engine_tree::{ chain::{ChainEvent, ChainOrchestrator}, @@ -68,6 +68,7 @@ where blockchain_db: BlockchainProvider2, pruner: Pruner>, payload_builder: PayloadBuilderHandle, + tree_config: TreeConfig, ) -> Self { let consensus = Arc::new(EthBeaconConsensus::new(chain_spec.clone())); let downloader = BasicBlockDownloader::new(client, consensus.clone()); @@ -89,6 +90,7 @@ where persistence_handle, payload_builder, canonical_in_memory_state, + tree_config, ); let engine_handler = EngineApiRequestHandler::new(to_tree_tx, from_tree); @@ -174,6 +176,7 @@ mod tests { blockchain_db, pruner, PayloadBuilderHandle::new(tx), + TreeConfig::default(), ); } } diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index f22490859a95..7b2cef39e538 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -36,6 +36,7 @@ reth-node-events.workspace = true reth-node-core.workspace = true reth-exex.workspace = true reth-blockchain-tree.workspace = true +reth-engine-tree.workspace = true # misc eyre.workspace = true diff --git a/crates/ethereum/node/src/launch.rs b/crates/ethereum/node/src/launch.rs index 898b376025fb..2ecc57ac08b2 100644 --- a/crates/ethereum/node/src/launch.rs +++ b/crates/ethereum/node/src/launch.rs @@ -6,6 +6,7 @@ use reth_beacon_consensus::{ BeaconConsensusEngineHandle, }; use reth_blockchain_tree::BlockchainTreeConfig; +use reth_engine_tree::tree::TreeConfig; use reth_ethereum_engine::service::{ChainEvent, EthService}; use reth_ethereum_engine_primitives::EthEngineTypes; use reth_exex::ExExManagerHandle; @@ -171,6 +172,8 @@ where let pruner_events = pruner.events(); info!(target: "reth::cli", prune_config=?ctx.prune_config().unwrap_or_default(), "Pruner initialized"); + let tree_config = TreeConfig::default().with_persistence_threshold(120); + // Configure the consensus engine let mut eth_service = EthService::new( ctx.chain_spec(), @@ -182,6 +185,7 @@ where ctx.blockchain_db().clone(), pruner, ctx.components().payload_builder().clone(), + tree_config, ); let event_sender = EventSender::default();