1use crate::{backfill::stream::SingleBlockStreamItem, StreamBackfillJob};
2use reth_evm::{
3 database::StateProviderDatabase, BlockExecutionError, BlockExecutionOutput, ConfigureEvm,
4 Executor,
5};
6use std::{
7 collections::BTreeMap,
8 ops::RangeInclusive,
9 time::{Duration, Instant},
10};
11
12use alloy_consensus::BlockHeader;
13use alloy_primitives::BlockNumber;
14use reth_node_api::{Block as _, BlockBody as _, NodePrimitives};
15use reth_primitives_traits::{format_gas_throughput, RecoveredBlock, SignedTransaction};
16use reth_provider::{
17 BlockReader, Chain, ExecutionOutcome, ProviderError, StateProvider, StateProviderFactory,
18 TransactionVariant,
19};
20use reth_prune_types::PruneModes;
21use reth_stages_api::ExecutionStageThresholds;
22use tracing::{debug, trace};
23
24pub(super) type BackfillJobResult<T> = Result<T, BlockExecutionError>;
25
26#[derive(Debug)]
32pub struct BackfillJob<E, P> {
33 pub(crate) evm_config: E,
34 pub(crate) provider: P,
35 pub(crate) prune_modes: PruneModes,
36 pub(crate) thresholds: ExecutionStageThresholds,
37 pub(crate) range: RangeInclusive<BlockNumber>,
38 pub(crate) stream_parallelism: usize,
39}
40
41impl<E, P> Iterator for BackfillJob<E, P>
42where
43 E: ConfigureEvm<Primitives: NodePrimitives<Block = P::Block>> + 'static,
44 P: BlockReader<Transaction: SignedTransaction> + StateProviderFactory,
45{
46 type Item = BackfillJobResult<Chain<E::Primitives>>;
47
48 fn next(&mut self) -> Option<Self::Item> {
49 if self.range.is_empty() {
50 return None
51 }
52
53 Some(self.execute_range())
54 }
55}
56
57impl<E, P> BackfillJob<E, P>
58where
59 E: ConfigureEvm<Primitives: NodePrimitives<Block = P::Block>> + 'static,
60 P: BlockReader<Transaction: SignedTransaction> + StateProviderFactory,
61{
62 pub fn into_single_blocks(self) -> SingleBlockBackfillJob<E, P> {
64 self.into()
65 }
66
67 pub fn into_stream(self) -> StreamBackfillJob<E, P, Chain<E::Primitives>> {
69 self.into()
70 }
71
72 fn execute_range(&mut self) -> BackfillJobResult<Chain<E::Primitives>> {
73 debug!(
74 target: "exex::backfill",
75 range = ?self.range,
76 "Executing block range"
77 );
78
79 let mut executor = self.evm_config.batch_executor(StateProviderDatabase::new(
80 self.provider
81 .history_by_block_number(self.range.start().saturating_sub(1))
82 .map_err(BlockExecutionError::other)?
83 .into_evm_state_provider(),
84 ));
85
86 let mut fetch_block_duration = Duration::default();
87 let mut execution_duration = Duration::default();
88 let mut cumulative_gas = 0;
89 let batch_start = Instant::now();
90
91 let mut blocks = Vec::new();
92 let mut results = Vec::new();
93 for block_number in self.range.clone() {
94 let fetch_block_start = Instant::now();
96
97 let block = self
99 .provider
100 .sealed_block_with_senders(block_number.into(), TransactionVariant::WithHash)
101 .map_err(BlockExecutionError::other)?
102 .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))
103 .map_err(BlockExecutionError::other)?;
104
105 fetch_block_duration += fetch_block_start.elapsed();
106
107 cumulative_gas += block.gas_used();
108
109 trace!(target: "exex::backfill", number = block_number, txs = block.body().transactions().len(), "Executing block");
111
112 let execute_start = Instant::now();
114
115 let (block, senders) = block.split_sealed();
117 let (header, body) = block.split_sealed_header_body();
118 let block = P::Block::new_sealed(header, body).with_senders(senders);
119
120 results.push(executor.execute_one(&block)?);
121 execution_duration += execute_start.elapsed();
122
123 blocks.push(block);
125 if self.thresholds.is_end_of_batch(
127 block_number - *self.range.start() + 1,
128 executor.size_hint() as u64,
129 cumulative_gas,
130 batch_start.elapsed(),
131 ) {
132 break
133 }
134 }
135
136 let first_block_number = blocks.first().expect("blocks should not be empty").number();
137 let last_block_number = blocks.last().expect("blocks should not be empty").number();
138 debug!(
139 target: "exex::backfill",
140 range = ?*self.range.start()..=last_block_number,
141 block_fetch = ?fetch_block_duration,
142 execution = ?execution_duration,
143 throughput = format_gas_throughput(cumulative_gas, execution_duration),
144 "Finished executing block range"
145 );
146 self.range = last_block_number + 1..=*self.range.end();
147
148 let outcome =
149 ExecutionOutcome::from_blocks(first_block_number, executor.into_state(), results);
150 let chain = Chain::new(blocks, outcome, BTreeMap::new());
151 Ok(chain)
152 }
153}
154
155#[derive(Debug, Clone)]
160pub struct SingleBlockBackfillJob<E, P> {
161 pub(crate) evm_config: E,
162 pub(crate) provider: P,
163 pub(crate) range: RangeInclusive<BlockNumber>,
164 pub(crate) stream_parallelism: usize,
165}
166
167impl<E, P> Iterator for SingleBlockBackfillJob<E, P>
168where
169 E: ConfigureEvm<Primitives: NodePrimitives<Block = P::Block>> + 'static,
170 P: BlockReader + StateProviderFactory,
171{
172 type Item = BackfillJobResult<(
173 RecoveredBlock<P::Block>,
174 BlockExecutionOutput<<E::Primitives as NodePrimitives>::Receipt>,
175 )>;
176
177 fn next(&mut self) -> Option<Self::Item> {
178 self.range.next().map(|block_number| self.execute_block(block_number))
179 }
180}
181
182impl<E, P> SingleBlockBackfillJob<E, P>
183where
184 E: ConfigureEvm<Primitives: NodePrimitives<Block = P::Block>> + 'static,
185 P: BlockReader + StateProviderFactory,
186{
187 pub fn into_stream(self) -> StreamBackfillJob<E, P, SingleBlockStreamItem<E::Primitives>> {
189 self.into()
190 }
191
192 #[expect(clippy::type_complexity)]
193 pub(crate) fn execute_block(
194 &self,
195 block_number: u64,
196 ) -> BackfillJobResult<(
197 RecoveredBlock<P::Block>,
198 BlockExecutionOutput<<E::Primitives as NodePrimitives>::Receipt>,
199 )> {
200 let block_with_senders = self
202 .provider
203 .recovered_block(block_number.into(), TransactionVariant::WithHash)
204 .map_err(BlockExecutionError::other)?
205 .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))
206 .map_err(BlockExecutionError::other)?;
207
208 let executor = self.evm_config.batch_executor(StateProviderDatabase::new(
210 self.provider
211 .history_by_block_number(block_number.saturating_sub(1))
212 .map_err(BlockExecutionError::other)?
213 .into_evm_state_provider(),
214 ));
215
216 trace!(target: "exex::backfill", number = block_number, txs = block_with_senders.body().transaction_count(), "Executing block");
217
218 let block_execution_output = executor.execute(&block_with_senders)?;
219
220 Ok((block_with_senders, block_execution_output))
221 }
222}
223
224impl<E, P> From<BackfillJob<E, P>> for SingleBlockBackfillJob<E, P> {
225 fn from(job: BackfillJob<E, P>) -> Self {
226 Self {
227 evm_config: job.evm_config,
228 provider: job.provider,
229 range: job.range,
230 stream_parallelism: job.stream_parallelism,
231 }
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use crate::{
238 backfill::{
239 job::ExecutionStageThresholds,
240 test_utils::{blocks_and_execution_outputs, chain_spec, to_execution_outcome},
241 },
242 BackfillJobFactory,
243 };
244 use alloy_consensus::BlockHeader;
245 use alloy_primitives::Address;
246 use reth_db_common::init::init_genesis;
247 use reth_evm_ethereum::EthEvmConfig;
248 use reth_provider::{
249 providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec,
250 };
251 use reth_testing_utils::generators;
252
253 #[test]
254 fn test_backfill() -> eyre::Result<()> {
255 reth_tracing::init_test_tracing();
256
257 let key_pair = generators::generate_key(&mut generators::rng());
259 let address =
260 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
261
262 let chain_spec = chain_spec(address);
263
264 let executor = EthEvmConfig::ethereum(chain_spec.clone());
265 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
266 init_genesis(&provider_factory)?;
267 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
268
269 let blocks_and_execution_outputs =
270 blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?;
271 let (block, block_execution_output) = blocks_and_execution_outputs.first().unwrap();
272 let execution_outcome = to_execution_outcome(block.number, block_execution_output);
273
274 let factory = BackfillJobFactory::new(executor, blockchain_db);
276 let job = factory.backfill(1..=1);
277 let chains = job.collect::<Result<Vec<_>, _>>()?;
278
279 assert_eq!(chains.len(), 1);
282 let chain = chains.into_iter().next().unwrap();
283 assert_eq!(chain.blocks().len(), 1);
284 assert_eq!(chain.blocks().get(&1).map(|block| block.as_ref()), Some(block));
285 assert_eq!(chain.execution_outcome(), &execution_outcome);
286
287 Ok(())
288 }
289
290 #[test]
291 fn test_single_block_backfill() -> eyre::Result<()> {
292 reth_tracing::init_test_tracing();
293
294 let key_pair = generators::generate_key(&mut generators::rng());
296 let address =
297 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
298
299 let chain_spec = chain_spec(address);
300
301 let executor = EthEvmConfig::ethereum(chain_spec.clone());
302 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
303 init_genesis(&provider_factory)?;
304 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
305
306 let blocks_and_execution_outcomes =
307 blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?;
308
309 let factory = BackfillJobFactory::new(executor, blockchain_db);
311 let job = factory.backfill(1..=1);
312 let single_job = job.into_single_blocks();
313 let block_execution_it = single_job.into_iter();
314
315 let blocks_and_outcomes = block_execution_it.collect::<Vec<_>>();
317 assert_eq!(blocks_and_outcomes.len(), 1);
318
319 for (i, res) in blocks_and_outcomes.into_iter().enumerate() {
322 let (block, execution_output) = res?;
323
324 let expected_block = blocks_and_execution_outcomes[i].0.clone();
325 let expected_output = &blocks_and_execution_outcomes[i].1;
326
327 assert_eq!(block, expected_block);
328 assert_eq!(&execution_output, expected_output);
329 }
330
331 Ok(())
332 }
333
334 #[test]
342 fn test_backfill_state_provider_parity() -> eyre::Result<()> {
343 reth_tracing::init_test_tracing();
344
345 let key_pair = generators::generate_key(&mut generators::rng());
346 let address =
347 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
348
349 let chain_spec = chain_spec(address);
350
351 let executor = EthEvmConfig::ethereum(chain_spec.clone());
352 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
353 init_genesis(&provider_factory)?;
354 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
355
356 let pipeline_results =
359 blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?;
360
361 let factory = BackfillJobFactory::new(executor, blockchain_db);
363 let job = factory.backfill(1..=2);
364 let single_job = job.into_single_blocks();
365 let backfill_results: Vec<_> = single_job.into_iter().collect::<Result<Vec<_>, _>>()?;
366
367 assert_eq!(
368 pipeline_results.len(),
369 backfill_results.len(),
370 "should produce same number of block results"
371 );
372
373 for (i, ((pipeline_block, pipeline_output), (backfill_block, backfill_output))) in
374 pipeline_results.iter().zip(backfill_results).enumerate()
375 {
376 assert_eq!(
377 backfill_block, *pipeline_block,
378 "block {i} mismatch between pipeline and backfill"
379 );
380
381 assert_eq!(
382 backfill_output.receipts, pipeline_output.receipts,
383 "block {i}: receipts differ — gas accounting divergence between \
384 LatestStateProvider and history_by_block_number"
385 );
386
387 assert_eq!(
388 backfill_output.gas_used, pipeline_output.gas_used,
389 "block {i}: gas_used differs"
390 );
391
392 assert_eq!(
393 &backfill_output, pipeline_output,
394 "block {i}: full execution output differs between pipeline and backfill"
395 );
396 }
397
398 Ok(())
399 }
400
401 #[test]
405 fn test_backfill_batch_state_provider_parity() -> eyre::Result<()> {
406 reth_tracing::init_test_tracing();
407
408 let key_pair = generators::generate_key(&mut generators::rng());
409 let address =
410 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
411
412 let chain_spec = chain_spec(address);
413
414 let executor = EthEvmConfig::ethereum(chain_spec.clone());
415 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
416 init_genesis(&provider_factory)?;
417 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
418
419 let pipeline_results =
420 blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?;
421
422 let factory = BackfillJobFactory::new(executor, blockchain_db);
424 let job = factory.backfill(1..=2);
425 let chains = job.collect::<Result<Vec<_>, _>>()?;
426
427 assert_eq!(chains.len(), 1, "two blocks without threshold should yield one chain");
428 let chain = chains.into_iter().next().unwrap();
429
430 for (i, (pipeline_block, pipeline_output)) in pipeline_results.iter().enumerate() {
432 let block_number = pipeline_block.number();
433 let chain_block = chain.blocks().get(&block_number).expect("block should be in chain");
434 assert_eq!(
435 chain_block.as_ref(),
436 pipeline_block,
437 "block {i}: block mismatch in batch backfill"
438 );
439
440 let chain_receipts = &chain.execution_outcome().receipts[i];
441 assert_eq!(
442 chain_receipts, &pipeline_output.receipts,
443 "block {i}: receipts differ in batch backfill — potential gas accounting \
444 divergence between LatestStateProvider and history_by_block_number"
445 );
446 }
447
448 let mut expected = to_execution_outcome(1, &pipeline_results[0].1);
449 expected.extend(to_execution_outcome(2, &pipeline_results[1].1));
450 expected.state_mut().reverts.sort();
451 let mut actual = chain.execution_outcome().clone();
452 actual.state_mut().reverts.sort();
453 assert_eq!(actual, expected, "batch state and reverts must match sequential execution");
454
455 Ok(())
456 }
457
458 #[test]
459 fn test_backfill_with_batch_threshold() -> eyre::Result<()> {
460 reth_tracing::init_test_tracing();
461
462 let key_pair = generators::generate_key(&mut generators::rng());
464 let address =
465 Address::from_raw_public_key(&key_pair.public_key().serialize_uncompressed()[1..]);
466
467 let chain_spec = chain_spec(address);
468
469 let executor = EthEvmConfig::ethereum(chain_spec.clone());
470 let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
471 init_genesis(&provider_factory)?;
472 let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
473
474 let blocks_and_execution_outputs =
475 blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?;
476 let (block1, output1) = blocks_and_execution_outputs[0].clone();
477 let (block2, output2) = blocks_and_execution_outputs[1].clone();
478
479 let factory = BackfillJobFactory::new(executor, blockchain_db).with_thresholds(
481 ExecutionStageThresholds { max_blocks: Some(1), ..Default::default() },
482 );
483 let job = factory.backfill(1..=2);
484 let chains = job.collect::<Result<Vec<_>, _>>()?;
485
486 assert_eq!(chains.len(), 2);
488
489 let chain1 = chains[0].clone();
490 assert_eq!(chain1.blocks().len(), 1);
491 assert_eq!(chain1.blocks().get(&1).map(|block| block.as_ref()), Some(&block1));
492 assert_eq!(chain1.execution_outcome(), &to_execution_outcome(1, &output1));
493
494 let chain2 = chains[1].clone();
495 assert_eq!(chain2.blocks().len(), 1);
496 assert_eq!(chain2.blocks().get(&2).map(|block| block.as_ref()), Some(&block2));
497 assert_eq!(chain2.execution_outcome(), &to_execution_outcome(2, &output2));
498
499 Ok(())
500 }
501}