Skip to content

Commit

Permalink
More comprehensive Observation tests
Browse files Browse the repository at this point in the history
  • Loading branch information
samsondav committed Sep 18, 2024
1 parent 45b18c7 commit bc49076
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 14 deletions.
2 changes: 1 addition & 1 deletion llo/channel_definitions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func Test_VerifyChannelDefinitions(t *testing.T) {
channelDefs[i] = llotypes.ChannelDefinition{}
}
err := VerifyChannelDefinitions(channelDefs)
assert.EqualError(t, err, "too many channels, got: 10001/10000")
assert.EqualError(t, err, "too many channels, got: 2001/2000")
})

t.Run("fails for channel with no streams", func(t *testing.T) {
Expand Down
4 changes: 0 additions & 4 deletions llo/plugin_codecs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import (
llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo"
)

// TODO: probably ought to have fuzz testing to detect crashes
// TODO: what about resource starvation attacks? maximum length? Does OCR
// protect us from this?

func Test_protoObservationCodec(t *testing.T) {
t.Run("encode and decode empty struct", func(t *testing.T) {
obs := Observation{}
Expand Down
134 changes: 125 additions & 9 deletions llo/plugin_observation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package llo

import (
"context"
"errors"
"testing"
"time"

Expand All @@ -11,12 +12,27 @@ import (
"golang.org/x/exp/maps"

"github.com/smartcontractkit/libocr/offchainreporting2/types"
ocr2types "github.com/smartcontractkit/libocr/offchainreporting2/types"
"github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo"
)

type mockPredecessorRetirementReportCache struct {
retirementReports map[ocr2types.ConfigDigest][]byte
err error
}

var _ PredecessorRetirementReportCache = &mockPredecessorRetirementReportCache{}

func (p *mockPredecessorRetirementReportCache) AttestedRetirementReport(predecessorConfigDigest ocr2types.ConfigDigest) ([]byte, error) {
return p.retirementReports[predecessorConfigDigest], p.err
}
func (p *mockPredecessorRetirementReportCache) CheckAttestedRetirementReport(predecessorConfigDigest ocr2types.ConfigDigest, attestedRetirementReport []byte) (RetirementReport, error) {
panic("not implemented")
}

func Test_Observation(t *testing.T) {
smallDefinitions := map[llotypes.ChannelID]llotypes.ChannelDefinition{
1: {
Expand Down Expand Up @@ -272,18 +288,29 @@ func Test_Observation(t *testing.T) {
assert.Equal(t, ds.s, decoded.StreamValues)
})

t.Run("number of channels to be added exceeds MaxOutcomeChannelDefinitionsLength", func(t *testing.T) {
t.Fatal("TODO")
})
t.Run("number of streams to be added exceeds MaxObservationStreamValuesLength", func(t *testing.T) {
t.Fatal("TODO")
t.Run("in case previous outcome channel definitions is invalid, returns error", func(t *testing.T) {
dfns := make(llotypes.ChannelDefinitions)
for i := 0; i < 2*MaxOutcomeChannelDefinitionsLength; i++ {
dfns[llotypes.ChannelID(i)] = llotypes.ChannelDefinition{
ReportFormat: llotypes.ReportFormatEVMPremiumLegacy,
Streams: []llotypes.Stream{{StreamID: uint32(i), Aggregator: llotypes.AggregatorMedian}},
}
}

previousOutcome := Outcome{
ChannelDefinitions: dfns,
}
encodedPreviousOutcome, err := p.OutcomeCodec.Encode(previousOutcome)
require.NoError(t, err)

outctx := ocr3types.OutcomeContext{SeqNr: 3, PreviousOutcome: encodedPreviousOutcome}
_, err = p.Observation(context.Background(), outctx, query)
assert.EqualError(t, err, "previousOutcome.Definitions is invalid: too many channels, got: 4000/2000")
})
})

cdc.definitions = smallDefinitions

// TODO: huge (greater than max allowed)

t.Run("votes to remove channel IDs", func(t *testing.T) {
t.Run("first round of removals", func(t *testing.T) {
testStartTS := time.Now()
Expand Down Expand Up @@ -357,7 +384,96 @@ func Test_Observation(t *testing.T) {
})
})

t.Run("channel definitions in previous outcome is invalid, returns error", func(t *testing.T) {
t.Fatal("TODO")
t.Run("sets shouldRetire if ShouldRetireCache.ShouldRetire() is true", func(t *testing.T) {
previousOutcome := Outcome{}
src := &mockShouldRetireCache{shouldRetire: true}
p.ShouldRetireCache = src
encodedPreviousOutcome, err := p.OutcomeCodec.Encode(previousOutcome)
require.NoError(t, err)

outctx := ocr3types.OutcomeContext{SeqNr: 3, PreviousOutcome: encodedPreviousOutcome}
_, err = p.Observation(context.Background(), outctx, query)

Check failure on line 395 in llo/plugin_observation_test.go

View workflow job for this annotation

GitHub Actions / ci-lint

ineffectual assignment to err (ineffassign)
obs, err := p.Observation(context.Background(), outctx, query)
require.NoError(t, err)
decoded, err := p.ObservationCodec.Decode(obs)
require.NoError(t, err)

assert.True(t, decoded.ShouldRetire)
})

t.Run("when predecessor config digest is set", func(t *testing.T) {
testStartTS := time.Now()
cd := types.ConfigDigest{2, 3, 4, 5, 6}
p.PredecessorConfigDigest = &cd
t.Run("in staging lifecycle stage, adds attestedRetirementReport to observation", func(t *testing.T) {
prrc := &mockPredecessorRetirementReportCache{
retirementReports: map[ocr2types.ConfigDigest][]byte{
{2, 3, 4, 5, 6}: []byte("foo"),
},
}
p.PredecessorRetirementReportCache = prrc
previousOutcome := Outcome{
LifeCycleStage: LifeCycleStageStaging,
ObservationsTimestampNanoseconds: testStartTS.UnixNano(),
ChannelDefinitions: cdc.definitions,
ValidAfterSeconds: nil,
StreamAggregates: nil,
}
encodedPreviousOutcome, err := p.OutcomeCodec.Encode(previousOutcome)
require.NoError(t, err)

outctx := ocr3types.OutcomeContext{SeqNr: 2, PreviousOutcome: encodedPreviousOutcome}
obs, err := p.Observation(context.Background(), outctx, query)
require.NoError(t, err)
decoded, err := p.ObservationCodec.Decode(obs)
require.NoError(t, err)

assert.Equal(t, []byte("foo"), decoded.AttestedPredecessorRetirement)
})
t.Run("if predecessor retirement report cache returns error, returns error", func(t *testing.T) {
prrc := &mockPredecessorRetirementReportCache{
err: errors.New("retirement report not found error"),
}
p.PredecessorRetirementReportCache = prrc
previousOutcome := Outcome{
LifeCycleStage: LifeCycleStageStaging,
ObservationsTimestampNanoseconds: testStartTS.UnixNano(),
ChannelDefinitions: cdc.definitions,
ValidAfterSeconds: nil,
StreamAggregates: nil,
}
encodedPreviousOutcome, err := p.OutcomeCodec.Encode(previousOutcome)
require.NoError(t, err)

outctx := ocr3types.OutcomeContext{SeqNr: 2, PreviousOutcome: encodedPreviousOutcome}
_, err = p.Observation(context.Background(), outctx, query)
assert.EqualError(t, err, "error fetching attested retirement report from cache: retirement report not found error")
})
t.Run("in production lifecycle stage, does not add attestedRetirementReport to observation", func(t *testing.T) {
prrc := &mockPredecessorRetirementReportCache{
retirementReports: map[ocr2types.ConfigDigest][]byte{
{2, 3, 4, 5, 6}: []byte("foo"),
},
err: nil,
}
p.PredecessorRetirementReportCache = prrc
previousOutcome := Outcome{
LifeCycleStage: LifeCycleStageProduction,
ObservationsTimestampNanoseconds: testStartTS.UnixNano(),
ChannelDefinitions: cdc.definitions,
ValidAfterSeconds: nil,
StreamAggregates: nil,
}
encodedPreviousOutcome, err := p.OutcomeCodec.Encode(previousOutcome)
require.NoError(t, err)

outctx := ocr3types.OutcomeContext{SeqNr: 2, PreviousOutcome: encodedPreviousOutcome}
obs, err := p.Observation(context.Background(), outctx, query)
require.NoError(t, err)
decoded, err := p.ObservationCodec.Decode(obs)
require.NoError(t, err)

assert.Equal(t, []byte(nil), decoded.AttestedPredecessorRetirement)
})
})
}

0 comments on commit bc49076

Please sign in to comment.