reth_engine_tree/tree/txpool_prewarm/
mod.rs1mod control;
8mod worker;
9
10use self::control::Control;
11use crate::tree::{
12 payload_processor::reuse::{
13 cfg_reuses_prewarm_results, BlockEnvFields, PoolResults, PoolSeeds, POOL_BENEFICIARY,
14 },
15 TxPoolPrewarmCacheSnapshot,
16};
17use alloy_consensus::transaction::Recovered;
18use alloy_primitives::{Address, B256};
19use parking_lot::RwLock;
20use reth_evm::{ConfigureEvm, EvmEnv, EvmEnvFor, EvmTypesFor};
21use reth_primitives_traits::{NodePrimitives, SealedHeader, TxTy};
22use reth_provider::{
23 BlockNumReader, ChangeSetReader, DatabaseProviderFactory, DatabaseProviderROFactory,
24 PruneCheckpointReader, StageCheckpointReader, StateProvider, StorageChangeSetReader,
25 StorageSettingsCache,
26};
27use reth_storage_overlay::OverlayStateProviderFactory;
28use std::{fmt::Debug, sync::Arc};
29
30pub(crate) struct Handle<N, P, Evm>
32where
33 N: NodePrimitives,
34 Evm: ConfigureEvm<Primitives = N>,
35{
36 control: Arc<Control<Job<N, P, Evm>>>,
37 evm_config: Evm,
39 record_results: bool,
42 speculation: RwLock<Option<Speculation<Evm>>>,
44}
45
46impl<N, P, Evm> Debug for Handle<N, P, Evm>
47where
48 N: NodePrimitives,
49 Evm: ConfigureEvm<Primitives = N>,
50{
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("Handle").field("control", &self.control).finish()
53 }
54}
55
56impl<N, P, Evm> Handle<N, P, Evm>
57where
58 N: NodePrimitives,
59 P: DatabaseProviderFactory + 'static,
60 P::Provider: BlockNumReader
61 + PruneCheckpointReader
62 + StageCheckpointReader
63 + ChangeSetReader
64 + StorageChangeSetReader
65 + StorageSettingsCache
66 + 'static,
67 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: StateProvider> + Send,
68 Evm: ConfigureEvm<Primitives = N> + 'static,
69{
70 pub(crate) fn spawn(
76 runtime: &reth_tasks::Runtime,
77 source: Arc<dyn Source<N>>,
78 evm_config: Evm,
79 record_results: bool,
80 ) -> Self {
81 let (control, commands) = Control::new();
82 let publication = control.publication();
83 let worker_evm_config = evm_config.clone();
84 runtime.spawn_critical_os_thread("txpool-prewarm", "txpool prewarm worker", async move {
85 worker::Worker::new(commands, publication, source, worker_evm_config).run()
86 });
87 Self { control, evm_config, record_results, speculation: RwLock::new(None) }
88 }
89
90 pub(crate) fn pause(&self) -> impl Drop + Send + 'static {
99 self.control.pause()
100 }
101
102 pub(crate) fn snapshot(&self, parent_hash: B256) -> Option<TxPoolPrewarmCacheSnapshot> {
105 self.control.snapshot(parent_hash)
106 }
107
108 pub(crate) fn start(
114 &self,
115 parent: &SealedHeader<N::BlockHeader>,
116 evm_env: EvmEnvFor<Evm>,
117 state_provider_factory: OverlayStateProviderFactory<P, N>,
118 ) {
119 let mut speculation = self.speculation.write();
120 if speculation.as_ref().is_none_or(|s| s.parent_hash != parent.hash()) {
123 *speculation = self
124 .record_results
125 .then(|| self.evm_config.speculative_next_evm_env(parent, POOL_BENEFICIARY))
126 .flatten()
127 .filter(|evm_env| cfg_reuses_prewarm_results(evm_env.version()))
128 .map(|evm_env| Speculation {
129 parent_hash: parent.hash(),
130 evm_env,
131 results: Arc::default(),
132 });
133 }
134 let job = Job {
135 evm_env: speculation.as_ref().map_or(evm_env, |s| s.evm_env.clone()),
136 state_provider_factory,
137 results: speculation.as_ref().map(|s| Arc::clone(&s.results)),
138 };
139 drop(speculation);
140 self.control.start(parent.hash(), job);
141 }
142
143 pub(crate) fn seeds(
146 &self,
147 parent_hash: B256,
148 evm_env: &EvmEnvFor<Evm>,
149 ) -> Option<PoolSeeds<EvmTypesFor<Evm>>> {
150 let speculation = self.speculation.read();
151 let speculation = speculation
152 .as_ref()
153 .filter(|s| s.parent_hash == parent_hash && !s.results.is_empty())?;
154 let differs = env_differences(&speculation.evm_env, evm_env)?;
155 Some(PoolSeeds::new(
156 Arc::clone(&speculation.results),
157 evm_env.block_env().beneficiary,
158 differs,
159 ))
160 }
161}
162
163struct Speculation<Evm: ConfigureEvm> {
165 parent_hash: B256,
166 evm_env: EvmEnvFor<Evm>,
167 results: Arc<PoolResults<EvmTypesFor<Evm>>>,
168}
169
170fn env_differences<E: EvmEnv>(predicted: &E, actual: &E) -> Option<BlockEnvFields> {
173 let predicted_version = predicted.version();
174 let actual_version = actual.version();
175 let checks = evm2::EvmFeatures::NONCE_CHECK | evm2::EvmFeatures::BALANCE_CHECK;
176 if Into::<evm2::SpecId>::into(predicted.spec_id()) !=
177 Into::<evm2::SpecId>::into(actual.spec_id()) ||
178 predicted_version.gas_params != actual_version.gas_params ||
179 (predicted_version.features | checks) != (actual_version.features | checks) ||
180 predicted_version.chain_id != actual_version.chain_id ||
181 predicted_version.tx_gas_limit_cap != actual_version.tx_gas_limit_cap ||
182 predicted_version.memory_limit != actual_version.memory_limit ||
183 predicted_version.max_code_size != actual_version.max_code_size ||
184 predicted_version.max_initcode_size != actual_version.max_initcode_size ||
185 predicted_version.max_blobs_per_tx != actual_version.max_blobs_per_tx
186 {
187 return None
188 }
189 let (predicted, actual) = (predicted.block_env(), actual.block_env());
190 (predicted.number == actual.number &&
191 predicted.basefee == actual.basefee &&
192 predicted.difficulty == actual.difficulty &&
193 predicted.blob_basefee == actual.blob_basefee &&
194 predicted.slot_num == actual.slot_num)
195 .then(|| BlockEnvFields {
196 beneficiary: false,
197 timestamp: predicted.timestamp != actual.timestamp,
198 prevrandao: predicted.prevrandao != actual.prevrandao,
199 gas_limit: predicted.gas_limit != actual.gas_limit,
200 })
201}
202
203pub type Transactions<N> = Box<dyn Iterator<Item = Transaction<N>> + Send>;
208
209#[derive(Debug, Clone)]
211pub struct Transaction<N: NodePrimitives> {
212 pub hash: B256,
214 pub sender: Address,
216 pub transaction: Recovered<TxTy<N>>,
218}
219
220pub trait Source<N: NodePrimitives>: Send + Sync + Debug {
222 fn best_transactions(&self, parent_hash: B256) -> Option<Transactions<N>>;
228}
229
230struct Job<N: NodePrimitives, P, Evm: ConfigureEvm<Primitives = N>> {
232 evm_env: EvmEnvFor<Evm>,
233 state_provider_factory: OverlayStateProviderFactory<P, N>,
234 results: Option<Arc<PoolResults<EvmTypesFor<Evm>>>>,
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use alloy_consensus::Header;
243 use alloy_primitives::U256;
244 use evm2::BaseEvmTypes;
245 use reth_chainspec::ChainSpecBuilder;
246 use reth_ethereum_primitives::EthPrimitives;
247 use reth_evm_ethereum::EthEvmConfig;
248 use reth_provider::test_utils::MockEthProvider;
249 use reth_storage_overlay::OverlayManager;
250 use reth_tasks::Runtime;
251
252 type TestHandle = Handle<EthPrimitives, MockEthProvider, EthEvmConfig>;
253
254 #[derive(Debug)]
256 struct NoPool;
257
258 impl Source<EthPrimitives> for NoPool {
259 fn best_transactions(&self, _parent_hash: B256) -> Option<Transactions<EthPrimitives>> {
260 None
261 }
262 }
263
264 fn start(handle: &TestHandle, parent: &SealedHeader) {
265 let state_provider_factory = OverlayStateProviderFactory::new(
266 MockEthProvider::default(),
267 OverlayManager::default().overlay_builder(parent.hash()),
268 );
269 handle.start(parent, Default::default(), state_provider_factory);
270 }
271
272 fn recorded(handle: &TestHandle) -> Option<Arc<PoolResults<BaseEvmTypes>>> {
273 handle.speculation.read().as_ref().map(|s| Arc::clone(&s.results))
274 }
275
276 #[test]
277 fn keeps_results_of_the_same_head() {
278 let handle =
279 TestHandle::spawn(&Runtime::test(), Arc::new(NoPool), EthEvmConfig::mainnet(), true);
280 let parent = SealedHeader::seal_slow(Header::default());
281 start(&handle, &parent);
282 let results = recorded(&handle).unwrap();
283 start(&handle, &parent);
284 assert!(Arc::ptr_eq(&results, &recorded(&handle).unwrap()));
285
286 start(&handle, &SealedHeader::seal_slow(Header { number: 1, ..Default::default() }));
287 assert!(!Arc::ptr_eq(&results, &recorded(&handle).unwrap()));
288 }
289
290 #[test]
291 fn offers_no_seeds_without_results() {
292 let handle =
293 TestHandle::spawn(&Runtime::test(), Arc::new(NoPool), EthEvmConfig::mainnet(), true);
294 let parent = SealedHeader::seal_slow(Header::default());
295 start(&handle, &parent);
296 let evm_env = handle.speculation.read().as_ref().unwrap().evm_env.clone();
297 assert!(handle.seeds(parent.hash(), &evm_env).is_none());
298 }
299
300 #[test]
301 fn records_no_results_with_eip8037() {
302 let chain_spec = ChainSpecBuilder::mainnet().amsterdam_activated().build();
303 let evm_config = EthEvmConfig::new(Arc::new(chain_spec));
304 let handle = TestHandle::spawn(&Runtime::test(), Arc::new(NoPool), evm_config, true);
305 start(&handle, &SealedHeader::seal_slow(Header::default()));
306 assert!(recorded(&handle).is_none());
307 }
308
309 #[test]
310 fn predicted_env_differs_in_unobserved_fields() {
311 let config = EthEvmConfig::mainnet();
312 let parent = Header {
314 number: 25_000_000,
315 timestamp: 1_800_000_000,
316 gas_limit: 60_000_000,
317 gas_used: 30_000_000,
318 base_fee_per_gas: Some(1_000_000_000),
319 excess_blob_gas: Some(0),
320 blob_gas_used: Some(0),
321 ..Default::default()
322 };
323 let block = Header {
324 number: parent.number + 1,
325 timestamp: parent.timestamp + 12,
326 beneficiary: Address::repeat_byte(1),
327 mix_hash: B256::repeat_byte(2),
328 gas_limit: parent.gas_limit + 1,
329 ..parent.clone()
330 };
331 let mut predicted = config
332 .speculative_next_evm_env(&SealedHeader::seal_slow(parent), POOL_BENEFICIARY)
333 .unwrap();
334 predicted.version.features.remove(evm2::EvmFeatures::NONCE_CHECK);
336 let mut actual = config.evm_env(&block).unwrap();
337 assert_eq!(
338 env_differences(&predicted, &actual),
339 Some(BlockEnvFields { prevrandao: true, gas_limit: true, ..Default::default() })
340 );
341
342 actual.block.timestamp += U256::from(12);
343 assert_eq!(
344 env_differences(&predicted, &actual),
345 Some(BlockEnvFields {
346 timestamp: true,
347 prevrandao: true,
348 gas_limit: true,
349 ..Default::default()
350 })
351 );
352
353 let mut other_chain = actual.clone();
354 other_chain.version.chain_id += 1;
355 assert_eq!(env_differences(&predicted, &other_chain), None);
356
357 actual.block.basefee += U256::ONE;
358 assert_eq!(env_differences(&predicted, &actual), None);
359 }
360}