Skip to content

Commit 2b2d0f8

Browse files
cristalolegWondertan
authored andcommitted
feat!(store): add Tail (#261)
Fixes #203
1 parent 4c7af2c commit 2b2d0f8

File tree

6 files changed

+73
-3
lines changed

6 files changed

+73
-3
lines changed

headertest/store.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ func (m *Store[H]) Head(context.Context, ...header.HeadOption[H]) (H, error) {
5151
return m.Headers[m.HeadHeight], nil
5252
}
5353

54+
func (m *Store[H]) Tail(context.Context) (H, error) {
55+
err := header.ErrNotFound
56+
57+
var tail H
58+
for _, h := range m.Headers {
59+
if tail.IsZero() || h.Height() < tail.Height() {
60+
tail = h
61+
}
62+
}
63+
64+
return tail, err
65+
}
66+
5467
func (m *Store[H]) Get(_ context.Context, hash header.Hash) (H, error) {
5568
for _, header := range m.Headers {
5669
if bytes.Equal(header.Hash(), hash) {

interface.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ type Store[H Header[H]] interface {
7070
// Init initializes Store with the given head, meaning it is initialized with the genesis header.
7171
Init(context.Context, H) error
7272

73+
// Tail returns the oldest known header.
74+
Tail(context.Context) (H, error)
75+
7376
// Height reports current height of the chain head.
7477
Height() uint64
7578

p2p/server_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ func (timeoutStore[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H,
139139
return zero, ctx.Err()
140140
}
141141

142+
func (timeoutStore[H]) Tail(ctx context.Context) (H, error) {
143+
<-ctx.Done()
144+
var zero H
145+
return zero, ctx.Err()
146+
}
147+
142148
func (timeoutStore[H]) Get(ctx context.Context, _ header.Hash) (H, error) {
143149
<-ctx.Done()
144150
var zero H

store/keys.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import (
88
"github.com/celestiaorg/go-header"
99
)
1010

11-
var headKey = datastore.NewKey("head")
11+
var (
12+
headKey = datastore.NewKey("head")
13+
tailKey = datastore.NewKey("tail")
14+
)
1215

1316
func heightKey(h uint64) datastore.Key {
1417
return datastore.NewKey(strconv.FormatUint(h, 10))

store/store.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,23 @@ func (s *Store[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, erro
206206
}
207207
}
208208

209+
// 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 {
218+
var zero H
219+
return zero, err
220+
}
221+
222+
s.tailHeader.Store(&tail)
223+
return tail, nil
224+
}
225+
209226
func (s *Store[H]) Get(ctx context.Context, hash header.Hash) (H, error) {
210227
var zero H
211228
if v, ok := s.cache.Get(hash.String()); ok {
@@ -476,6 +493,22 @@ func (s *Store[H]) readHead(ctx context.Context) (H, error) {
476493
return s.Get(ctx, head)
477494
}
478495

496+
// readTail loads the tail from the datastore.
497+
func (s *Store[H]) readTail(ctx context.Context) (H, error) {
498+
var zero H
499+
b, err := s.ds.Get(ctx, tailKey)
500+
if err != nil {
501+
return zero, err
502+
}
503+
504+
var tail header.Hash
505+
if err := tail.UnmarshalJSON(b); err != nil {
506+
return zero, err
507+
}
508+
509+
return s.Get(ctx, tail)
510+
}
511+
479512
func (s *Store[H]) get(ctx context.Context, hash header.Hash) ([]byte, error) {
480513
startTime := time.Now()
481514
data, err := s.ds.Get(ctx, datastore.NewKey(hash.String()))

store/store_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,22 @@ func TestStore(t *testing.T) {
2323

2424
suite := headertest.NewTestSuite(t)
2525

26+
genesis := suite.Head()
2627
ds := sync.MutexWrap(datastore.NewMapDatastore())
27-
store := NewTestStore(t, ctx, ds, suite.Head())
28+
store := NewTestStore(t, ctx, ds, genesis)
29+
30+
assert.Equal(t, *store.tailHeader.Load(), suite.Head())
2831

2932
assert.Equal(t, *store.tailHeader.Load(), suite.Head())
3033

3134
head, err := store.Head(ctx)
3235
require.NoError(t, err)
3336
assert.EqualValues(t, suite.Head().Hash(), head.Hash())
3437

38+
tail, err := store.Tail(ctx)
39+
require.NoError(t, err)
40+
assert.Equal(t, tail.Hash(), genesis.Hash())
41+
3542
in := suite.GenDummyHeaders(10)
3643
err = store.Append(ctx, in...)
3744
require.NoError(t, err)
@@ -521,6 +528,11 @@ func TestStoreInit(t *testing.T) {
521528
require.NoError(t, err)
522529

523530
headers := suite.GenDummyHeaders(10)
524-
err = store.Init(ctx, headers[len(headers)-1]) // init should work with any height, not only 1
531+
h := headers[len(headers)-1]
532+
err = store.Init(ctx, h) // init should work with any height, not only 1
533+
require.NoError(t, err)
534+
535+
tail, err := store.Tail(ctx)
536+
assert.Equal(t, tail.Hash(), h.Hash())
525537
require.NoError(t, err)
526538
}

0 commit comments

Comments
 (0)