1use super::job::BackfillJobResult;
2use crate::{BackfillJob, SingleBlockBackfillJob};
3use alloy_primitives::BlockNumber;
4use futures::{
5 stream::{FuturesOrdered, Stream},
6 StreamExt,
7};
8use reth_evm::{
9 execute::{BlockExecutionError, BlockExecutionOutput},
10 ConfigureEvm,
11};
12use reth_node_api::NodePrimitives;
13use reth_primitives_traits::RecoveredBlock;
14use reth_provider::{BlockReader, Chain, StateProviderFactory};
15use reth_prune_types::PruneModes;
16use reth_stages_api::ExecutionStageThresholds;
17use std::{
18 ops::RangeInclusive,
19 pin::Pin,
20 task::{ready, Context, Poll},
21};
22use tokio::task::JoinHandle;
23use tracing::debug;
24
25pub(crate) const DEFAULT_PARALLELISM: usize = 4;
27const DEFAULT_BATCH_SIZE: usize = 100;
29
30type BackfillTaskIterator<T> =
32 Box<dyn Iterator<Item = BackfillJobResult<T>> + Send + Sync + 'static>;
33
34struct BackfillTaskOutput<T> {
36 job: BackfillTaskIterator<T>,
37 result: Option<BackfillJobResult<T>>,
38}
39
40type BackfillTasks<T> = FuturesOrdered<JoinHandle<BackfillTaskOutput<T>>>;
42
43pub(super) type SingleBlockStreamItem<N> = (
44 RecoveredBlock<<N as NodePrimitives>::Block>,
45 BlockExecutionOutput<<N as NodePrimitives>::Receipt>,
46);
47type BatchBlockStreamItem<N> = Chain<N>;
48
49#[derive(Debug)]
54pub struct StreamBackfillJob<E, P, T> {
55 evm_config: E,
56 provider: P,
57 prune_modes: PruneModes,
58 range: RangeInclusive<BlockNumber>,
59 tasks: BackfillTasks<T>,
60 parallelism: usize,
61 batch_size: usize,
62 thresholds: ExecutionStageThresholds,
63}
64
65impl<E, P, T> StreamBackfillJob<E, P, T>
66where
67 T: Send + Sync + 'static,
68{
69 pub const fn with_parallelism(mut self, parallelism: usize) -> Self {
71 self.parallelism = parallelism;
72 self
73 }
74
75 pub const fn with_batch_size(mut self, batch_size: usize) -> Self {
77 self.batch_size = batch_size;
78 self
79 }
80
81 fn push_back(&mut self, mut job: BackfillTaskIterator<T>) {
84 self.tasks.push_back(tokio::task::spawn_blocking(move || BackfillTaskOutput {
85 result: job.next(),
86 job,
87 }));
88 }
89
90 fn push_front(&mut self, mut job: BackfillTaskIterator<T>) {
93 self.tasks.push_front(tokio::task::spawn_blocking(move || BackfillTaskOutput {
94 result: job.next(),
95 job,
96 }));
97 }
98
99 fn poll_next_task(&mut self, cx: &mut Context<'_>) -> Poll<Option<BackfillJobResult<T>>> {
101 while let Some(res) = ready!(self.tasks.poll_next_unpin(cx)) {
102 let task_result = res.map_err(BlockExecutionError::other)?;
103
104 if let BackfillTaskOutput { result: Some(job_result), job } = task_result {
105 self.push_front(job);
109
110 return Poll::Ready(Some(job_result))
111 };
112 }
113
114 Poll::Ready(None)
115 }
116}
117
118impl<E, P> Stream for StreamBackfillJob<E, P, SingleBlockStreamItem<E::Primitives>>
119where
120 E: ConfigureEvm<Primitives: NodePrimitives<Block = P::Block>> + 'static,
121 P: BlockReader + StateProviderFactory + Clone + Unpin + 'static,
122{
123 type Item = BackfillJobResult<SingleBlockStreamItem<E::Primitives>>;
124
125 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
126 let this = self.get_mut();
127
128 while this.tasks.len() < this.parallelism {
130 let Some(block_number) = this.range.next() else {
132 debug!(target: "exex::backfill", tasks = %this.tasks.len(), range = ?this.range, "No more single blocks to backfill");
133 break;
134 };
135
136 debug!(target: "exex::backfill", tasks = %this.tasks.len(), ?block_number, "Spawning new single block backfill task");
138 let job = Box::new(SingleBlockBackfillJob {
139 evm_config: this.evm_config.clone(),
140 provider: this.provider.clone(),
141 range: block_number..=block_number,
142 stream_parallelism: this.parallelism,
143 }) as BackfillTaskIterator<_>;
144 this.push_back(job);
145 }
146
147 this.poll_next_task(cx)
148 }
149}
150
151impl<E, P> Stream for StreamBackfillJob<E, P, BatchBlockStreamItem<E::Primitives>>
152where
153 E: ConfigureEvm<Primitives: NodePrimitives<Block = P::Block>> + 'static,
154 P: BlockReader + StateProviderFactory + Clone + Unpin + 'static,
155{
156 type Item = BackfillJobResult<BatchBlockStreamItem<E::Primitives>>;
157
158 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
159 let this = self.get_mut();
160
161 loop {
162 while this.tasks.len() < this.parallelism {
164 let mut range = this.range.by_ref().take(this.batch_size);
166 let start = range.next();
167 let range_bounds = start.zip(range.last().or(start));
168
169 let Some(range) = range_bounds.map(|(first, last)| first..=last) else {
171 debug!(target: "exex::backfill", tasks = %this.tasks.len(), range = ?this.range, "No more block batches to backfill");
172 break;
173 };
174
175 debug!(target: "exex::backfill", tasks = %this.tasks.len(), ?range, "Spawning new block batch backfill task");
177 let job = Box::new(BackfillJob {
178 evm_config: this.evm_config.clone(),
179 provider: this.provider.clone(),
180 prune_modes: this.prune_modes.clone(),
181 thresholds: this.thresholds.clone(),
182 range,
183 stream_parallelism: this.parallelism,
184 }) as BackfillTaskIterator<_>;
185 this.push_back(job);
186 }
187
188 let res = ready!(this.poll_next_task(cx));
189
190 if res.is_some() {
191 return Poll::Ready(res);
192 }
193
194 if this.range.is_empty() {
195 return Poll::Ready(None);
197 }
198 }
199 }
200}
201
202impl<E, P> From<SingleBlockBackfillJob<E, P>>
203 for StreamBackfillJob<E, P, SingleBlockStreamItem<E::Primitives>>
204where
205 E: ConfigureEvm,
206{
207 fn from(job: SingleBlockBackfillJob<E, P>) -> Self {
208 Self {
209 evm_config: job.evm_config,
210 provider: job.provider,
211 prune_modes: PruneModes::default(),
212 range: job.range,
213 tasks: FuturesOrdered::new(),
214 parallelism: job.stream_parallelism,
215 batch_size: 1,
216 thresholds: ExecutionStageThresholds { max_blocks: Some(1), ..Default::default() },
217 }
218 }
219}
220
221impl<E, P> From<BackfillJob<E, P>> for StreamBackfillJob<E, P, BatchBlockStreamItem<E::Primitives>>
222where
223 E: ConfigureEvm,
224{
225 fn from(job: BackfillJob<E, P>) -> Self {
226 let batch_size = job.thresholds.max_blocks.map_or(DEFAULT_BATCH_SIZE, |max| max as usize);
227 Self {
228 evm_config: job.evm_config,
229 provider: job.provider,
230 prune_modes: job.prune_modes,
231 range: job.range,
232 tasks: FuturesOrdered::new(),
233 parallelism: job.stream_parallelism,
234 batch_size,
235 thresholds: ExecutionStageThresholds {
236 max_blocks: Some(batch_size as u64),
237 ..job.thresholds
238 },
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::{
247 backfill::test_utils::{
248 blocks_and_execution_outcome, blocks_and_execution_outputs, chain_spec,
249 execute_block_and_commit_to_database,
250 },
251 BackfillJobFactory,
252 };
253 use alloy_consensus::{constants::ETH_TO_WEI, Header, TxEip2930};
254 use alloy_primitives::{b256, Address, TxKind, U256};
255 use eyre::Result;
256 use futures::StreamExt;
257 use reth_chainspec::{ChainSpec, EthereumHardfork, MIN_TRANSACTION_GAS};
258 use reth_db_common::init::init_genesis;
259 use reth_ethereum_primitives::{Block, BlockBody, Transaction};
260 use reth_evm_ethereum::EthEvmConfig;
261 use reth_primitives_traits::{Block as _, NodePrimitives};
262 use reth_provider::{
263 providers::{BlockchainProvider, ProviderNodeTypes},
264 test_utils::create_test_provider_factory_with_chain_spec,
265 ProviderFactory,
266 };
267 use reth_stages_api::ExecutionStageThresholds;
268 use reth_testing_utils::{generators, generators::sign_tx_with_key_pair};
269 use secp256k1::Keypair;
270 use std::sync::Arc;
271
272 #[tokio::test]
273 async fn test_single_blocks() -> eyre::Result<()> {
274 reth_tracing::init_test_tracing();
275
276 let key_pair = generators::generate_key(&mut generators::rng());
278 let address =
279 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
280
281 let chain_spec = chain_spec(address);
282
283 let executor = EthEvmConfig::ethereum(chain_spec.clone());
284 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
285 init_genesis(&provider_factory)?;
286 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
287
288 let blocks_and_execution_outcomes =
290 blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?;
291
292 let factory = BackfillJobFactory::new(executor.clone(), blockchain_db.clone());
294 let mut backfill_stream = factory.backfill(1..=1).into_single_blocks().into_stream();
295
296 let (block, execution_output) = backfill_stream.next().await.unwrap().unwrap();
298 let expected_block = blocks_and_execution_outcomes[0].0.clone();
299 let expected_output = &blocks_and_execution_outcomes[0].1;
300 assert_eq!(block, expected_block);
301 assert_eq!(&execution_output, expected_output);
302
303 assert!(backfill_stream.next().await.is_none());
305
306 Ok(())
307 }
308
309 #[tokio::test]
310 async fn test_batch() -> eyre::Result<()> {
311 reth_tracing::init_test_tracing();
312
313 let key_pair = generators::generate_key(&mut generators::rng());
315 let address =
316 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
317
318 let chain_spec = chain_spec(address);
319
320 let executor = EthEvmConfig::ethereum(chain_spec.clone());
321 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
322 init_genesis(&provider_factory)?;
323 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
324
325 let (blocks, execution_outcome) =
327 blocks_and_execution_outcome(provider_factory, chain_spec, key_pair)?;
328
329 let factory = BackfillJobFactory::new(executor.clone(), blockchain_db.clone())
331 .with_thresholds(ExecutionStageThresholds { max_blocks: Some(2), ..Default::default() })
332 .with_stream_parallelism(1);
333 let mut backfill_stream = factory.backfill(1..=2).into_stream();
334 let chain = backfill_stream.next().await.unwrap().unwrap();
335
336 assert!(chain.blocks_iter().map(|block| block.as_ref()).eq(&blocks));
337 assert_eq!(chain.execution_outcome(), &execution_outcome);
338
339 assert!(backfill_stream.next().await.is_none());
341
342 Ok(())
343 }
344
345 fn create_blocks(
346 chain_spec: &Arc<ChainSpec>,
347 key_pair: Keypair,
348 n: u64,
349 ) -> Result<Vec<RecoveredBlock<reth_ethereum_primitives::Block>>> {
350 let mut blocks = Vec::with_capacity(n as usize);
351 let mut parent_hash = chain_spec.genesis_hash();
352
353 for (i, nonce) in (1..=n).zip(0..n) {
354 let block = Block {
355 header: Header {
356 parent_hash,
357 receipts_root: b256!(
359 "0xd3a6acf9a244d78b33831df95d472c4128ea85bf079a1d41e32ed0b7d2244c9e"
360 ),
361 difficulty: chain_spec.fork(EthereumHardfork::Paris).ttd().expect("Paris TTD"),
362 number: i,
363 gas_limit: MIN_TRANSACTION_GAS,
364 gas_used: MIN_TRANSACTION_GAS,
365 ..Default::default()
366 },
367 body: BlockBody {
368 transactions: vec![sign_tx_with_key_pair(
369 key_pair,
370 Transaction::Eip2930(TxEip2930 {
371 chain_id: chain_spec.chain.id(),
372 nonce,
373 gas_limit: MIN_TRANSACTION_GAS,
374 gas_price: 1_500_000_000,
375 to: TxKind::Call(Address::ZERO),
376 value: U256::from(0.1 * ETH_TO_WEI as f64),
377 ..Default::default()
378 }),
379 )],
380 ..Default::default()
381 },
382 }
383 .try_into_recovered()?;
384
385 parent_hash = block.hash();
386 blocks.push(block);
387 }
388
389 Ok(blocks)
390 }
391
392 fn execute_and_commit_blocks<N>(
393 provider_factory: &ProviderFactory<N>,
394 chain_spec: &Arc<ChainSpec>,
395 blocks: &[RecoveredBlock<reth_ethereum_primitives::Block>],
396 ) -> Result<()>
397 where
398 N: ProviderNodeTypes<
399 Primitives: NodePrimitives<
400 Block = reth_ethereum_primitives::Block,
401 BlockBody = reth_ethereum_primitives::BlockBody,
402 Receipt = reth_ethereum_primitives::Receipt,
403 >,
404 >,
405 {
406 for block in blocks {
407 execute_block_and_commit_to_database(provider_factory, chain_spec.clone(), block)?;
408 }
409 Ok(())
410 }
411
412 #[tokio::test]
413 async fn test_batch_parallel_range_advance() -> Result<()> {
414 reth_tracing::init_test_tracing();
415
416 let key_pair = generators::generate_key(&mut generators::rng());
418 let address =
419 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
420
421 let chain_spec = chain_spec(address);
422
423 let executor = EthEvmConfig::ethereum(chain_spec.clone());
424 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
425 init_genesis(&provider_factory)?;
426 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
427
428 let blocks = create_blocks(&chain_spec, key_pair, 4)?;
430 execute_and_commit_blocks(&provider_factory, &chain_spec, &blocks)?;
431
432 let factory = BackfillJobFactory::new(executor.clone(), blockchain_db.clone())
434 .with_thresholds(ExecutionStageThresholds { max_blocks: Some(2), ..Default::default() })
435 .with_stream_parallelism(2);
436
437 let mut backfill_stream = factory.backfill(1..=4).into_stream();
439
440 let chain1 = backfill_stream.next().await.unwrap()?;
442 let chain2 = backfill_stream.next().await.unwrap()?;
443 assert!(backfill_stream.next().await.is_none());
444
445 let factory_seq =
447 BackfillJobFactory::new(executor.clone(), blockchain_db.clone()).with_thresholds(
448 ExecutionStageThresholds { max_blocks: Some(2), ..Default::default() },
449 );
450
451 let expected_chain1 =
452 factory_seq.backfill(1..=2).collect::<Result<Vec<_>, _>>()?.into_iter().next().unwrap();
453 let expected_chain2 =
454 factory_seq.backfill(3..=4).collect::<Result<Vec<_>, _>>()?.into_iter().next().unwrap();
455
456 assert_eq!(chain1.blocks(), expected_chain1.blocks());
458 assert_eq!(chain1.execution_outcome(), expected_chain1.execution_outcome());
459 assert_eq!(chain2.blocks(), expected_chain2.blocks());
460 assert_eq!(chain2.execution_outcome(), expected_chain2.execution_outcome());
461
462 Ok(())
463 }
464}