@@ -182,28 +182,9 @@ func NewHeadersImport(options *ImportOptions) (*headersImport, error) {
182182 return importer , nil
183183}
184184
185- // Import is a multi-pass algorithm that loads, validates, and processes headers
186- // from the configured import sources into the target header stores. The Import
187- // process is currently performed only if the target stores are completely empty
188- // except for gensis block/filter header otherwise it is entirely skipped. On
189- // first development iteration, it is designed to serve new users who don't yet
190- // have headers data, or existing users who are willing to reset their headers
191- // data.
185+ // Import is a multi-pass algorithm that loads, validates, and processes
186+ // headers from the configured import sources into the target header stores.
192187func (h * headersImport ) Import (ctx context.Context ) (* ImportResult , error ) {
193- isFresh , err := h .isTargetFresh (
194- h .options .TargetBlockHeaderStore ,
195- h .options .TargetFilterHeaderStore ,
196- )
197- if err != nil {
198- return nil , fmt .Errorf ("failed to detect if target stores " +
199- "are fresh import failed: %w" , err )
200- }
201- if ! isFresh {
202- log .Info ("Skipping headers import: target header stores are " +
203- "not empty" )
204- return & ImportResult {}, nil
205- }
206-
207188 result := & ImportResult {
208189 StartTime : time .Now (),
209190 }
@@ -325,50 +306,81 @@ func (h *headersImport) validateChainContinuity() error {
325306 "tip: %w" , err )
326307 }
327308
328- // Ensure that both target header stores have the same tip height.
329- // A mismatch indicates a divergence region that has not yet been
330- // processed. Once a resolution strategy is implemented, this check
331- // will no longer return an error, and the effective tip height will be
332- // defined as the minimum of the two.
309+ // Take the minimum of the two heights as the effective chain tip height
310+ // to handle the case where one store might be ahead in case of existent
311+ // divergence region.
312+ effectiveTipHeight := min (blockTipHeight , filterTipHeight )
333313 if blockTipHeight != filterTipHeight {
334- return fmt . Errorf ( "divergence detected between target header "+
335- "store tip heights (block=%d, filter=%d)" ,
336- blockTipHeight , filterTipHeight )
314+ log . Infof ( "Target header stores at different heights "+
315+ "(block=%d, filter=%d), using effective tip height %d " ,
316+ blockTipHeight , filterTipHeight , effectiveTipHeight )
337317 }
338318
339319 importStartHeight := sourceMetadata .startHeight
340320 importEndHeight := sourceMetadata .endHeight
341321
342- // If import wants to start after height 1, we'd have a gap.
343- if importStartHeight > 1 {
344- return fmt .Errorf ("target stores contain only genesis block " +
345- "(height 0) but import data starts at height %d, " +
346- "creating a gap" , importStartHeight )
347- }
348-
349- // If import includes genesis block (starts at 0), verify it matches.
350- if importStartHeight == 0 {
351- if err := h .verifyHeadersAtTargetHeight (
352- importStartHeight , verifyBlockAndFilter ,
353- ); err != nil {
354- return fmt .Errorf ("genesis header mismatch: %v" , err )
355- }
356- log .Infof ("Genesis headers verified, import data will extend " +
357- "chain from genesis" )
358- } else {
359- // Import starts at height 1, which connects to genesis.
360- // Validate that the block header at height 1 from the import
361- // source connects with the previous header in the target block
362- // store.
322+ switch {
323+ case importStartHeight > effectiveTipHeight + 1 :
324+ // Import data doesn't start at the next height after the target
325+ // tip height, there would be a gap in the chain.
326+ return fmt .Errorf ("import data starts at height %d but target " +
327+ "tip is at %d, creating a gap" , importStartHeight ,
328+ effectiveTipHeight )
329+
330+ case importStartHeight > effectiveTipHeight :
331+ // Import data starts immediately after the target tip height.
332+ // This is a forward extension.
363333 if err := h .validateHeaderConnection (
364334 importStartHeight , blockTipHeight , sourceMetadata ,
365335 ); err != nil {
366336 return fmt .Errorf ("failed to validate header " +
367337 "connection: %v" , err )
368338 }
339+ log .Infof ("Import headers data will extend chain from height " +
340+ "%d" , importStartHeight )
341+
342+ default :
343+ // Import data starts before or at the target tip height. This
344+ // means there is an overlap, so we need to verify compatibility
345+ // using sampling approach for minimal processing time.
346+
347+ // First we need to determine the overlap range.
348+ overlapStart := importStartHeight
349+ overlapEnd := min (effectiveTipHeight , importEndHeight )
350+
351+ // Now we can verify headers at the start of the overlap range.
352+ if err = h .verifyHeadersAtTargetHeight (
353+ overlapStart , verifyBlockAndFilter ,
354+ ); err != nil {
355+ return err
356+ }
369357
370- log .Info ("Target stores contain only genesis block, import " +
371- "data will extend chain from height 1" )
358+ // If overlap range is more than 1 header, we can also verify at
359+ // the end.
360+ if overlapEnd > overlapStart {
361+ if err = h .verifyHeadersAtTargetHeight (
362+ overlapEnd , verifyBlockAndFilter ,
363+ ); err != nil {
364+ return err
365+ }
366+ }
367+
368+ // Validate headers beyond the overlap region if there are any
369+ // remaining headers to import.
370+ if overlapEnd < importEndHeight {
371+ // Ensure the first header from the import source exists
372+ // the overlap range properly connects to the existing
373+ // chain.
374+ if err := h .validateHeaderConnection (
375+ overlapEnd + 1 , blockTipHeight , sourceMetadata ,
376+ ); err != nil {
377+ return fmt .Errorf ("failed to validate header " +
378+ "connection: %v" , err )
379+ }
380+
381+ log .Infof ("Import headers data will extend chain " +
382+ "from height %d" , overlapEnd + 1 )
383+ }
372384 }
373385
374386 log .Infof ("Chain continuity validation successful: import data " +
@@ -981,32 +993,6 @@ func (h *headersImport) validateHeaderConnection(targetStartHeight,
981993 return nil
982994}
983995
984- // isTargetFresh checks if the target header stores are in their initial state,
985- // meaning they contain only the genesis header (height 0).
986- func (h * headersImport ) isTargetFresh (
987- targetBlockHeaderStore headerfs.BlockHeaderStore ,
988- targetFilterHeaderStore headerfs.FilterHeaderStore ) (bool , error ) {
989-
990- // Get the chain tip from both target stores.
991- _ , blockTipHeight , err := targetBlockHeaderStore .ChainTip ()
992- if err != nil {
993- return false , fmt .Errorf ("failed to get target block header " +
994- "chain tip: %w" , err )
995- }
996-
997- _ , filterTipHeight , err := targetFilterHeaderStore .ChainTip ()
998- if err != nil {
999- return false , fmt .Errorf ("failed to get target filter header " +
1000- "chain tip: %w" , err )
1001- }
1002-
1003- if blockTipHeight == 0 && filterTipHeight == 0 {
1004- return true , nil
1005- }
1006-
1007- return false , nil
1008- }
1009-
1010996// openSources initializes and opens all required header import sources. It
1011997// verifies that all necessary import sources and validators are properly
1012998// configured, then opens each source to prepare for data reading. Returns an
0 commit comments