diff --git a/crates/stages/stages/src/sets.rs b/crates/stages/stages/src/sets.rs index 015be507336..48a2a995809 100644 --- a/crates/stages/stages/src/sets.rs +++ b/crates/stages/stages/src/sets.rs @@ -270,8 +270,14 @@ where Stage, { fn builder(self) -> StageSetBuilder { - StageSetBuilder::default() - .add_stage(EraStage::new(self.era_import_source, self.stages_config.etl.clone())) + let mut builder = StageSetBuilder::default(); + + if self.era_import_source.is_some() { + builder = builder + .add_stage(EraStage::new(self.era_import_source, self.stages_config.etl.clone())); + } + + builder .add_stage(HeaderStage::new( self.provider, self.header_downloader, diff --git a/crates/stages/stages/src/stages/era.rs b/crates/stages/stages/src/stages/era.rs index 10598f90112..6fa10a297c7 100644 --- a/crates/stages/stages/src/stages/era.rs +++ b/crates/stages/stages/src/stages/era.rs @@ -204,13 +204,24 @@ where height } else { - // It's possible for a pipeline sync to be executed with a None target, e.g. after a - // stage was manually dropped, and `reth node` is then called without a `--debug.tip`. + // No era files to process. Return the highest block we're aware of to avoid + // limiting subsequent stages with an outdated checkpoint. // - // In this case we don't want to simply default to zero, as that would overwrite the - // previously stored checkpoint block number. Instead we default to that previous - // checkpoint. - input.target.unwrap_or_else(|| input.checkpoint().block_number) + // This can happen when: + // 1. Era import is complete (all pre-merge blocks imported) + // 2. No era import source was configured + // + // We return max(checkpoint, highest_header, target) to ensure we don't return + // a stale checkpoint that could limit subsequent stages like Headers. + let highest_header = provider + .static_file_provider() + .get_highest_static_file_block(StaticFileSegment::Headers) + .unwrap_or_default(); + + let checkpoint = input.checkpoint().block_number; + let from_target = input.target.unwrap_or(checkpoint); + + checkpoint.max(highest_header).max(from_target) }; Ok(ExecOutput { checkpoint: StageCheckpoint::new(height), done: height >= input.target() })