Skip to content

Commit 8d24e80

Browse files
committed
precheck if lp started before calling lp-related endpoints on solana_service
1 parent e73fbdf commit 8d24e80

File tree

2 files changed

+147
-5
lines changed

2 files changed

+147
-5
lines changed

pkg/solana/solana_service.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,22 @@ import (
2121
"github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils"
2222
)
2323

24+
var ErrLogPollerNotStarted = errors.New("log poller is not started; register/unregister/query operations require a running log poller")
25+
2426
type solanaService struct {
2527
commontypes.UnimplementedSolanaService
2628
chain Chain
2729
logger logger.Logger
2830
}
2931

32+
func (ss *solanaService) requireLogPoller() (LogPoller, error) {
33+
lp := ss.chain.LogPoller()
34+
if err := lp.Ready(); err != nil {
35+
return nil, ErrLogPollerNotStarted
36+
}
37+
return lp, nil
38+
}
39+
3040
func (ss *solanaService) GetBlock(ctx context.Context, req commonsol.GetBlockRequest) (*commonsol.GetBlockReply, error) {
3141
reader, err := ss.chain.Reader()
3242
if err != nil {
@@ -44,7 +54,11 @@ func (ss *solanaService) GetBlock(ctx context.Context, req commonsol.GetBlockReq
4454
}
4555

4656
func (ss *solanaService) GetLatestLPBlock(ctx context.Context) (*commonsol.LPBlock, error) {
47-
lp := ss.chain.LogPoller()
57+
lp, err := ss.requireLogPoller()
58+
if err != nil {
59+
return nil, err
60+
}
61+
4862
n, err := lp.GetLatestBlock(ctx)
4963
if err != nil {
5064
return nil, fmt.Errorf("failed to get latest lp block: %w", err)
@@ -125,7 +139,11 @@ func (ss *solanaService) SimulateTX(ctx context.Context, req commonsol.SimulateT
125139
}
126140

127141
func (ss *solanaService) RegisterLogTracking(ctx context.Context, req commonsol.LPFilterQuery) error {
128-
lp := ss.chain.LogPoller()
142+
lp, err := ss.requireLogPoller()
143+
if err != nil {
144+
return err
145+
}
146+
129147
if lp.HasFilter(ctx, req.Name) {
130148
return nil
131149
}
@@ -144,7 +162,11 @@ func (ss *solanaService) RegisterLogTracking(ctx context.Context, req commonsol.
144162
}
145163

146164
func (ss *solanaService) UnregisterLogTracking(ctx context.Context, filterName string) error {
147-
lp := ss.chain.LogPoller()
165+
lp, err := ss.requireLogPoller()
166+
if err != nil {
167+
return err
168+
}
169+
148170
if !lp.HasFilter(ctx, filterName) {
149171
return nil
150172
}
@@ -154,7 +176,11 @@ func (ss *solanaService) UnregisterLogTracking(ctx context.Context, filterName s
154176

155177
func (ss *solanaService) QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression,
156178
limitAndSort query.LimitAndSort) ([]*commonsol.Log, error) {
157-
lp := ss.chain.LogPoller()
179+
lp, err := ss.requireLogPoller()
180+
if err != nil {
181+
return nil, err
182+
}
183+
158184
queryName, err := deriveNameFromFilterQuery(filterQuery)
159185
if err != nil {
160186
return nil, err
@@ -186,7 +212,12 @@ func (ss *solanaService) QueryTrackedLogs(ctx context.Context, filterQuery []que
186212
}
187213

188214
func (ss *solanaService) GetFiltersNames(ctx context.Context) ([]string, error) {
189-
filters, err := ss.chain.LogPoller().GetFilters(ctx)
215+
lp, err := ss.requireLogPoller()
216+
if err != nil {
217+
return nil, err
218+
}
219+
220+
filters, err := lp.GetFilters(ctx)
190221
if err != nil {
191222
return nil, err
192223
}

pkg/solana/solana_service_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package solana
22

33
import (
4+
"context"
5+
"errors"
46
"testing"
57

68
solana "github.com/gagliardetto/solana-go"
79
"github.com/gagliardetto/solana-go/rpc"
810
"github.com/stretchr/testify/require"
911

12+
"github.com/smartcontractkit/chainlink-common/pkg/logger"
1013
commonsol "github.com/smartcontractkit/chainlink-common/pkg/types/chains/solana"
14+
"github.com/smartcontractkit/chainlink-common/pkg/types/query"
15+
16+
logpollertypes "github.com/smartcontractkit/chainlink-solana/pkg/solana/logpoller/types"
1117
)
1218

1319
func Test_Converters(t *testing.T) {
@@ -288,3 +294,108 @@ func sig(i byte) solana.Signature {
288294
func csig(i byte) commonsol.Signature { return commonsol.Signature(sig(i)) }
289295

290296
func uint64Ptr(v uint64) *uint64 { return &v }
297+
298+
// mockLogPoller is a minimal LogPoller implementation for testing readiness guards.
299+
type mockLogPoller struct {
300+
ready error
301+
}
302+
303+
func (m *mockLogPoller) Start(context.Context) error { return nil }
304+
func (m *mockLogPoller) Ready() error { return m.ready }
305+
func (m *mockLogPoller) Close() error { return nil }
306+
func (m *mockLogPoller) HasFilter(context.Context, string) bool { return false }
307+
func (m *mockLogPoller) RegisterFilter(context.Context, logpollertypes.Filter) error {
308+
return nil
309+
}
310+
func (m *mockLogPoller) UnregisterFilter(context.Context, string) error { return nil }
311+
func (m *mockLogPoller) GetFilters(context.Context) (map[string]logpollertypes.Filter, error) {
312+
return nil, nil
313+
}
314+
func (m *mockLogPoller) GetLatestBlock(context.Context) (int64, error) { return 0, nil }
315+
func (m *mockLogPoller) FilteredLogs(context.Context, []query.Expression, query.LimitAndSort, string) ([]logpollertypes.Log, error) {
316+
return nil, nil
317+
}
318+
func (m *mockLogPoller) Replay(int64) {}
319+
func (m *mockLogPoller) CPIEventsEnabled() bool { return false }
320+
321+
// stubChain is a minimal Chain stub that only provides a LogPoller.
322+
// It embeds the Chain interface so unimplemented methods panic if called.
323+
type stubChain struct {
324+
Chain
325+
lp LogPoller
326+
}
327+
328+
func (s *stubChain) LogPoller() LogPoller { return s.lp }
329+
330+
func newSolanaService(t *testing.T, lpReady error) *solanaService {
331+
t.Helper()
332+
return &solanaService{
333+
chain: &stubChain{lp: &mockLogPoller{ready: lpReady}},
334+
logger: logger.Test(t),
335+
}
336+
}
337+
338+
func TestLogPollerReadinessGuard(t *testing.T) {
339+
ctx := context.Background()
340+
errNotStarted := errors.New("not started")
341+
342+
t.Run("RegisterLogTracking returns error when LogPoller not started", func(t *testing.T) {
343+
ss := newSolanaService(t, errNotStarted)
344+
err := ss.RegisterLogTracking(ctx, commonsol.LPFilterQuery{Name: "test"})
345+
require.ErrorIs(t, err, ErrLogPollerNotStarted)
346+
})
347+
348+
t.Run("RegisterLogTracking succeeds when LogPoller is ready", func(t *testing.T) {
349+
ss := newSolanaService(t, nil)
350+
err := ss.RegisterLogTracking(ctx, commonsol.LPFilterQuery{
351+
Name: "test",
352+
ContractIdlJSON: []byte(`{}`),
353+
})
354+
require.NoError(t, err)
355+
})
356+
357+
t.Run("UnregisterLogTracking returns error when LogPoller not started", func(t *testing.T) {
358+
ss := newSolanaService(t, errNotStarted)
359+
err := ss.UnregisterLogTracking(ctx, "test")
360+
require.ErrorIs(t, err, ErrLogPollerNotStarted)
361+
})
362+
363+
t.Run("UnregisterLogTracking succeeds when LogPoller is ready", func(t *testing.T) {
364+
ss := newSolanaService(t, nil)
365+
err := ss.UnregisterLogTracking(ctx, "test")
366+
require.NoError(t, err)
367+
})
368+
369+
t.Run("QueryTrackedLogs returns error when LogPoller not started", func(t *testing.T) {
370+
ss := newSolanaService(t, errNotStarted)
371+
_, err := ss.QueryTrackedLogs(ctx, nil, query.LimitAndSort{})
372+
require.ErrorIs(t, err, ErrLogPollerNotStarted)
373+
})
374+
375+
t.Run("GetLatestLPBlock returns error when LogPoller not started", func(t *testing.T) {
376+
ss := newSolanaService(t, errNotStarted)
377+
_, err := ss.GetLatestLPBlock(ctx)
378+
require.ErrorIs(t, err, ErrLogPollerNotStarted)
379+
})
380+
381+
t.Run("GetLatestLPBlock succeeds when LogPoller is ready", func(t *testing.T) {
382+
ss := newSolanaService(t, nil)
383+
block, err := ss.GetLatestLPBlock(ctx)
384+
require.NoError(t, err)
385+
require.NotNil(t, block)
386+
require.Equal(t, uint64(0), block.Slot)
387+
})
388+
389+
t.Run("GetFiltersNames returns error when LogPoller not started", func(t *testing.T) {
390+
ss := newSolanaService(t, errNotStarted)
391+
_, err := ss.GetFiltersNames(ctx)
392+
require.ErrorIs(t, err, ErrLogPollerNotStarted)
393+
})
394+
395+
t.Run("GetFiltersNames succeeds when LogPoller is ready", func(t *testing.T) {
396+
ss := newSolanaService(t, nil)
397+
names, err := ss.GetFiltersNames(ctx)
398+
require.NoError(t, err)
399+
require.Empty(t, names)
400+
})
401+
}

0 commit comments

Comments
 (0)