perf: warm transactions in parallel (#13759)

This commit is contained in:
Dan Cline
2025-02-04 11:25:51 -05:00
committed by GitHub
parent 6fecdac4ea
commit b6ce1d90fd
14 changed files with 263 additions and 92 deletions

1
Cargo.lock generated
View File

@ -7227,6 +7227,7 @@ dependencies = [
"reth-ethereum-engine-primitives", "reth-ethereum-engine-primitives",
"reth-ethereum-primitives", "reth-ethereum-primitives",
"reth-evm", "reth-evm",
"reth-evm-ethereum",
"reth-exex-types", "reth-exex-types",
"reth-metrics", "reth-metrics",
"reth-network-p2p", "reth-network-p2p",

View File

@ -9,9 +9,7 @@ use alloy_rlp::Decodable;
use alloy_rpc_types::engine::{BlobsBundleV1, PayloadAttributes}; use alloy_rpc_types::engine::{BlobsBundleV1, PayloadAttributes};
use clap::Parser; use clap::Parser;
use eyre::Context; use eyre::Context;
use reth_basic_payload_builder::{ use reth_basic_payload_builder::{BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig};
BuildArguments, BuildOutcome, Cancelled, PayloadBuilder, PayloadConfig,
};
use reth_chainspec::ChainSpec; use reth_chainspec::ChainSpec;
use reth_cli::chainspec::ChainSpecParser; use reth_cli::chainspec::ChainSpecParser;
use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs}; use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
@ -34,7 +32,10 @@ use reth_provider::{
BlockHashReader, BlockReader, BlockWriter, ChainSpecProvider, ProviderFactory, BlockHashReader, BlockReader, BlockWriter, ChainSpecProvider, ProviderFactory,
StageCheckpointReader, StateProviderFactory, StageCheckpointReader, StateProviderFactory,
}; };
use reth_revm::{cached::CachedReads, database::StateProviderDatabase, primitives::KzgSettings}; use reth_revm::{
cached::CachedReads, cancelled::Cancelled, database::StateProviderDatabase,
primitives::KzgSettings,
};
use reth_stages::StageId; use reth_stages::StageId;
use reth_transaction_pool::{ use reth_transaction_pool::{
blobstore::InMemoryBlobStore, BlobStore, EthPooledTransaction, PoolConfig, TransactionOrigin, blobstore::InMemoryBlobStore, BlobStore, EthPooledTransaction, PoolConfig, TransactionOrigin,

View File

@ -29,8 +29,8 @@ use reth_engine_tree::{
persistence::PersistenceHandle, persistence::PersistenceHandle,
tree::{EngineApiTreeHandler, InvalidBlockHook, TreeConfig}, tree::{EngineApiTreeHandler, InvalidBlockHook, TreeConfig},
}; };
use reth_evm::execute::BlockExecutorProvider; use reth_evm::{execute::BlockExecutorProvider, ConfigureEvm};
use reth_node_types::BlockTy; use reth_node_types::{BlockTy, HeaderTy, TxTy};
use reth_payload_builder::PayloadBuilderHandle; use reth_payload_builder::PayloadBuilderHandle;
use reth_payload_primitives::{PayloadAttributesBuilder, PayloadTypes}; use reth_payload_primitives::{PayloadAttributesBuilder, PayloadTypes};
use reth_provider::{ use reth_provider::{
@ -65,7 +65,7 @@ where
{ {
/// Constructor for [`LocalEngineService`]. /// Constructor for [`LocalEngineService`].
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new<B, V>( pub fn new<B, V, C>(
consensus: Arc<dyn FullConsensus<N::Primitives, Error = ConsensusError>>, consensus: Arc<dyn FullConsensus<N::Primitives, Error = ConsensusError>>,
executor_factory: impl BlockExecutorProvider<Primitives = N::Primitives>, executor_factory: impl BlockExecutorProvider<Primitives = N::Primitives>,
provider: ProviderFactory<N>, provider: ProviderFactory<N>,
@ -80,10 +80,12 @@ where
from_engine: EngineMessageStream<N::Engine>, from_engine: EngineMessageStream<N::Engine>,
mode: MiningMode, mode: MiningMode,
payload_attributes_builder: B, payload_attributes_builder: B,
evm_config: C,
) -> Self ) -> Self
where where
B: PayloadAttributesBuilder<<N::Engine as PayloadTypes>::PayloadAttributes>, B: PayloadAttributesBuilder<<N::Engine as PayloadTypes>::PayloadAttributes>,
V: EngineValidator<N::Engine, Block = BlockTy<N>>, V: EngineValidator<N::Engine, Block = BlockTy<N>>,
C: ConfigureEvm<Header = HeaderTy<N>, Transaction = TxTy<N>>,
{ {
let chain_spec = provider.chain_spec(); let chain_spec = provider.chain_spec();
let engine_kind = let engine_kind =
@ -93,18 +95,20 @@ where
PersistenceHandle::<N::Primitives>::spawn_service(provider, pruner, sync_metrics_tx); PersistenceHandle::<N::Primitives>::spawn_service(provider, pruner, sync_metrics_tx);
let canonical_in_memory_state = blockchain_db.canonical_in_memory_state(); let canonical_in_memory_state = blockchain_db.canonical_in_memory_state();
let (to_tree_tx, from_tree) = EngineApiTreeHandler::<N::Primitives, _, _, _, _>::spawn_new( let (to_tree_tx, from_tree) =
blockchain_db.clone(), EngineApiTreeHandler::<N::Primitives, _, _, _, _, _>::spawn_new(
executor_factory, blockchain_db.clone(),
consensus, executor_factory,
payload_validator, consensus,
persistence_handle, payload_validator,
payload_builder.clone(), persistence_handle,
canonical_in_memory_state, payload_builder.clone(),
tree_config, canonical_in_memory_state,
invalid_block_hook, tree_config,
engine_kind, invalid_block_hook,
); engine_kind,
evm_config,
);
let handler = EngineApiRequestHandler::new(to_tree_tx, from_tree); let handler = EngineApiRequestHandler::new(to_tree_tx, from_tree);

View File

@ -14,9 +14,9 @@ pub use reth_engine_tree::{
chain::{ChainEvent, ChainOrchestrator}, chain::{ChainEvent, ChainOrchestrator},
engine::EngineApiEvent, engine::EngineApiEvent,
}; };
use reth_evm::execute::BlockExecutorProvider; use reth_evm::{execute::BlockExecutorProvider, ConfigureEvm};
use reth_network_p2p::BlockClient; use reth_network_p2p::BlockClient;
use reth_node_types::{BlockTy, NodeTypes, NodeTypesWithEngine}; use reth_node_types::{BlockTy, HeaderTy, NodeTypes, NodeTypesWithEngine, TxTy};
use reth_payload_builder::PayloadBuilderHandle; use reth_payload_builder::PayloadBuilderHandle;
use reth_primitives::EthPrimitives; use reth_primitives::EthPrimitives;
use reth_provider::{ use reth_provider::{
@ -73,7 +73,7 @@ where
{ {
/// Constructor for `EngineService`. /// Constructor for `EngineService`.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new<V>( pub fn new<V, C>(
consensus: Arc<dyn FullConsensus<N::Primitives, Error = ConsensusError>>, consensus: Arc<dyn FullConsensus<N::Primitives, Error = ConsensusError>>,
executor_factory: E, executor_factory: E,
chain_spec: Arc<N::ChainSpec>, chain_spec: Arc<N::ChainSpec>,
@ -89,9 +89,11 @@ where
tree_config: TreeConfig, tree_config: TreeConfig,
invalid_block_hook: Box<dyn InvalidBlockHook<N::Primitives>>, invalid_block_hook: Box<dyn InvalidBlockHook<N::Primitives>>,
sync_metrics_tx: MetricEventsSender, sync_metrics_tx: MetricEventsSender,
evm_config: C,
) -> Self ) -> Self
where where
V: EngineValidator<N::Engine, Block = BlockTy<N>>, V: EngineValidator<N::Engine, Block = BlockTy<N>>,
C: ConfigureEvm<Header = HeaderTy<N>, Transaction = TxTy<N>>,
{ {
let engine_kind = let engine_kind =
if chain_spec.is_optimism() { EngineApiKind::OpStack } else { EngineApiKind::Ethereum }; if chain_spec.is_optimism() { EngineApiKind::OpStack } else { EngineApiKind::Ethereum };
@ -103,18 +105,20 @@ where
let canonical_in_memory_state = blockchain_db.canonical_in_memory_state(); let canonical_in_memory_state = blockchain_db.canonical_in_memory_state();
let (to_tree_tx, from_tree) = EngineApiTreeHandler::<N::Primitives, _, _, _, _>::spawn_new( let (to_tree_tx, from_tree) =
blockchain_db, EngineApiTreeHandler::<N::Primitives, _, _, _, _, _>::spawn_new(
executor_factory, blockchain_db,
consensus, executor_factory,
payload_validator, consensus,
persistence_handle, payload_validator,
payload_builder, persistence_handle,
canonical_in_memory_state, payload_builder,
tree_config, canonical_in_memory_state,
invalid_block_hook, tree_config,
engine_kind, invalid_block_hook,
); engine_kind,
evm_config,
);
let engine_handler = EngineApiRequestHandler::new(to_tree_tx, from_tree); let engine_handler = EngineApiRequestHandler::new(to_tree_tx, from_tree);
let handler = EngineHandler::new(engine_handler, downloader, incoming_requests); let handler = EngineHandler::new(engine_handler, downloader, incoming_requests);
@ -160,7 +164,7 @@ mod tests {
use reth_engine_tree::{test_utils::TestPipelineBuilder, tree::NoopInvalidBlockHook}; use reth_engine_tree::{test_utils::TestPipelineBuilder, tree::NoopInvalidBlockHook};
use reth_ethereum_consensus::EthBeaconConsensus; use reth_ethereum_consensus::EthBeaconConsensus;
use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator}; use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator};
use reth_evm_ethereum::execute::EthExecutorProvider; use reth_evm_ethereum::{execute::EthExecutorProvider, EthEvmConfig};
use reth_exex_types::FinishedExExHeight; use reth_exex_types::FinishedExExHeight;
use reth_network_p2p::test_utils::TestFullBlockClient; use reth_network_p2p::test_utils::TestFullBlockClient;
use reth_primitives::SealedHeader; use reth_primitives::SealedHeader;
@ -200,6 +204,7 @@ mod tests {
let engine_payload_validator = EthereumEngineValidator::new(chain_spec.clone()); let engine_payload_validator = EthereumEngineValidator::new(chain_spec.clone());
let (_tx, rx) = watch::channel(FinishedExExHeight::NoExExs); let (_tx, rx) = watch::channel(FinishedExExHeight::NoExExs);
let pruner = Pruner::new_with_factory(provider_factory.clone(), vec![], 0, 0, None, rx); let pruner = Pruner::new_with_factory(provider_factory.clone(), vec![], 0, 0, None, rx);
let evm_config = EthEvmConfig::new(chain_spec.clone());
let (sync_metrics_tx, _sync_metrics_rx) = unbounded_channel(); let (sync_metrics_tx, _sync_metrics_rx) = unbounded_channel();
let (tx, _rx) = unbounded_channel(); let (tx, _rx) = unbounded_channel();
@ -219,6 +224,7 @@ mod tests {
TreeConfig::default(), TreeConfig::default(),
Box::new(NoopInvalidBlockHook::default()), Box::new(NoopInvalidBlockHook::default()),
sync_metrics_tx, sync_metrics_tx,
evm_config,
); );
} }
} }

View File

@ -72,6 +72,7 @@ reth-chain-state = { workspace = true, features = ["test-utils"] }
reth-chainspec.workspace = true reth-chainspec.workspace = true
reth-ethereum-engine-primitives.workspace = true reth-ethereum-engine-primitives.workspace = true
reth-ethereum-consensus.workspace = true reth-ethereum-consensus.workspace = true
reth-evm-ethereum.workspace = true
reth-evm = { workspace = true, features = ["test-utils"] } reth-evm = { workspace = true, features = ["test-utils"] }
reth-exex-types.workspace = true reth-exex-types.workspace = true
reth-network-p2p = { workspace = true, features = ["test-utils"] } reth-network-p2p = { workspace = true, features = ["test-utils"] }

View File

@ -15,7 +15,7 @@ use reth_trie::{
}; };
use revm_primitives::map::DefaultHashBuilder; use revm_primitives::map::DefaultHashBuilder;
type Cache<K, V> = moka::sync::Cache<K, V, alloy_primitives::map::DefaultHashBuilder>; pub(crate) type Cache<K, V> = moka::sync::Cache<K, V, alloy_primitives::map::DefaultHashBuilder>;
/// A wrapper of a state provider and a shared cache. /// A wrapper of a state provider and a shared cache.
pub(crate) struct CachedStateProvider<S> { pub(crate) struct CachedStateProvider<S> {

View File

@ -11,13 +11,15 @@ use crate::{
use alloy_consensus::BlockHeader; use alloy_consensus::BlockHeader;
use alloy_eips::BlockNumHash; use alloy_eips::BlockNumHash;
use alloy_primitives::{ use alloy_primitives::{
map::{HashMap, HashSet}, keccak256,
BlockNumber, B256, U256, map::{B256Set, HashMap, HashSet},
Address, BlockNumber, B256, U256,
}; };
use alloy_rpc_types_engine::{ use alloy_rpc_types_engine::{
ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError, ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError,
}; };
use block_buffer::BlockBuffer; use block_buffer::BlockBuffer;
use cached_state::ProviderCaches;
use error::{InsertBlockError, InsertBlockErrorKind, InsertBlockFatalError}; use error::{InsertBlockError, InsertBlockErrorKind, InsertBlockFatalError};
use persistence_state::CurrentPersistenceAction; use persistence_state::CurrentPersistenceAction;
use reth_chain_state::{ use reth_chain_state::{
@ -35,26 +37,32 @@ use reth_ethereum_primitives::EthPrimitives;
use reth_evm::{ use reth_evm::{
execute::BlockExecutorProvider, execute::BlockExecutorProvider,
system_calls::{NoopHook, OnStateHook}, system_calls::{NoopHook, OnStateHook},
ConfigureEvm, Evm, TransactionEnv,
}; };
use reth_payload_builder::PayloadBuilderHandle; use reth_payload_builder::PayloadBuilderHandle;
use reth_payload_builder_primitives::PayloadBuilder; use reth_payload_builder_primitives::PayloadBuilder;
use reth_payload_primitives::{EngineApiMessageVersion, PayloadBuilderAttributes}; use reth_payload_primitives::{EngineApiMessageVersion, PayloadBuilderAttributes};
use reth_primitives_traits::{ use reth_primitives_traits::{
Block, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, Block, BlockBody, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader,
SignedTransaction,
}; };
use reth_provider::{ use reth_provider::{
providers::ConsistentDbView, BlockReader, DBProvider, DatabaseProviderFactory, providers::ConsistentDbView, BlockReader, DBProvider, DatabaseProviderFactory,
ExecutionOutcome, HashedPostStateProvider, ProviderError, StateCommitmentProvider, ExecutionOutcome, HashedPostStateProvider, ProviderError, StateCommitmentProvider,
StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, TransactionVariant, StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, TransactionVariant,
}; };
use reth_revm::database::StateProviderDatabase; use reth_revm::{cancelled::Cancelled, database::StateProviderDatabase};
use reth_stages_api::ControlFlow; use reth_stages_api::ControlFlow;
use reth_trie::{ use reth_trie::{
trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdates, HashedPostState, TrieInput, trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdates, HashedPostState,
MultiProofTargets, TrieInput,
}; };
use reth_trie_db::DatabaseTrieCursorFactory; use reth_trie_db::DatabaseTrieCursorFactory;
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError}; use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
use root::{StateRootComputeOutcome, StateRootConfig, StateRootHandle, StateRootTask}; use revm_primitives::ResultAndState;
use root::{
StateRootComputeOutcome, StateRootConfig, StateRootHandle, StateRootMessage, StateRootTask,
};
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::{btree_map, hash_map, BTreeMap, VecDeque}, collections::{btree_map, hash_map, BTreeMap, VecDeque},
@ -518,13 +526,14 @@ pub enum TreeAction {
/// ///
/// This type is responsible for processing engine API requests, maintaining the canonical state and /// This type is responsible for processing engine API requests, maintaining the canonical state and
/// emitting events. /// emitting events.
pub struct EngineApiTreeHandler<N, P, E, T, V> pub struct EngineApiTreeHandler<N, P, E, T, V, C>
where where
N: NodePrimitives, N: NodePrimitives,
T: EngineTypes, T: EngineTypes,
{ {
provider: P, provider: P,
executor_provider: E, executor_provider: E,
evm_config: C,
consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>, consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
payload_validator: V, payload_validator: V,
/// Keeps track of internals such as executed and buffered blocks. /// Keeps track of internals such as executed and buffered blocks.
@ -562,18 +571,19 @@ where
invalid_block_hook: Box<dyn InvalidBlockHook<N>>, invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
/// The engine API variant of this handler /// The engine API variant of this handler
engine_kind: EngineApiKind, engine_kind: EngineApiKind,
/// state root task thread pool /// Thread pool used for the state root task and prewarming
state_root_task_pool: Arc<rayon::ThreadPool>, thread_pool: Arc<rayon::ThreadPool>,
} }
impl<N, P: Debug, E: Debug, T: EngineTypes + Debug, V: Debug> std::fmt::Debug impl<N, P: Debug, E: Debug, T: EngineTypes + Debug, V: Debug, C: Debug> std::fmt::Debug
for EngineApiTreeHandler<N, P, E, T, V> for EngineApiTreeHandler<N, P, E, T, V, C>
where where
N: NodePrimitives, N: NodePrimitives,
{ {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EngineApiTreeHandler") f.debug_struct("EngineApiTreeHandler")
.field("provider", &self.provider) .field("provider", &self.provider)
.field("evm_config", &self.evm_config)
.field("executor_provider", &self.executor_provider) .field("executor_provider", &self.executor_provider)
.field("consensus", &self.consensus) .field("consensus", &self.consensus)
.field("payload_validator", &self.payload_validator) .field("payload_validator", &self.payload_validator)
@ -592,7 +602,7 @@ where
} }
} }
impl<N, P, E, T, V> EngineApiTreeHandler<N, P, E, T, V> impl<N, P, E, T, V, C> EngineApiTreeHandler<N, P, E, T, V, C>
where where
N: NodePrimitives, N: NodePrimitives,
P: DatabaseProviderFactory P: DatabaseProviderFactory
@ -606,6 +616,7 @@ where
<P as DatabaseProviderFactory>::Provider: <P as DatabaseProviderFactory>::Provider:
BlockReader<Block = N::Block, Header = N::BlockHeader>, BlockReader<Block = N::Block, Header = N::BlockHeader>,
E: BlockExecutorProvider<Primitives = N>, E: BlockExecutorProvider<Primitives = N>,
C: ConfigureEvm<Header = N::BlockHeader, Transaction = N::SignedTx>,
T: EngineTypes, T: EngineTypes,
V: EngineValidator<T, Block = N::Block>, V: EngineValidator<T, Block = N::Block>,
{ {
@ -624,12 +635,13 @@ where
payload_builder: PayloadBuilderHandle<T>, payload_builder: PayloadBuilderHandle<T>,
config: TreeConfig, config: TreeConfig,
engine_kind: EngineApiKind, engine_kind: EngineApiKind,
evm_config: C,
) -> Self { ) -> Self {
let (incoming_tx, incoming) = std::sync::mpsc::channel(); let (incoming_tx, incoming) = std::sync::mpsc::channel();
let num_threads = root::thread_pool_size(); let num_threads = root::thread_pool_size();
let state_root_task_pool = Arc::new( let thread_pool = Arc::new(
rayon::ThreadPoolBuilder::new() rayon::ThreadPoolBuilder::new()
.num_threads(num_threads) .num_threads(num_threads)
.thread_name(|i| format!("srt-worker-{}", i)) .thread_name(|i| format!("srt-worker-{}", i))
@ -640,6 +652,7 @@ where
Self { Self {
provider, provider,
executor_provider, executor_provider,
evm_config,
consensus, consensus,
payload_validator, payload_validator,
incoming, incoming,
@ -655,7 +668,7 @@ where
incoming_tx, incoming_tx,
invalid_block_hook: Box::new(NoopInvalidBlockHook), invalid_block_hook: Box::new(NoopInvalidBlockHook),
engine_kind, engine_kind,
state_root_task_pool, thread_pool,
} }
} }
@ -681,6 +694,7 @@ where
config: TreeConfig, config: TreeConfig,
invalid_block_hook: Box<dyn InvalidBlockHook<N>>, invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
kind: EngineApiKind, kind: EngineApiKind,
evm_config: C,
) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>) ) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>)
{ {
let best_block_number = provider.best_block_number().unwrap_or(0); let best_block_number = provider.best_block_number().unwrap_or(0);
@ -712,6 +726,7 @@ where
payload_builder, payload_builder,
config, config,
kind, kind,
evm_config,
); );
task.set_invalid_block_hook(invalid_block_hook); task.set_invalid_block_hook(invalid_block_hook);
let incoming = task.incoming_tx.clone(); let incoming = task.incoming_tx.clone();
@ -2347,17 +2362,6 @@ where
return Err(e.into()) return Err(e.into())
} }
// Use cached state provider before executing, this does nothing currently, will be used in
// prewarming
let caches = ProviderCacheBuilder::default().build_caches();
let cache_metrics = CachedStateMetrics::zeroed();
let state_provider =
CachedStateProvider::new_with_caches(state_provider, caches, cache_metrics);
trace!(target: "engine::tree", block=?block_num_hash, "Executing block");
let executor = self.executor_provider.executor(StateProviderDatabase::new(&state_provider));
let sealed_block = Arc::new(block.clone_sealed_block()); let sealed_block = Arc::new(block.clone_sealed_block());
// We only run the parallel state root if we are currently persisting blocks that are all // We only run the parallel state root if we are currently persisting blocks that are all
@ -2370,7 +2374,10 @@ where
let is_descendant_of_persisting_blocks = let is_descendant_of_persisting_blocks =
self.is_descendant_of_persisting_blocks(block.header()); self.is_descendant_of_persisting_blocks(block.header());
let (state_root_handle, state_root_task_config, state_hook) = // Atomic bool for letting the prewarm tasks know when to stop
let cancel_execution = Cancelled::default();
let (state_root_handle, state_root_task_config, state_root_sender, state_hook) =
if is_descendant_of_persisting_blocks && self.config.use_state_root_task() { if is_descendant_of_persisting_blocks && self.config.use_state_root_task() {
let consistent_view = ConsistentDbView::new_with_latest_tip(self.provider.clone())?; let consistent_view = ConsistentDbView::new_with_latest_tip(self.provider.clone())?;
@ -2397,21 +2404,69 @@ where
.state_root_config_duration .state_root_config_duration
.set(config_elapsed.as_secs_f64()); .set(config_elapsed.as_secs_f64());
let state_root_task = StateRootTask::new( let state_root_task =
state_root_config.clone(), StateRootTask::new(state_root_config.clone(), self.thread_pool.clone());
self.state_root_task_pool.clone(), let state_root_sender = state_root_task.state_root_message_sender();
);
let state_hook = Box::new(state_root_task.state_hook()) as Box<dyn OnStateHook>; let state_hook = Box::new(state_root_task.state_hook()) as Box<dyn OnStateHook>;
(Some(state_root_task.spawn()), Some(state_root_config), state_hook) (
Some(state_root_task.spawn()),
Some(state_root_config),
Some(state_root_sender),
state_hook,
)
} else { } else {
(None, None, Box::new(NoopHook::default()) as Box<dyn OnStateHook>) (None, None, None, Box::new(NoopHook::default()) as Box<dyn OnStateHook>)
}; };
// Use cached state provider before executing, used in execution after prewarming threads
// complete
let caches = ProviderCacheBuilder::default().build_caches();
let cache_metrics = CachedStateMetrics::zeroed();
let state_provider = CachedStateProvider::new_with_caches(
state_provider,
caches.clone(),
cache_metrics.clone(),
);
if self.config.use_caching_and_prewarming() {
debug!(target: "engine::tree", "Spawning prewarm threads");
let prewarm_start = Instant::now();
// Prewarm transactions
for (tx_idx, (tx, sender)) in
block.body().transactions().iter().zip(block.senders()).enumerate()
{
let state_root_sender = state_root_sender.clone();
let start = Instant::now();
self.prewarm_transaction(
block.header().clone(),
tx.clone(),
*sender,
caches.clone(),
cache_metrics.clone(),
state_root_sender,
cancel_execution.clone(),
)?;
let elapsed = start.elapsed();
debug!(target: "engine::tree", ?tx_idx, elapsed = ?elapsed, "Spawned transaction prewarm");
}
drop(state_root_sender);
let elapsed = prewarm_start.elapsed();
debug!(target: "engine::tree", ?elapsed, "Done spawning prewarm threads");
}
trace!(target: "engine::tree", block=?block_num_hash, "Executing block");
let executor = self.executor_provider.executor(StateProviderDatabase::new(&state_provider));
let execution_start = Instant::now(); let execution_start = Instant::now();
let output = self.metrics.executor.execute_metered(executor, &block, state_hook)?; let output = self.metrics.executor.execute_metered(executor, &block, state_hook)?;
let execution_time = execution_start.elapsed(); let execution_time = execution_start.elapsed();
trace!(target: "engine::tree", elapsed = ?execution_time, number=?block_num_hash.number, "Executed block"); trace!(target: "engine::tree", elapsed = ?execution_time, number=?block_num_hash.number, "Executed block");
// Ensure that prewarm tasks don't send proof messages after state root sender is dropped
drop(cancel_execution);
if let Err(err) = self.consensus.validate_block_post_execution( if let Err(err) = self.consensus.validate_block_post_execution(
&block, &block,
PostExecutionInput::new(&output.receipts, &output.requests), PostExecutionInput::new(&output.receipts, &output.requests),
@ -2573,6 +2628,89 @@ where
Ok(input) Ok(input)
} }
/// Runs execution for a single transaction, spawning it in the prewarm threadpool.
#[allow(clippy::too_many_arguments)]
fn prewarm_transaction(
&self,
block: N::BlockHeader,
tx: N::SignedTx,
sender: Address,
caches: ProviderCaches,
cache_metrics: CachedStateMetrics,
state_root_sender: Option<Sender<StateRootMessage>>,
cancel_execution: Cancelled,
) -> Result<(), InsertBlockErrorKind> {
let Some(state_provider) = self.state_provider(block.parent_hash())? else {
trace!(target: "engine::tree", parent=%block.parent_hash(), "Could not get state provider for prewarm");
return Ok(())
};
// Use the caches to create a new executor
let state_provider =
CachedStateProvider::new_with_caches(state_provider, caches, cache_metrics);
// clone and copy info required for execution
let evm_config = self.evm_config.clone();
// spawn task executing the individual tx
self.thread_pool.spawn(move || {
let state_provider = StateProviderDatabase::new(&state_provider);
// create a new executor and disable nonce checks in the env
let mut evm = evm_config.evm_for_block(state_provider, &block);
// create the tx env and reset nonce
let mut tx_env = evm_config.tx_env(&tx, sender);
tx_env.unset_nonce();
// exit early if execution is done
if cancel_execution.is_cancelled() {
return
}
let ResultAndState { state, .. } = match evm.transact(tx_env) {
Ok(res) => res,
Err(err) => {
trace!(target: "engine::tree", %err, tx_hash=%tx.tx_hash(), %sender, "Error when executing prewarm transaction");
return
}
};
// if execution is finished there is no point to sending proof targets
if cancel_execution.is_cancelled() {
return
}
let Some(state_root_sender) = state_root_sender else {
return
};
let mut targets = MultiProofTargets::default();
for (addr, account) in state {
// if account was not touched, do not fetch for it
if !account.is_touched() {
continue
}
let mut storage_set = B256Set::default();
for (key, slot) in account.storage {
// do nothing if unchanged
if !slot.is_changed() {
continue
}
storage_set.insert(keccak256(B256::new(key.to_be_bytes())));
}
targets.insert(keccak256(addr), storage_set);
}
let _ = state_root_sender.send(StateRootMessage::PrefetchProofs(targets));
});
Ok(())
}
/// Handles an error that occurred while inserting a block. /// Handles an error that occurred while inserting a block.
/// ///
/// If this is a validation error this will mark the block as invalid. /// If this is a validation error this will mark the block as invalid.
@ -2925,6 +3063,7 @@ mod tests {
use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator}; use reth_ethereum_engine_primitives::{EthEngineTypes, EthereumEngineValidator};
use reth_ethereum_primitives::{Block, EthPrimitives}; use reth_ethereum_primitives::{Block, EthPrimitives};
use reth_evm::test_utils::MockExecutorProvider; use reth_evm::test_utils::MockExecutorProvider;
use reth_evm_ethereum::EthEvmConfig;
use reth_primitives_traits::Block as _; use reth_primitives_traits::Block as _;
use reth_provider::test_utils::MockEthProvider; use reth_provider::test_utils::MockEthProvider;
use reth_trie::{updates::TrieUpdates, HashedPostState}; use reth_trie::{updates::TrieUpdates, HashedPostState};
@ -2995,6 +3134,7 @@ mod tests {
MockExecutorProvider, MockExecutorProvider,
EthEngineTypes, EthEngineTypes,
EthereumEngineValidator, EthereumEngineValidator,
EthEvmConfig,
>, >,
to_tree_tx: Sender<FromEngine<EngineApiRequest<EthEngineTypes, EthPrimitives>, Block>>, to_tree_tx: Sender<FromEngine<EngineApiRequest<EthEngineTypes, EthPrimitives>, Block>>,
from_tree_rx: UnboundedReceiver<EngineApiEvent>, from_tree_rx: UnboundedReceiver<EngineApiEvent>,
@ -3041,6 +3181,8 @@ mod tests {
let (to_payload_service, _payload_command_rx) = unbounded_channel(); let (to_payload_service, _payload_command_rx) = unbounded_channel();
let payload_builder = PayloadBuilderHandle::new(to_payload_service); let payload_builder = PayloadBuilderHandle::new(to_payload_service);
let evm_config = EthEvmConfig::new(chain_spec.clone());
let tree = EngineApiTreeHandler::new( let tree = EngineApiTreeHandler::new(
provider.clone(), provider.clone(),
executor_provider.clone(), executor_provider.clone(),
@ -3054,6 +3196,7 @@ mod tests {
payload_builder, payload_builder,
TreeConfig::default(), TreeConfig::default(),
EngineApiKind::Ethereum, EngineApiKind::Ethereum,
evm_config,
); );
let block_builder = TestBlockBuilder::default().with_chain_spec((*chain_spec).clone()); let block_builder = TestBlockBuilder::default().with_chain_spec((*chain_spec).clone());

View File

@ -509,6 +509,11 @@ where
} }
} }
/// Returns a [`Sender`] that can be used to send arbitrary [`StateRootMessage`]s to this task.
pub fn state_root_message_sender(&self) -> Sender<StateRootMessage> {
self.tx.clone()
}
/// Returns a [`StateHookSender`] that can be used to send state updates to this task. /// Returns a [`StateHookSender`] that can be used to send state updates to this task.
pub fn state_hook_sender(&self) -> StateHookSender { pub fn state_hook_sender(&self) -> StateHookSender {
StateHookSender::new(self.tx.clone()) StateHookSender::new(self.tx.clone())

View File

@ -565,6 +565,7 @@ where
.with_persistence_threshold(builder.config.engine.persistence_threshold) .with_persistence_threshold(builder.config.engine.persistence_threshold)
.with_memory_block_buffer_target(builder.config.engine.memory_block_buffer_target) .with_memory_block_buffer_target(builder.config.engine.memory_block_buffer_target)
.with_state_root_task(builder.config.engine.state_root_task_enabled) .with_state_root_task(builder.config.engine.state_root_task_enabled)
.with_caching_and_prewarming(builder.config.engine.caching_and_prewarming_enabled)
.with_always_compare_trie_updates( .with_always_compare_trie_updates(
builder.config.engine.state_root_task_compare_updates, builder.config.engine.state_root_task_compare_updates,
); );

View File

@ -227,6 +227,7 @@ where
Box::pin(consensus_engine_stream), Box::pin(consensus_engine_stream),
ctx.dev_mining_mode(ctx.components().pool()), ctx.dev_mining_mode(ctx.components().pool()),
LocalPayloadAttributesBuilder::new(ctx.chain_spec()), LocalPayloadAttributesBuilder::new(ctx.chain_spec()),
ctx.components().evm_config().clone(),
); );
Either::Left(eth_service) Either::Left(eth_service)
@ -247,6 +248,7 @@ where
engine_tree_config, engine_tree_config,
ctx.invalid_block_hook()?, ctx.invalid_block_hook()?,
ctx.sync_metrics_tx(), ctx.sync_metrics_tx(),
ctx.components().evm_config().clone(),
); );
Either::Right(eth_service) Either::Right(eth_service)

