@@ -110,10 +110,16 @@ const (
110110type AppendMode uint8
111111
112112const (
113+ // AppendBlockAndFilter indicates both block and filter headers should
114+ // be appended during synchronization.
113115 AppendBlockAndFilter AppendMode = iota
114116
117+ // AppendBlockOnly indicates only block headers should be appended
118+ // during synchronization.
115119 AppendBlockOnly
116120
121+ // AppendFilterOnly indicates only filter headers should be appended
122+ // during synchronization.
117123 AppendFilterOnly
118124)
119125
@@ -1043,7 +1049,9 @@ func (s *HeadersImport) validateSourcesCompatibility() error {
10431049}
10441050
10451051// validateChainContinuity ensures that headers from import sources can be
1046- // properly connected to the existing headers in the target stores.
1052+ // properly connected to the existing headers in the target stores. It uses
1053+ // sampling method to validate headers data in overlapping ranges (if any) to
1054+ // minimize processing time.
10471055func (s * HeadersImport ) validateChainContinuity () error {
10481056 // Get metadata from block header source. We can safely use this count
10491057 // for both block headers and filter headers since we've already
@@ -1067,51 +1075,80 @@ func (s *HeadersImport) validateChainContinuity() error {
10671075 "tip: %w" , err )
10681076 }
10691077
1070- // Ensure that both target header stores have the same tip height.
1071- // A mismatch indicates a divergence region that has not yet been
1072- // processed. Once a resolution strategy is implemented, this check
1073- // will no longer return an error, and the effective tip height
1074- // will be defined as the minimum of the two.
1078+ // Take the minimum of the two heights as the effective chain tip height
1079+ // to handle the case where one store might be ahead in case of existent
1080+ // divergence region.
1081+ effectiveTipHeight := min (blockTipHeight , filterTipHeight )
10751082 if blockTipHeight != filterTipHeight {
1076- return fmt . Errorf ( "divergence detected between target header "+
1077- "store tip heights (block=%d, filter=%d)" ,
1078- blockTipHeight , filterTipHeight )
1083+ log . Infof ( "Target header stores at different heights "+
1084+ "(block=%d, filter=%d), using effective tip height %d " ,
1085+ blockTipHeight , filterTipHeight , effectiveTipHeight )
10791086 }
10801087
10811088 // Extract import height range.
10821089 importStartHeight := sourceMetadata .StartHeight
10831090 importEndHeight := sourceMetadata .EndHeight
10841091
1085- // If import wants to start after height 1, we'd have a gap.
1086- if importStartHeight > 1 {
1087- return fmt .Errorf ("target stores contain only genesis block " +
1088- "(height 0) but import data starts at height %d, " +
1089- "creating a gap" , importStartHeight )
1090- }
1091-
1092- // If import includes genesis block (starts at 0), verify it matches.
1093- if importStartHeight == 0 {
1094- if err := s .verifyHeadersAtTargetHeight (
1095- importStartHeight , VerifyBlockAndFilter ,
1096- ); err != nil {
1097- return fmt .Errorf ("genesis header mismatch: %v" , err )
1098- }
1099- log .Infof ("Genesis headers verified, import data will extend " +
1100- "chain from genesis" )
1101- } else {
1102- // Import starts at height 1, which connects to genesis.
1103- // Validate that the block header at height 1 from the import
1104- // source connects with the previous header in the target block
1105- // store.
1092+ switch {
1093+ case importStartHeight > effectiveTipHeight + 1 :
1094+ // Import data doesn't start at the next height after the target
1095+ // tip height, there would be a gap in the chain.
1096+ return fmt .Errorf ("import data starts at height %d but target " +
1097+ "tip is at %d, creating a gap" ,
1098+ importStartHeight , effectiveTipHeight )
1099+
1100+ case importStartHeight > effectiveTipHeight :
1101+ // Import data starts immediately after the target tip height.
1102+ // This is a forward extension.
11061103 if err := s .validateHeaderConnection (
1107- importStartHeight , blockTipHeight , sourceMetadata ,
1104+ importStartHeight , blockTipHeight ,
1105+ sourceMetadata ,
11081106 ); err != nil {
11091107 return fmt .Errorf ("failed to validate header " +
11101108 "connection: %v" , err )
11111109 }
1110+ log .Infof ("Import headers data will extend chain from height " +
1111+ "%d" , importStartHeight )
1112+
1113+ case importStartHeight <= effectiveTipHeight :
1114+ // Import data starts before or at the target tip height. This
1115+ // means there is an overlap, so we need to verify compatibility
1116+ // using sampling approach for minimal processing time.
1117+
1118+ // First we need to determine the overlap range.
1119+ overlapStart := importStartHeight
1120+ overlapEnd := min (effectiveTipHeight , importEndHeight )
1121+
1122+ // Now we can verify headers at the start of the overlap range.
1123+ if err = s .verifyHeadersAtTargetHeight (
1124+ overlapStart , VerifyBlockAndFilter ,
1125+ ); err != nil {
1126+ return err
1127+ }
1128+
1129+ // If overlap range is more than 1 header, we can also verify at
1130+ // the end.
1131+ if overlapEnd > overlapStart {
1132+ if err = s .verifyHeadersAtTargetHeight (
1133+ overlapEnd , VerifyBlockAndFilter ,
1134+ ); err != nil {
1135+ return err
1136+ }
1137+ }
1138+
1139+ // If the overlap range is more than 2 headers, also verify at
1140+ // the middle point.
1141+ if overlapEnd - overlapStart > 2 {
1142+ middleHeight := (overlapStart + overlapEnd ) / 2
1143+ if err = s .verifyHeadersAtTargetHeight (
1144+ middleHeight , VerifyBlockAndFilter ,
1145+ ); err != nil {
1146+ return err
1147+ }
1148+ }
11121149
1113- log .Info ( "Target stores contain only genesis block, import " +
1114- "data will extend chain from height 1" )
1150+ log .Infof ( "Sampling validation successful for overlap headers " +
1151+ "range (%d-%d)" , overlapStart , overlapEnd )
11151152 }
11161153
11171154 log .Infof ("Chain continuity validation successful: import data " +
@@ -1568,8 +1605,8 @@ func (s *HeadersImport) processDivergenceHeadersRegion(ctx context.Context,
15681605}
15691606
15701607// validateLeadAndSyncLag resolves divergence between target stores by
1571- // validating the leading store against import source and syncing the lagging
1572- // store with headers from the import source.
1608+ // validating the leading target store against import source and syncing the
1609+ // lagging target store with headers from the import source.
15731610func (s * HeadersImport ) validateLeadAndSyncLag (ctx context.Context ,
15741611 region HeaderRegion , options * ImportOptions ) error {
15751612
@@ -1616,21 +1653,25 @@ func (s *HeadersImport) validateLeadAndSyncLag(ctx context.Context,
16161653
16171654// determineSyncModes determines the verification and append modes based on the
16181655// relative heights of block and filter header stores.
1656+ //
1657+ // NOTE: This is supposed to be called after divergence region has been
1658+ // detected.
16191659func (s * HeadersImport ) determineSyncModes (blockTipHeight ,
1620- filterTipHeight uint32 ) (SyncModes , error ) {
1660+ filterTipHeight uint32 ) (* SyncModes , error ) {
16211661
1622- if blockTipHeight > filterTipHeight {
1623- return SyncModes {
1662+ switch {
1663+ case blockTipHeight > filterTipHeight :
1664+ return & SyncModes {
16241665 Verify : VerifyBlockOnly ,
16251666 Append : AppendFilterOnly ,
16261667 }, nil
1627- } else if blockTipHeight < filterTipHeight {
1628- return SyncModes {
1668+ case blockTipHeight < filterTipHeight :
1669+ return & SyncModes {
16291670 Verify : VerifyFilterOnly ,
16301671 Append : AppendBlockOnly ,
16311672 }, nil
1632- } else {
1633- return SyncModes {} , fmt .Errorf ("both header stores have " +
1673+ default :
1674+ return nil , fmt .Errorf ("both header stores have " +
16341675 "equal height (%d) - this indicates a logic error in " +
16351676 "divergence detection" , blockTipHeight )
16361677 }
@@ -1762,11 +1803,11 @@ func (s *HeadersImport) appendNewHeaders(startHeight, endHeight uint32,
17621803
17631804 for {
17641805 header , hasMore , err := filterIter .Next ()
1765- height := batchStart + uint32 (len (filterHeaders ))
1806+ h := batchStart + uint32 (len (filterHeaders ))
17661807 if err != nil {
17671808 return fmt .Errorf ("failed to read " +
17681809 "filter header at height %d: " +
1769- "%w" , height , err )
1810+ "%w" , h , err )
17701811 }
17711812
17721813 if header != nil {
@@ -1793,31 +1834,25 @@ func (s *HeadersImport) appendNewHeaders(startHeight, endHeight uint32,
17931834 "batch %d-%d" , batchStart , batchEnd )
17941835 }
17951836
1796- // Update the block header hash of the last filter
1797- // header in the very last batch.
1798-
1799- // Get the chain tip from both target stores.
1800- tBHS := s .options .TargetBlockHeaderStore
1801- lH , height , err := tBHS .ChainTip ()
1802- if err != nil {
1803- return fmt .Errorf ("failed to get target block " +
1804- "header chain tip: %w" , err )
1805- }
1806-
18071837 // Check if we are in append filter only mode and
18081838 // this is the very last batch before updating the last
18091839 // header hash.
1810- if appendMode == AppendFilterOnly &&
1811- batchEnd + 1 > endHeight {
1840+ isLastBatch := batchEnd + 1 > endHeight
1841+ if appendMode == AppendFilterOnly && isLastBatch {
1842+ // Get the chain tip from both target stores.
1843+ tBHS := s .options .TargetBlockHeaderStore
1844+ lastH , height , err := tBHS .ChainTip ()
1845+ if err != nil {
1846+ return fmt .Errorf ("failed to get " +
1847+ "target block header chain " +
1848+ "tip: %w" , err )
1849+ }
18121850
1813- lBH := headerfs.BlockHeader {
1814- BlockHeader : lH ,
1851+ lastBH := headerfs.BlockHeader {
1852+ BlockHeader : lastH ,
18151853 Height : height ,
18161854 }
1817- blockHeaders := []headerfs.BlockHeader {lBH }
1818- setLastFilterHeaderHash (
1819- filterHeaders , blockHeaders ,
1820- )
1855+ setLastFilterHeaderHash (filterHeaders , lastBH )
18211856 }
18221857 }
18231858
@@ -1835,7 +1870,8 @@ func (s *HeadersImport) appendNewHeaders(startHeight, endHeight uint32,
18351870 }
18361871
18371872 // Set the filter header hash.
1838- setLastFilterHeaderHash (filterHeaders , blockHeaders )
1873+ lastBH := blockHeaders [len (blockHeaders )- 1 ]
1874+ setLastFilterHeaderHash (filterHeaders , lastBH )
18391875 }
18401876
18411877 // Write the headers to the target stores.
@@ -2172,13 +2208,12 @@ func AddHeadersImportMetadata(sourceFilePath string, chainType wire.BitcoinNet,
21722208// to match the block hash of the corresponding block header. This maintains
21732209// chain tip consistency for the regular tip.
21742210func setLastFilterHeaderHash (filterHeaders []headerfs.FilterHeader ,
2175- blockHeaders [] headerfs.BlockHeader ) {
2211+ chainTipBlockHeader headerfs.BlockHeader ) {
21762212
21772213 // We only need to set the block header hash of the last filter
21782214 // header to maintain chain tip consistency for regular tip.
2179- lastIdx := len (filterHeaders ) - 1
2180- chainTipHash := blockHeaders [lastIdx ].BlockHeader .BlockHash ()
2181- filterHeaders [lastIdx ].HeaderHash = chainTipHash
2215+ chainTipHash := chainTipBlockHeader .BlockHeader .BlockHash ()
2216+ filterHeaders [len (filterHeaders )- 1 ].HeaderHash = chainTipHash
21822217}
21832218
21842219// targetHeightToImportSourceIndex converts the absolute blockchain target
0 commit comments