@@ -113,27 +113,6 @@ func newStore[H header.Header[H]](ds datastore.Batching, opts ...Option) (*Store
113
113
}, nil
114
114
}
115
115
116
- func (s * Store [H ]) Init (ctx context.Context , initial H ) error {
117
- if s .heightSub .Height () != 0 {
118
- return errors .New ("store already initialized" )
119
- }
120
-
121
- // initialize with the initial head before first flush.
122
- s .contiguousHead .Store (& initial )
123
- s .heightSub .Init (initial .Height ())
124
-
125
- // trust the given header as the initial head
126
- err := s .flush (ctx , initial )
127
- if err != nil {
128
- return err
129
- }
130
-
131
- s .tailHeader .Store (& initial )
132
-
133
- log .Infow ("initialized head" , "height" , initial .Height (), "hash" , initial .Hash ())
134
- return nil
135
- }
136
-
137
116
func (s * Store [H ]) Start (ctx context.Context ) error {
138
117
// closed s.writesDn means that store was stopped before, recreate chan.
139
118
select {
@@ -142,11 +121,8 @@ func (s *Store[H]) Start(ctx context.Context) error {
142
121
default :
143
122
}
144
123
145
- if err := s .loadContiguousHead (ctx ); err != nil {
146
- // we might start on an empty datastore, no key is okay.
147
- if ! errors .Is (err , header .ErrNotFound ) {
148
- return fmt .Errorf ("header/store: cannot load headKey: %w" , err )
149
- }
124
+ if err := s .loadHeadAndTail (ctx ); err != nil && ! errors .Is (err , header .ErrNotFound ) {
125
+ return err
150
126
}
151
127
152
128
go s .flushLoop ()
@@ -175,52 +151,34 @@ func (s *Store[H]) Stop(ctx context.Context) error {
175
151
// cleanup caches
176
152
s .cache .Purge ()
177
153
s .heightIndex .cache .Purge ()
154
+ s .contiguousHead .Store (nil )
155
+ s .tailHeader .Store (nil )
178
156
return s .metrics .Close ()
179
157
}
180
158
181
159
func (s * Store [H ]) Height () uint64 {
182
160
return s .heightSub .Height ()
183
161
}
184
162
185
- func (s * Store [H ]) Head (ctx context.Context , _ ... header.HeadOption [H ]) (H , error ) {
186
- if head := s .contiguousHead .Load (); head != nil {
187
- return * head , nil
188
- }
189
-
190
- head , err := s .GetByHeight (ctx , s .heightSub .Height ())
191
- if err == nil {
192
- return head , nil
163
+ func (s * Store [H ]) Head (_ context.Context , _ ... header.HeadOption [H ]) (H , error ) {
164
+ head := s .contiguousHead .Load ()
165
+ if head == nil {
166
+ var zero H
167
+ return zero , header .ErrEmptyStore
193
168
}
194
169
195
- var zero H
196
- head , err = s .readHead (ctx )
197
- switch {
198
- default :
199
- return zero , err
200
- case errors .Is (err , datastore .ErrNotFound ), errors .Is (err , header .ErrNotFound ):
201
- return zero , header .ErrNoHead
202
- case err == nil :
203
- s .heightSub .SetHeight (head .Height ())
204
- log .Infow ("loaded head" , "height" , head .Height (), "hash" , head .Hash ())
205
- return head , nil
206
- }
170
+ return * head , nil
207
171
}
208
172
209
173
// Tail implements [header.Store] interface.
210
- func (s * Store [H ]) Tail (ctx context.Context ) (H , error ) {
211
- tailPtr := s .tailHeader .Load ()
212
- if tailPtr != nil {
213
- return * tailPtr , nil
214
- }
215
-
216
- tail , err := s .readTail (ctx )
217
- if err != nil {
174
+ func (s * Store [H ]) Tail (_ context.Context ) (H , error ) {
175
+ tail := s .tailHeader .Load ()
176
+ if tail == nil {
218
177
var zero H
219
- return zero , err
178
+ return zero , header . ErrEmptyStore
220
179
}
221
180
222
- s .tailHeader .Store (& tail )
223
- return tail , nil
181
+ return * tail , nil
224
182
}
225
183
226
184
func (s * Store [H ]) Get (ctx context.Context , hash header.Hash ) (H , error ) {
@@ -239,8 +197,7 @@ func (s *Store[H]) Get(ctx context.Context, hash header.Hash) (H, error) {
239
197
}
240
198
241
199
h := header .New [H ]()
242
- err = h .UnmarshalBinary (b )
243
- if err != nil {
200
+ if err := h .UnmarshalBinary (b ); err != nil {
244
201
return zero , err
245
202
}
246
203
@@ -481,6 +438,7 @@ func (s *Store[H]) flushLoop() {
481
438
defer close (s .writesDn )
482
439
ctx := context .Background ()
483
440
for headers := range s .writes {
441
+ s .ensureInit (headers )
484
442
// add headers to the pending and ensure they are accessible
485
443
s .pending .Append (headers ... )
486
444
// always inform heightSub about new headers seen.
@@ -547,15 +505,13 @@ func (s *Store[H]) flush(ctx context.Context, headers ...H) error {
547
505
}
548
506
}
549
507
550
- // marshal and add to batch reference to the new head
508
+ // marshal and add to batch reference to the new head and tail
551
509
head := * s .contiguousHead .Load ()
552
- b , err := head .Hash ().MarshalJSON ()
553
- if err != nil {
510
+ if err := writeHeaderHashTo (ctx , batch , head , headKey ); err != nil {
554
511
return err
555
512
}
556
-
557
- err = batch .Put (ctx , headKey , b )
558
- if err != nil {
513
+ tail := * s .tailHeader .Load ()
514
+ if err := writeHeaderHashTo (ctx , batch , tail , tailKey ); err != nil {
559
515
return err
560
516
}
561
517
@@ -568,55 +524,23 @@ func (s *Store[H]) flush(ctx context.Context, headers ...H) error {
568
524
return batch .Commit (ctx )
569
525
}
570
526
571
- // loadContiguousHead from the disk and sets contiguousHead and heightSub.
572
- func (s * Store [H ]) loadContiguousHead (ctx context.Context ) error {
573
- h , err := s .readHead (ctx )
574
- if err != nil {
575
- return err
576
- }
577
-
578
- s .contiguousHead .Store (& h )
579
- s .heightSub .SetHeight (h .Height ())
580
- return nil
581
- }
582
-
583
- // readHead loads the head from the datastore.
584
- func (s * Store [H ]) readHead (ctx context.Context ) (H , error ) {
585
- var zero H
586
- b , err := s .ds .Get (ctx , headKey )
587
- if err != nil {
588
- if errors .Is (err , datastore .ErrNotFound ) {
589
- return zero , header .ErrNotFound
590
- }
591
- return zero , err
592
- }
593
-
594
- var head header.Hash
595
- err = head .UnmarshalJSON (b )
596
- if err != nil {
597
- return zero , err
598
- }
599
-
600
- return s .Get (ctx , head )
601
- }
602
-
603
- // readTail loads the tail from the datastore.
604
- func (s * Store [H ]) readTail (ctx context.Context ) (H , error ) {
527
+ // readByKey the hash under the given key from datastore and fetch the header by hash.
528
+ func (s * Store [H ]) readByKey (ctx context.Context , key datastore.Key ) (H , error ) {
605
529
var zero H
606
- b , err := s .ds .Get (ctx , tailKey )
530
+ b , err := s .ds .Get (ctx , key )
607
531
if err != nil {
608
532
if errors .Is (err , datastore .ErrNotFound ) {
609
533
return zero , header .ErrNotFound
610
534
}
611
535
return zero , err
612
536
}
613
537
614
- var tail header.Hash
615
- if err := tail .UnmarshalJSON (b ); err != nil {
538
+ var h header.Hash
539
+ if err := h .UnmarshalJSON (b ); err != nil {
616
540
return zero , err
617
541
}
618
542
619
- return s .Get (ctx , tail )
543
+ return s .Get (ctx , h )
620
544
}
621
545
622
546
func (s * Store [H ]) get (ctx context.Context , hash header.Hash ) ([]byte , error ) {
@@ -663,6 +587,57 @@ func (s *Store[H]) nextContiguousHead(ctx context.Context, height uint64) H {
663
587
return newHead
664
588
}
665
589
590
+ func (s * Store [H ]) loadHeadAndTail (ctx context.Context ) error {
591
+ head , err := s .readByKey (ctx , headKey )
592
+ if err != nil {
593
+ return fmt .Errorf ("header/store: cannot load headKey: %w" , err )
594
+ }
595
+
596
+ tail , err := s .readByKey (ctx , tailKey )
597
+ if err != nil {
598
+ return fmt .Errorf ("header/store: cannot load tailKey: %w" , err )
599
+ }
600
+
601
+ s .init (head , tail )
602
+ return nil
603
+ }
604
+
605
+ func (s * Store [H ]) ensureInit (headers []H ) {
606
+ headExist , tailExist := s .contiguousHead .Load () != nil , s .tailHeader .Load () != nil
607
+ if tailExist && headExist {
608
+ return
609
+ } else if tailExist || headExist {
610
+ panic ("header/store: head and tail must be both present or absent" )
611
+ }
612
+
613
+ tail , head := headers [0 ], headers [len (headers )- 1 ]
614
+ s .init (head , tail )
615
+ }
616
+
617
+ func (s * Store [H ]) init (head , tail H ) {
618
+ s .contiguousHead .Store (& head )
619
+ s .heightSub .Init (head .Height ())
620
+ s .tailHeader .Store (& tail )
621
+ }
622
+
623
+ func writeHeaderHashTo [H header.Header [H ]](
624
+ ctx context.Context ,
625
+ batch datastore.Batch ,
626
+ h H ,
627
+ key datastore.Key ,
628
+ ) error {
629
+ hashBytes , err := h .Hash ().MarshalJSON ()
630
+ if err != nil {
631
+ return err
632
+ }
633
+
634
+ if err := batch .Put (ctx , key , hashBytes ); err != nil {
635
+ return err
636
+ }
637
+
638
+ return nil
639
+ }
640
+
666
641
// indexTo saves mapping between header Height and Hash to the given batch.
667
642
func indexTo [H header.Header [H ]](ctx context.Context , batch datastore.Batch , headers ... H ) error {
668
643
for _ , h := range headers {
0 commit comments