View File

@ -38,7 +38,9 @@ use reth_provider::{
HashedPostStateProvider, ProviderError, StateProofProvider, StateProviderFactory, HashedPostStateProvider, ProviderError, StateProofProvider, StateProviderFactory,
StateRootProvider, StateRootProvider,
}; };
use reth_revm::{database::StateProviderDatabase, witness::ExecutionWitnessRecord}; use reth_revm::{
cancelled::Cancelled, database::StateProviderDatabase, witness::ExecutionWitnessRecord,
};
use reth_transaction_pool::{ use reth_transaction_pool::{
pool::BestPayloadTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, pool::BestPayloadTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool,
}; };

View File

@ -22,7 +22,7 @@ use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes, PayloadKin
use reth_primitives::{NodePrimitives, SealedHeader}; use reth_primitives::{NodePrimitives, SealedHeader};
use reth_primitives_traits::proofs; use reth_primitives_traits::proofs;
use reth_provider::{BlockReaderIdExt, CanonStateNotification, StateProviderFactory}; use reth_provider::{BlockReaderIdExt, CanonStateNotification, StateProviderFactory};
use reth_revm::cached::CachedReads; use reth_revm::{cached::CachedReads, cancelled::Cancelled};
use reth_tasks::TaskSpawner; use reth_tasks::TaskSpawner;
use reth_transaction_pool::TransactionPool; use reth_transaction_pool::TransactionPool;
use revm::{Database, State}; use revm::{Database, State};
@ -31,7 +31,7 @@ use std::{
future::Future, future::Future,
ops::Deref, ops::Deref,
pin::Pin, pin::Pin,
sync::{atomic::AtomicBool, Arc}, sync::Arc,
task::{Context, Poll}, task::{Context, Poll},
time::{Duration, SystemTime, UNIX_EPOCH}, time::{Duration, SystemTime, UNIX_EPOCH},
}; };
@ -681,27 +681,6 @@ impl<P> Future for PendingPayload<P> {
} }
} }
/// A marker that can be used to cancel a job.
///
/// If dropped, it will set the `cancelled` flag to true.
#[derive(Default, Clone, Debug)]
pub struct Cancelled(Arc<AtomicBool>);
// === impl Cancelled ===
impl Cancelled {
/// Returns true if the job was cancelled.
pub fn is_cancelled(&self) -> bool {
self.0.load(std::sync::atomic::Ordering::Relaxed)
}
}
impl Drop for Cancelled {
fn drop(&mut self) {
self.0.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
/// Static config for how to build a payload. /// Static config for how to build a payload.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PayloadConfig<Attributes> { pub struct PayloadConfig<Attributes> {

View File

@ -0,0 +1,23 @@
use alloc::sync::Arc;
use core::sync::atomic::AtomicBool;
/// A marker that can be used to cancel execution.
///
/// If dropped, it will set the `cancelled` flag to true.
#[derive(Default, Clone, Debug)]
pub struct Cancelled(Arc<AtomicBool>);
// === impl Cancelled ===
impl Cancelled {
/// Returns true if the job was cancelled.
pub fn is_cancelled(&self) -> bool {
self.0.load(core::sync::atomic::Ordering::Relaxed)
}
}
impl Drop for Cancelled {
fn drop(&mut self) {
self.0.store(true, core::sync::atomic::Ordering::Relaxed);
}
}

View File

@ -17,6 +17,9 @@ pub mod batch;
/// Database adapters for payload building. /// Database adapters for payload building.
pub mod cached; pub mod cached;
/// A marker that can be used to cancel execution.
pub mod cancelled;
/// Contains glue code for integrating reth database into revm's [Database]. /// Contains glue code for integrating reth database into revm's [Database].
pub mod database; pub mod database;