99 "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs"
1010 "github.com/bsv-blockchain/go-wallet-toolbox/pkg/internal/logging"
1111 "github.com/bsv-blockchain/go-wallet-toolbox/pkg/services/chaintracks/ingest"
12+ "github.com/bsv-blockchain/go-wallet-toolbox/pkg/services/chaintracks/internal"
1213 "github.com/bsv-blockchain/go-wallet-toolbox/pkg/services/chaintracks/models"
1314 "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wdk"
1415)
@@ -33,9 +34,18 @@ func newBulkManager(logger *slog.Logger, bulkIngestors []NamedBulkIngestor, chai
3334}
3435
3536func (bm * bulkManager ) SyncBulkStorage (ctx context.Context , presentHeight uint , initialRanges models.HeightRanges ) error {
37+ if presentHeight <= liveHeightThreshold {
38+ bm .logger .Info ("Skipping bulk synchronization - present height below live height threshold" , slog .Any ("present_height" , presentHeight ), slog .Any ("live_height_threshold" , liveHeightThreshold ))
39+ return nil
40+ }
41+
3642 bm .logger .Info ("Starting bulk synchronization" , slog .Any ("present_height" , presentHeight ), slog .Any ("initial_ranges" , initialRanges ))
3743
38- missingRange := models .NewHeightRange (0 , presentHeight )
44+ missingRange , err := models .NewHeightRange (0 , presentHeight - liveHeightThreshold ).Subtract (initialRanges .Bulk )
45+ if err != nil {
46+ return fmt .Errorf ("failed to compute missing bulk range: %w" , err )
47+ }
48+
3949 for _ , ingestor := range bm .bulkIngestors {
4050 if missingRange .IsEmpty () {
4151 break
@@ -47,7 +57,7 @@ func (bm *bulkManager) SyncBulkStorage(ctx context.Context, presentHeight uint,
4757 return fmt .Errorf ("bulk synchronization failed for ingestor %s: %w" , ingestor .Name , err )
4858 }
4959
50- if err := bm .processBulkChunks (ctx , bulkChunks , downloader ); err != nil {
60+ if err := bm .processBulkChunks (ctx , bulkChunks , downloader , missingRange . MaxHeight ); err != nil {
5161 return fmt .Errorf ("failed to process bulk chunks from ingestor %s: %w" , ingestor .Name , err )
5262 }
5363
@@ -80,6 +90,13 @@ func (bm *bulkManager) FindHeaderForHeight(height uint) (*wdk.ChainBlockHeader,
8090 return bm .container .FindHeaderForHeight (height )
8191}
8292
93+ func (bm * bulkManager ) LastHeader () (* wdk.ChainBlockHeader , * internal.ChainWork , error ) {
94+ bm .locker .RLock ()
95+ defer bm .locker .RUnlock ()
96+
97+ return bm .container .LastHeader ()
98+ }
99+
83100func (bm * bulkManager ) FilesInfo () * ingest.BulkHeaderFilesInfo {
84101 bm .locker .RLock ()
85102 defer bm .locker .RUnlock ()
@@ -97,7 +114,7 @@ func (bm *bulkManager) GetFileDataByIndex(fileID int) (*ingest.BulkFileData, err
97114 return bm .container .GetFileDataByIndex (fileID )
98115}
99116
100- func (bm * bulkManager ) processBulkChunks (ctx context.Context , bulkChunks []ingest.BulkHeaderMinimumInfo , downloader ingest.BulkFileDownloader ) error {
117+ func (bm * bulkManager ) processBulkChunks (ctx context.Context , bulkChunks []ingest.BulkHeaderMinimumInfo , downloader ingest.BulkFileDownloader , maxHeight uint ) error {
101118 chunksToLoad := bm .getChunksToLoad (bulkChunks )
102119 type chunkWithInfo struct {
103120 data []byte
@@ -130,7 +147,12 @@ func (bm *bulkManager) processBulkChunks(ctx context.Context, bulkChunks []inges
130147 continue
131148 }
132149
133- if err := bm .container .Add (ctx , fileData .data , fileData .info .ToHeightRange ()); err != nil {
150+ dataRange := fileData .info .ToHeightRange ()
151+ if dataRange .MaxHeight > maxHeight {
152+ dataRange .MaxHeight = maxHeight
153+ }
154+
155+ if err := bm .container .Add (ctx , fileData .data , dataRange ); err != nil {
134156 return fmt .Errorf ("failed to add bulk file %v to container: %w" , fileData .info , err )
135157 }
136158 }
@@ -157,3 +179,82 @@ func (bm *bulkManager) shouldAddNewFile(info *ingest.BulkHeaderMinimumInfo) bool
157179 rangeToAdd := info .ToHeightRange ().Above (currentRange )
158180 return ! rangeToAdd .IsEmpty ()
159181}
182+
183+ func (bm * bulkManager ) GetGapHeadersAsLive (ctx context.Context , presentHeight uint , liveInitialRange models.HeightRange ) ([]wdk.ChainBlockHeader , error ) {
184+ var newLiveHeaders []wdk.ChainBlockHeader
185+ maxBulkHeight := bm .GetHeightRange ().MaxHeight
186+ minLiveHeight := presentHeight
187+ if liveInitialRange .NotEmpty () {
188+ minLiveHeight = liveInitialRange .MinHeight
189+ }
190+
191+ if minLiveHeight <= maxBulkHeight || minLiveHeight < maxBulkHeight + addLiveRecursionLimit {
192+ // no gap to fill
193+ return nil , nil
194+ }
195+
196+ // use bulk ingestors to fill the gap and treat fetched headers as live headers
197+ missingRange := models .NewHeightRange (maxBulkHeight + 1 , minLiveHeight - 1 )
198+
199+ for _ , ingestor := range bm .bulkIngestors {
200+ if missingRange .IsEmpty () {
201+ break
202+ }
203+
204+ bulkChunks , downloader , err := ingestor .Ingestor .Synchronize (ctx , presentHeight , missingRange )
205+ if err != nil {
206+ bm .logger .Error ("Chaintracks service - error during bulk synchronization to fill gap" , slog .String ("ingestor_name" , ingestor .Name ), slog .String ("error" , err .Error ()))
207+ return nil , fmt .Errorf ("bulk synchronization to fill gap failed for ingestor %s: %w" , ingestor .Name , err )
208+ }
209+
210+ for _ , chunk := range bulkChunks {
211+ intersection := chunk .ToHeightRange ().Intersect (missingRange )
212+ if intersection .IsEmpty () {
213+ continue
214+ }
215+
216+ data , err := downloader (ctx , chunk )
217+ if err != nil {
218+ return nil , fmt .Errorf ("failed to download bulk file %v to fill gap: %w" , chunk , err )
219+ }
220+
221+ if err := chunk .Validate (data ); err != nil {
222+ return nil , fmt .Errorf ("downloaded bulk file %v to fill gap is invalid: %w" , chunk , err )
223+ }
224+
225+ minIndex := intersection .MinHeight - chunk .FirstHeight
226+ maxIndex := intersection .MaxHeight - chunk .FirstHeight
227+
228+ for i := minIndex ; i <= maxIndex ; i ++ {
229+ startByte := i * 80
230+ endByte := startByte + 80
231+ headerData := data [startByte :endByte ]
232+
233+ baseHeader , err := wdk .ChainBaseBlockHeaderFromBytes (headerData )
234+ if err != nil {
235+ return nil , fmt .Errorf ("failed to parse block header at height %d from bulk file %v: %w" , chunk .FirstHeight + i , chunk , err )
236+ }
237+
238+ blockHash , err := baseHeader .CalculateHash ()
239+ if err != nil {
240+ return nil , fmt .Errorf ("failed to compute hash for block header at height %d from bulk file %v: %w" , chunk .FirstHeight + i , chunk , err )
241+ }
242+
243+ header := wdk.ChainBlockHeader {
244+ ChainBaseBlockHeader : * baseHeader ,
245+ Height : chunk .FirstHeight + i ,
246+ Hash : blockHash .String (),
247+ }
248+
249+ newLiveHeaders = append (newLiveHeaders , header )
250+ }
251+
252+ missingRange , err = missingRange .Subtract (intersection )
253+ if err != nil {
254+ return nil , fmt .Errorf ("failed to compute missing range after filling gap with ingestor %s: %w" , ingestor .Name , err )
255+ }
256+ }
257+ }
258+
259+ return newLiveHeaders , nil
260+ }
0 commit comments