Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,23 @@ packages:
filename: store.go
github.com/celestiaorg/go-header:
interfaces:
Exchange:
config:
dir: ./test/mocks
pkgname: mocks
filename: external/hexchange.go
Store:
config:
dir: ./test/mocks
pkgname: mocks
filename: external/hstore.go
github.com/evstack/ev-node/pkg/sync:
interfaces:
P2PExchange:
config:
dir: ./test/mocks
pkgname: mocks
filename: external/p2pexchange.go
github.com/evstack/ev-node/block/internal/syncing:
interfaces:
DARetriever:
Expand Down
10 changes: 6 additions & 4 deletions node/full.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ func newFullNode(
mainKV := newPrefixKV(database, EvPrefix)
rktStore := store.New(mainKV)

headerSyncService, err := initHeaderSyncService(mainKV, nodeConfig, genesis, p2pClient, logger)
headerSyncService, err := initHeaderSyncService(mainKV, rktStore, nodeConfig, genesis, p2pClient, logger)
if err != nil {
return nil, err
}

dataSyncService, err := initDataSyncService(mainKV, nodeConfig, genesis, p2pClient, logger)
dataSyncService, err := initDataSyncService(mainKV, rktStore, nodeConfig, genesis, p2pClient, logger)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -149,14 +149,15 @@ func newFullNode(

func initHeaderSyncService(
mainKV ds.Batching,
daStore store.Store,
nodeConfig config.Config,
genesis genesispkg.Genesis,
p2pClient *p2p.Client,
logger zerolog.Logger,
) (*evsync.HeaderSyncService, error) {
componentLogger := logger.With().Str("component", "HeaderSyncService").Logger()

headerSyncService, err := evsync.NewHeaderSyncService(mainKV, nodeConfig, genesis, p2pClient, componentLogger)
headerSyncService, err := evsync.NewHeaderSyncService(mainKV, daStore, nodeConfig, genesis, p2pClient, componentLogger)
if err != nil {
return nil, fmt.Errorf("error while initializing HeaderSyncService: %w", err)
}
Expand All @@ -165,14 +166,15 @@ func initHeaderSyncService(

func initDataSyncService(
mainKV ds.Batching,
daStore store.Store,
nodeConfig config.Config,
genesis genesispkg.Genesis,
p2pClient *p2p.Client,
logger zerolog.Logger,
) (*evsync.DataSyncService, error) {
componentLogger := logger.With().Str("component", "DataSyncService").Logger()

dataSyncService, err := evsync.NewDataSyncService(mainKV, nodeConfig, genesis, p2pClient, componentLogger)
dataSyncService, err := evsync.NewDataSyncService(mainKV, daStore, nodeConfig, genesis, p2pClient, componentLogger)
if err != nil {
return nil, fmt.Errorf("error while initializing DataSyncService: %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions node/light.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ func newLightNode(
logger zerolog.Logger,
) (ln *LightNode, err error) {
componentLogger := logger.With().Str("component", "HeaderSyncService").Logger()
headerSyncService, err := sync.NewHeaderSyncService(database, conf, genesis, p2pClient, componentLogger)
store := store.New(database)

headerSyncService, err := sync.NewHeaderSyncService(database, store, conf, genesis, p2pClient, componentLogger)
if err != nil {
return nil, fmt.Errorf("error while initializing HeaderSyncService: %w", err)
}

store := store.New(database)

node := &LightNode{
P2P: p2pClient,
hSyncService: headerSyncService,
Expand Down
2 changes: 1 addition & 1 deletion pkg/da/jsonrpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This package is a **trimmed copy** of code from `celestia-node` to stay JSON-com
- `blob.go` comes from `celestia-node/blob/blob.go` @ tag `v0.28.4` (release v0.28.4), with unused pieces removed (blob v1, proof helpers, share length calc, appconsts dependency, etc.).
- `submit_options.go` mirrors the exported JSON fields of `celestia-node/state/tx_config.go` @ the same tag, leaving out functional options, defaults, and Cosmos keyring helpers.

## Why copy instead of import?
## Why copy instead of import

- Avoids pulling Cosmos SDK / celestia-app dependencies into ev-node for the small surface we need (blob JSON and commitment for v0).
- Keeps binary size and module graph smaller while remaining wire-compatible with celestia-node's blob service.
Expand Down
65 changes: 65 additions & 0 deletions pkg/sync/exchange_wrapper.go
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)
Comment on lines +10 to +11
Copy link
Collaborator

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?


// 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you by default pass daStore to constructor of exchangeWrapper for all node types? If so, you can assume it exists and don't need to nest this call

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)
}
116 changes: 116 additions & 0 deletions pkg/sync/exchange_wrapper_test.go
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)
})
}
Loading
Loading