Skip to content

Commit 837f306

Browse files
chainimport: trigger processing divergence headers region
With this commit, we remove the safe guards that previously preventing importing into non-empty target header stores
1 parent d9c850c commit 837f306

File tree

3 files changed

+224
-373
lines changed

3 files changed

+224
-373
lines changed

chainimport/headers_import.go

Lines changed: 64 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
192187
func (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

Comments
 (0)