@@ -260,7 +260,7 @@ func (s *Store[H]) getByHeight(ctx context.Context, height uint64) (H, error) {
260
260
if err != nil {
261
261
var zero H
262
262
if errors .Is (err , datastore .ErrNotFound ) {
263
- return zero , header .ErrNotFound
263
+ return zero , fmt . Errorf ( "height %d: %w" , height , header .ErrNotFound )
264
264
}
265
265
266
266
return zero , err
@@ -378,110 +378,128 @@ func (s *Store[H]) DeleteTo(ctx context.Context, to uint64) error {
378
378
if err := s .deleteRange (ctx , tail .Height (), to ); err != nil {
379
379
return fmt .Errorf ("header/store: delete to height %d: %w" , to , err )
380
380
}
381
- return nil
382
- }
383
-
384
- func (s * Store [H ]) deleteRange (ctx context.Context , from , to uint64 ) error {
385
- batch , err := s .ds .Batch (ctx )
386
- if err != nil {
387
- return fmt .Errorf ("delete batch: %w" , err )
388
- }
389
381
390
- if err := s .prepareDeleteRangeBatch (ctx , batch , from , to ); err != nil {
391
- return fmt .Errorf ("prepare: %w" , err )
392
- }
393
-
394
- if err := s .heightIndex .deleteRange (ctx , batch , from , to ); err != nil {
395
- return fmt .Errorf ("height index: %w" , err )
396
- }
397
-
398
- err = s .updateTail (ctx , batch , to )
399
- if err != nil {
400
- return fmt .Errorf ("update tail: %w" , err )
401
- }
402
-
403
- if err := batch .Commit (ctx ); err != nil {
404
- return fmt .Errorf ("delete commit: %w" , err )
382
+ if head .Height ()+ 1 == to {
383
+ // this is the case where we have deleted all the headers
384
+ // wipe the store
385
+ if err := s .wipe (ctx ); err != nil {
386
+ return fmt .Errorf ("header/store: wipe: %w" , err )
387
+ }
405
388
}
406
389
407
390
return nil
408
391
}
409
392
410
- func (s * Store [H ]) prepareDeleteRangeBatch (
411
- ctx context.Context , batch datastore.Batch , from , to uint64 ,
412
- ) error {
413
- for h := from ; h < to ; h ++ {
414
- hash , err := s .heightIndex .HashByHeight (ctx , h )
415
- if err != nil {
416
- if errors .Is (err , datastore .ErrNotFound ) {
417
- log .Warnw ("removing non-existent header" , "height" , h )
393
+ var maxHeadersLoadedPerDelete uint64 = 512
394
+
395
+ func (s * Store [H ]) deleteRange (ctx context.Context , from , to uint64 ) error {
396
+ log .Infow ("deleting headers" , "from_height" , from , "to_height" , to )
397
+ for from < to {
398
+ amount := min (to - from , maxHeadersLoadedPerDelete )
399
+ toDelete := from + amount
400
+ headers := make ([]H , 0 , amount )
401
+
402
+ for height := from ; height < toDelete ; height ++ {
403
+ // take headers individually instead of range
404
+ // as getRangeByHeight can't deal with potentially missing headers
405
+ h , err := s .getByHeight (ctx , height )
406
+ if errors .Is (err , header .ErrNotFound ) {
407
+ log .Warnf ("header/store: attempt to delete header that's not found" , height )
418
408
continue
419
409
}
420
- return fmt .Errorf ("hash by height(%d): %w" , h , err )
410
+ if err != nil {
411
+ return fmt .Errorf ("getting header while deleting: %w" , err )
412
+ }
413
+
414
+ headers = append (headers , h )
421
415
}
422
- s .cache .Remove (hash .String ())
423
416
424
- if err := batch .Delete (ctx , hashKey (hash )); err != nil {
425
- return fmt .Errorf ("delete hash key: %w" , err )
417
+ batch , err := s .ds .Batch (ctx )
418
+ if err != nil {
419
+ return fmt .Errorf ("new batch: %w" , err )
426
420
}
427
- }
428
421
429
- s .pending .DeleteRange (from , to )
430
- return nil
431
- }
422
+ for _ , h := range headers {
423
+ if err := batch .Delete (ctx , hashKey (h .Hash ())); err != nil {
424
+ return fmt .Errorf ("delete hash key (%X): %w" , h .Hash (), err )
425
+ }
426
+ if err := batch .Delete (ctx , heightKey (h .Height ())); err != nil {
427
+ return fmt .Errorf ("delete height key (%d): %w" , h .Height (), err )
428
+ }
429
+ }
432
430
433
- func (s * Store [H ]) updateTail (ctx context.Context , batch datastore.Batch , to uint64 ) error {
434
- head , err := s .Head (ctx )
435
- if err != nil {
436
- return err
437
- }
431
+ err = s .setTail (ctx , batch , toDelete )
432
+ if err != nil {
433
+ return fmt .Errorf ("setting tail to %d: %w" , toDelete , err )
434
+ }
438
435
439
- newTail , err := s .getByHeight (ctx , to )
440
- if err != nil {
441
- if ! errors .Is (err , header .ErrNotFound ) {
442
- return fmt .Errorf ("cannot fetch next tail: %w" , err )
436
+ if err := batch .Commit (ctx ); err != nil {
437
+ return fmt .Errorf ("committing delete batch [%d:%d): %w" , from , toDelete , err )
443
438
}
444
439
445
- if to <= head .Height () {
446
- return fmt .Errorf ("attempt to delete to %d: %w" , to , header .ErrNotFound )
440
+ // cleanup caches after disk is flushed
441
+ for _ , h := range headers {
442
+ s .cache .Remove (h .Hash ().String ())
443
+ s .heightIndex .cache .Remove (h .Height ())
447
444
}
445
+ s .pending .DeleteRange (from , toDelete )
448
446
449
- // TODO(@Wondertan): this is racy, but not critical
450
- // Will be eventually fixed by
451
- // https://github.com/celestiaorg/go-header/issues/263
452
- // this is the case where we have deleted all the headers
453
- // deinit the store
454
- s .deinit ()
447
+ log .Infow ("deleted header range" , "from_height" , from , "to_height" , toDelete )
455
448
456
- err = batch .Delete (ctx , headKey )
457
- if err != nil {
458
- return fmt .Errorf ("deleting head in a batch: %w" , err )
459
- }
460
- err = batch .Delete (ctx , tailKey )
461
- if err != nil {
462
- return fmt .Errorf ("deleting tail in a batch: %w" , err )
463
- }
449
+ // move iterator
450
+ from = toDelete
451
+ }
464
452
453
+ return nil
454
+ }
455
+
456
+ func (s * Store [H ]) setTail (ctx context.Context , batch datastore.Batch , to uint64 ) error {
457
+ newTail , err := s .getByHeight (ctx , to )
458
+ if errors .Is (err , header .ErrNotFound ) {
465
459
return nil
466
460
}
461
+ if err != nil {
462
+ return fmt .Errorf ("getting tail: %w" , err )
463
+ }
467
464
465
+ // set directly to `to`, avoiding iteration in recedeTail
466
+ s .tailHeader .Store (& newTail )
468
467
if err := writeHeaderHashTo (ctx , batch , newTail , tailKey ); err != nil {
469
- return fmt .Errorf ("put tail in batch: %w" , err )
468
+ return fmt .Errorf ("writing tailKey in batch: %w" , err )
470
469
}
471
- s .tailHeader .Store (& newTail )
472
- // do not recede tail, it must be equal to `to`
473
470
474
- // update head as well if head, if delete went over it
471
+ // update head as well, if delete went over it
472
+ head , err := s .Head (ctx )
473
+ if err != nil {
474
+ return err
475
+ }
475
476
if to > head .Height () {
476
477
if err := writeHeaderHashTo (ctx , batch , newTail , headKey ); err != nil {
477
- return fmt .Errorf ("put tail in batch: %w" , err )
478
+ return fmt .Errorf ("writing headKey in batch: %w" , err )
478
479
}
479
480
s .contiguousHead .Store (& newTail )
480
481
s .advanceHead (ctx )
481
482
}
482
483
return nil
483
484
}
484
485
486
+ func (s * Store [H ]) wipe (ctx context.Context ) (rerr error ) {
487
+ // TODO(@Wondertan): calling deinit here is racy, but not critical
488
+ // Will be eventually fixed by
489
+ // https://github.com/celestiaorg/go-header/issues/263
490
+ s .deinit ()
491
+
492
+ if err := s .ds .Delete (ctx , headKey ); err != nil {
493
+ rerr = errors .Join (rerr , fmt .Errorf ("deleting headKey DB pointer: %w" , err ))
494
+ }
495
+
496
+ if err := s .ds .Delete (ctx , tailKey ); err != nil {
497
+ rerr = errors .Join (rerr , fmt .Errorf ("deleting tailKey DB pointer: %w" , err ))
498
+ }
499
+
500
+ return rerr
501
+ }
502
+
485
503
func (s * Store [H ]) Append (ctx context.Context , headers ... H ) error {
486
504
lh := len (headers )
487
505
if lh == 0 {
@@ -651,7 +669,7 @@ func (s *Store[H]) get(ctx context.Context, hash header.Hash) ([]byte, error) {
651
669
if err != nil {
652
670
s .metrics .readSingle (ctx , time .Since (startTime ), true )
653
671
if errors .Is (err , datastore .ErrNotFound ) {
654
- return nil , header .ErrNotFound
672
+ return nil , fmt . Errorf ( "hash %X: %w" , hash , header .ErrNotFound )
655
673
}
656
674
return nil , err
657
675
}
0 commit comments