Skip to content

Commit 5c305ad

Browse files
authored
feat(store): allow partial init (#327)
Previously, if either head or tail is not found for whatever reason, the store will report being empty, causing the node to reinitialize and sync from scratch. This change enables it to recover either the head or the tail if the other is missing. For example, if the tail is missing due to an interrupted delete, the head will be retrieved successfully upon start. The tail will then be recovered via the receding tail functionality. Although if the chain is too long, receding may take too long making it hard for the node to stop, thus #326 This was tested manually, but it still needs a unit test. It will happen in a separate PR
1 parent 105a106 commit 5c305ad

File tree

1 file changed

+48
-20
lines changed

1 file changed

+48
-20
lines changed

store/store.go

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ func (s *Store[H]) Start(ctx context.Context) error {
130130
default:
131131
}
132132

133-
if err := s.loadHeadAndTail(ctx); err != nil && !errors.Is(err, header.ErrNotFound) {
134-
return err
133+
if err := s.init(ctx); err != nil {
134+
return fmt.Errorf("header/store: initializing: %w", err)
135135
}
136136

137137
ctx, cancel := context.WithCancel(context.Background())
@@ -560,10 +560,24 @@ func (s *Store[H]) readByKey(ctx context.Context, key datastore.Key) (H, error)
560560

561561
var h header.Hash
562562
if err := h.UnmarshalJSON(b); err != nil {
563+
return zero, fmt.Errorf("unmarshaling header hash at %s key: %w", key, err)
564+
}
565+
566+
hdr, err := s.Get(ctx, h)
567+
if err != nil {
568+
if errors.Is(err, header.ErrNotFound) {
569+
derr := s.ds.Delete(ctx, key)
570+
if derr != nil {
571+
err = errors.Join(
572+
err,
573+
fmt.Errorf("deleting key %s, header for which was not found: %w", key, derr),
574+
)
575+
}
576+
}
563577
return zero, err
564578
}
565579

566-
return s.Get(ctx, h)
580+
return hdr, nil
567581
}
568582

569583
func (s *Store[H]) get(ctx context.Context, hash header.Hash) ([]byte, error) {
@@ -677,39 +691,53 @@ func (s *Store[H]) nextTail(ctx context.Context) (tail H, changed bool) {
677691
return tail, changed
678692
}
679693

680-
func (s *Store[H]) loadHeadAndTail(ctx context.Context) error {
694+
// init loads the head and tail headers and sets them on the store.
695+
// allows partial initialization of either tail or head if one of the is not found.
696+
func (s *Store[H]) init(ctx context.Context) error {
681697
head, err := s.readByKey(ctx, headKey)
682-
if err != nil {
683-
return fmt.Errorf("header/store: cannot load headKey: %w", err)
698+
if err != nil && !errors.Is(err, header.ErrNotFound) {
699+
return fmt.Errorf("reading headKey: %w", err)
700+
}
701+
if !head.IsZero() {
702+
s.contiguousHead.Store(&head)
703+
s.heightSub.Init(head.Height())
704+
log.Debugw("initialized head", "height", head.Height())
684705
}
685706

686707
tail, err := s.readByKey(ctx, tailKey)
687-
if err != nil {
688-
return fmt.Errorf("header/store: cannot load tailKey: %w", err)
708+
if err != nil && !errors.Is(err, header.ErrNotFound) {
709+
return fmt.Errorf("reading tailKey: %w", err)
710+
}
711+
if !tail.IsZero() {
712+
s.tailHeader.Store(&tail)
713+
log.Debugw("initialized tail", "height", tail.Height())
689714
}
690715

691-
s.init(head, tail)
692716
return nil
693717
}
694718

719+
// ensureInit initializes the store with the given headers if it is not already initialized.
695720
func (s *Store[H]) ensureInit(headers []H) {
696-
headExist, tailExist := s.contiguousHead.Load() != nil, s.tailHeader.Load() != nil
697-
if len(headers) == 0 || (tailExist && headExist) {
721+
if len(headers) == 0 {
698722
return
699-
} else if tailExist || headExist {
700-
panic("header/store: head and tail must be both present or absent")
701723
}
702724

703-
tail, head := headers[0], headers[len(headers)-1]
704-
s.init(head, tail)
705-
}
725+
if headPtr := s.contiguousHead.Load(); headPtr == nil {
726+
head := headers[len(headers)-1]
727+
if s.contiguousHead.CompareAndSwap(headPtr, &head) {
728+
s.heightSub.Init(head.Height())
729+
log.Debugw("initialized head", "height", head.Height())
730+
}
731+
}
706732

707-
func (s *Store[H]) init(head, tail H) {
708-
s.contiguousHead.Store(&head)
709-
s.heightSub.Init(head.Height())
710-
s.tailHeader.Store(&tail)
733+
if tailPtr := s.tailHeader.Load(); tailPtr == nil {
734+
tail := headers[0]
735+
s.tailHeader.CompareAndSwap(tailPtr, &tail)
736+
log.Debugw("initialized tail", "height", tail.Height())
737+
}
711738
}
712739

740+
// deinit deinitializes the store.
713741
func (s *Store[H]) deinit() {
714742
s.cache.Purge()
715743
s.heightIndex.cache.Purge()

0 commit comments

Comments
 (0)