Skip to content

Commit 62e689e

Browse files
committed
test: cover dontime outcome changes
1 parent f1e2caf commit 62e689e

6 files changed

Lines changed: 247 additions & 2 deletions

File tree

pkg/loop/mocks/relayer.go

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

pkg/types/core/mocks/relayer.go

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

pkg/types/core/relayerset.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Relayer interface {
4141
NewContractReader(_ context.Context, contractReaderConfig []byte) (types.ContractReader, error)
4242
NewContractWriter(_ context.Context, contractWriterConfig []byte) (types.ContractWriter, error)
4343
LatestHead(context.Context) (types.Head, error)
44+
FinalizedHead(context.Context) (types.Head, error)
4445
GetChainInfo(ctx context.Context) (types.ChainInfo, error)
4546
}
4647

pkg/types/relayer.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ type ChainService interface {
101101

102102
// LatestHead returns the latest head for the underlying chain.
103103
LatestHead(ctx context.Context) (Head, error)
104+
// FinalizedHead returns the finalized head for the underlying chain. Implementations
105+
// that do not distinguish finalized heads may return the latest observed head.
106+
FinalizedHead(ctx context.Context) (Head, error)
104107
// GetChainInfo returns the ChainInfo for this Relayer.
105108
GetChainInfo(ctx context.Context) (ChainInfo, error)
106109
// GetChainStatus returns the ChainStatus for this Relayer.
@@ -115,8 +118,8 @@ type ChainService interface {
115118
}
116119

117120
// FinalizedHeadService is implemented by relayers that can distinguish finalized
118-
// heads from the latest observed head. Callers should fall back to LatestHead
119-
// when a chain does not implement this interface.
121+
// heads from the latest observed head. This remains useful for callers operating
122+
// on generic service values rather than the full ChainService interface.
120123
type FinalizedHeadService interface {
121124
FinalizedHead(ctx context.Context) (Head, error)
122125
}

pkg/workflows/dontime/plugin_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,101 @@ func TestPlugin_Outcome(t *testing.T) {
213213
require.Equal(t, []int64{timestamp}, outcomeProto.ObservedDonTimes[executionID].Timestamps)
214214
}
215215

216+
func TestPlugin_OutcomeInitializesMissingObservedDonTimesEntry(t *testing.T) {
217+
lggr := logger.Test(t)
218+
store := NewStore(DefaultRequestTimeout)
219+
config, offchainCfg := newTestPluginConfig(t), newTestPluginOffchainConfig(t)
220+
ctx := t.Context()
221+
222+
plugin, err := NewPlugin(store, config, offchainCfg, lggr)
223+
require.NoError(t, err)
224+
225+
query, err := plugin.Query(ctx, ocr3types.OutcomeContext{PreviousOutcome: []byte("")})
226+
require.NoError(t, err)
227+
228+
const executionID = "workflow-missing-prev-entry"
229+
timestamp := time.Now().UnixMilli()
230+
observations := []*pb.Observation{
231+
{Timestamp: timestamp, Requests: map[string]int64{executionID: 0}},
232+
{Timestamp: timestamp - int64(time.Second), Requests: map[string]int64{executionID: 0}},
233+
{Timestamp: timestamp + int64(time.Second), Requests: map[string]int64{executionID: 0}},
234+
{Timestamp: timestamp, Requests: map[string]int64{executionID: 0}},
235+
}
236+
237+
aos := make([]types.AttributedObservation, len(observations))
238+
for i, observation := range observations {
239+
rawObs, marshalErr := proto.Marshal(observation)
240+
require.NoError(t, marshalErr)
241+
aos[i] = types.AttributedObservation{
242+
Observation: rawObs,
243+
Observer: commontypes.OracleID(1),
244+
}
245+
}
246+
247+
prevOutcome := &pb.Outcome{
248+
Timestamp: 0,
249+
ObservedDonTimes: map[string]*pb.ObservedDonTimes{},
250+
}
251+
prevOutcomeBytes, err := proto.Marshal(prevOutcome)
252+
require.NoError(t, err)
253+
254+
outcome, err := plugin.Outcome(ctx, ocr3types.OutcomeContext{PreviousOutcome: prevOutcomeBytes}, query, aos)
255+
require.NoError(t, err)
256+
257+
outcomeProto := &pb.Outcome{}
258+
require.NoError(t, proto.Unmarshal(outcome, outcomeProto))
259+
require.Contains(t, outcomeProto.ObservedDonTimes, executionID)
260+
require.Equal(t, []int64{timestamp}, outcomeProto.ObservedDonTimes[executionID].Timestamps)
261+
}
262+
263+
func TestPlugin_OutcomeKeepsEmptyObservedDonTimesEntries(t *testing.T) {
264+
lggr := logger.Test(t)
265+
store := NewStore(DefaultRequestTimeout)
266+
config, offchainCfg := newTestPluginConfig(t), newTestPluginOffchainConfig(t)
267+
ctx := t.Context()
268+
269+
plugin, err := NewPlugin(store, config, offchainCfg, lggr)
270+
require.NoError(t, err)
271+
272+
query, err := plugin.Query(ctx, ocr3types.OutcomeContext{PreviousOutcome: []byte("")})
273+
require.NoError(t, err)
274+
275+
timestamp := time.Now().UnixMilli()
276+
observations := []*pb.Observation{
277+
{Timestamp: timestamp, Requests: map[string]int64{}},
278+
{Timestamp: timestamp - int64(time.Second), Requests: map[string]int64{}},
279+
{Timestamp: timestamp + int64(time.Second), Requests: map[string]int64{}},
280+
{Timestamp: timestamp, Requests: map[string]int64{}},
281+
}
282+
283+
aos := make([]types.AttributedObservation, len(observations))
284+
for i, observation := range observations {
285+
rawObs, marshalErr := proto.Marshal(observation)
286+
require.NoError(t, marshalErr)
287+
aos[i] = types.AttributedObservation{
288+
Observation: rawObs,
289+
Observer: commontypes.OracleID(1),
290+
}
291+
}
292+
293+
prevOutcome := &pb.Outcome{
294+
Timestamp: timestamp - int64(time.Second),
295+
ObservedDonTimes: map[string]*pb.ObservedDonTimes{
296+
"workflow-empty": {},
297+
},
298+
}
299+
prevOutcomeBytes, err := proto.Marshal(prevOutcome)
300+
require.NoError(t, err)
301+
302+
outcome, err := plugin.Outcome(ctx, ocr3types.OutcomeContext{PreviousOutcome: prevOutcomeBytes}, query, aos)
303+
require.NoError(t, err)
304+
305+
outcomeProto := &pb.Outcome{}
306+
require.NoError(t, proto.Unmarshal(outcome, outcomeProto))
307+
require.Contains(t, outcomeProto.ObservedDonTimes, "workflow-empty")
308+
require.Empty(t, outcomeProto.ObservedDonTimes["workflow-empty"].Timestamps)
309+
}
310+
216311
func TestPlugin_FinishedExecutions(t *testing.T) {
217312
lggr := logger.Test(t)
218313
store := NewStore(DefaultRequestTimeout)

pkg/workflows/dontime/transmitter_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,37 @@ func TestTransmitter_TransmitDonTimeRequest(t *testing.T) {
5151

5252
require.Empty(t, store.GetRequest(executionID))
5353
}
54+
55+
func TestTransmitter_TransmitPreservesCachedDonTimesForOmittedExecutionIDs(t *testing.T) {
56+
lggr := logger.Test(t)
57+
store := NewStore(DefaultRequestTimeout)
58+
ctx := t.Context()
59+
60+
transmitter := NewTransmitter(lggr, store, "")
61+
62+
store.setDonTimes("workflow-stale", []int64{11, 22})
63+
64+
timestamp := time.Now().UnixMilli()
65+
outcome := &pb.Outcome{
66+
Timestamp: timestamp,
67+
ObservedDonTimes: map[string]*pb.ObservedDonTimes{
68+
"workflow-fresh": {Timestamps: []int64{timestamp}},
69+
},
70+
}
71+
72+
r := ocr3types.ReportWithInfo[[]byte]{}
73+
var err error
74+
r.Report, err = proto.Marshal(outcome)
75+
require.NoError(t, err)
76+
77+
err = transmitter.Transmit(ctx, types.ConfigDigest{}, 0, r, []types.AttributedOnchainSignature{})
78+
require.NoError(t, err)
79+
80+
staleDonTimes, err := store.GetDonTimes("workflow-stale")
81+
require.NoError(t, err)
82+
require.Equal(t, []int64{11, 22}, staleDonTimes)
83+
84+
freshDonTimes, err := store.GetDonTimes("workflow-fresh")
85+
require.NoError(t, err)
86+
require.Equal(t, []int64{timestamp}, freshDonTimes)
87+
}

0 commit comments

Comments
 (0)