-
Notifications
You must be signed in to change notification settings - Fork 246
feat: p2p exchange wrapper #2855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
dc52948
630e51f
8645e31
ae8e0a2
210ffcc
71e2123
168c8cb
52762d4
a256a1c
896f116
4930acf
223f57e
dc1b78c
3233285
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package sync | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/celestiaorg/go-header" | ||
| "github.com/evstack/ev-node/pkg/store" | ||
| ) | ||
|
|
||
| type storeGetter[H header.Header[H]] func(context.Context, store.Store, header.Hash) (H, error) | ||
| type storeGetterByHeight[H header.Header[H]] func(context.Context, store.Store, uint64) (H, error) | ||
|
|
||
| // P2PExchange defines the interface for the underlying P2P exchange. | ||
| type P2PExchange[H header.Header[H]] interface { | ||
| header.Exchange[H] | ||
| Start(context.Context) error | ||
| Stop(context.Context) error | ||
| } | ||
|
|
||
| type exchangeWrapper[H header.Header[H]] struct { | ||
| p2pExchange P2PExchange[H] | ||
| daStore store.Store | ||
| getter storeGetter[H] | ||
| getterByHeight storeGetterByHeight[H] | ||
| } | ||
|
|
||
| func (ew *exchangeWrapper[H]) Get(ctx context.Context, hash header.Hash) (H, error) { | ||
| // Check DA store first | ||
| if ew.daStore != nil && ew.getter != nil { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't you by default pass daStore to constructor of Although exchangeWrapper start should ensure that DA datastore is started (so that get calls won't fail) |
||
| if h, err := ew.getter(ctx, ew.daStore, hash); err == nil && !h.IsZero() { | ||
| return h, nil | ||
| } | ||
| } | ||
|
|
||
| // Fallback to network exchange | ||
| return ew.p2pExchange.Get(ctx, hash) | ||
| } | ||
|
|
||
| func (ew *exchangeWrapper[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { | ||
| // Check DA store first | ||
| if ew.daStore != nil && ew.getterByHeight != nil { | ||
| if h, err := ew.getterByHeight(ctx, ew.daStore, height); err == nil && !h.IsZero() { | ||
| return h, nil | ||
| } | ||
| } | ||
|
|
||
| // Fallback to network exchange | ||
| return ew.p2pExchange.GetByHeight(ctx, height) | ||
| } | ||
|
|
||
| func (ew *exchangeWrapper[H]) Head(ctx context.Context, opts ...header.HeadOption[H]) (H, error) { | ||
| return ew.p2pExchange.Head(ctx, opts...) | ||
| } | ||
|
|
||
| func (ew *exchangeWrapper[H]) GetRangeByHeight(ctx context.Context, from H, to uint64) ([]H, error) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to actually implement this method w/ the p2p as a fallback system bc this is the method called by syncer. So here, check da store + pull out whatever contiguous range it has + request remainder from network (if there is remainder) |
||
| return ew.p2pExchange.GetRangeByHeight(ctx, from, to) | ||
| } | ||
|
|
||
| func (ew *exchangeWrapper[H]) Start(ctx context.Context) error { | ||
| return ew.p2pExchange.Start(ctx) | ||
| } | ||
|
|
||
| func (ew *exchangeWrapper[H]) Stop(ctx context.Context) error { | ||
| return ew.p2pExchange.Stop(ctx) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| package sync | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "testing" | ||
|
|
||
| "github.com/celestiaorg/go-header" | ||
| "github.com/evstack/ev-node/pkg/store" | ||
| "github.com/evstack/ev-node/test/mocks" | ||
| extmocks "github.com/evstack/ev-node/test/mocks/external" | ||
| "github.com/evstack/ev-node/types" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestExchangeWrapper_Get(t *testing.T) { | ||
| ctx := context.Background() | ||
| hash := header.Hash([]byte("test-hash")) | ||
| expectedHeader := &types.SignedHeader{} // Just a dummy | ||
|
|
||
| t.Run("Hit in Store", func(t *testing.T) { | ||
| mockEx := extmocks.NewMockP2PExchange[*types.SignedHeader](t) | ||
| // Exchange should NOT be called | ||
|
|
||
| getter := func(ctx context.Context, s store.Store, h header.Hash) (*types.SignedHeader, error) { | ||
| return expectedHeader, nil | ||
| } | ||
|
|
||
| ew := &exchangeWrapper[*types.SignedHeader]{ | ||
| p2pExchange: mockEx, | ||
| daStore: mocks.NewMockStore(t), | ||
| getter: getter, | ||
| } | ||
|
|
||
| h, err := ew.Get(ctx, hash) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, expectedHeader, h) | ||
| }) | ||
|
|
||
| t.Run("Miss in Store", func(t *testing.T) { | ||
| mockEx := extmocks.NewMockP2PExchange[*types.SignedHeader](t) | ||
| mockEx.EXPECT().Get(ctx, hash).Return(expectedHeader, nil) | ||
|
|
||
| getter := func(ctx context.Context, s store.Store, h header.Hash) (*types.SignedHeader, error) { | ||
| return nil, errors.New("not found") | ||
| } | ||
|
|
||
| ew := &exchangeWrapper[*types.SignedHeader]{ | ||
| p2pExchange: mockEx, | ||
| daStore: mocks.NewMockStore(t), | ||
| getter: getter, | ||
| } | ||
|
|
||
| h, err := ew.Get(ctx, hash) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, expectedHeader, h) | ||
| }) | ||
|
|
||
| t.Run("Store Not Configured", func(t *testing.T) { | ||
| mockEx := extmocks.NewMockP2PExchange[*types.SignedHeader](t) | ||
| mockEx.EXPECT().Get(ctx, hash).Return(expectedHeader, nil) | ||
|
|
||
| ew := &exchangeWrapper[*types.SignedHeader]{ | ||
| p2pExchange: mockEx, | ||
| daStore: nil, // No store | ||
| getter: nil, | ||
| } | ||
|
|
||
| h, err := ew.Get(ctx, hash) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, expectedHeader, h) | ||
| }) | ||
| } | ||
|
|
||
| func TestExchangeWrapper_GetByHeight(t *testing.T) { | ||
| ctx := context.Background() | ||
| height := uint64(10) | ||
| expectedHeader := &types.SignedHeader{} | ||
|
|
||
| t.Run("Hit in Store", func(t *testing.T) { | ||
| mockEx := extmocks.NewMockP2PExchange[*types.SignedHeader](t) | ||
|
|
||
| getterByHeight := func(ctx context.Context, s store.Store, h uint64) (*types.SignedHeader, error) { | ||
| return expectedHeader, nil | ||
| } | ||
|
|
||
| ew := &exchangeWrapper[*types.SignedHeader]{ | ||
| p2pExchange: mockEx, | ||
| daStore: mocks.NewMockStore(t), | ||
| getterByHeight: getterByHeight, | ||
| } | ||
|
|
||
| h, err := ew.GetByHeight(ctx, height) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, expectedHeader, h) | ||
| }) | ||
|
|
||
| t.Run("Miss in Store", func(t *testing.T) { | ||
| mockEx := extmocks.NewMockP2PExchange[*types.SignedHeader](t) | ||
| mockEx.EXPECT().GetByHeight(ctx, height).Return(expectedHeader, nil) | ||
|
|
||
| getterByHeight := func(ctx context.Context, s store.Store, h uint64) (*types.SignedHeader, error) { | ||
| return nil, errors.New("not found") | ||
| } | ||
|
|
||
| ew := &exchangeWrapper[*types.SignedHeader]{ | ||
| p2pExchange: mockEx, | ||
| daStore: mocks.NewMockStore(t), | ||
| getterByHeight: getterByHeight, | ||
| } | ||
|
|
||
| h, err := ew.GetByHeight(ctx, height) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, expectedHeader, h) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why don't you just define these as functions that don't take the store, and just pass the function itself into the constructor so u don't need to store DA ds on the exchangeWrapper?