Skip to content

Commit dcf6c3c

Browse files
authored
feat: Support GetHeader in store (#2422)
Resolves #2419 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added the ability to retrieve a block header by height without fetching full block data. * **Tests** * Introduced new tests to verify block header retrieval, including error scenarios. * Enhanced mocking support for header retrieval in test environments. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 1cc0afa commit dcf6c3c

4 files changed

Lines changed: 140 additions & 10 deletions

File tree

pkg/store/store.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,10 @@ func (s *DefaultStore) SaveBlockData(ctx context.Context, header *types.SignedHe
106106

107107
// GetBlockData returns block header and data at given height, or error if it's not found in Store.
108108
func (s *DefaultStore) GetBlockData(ctx context.Context, height uint64) (*types.SignedHeader, *types.Data, error) {
109-
headerBlob, err := s.db.Get(ctx, ds.NewKey(getHeaderKey(height)))
110-
if err != nil {
111-
return nil, nil, fmt.Errorf("failed to load block header: %w", err)
112-
}
113-
header := new(types.SignedHeader)
114-
err = header.UnmarshalBinary(headerBlob)
109+
header, err := s.GetHeader(ctx, height)
115110
if err != nil {
116-
return nil, nil, fmt.Errorf("failed to unmarshal block header: %w", err)
111+
return nil, nil, err
117112
}
118-
119113
dataBlob, err := s.db.Get(ctx, ds.NewKey(getDataKey(height)))
120114
if err != nil {
121115
return nil, nil, fmt.Errorf("failed to load block data: %w", err)
@@ -159,6 +153,19 @@ func (s *DefaultStore) GetSignatureByHash(ctx context.Context, hash []byte) (*ty
159153
return s.GetSignature(ctx, height)
160154
}
161155

156+
// GetHeader returns the header at the given height or error if it's not found in Store.
157+
func (s *DefaultStore) GetHeader(ctx context.Context, height uint64) (*types.SignedHeader, error) {
158+
headerBlob, err := s.db.Get(ctx, ds.NewKey(getHeaderKey(height)))
159+
if err != nil {
160+
return nil, fmt.Errorf("load block header: %w", err)
161+
}
162+
header := new(types.SignedHeader)
163+
if err = header.UnmarshalBinary(headerBlob); err != nil {
164+
return nil, fmt.Errorf("unmarshal block header: %w", err)
165+
}
166+
return header, nil
167+
}
168+
162169
// GetSignature returns signature for a block with given block header hash, or error if it's not found in Store.
163170
func (s *DefaultStore) GetSignature(ctx context.Context, height uint64) (*types.Signature, error) {
164171
signatureData, err := s.db.Get(ctx, ds.NewKey(getSignatureKey(height)))

pkg/store/store_test.go

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ func TestMetadata(t *testing.T) {
314314
require.Error(err)
315315
require.Nil(v)
316316
}
317+
317318
func TestGetBlockDataErrors(t *testing.T) {
318319
t.Parallel()
319320
chainID := "TestGetBlockDataErrors"
@@ -333,15 +334,15 @@ func TestGetBlockDataErrors(t *testing.T) {
333334
name: "header get error",
334335
mock: &mockBatchingDatastore{Batching: mustNewInMem(), getError: mockErr("get header")},
335336
prepare: func(_ context.Context, _ *mockBatchingDatastore) {}, // nothing to pre-seed
336-
expectSub: "failed to load block header",
337+
expectSub: "load block header",
337338
},
338339
{
339340
name: "header unmarshal error",
340341
mock: &mockBatchingDatastore{Batching: mustNewInMem(), unmarshalErrorOnCall: 1},
341342
prepare: func(ctx context.Context, m *mockBatchingDatastore) {
342343
_ = m.Put(ctx, ds.NewKey(getHeaderKey(header.Height())), []byte("garbage"))
343344
},
344-
expectSub: "failed to unmarshal block header",
345+
expectSub: "unmarshal block header",
345346
},
346347
{
347348
name: "data get error",
@@ -561,3 +562,54 @@ func TestGetMetadataError(t *testing.T) {
561562
require.Contains(err.Error(), mockErrGet.Error())
562563
require.Contains(err.Error(), fmt.Sprintf("failed to get metadata for key '%s'", key))
563564
}
565+
566+
func TestGetHeader(t *testing.T) {
567+
t.Parallel()
568+
chainID := "TestGetHeader"
569+
header, _ := types.GetRandomBlock(1, 0, chainID)
570+
headerBlob, _ := header.MarshalBinary()
571+
572+
mockErr := func(msg string) error { return fmt.Errorf("mock %s error", msg) }
573+
574+
cases := map[string]struct {
575+
mock *mockBatchingDatastore
576+
prepare func(context.Context, *mockBatchingDatastore)
577+
expErr string
578+
}{
579+
"all good": {
580+
mock: &mockBatchingDatastore{Batching: mustNewInMem()},
581+
prepare: func(ctx context.Context, db *mockBatchingDatastore) {
582+
_ = db.Put(ctx, ds.NewKey(getHeaderKey(header.Height())), headerBlob)
583+
},
584+
},
585+
"db error": {
586+
mock: &mockBatchingDatastore{Batching: mustNewInMem(), getError: mockErr("get header")},
587+
prepare: func(_ context.Context, _ *mockBatchingDatastore) {}, // nothing to pre-seed
588+
expErr: "load block header",
589+
},
590+
"unmarshal error": {
591+
mock: &mockBatchingDatastore{Batching: mustNewInMem(), unmarshalErrorOnCall: 1},
592+
prepare: func(ctx context.Context, m *mockBatchingDatastore) {
593+
_ = m.Put(ctx, ds.NewKey(getHeaderKey(header.Height())), []byte("garbage"))
594+
},
595+
expErr: "unmarshal block header",
596+
},
597+
}
598+
599+
for name, tc := range cases {
600+
t.Run(name, func(t *testing.T) {
601+
t.Parallel()
602+
s := New(tc.mock)
603+
tc.prepare(t.Context(), tc.mock)
604+
605+
gotHeader, gotErr := s.GetHeader(t.Context(), header.Height())
606+
if tc.expErr != "" {
607+
require.Error(t, gotErr)
608+
require.ErrorContains(t, gotErr, tc.expErr)
609+
return
610+
}
611+
require.NoError(t, gotErr)
612+
assert.Equal(t, header, gotHeader)
613+
})
614+
}
615+
}

pkg/store/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ type Store interface {
2222
// GetBlockByHash returns block with given block header hash, or error if it's not found in Store.
2323
GetBlockByHash(ctx context.Context, hash []byte) (*types.SignedHeader, *types.Data, error)
2424

25+
// GetHeader returns the header at the given height or error if it's not found in Store.
26+
GetHeader(ctx context.Context, height uint64) (*types.SignedHeader, error)
27+
2528
// GetSignature returns signature for a block at given height, or error if it's not found in Store.
2629
GetSignature(ctx context.Context, height uint64) (*types.Signature, error)
2730
// GetSignatureByHash returns signature for a block with given block header hash, or error if it's not found in Store.

test/mocks/Store.go

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)