Skip to content

Commit 8f6418e

Browse files
committed
Remove source and tombstone from outcome
1 parent e2979eb commit 8f6418e

11 files changed

+192
-77
lines changed

llo/channel_definitions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func VerifyChannelDefinitions(codecs map[llotypes.ReportFormat]ReportCodec, chan
5252
func subtractChannelDefinitions(minuend llotypes.ChannelDefinitions, subtrahend llotypes.ChannelDefinitions, limit int) llotypes.ChannelDefinitions {
5353
differenceList := []ChannelDefinitionWithID{}
5454
for channelID, channelDefinition := range minuend {
55-
if _, ok := subtrahend[channelID]; !ok {
55+
if _, ok := subtrahend[channelID]; !ok || subtrahend[channelID].Tombstone {
5656
differenceList = append(differenceList, ChannelDefinitionWithID{channelDefinition, channelID})
5757
}
5858
}

llo/observation_codec.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,6 @@ func channelDefinitionsToProtoObservation(in llotypes.ChannelDefinitions) (out m
102102
ReportFormat: uint32(d.ReportFormat),
103103
Streams: streams,
104104
Opts: d.Opts,
105-
Tombstone: d.Tombstone,
106-
Source: d.Source,
107105
}
108106
}
109107
}
@@ -190,8 +188,6 @@ func channelDefinitionsFromProtoObservation(channelDefinitions map[uint32]*LLOCh
190188
ReportFormat: llotypes.ReportFormat(d.ReportFormat),
191189
Streams: streams,
192190
Opts: d.Opts,
193-
Tombstone: d.Tombstone,
194-
Source: d.Source,
195191
}
196192
}
197193
return dfns

llo/outcome_codec_common.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ func makeChannelDefinitionProto(d llotypes.ChannelDefinition) *LLOChannelDefinit
7878
ReportFormat: uint32(d.ReportFormat),
7979
Streams: streams,
8080
Opts: d.Opts,
81-
Tombstone: d.Tombstone,
82-
Source: d.Source,
8381
}
8482
}
8583

@@ -103,8 +101,6 @@ func channelDefinitionsFromProtoOutcome(in []*LLOChannelIDAndDefinitionProto) (o
103101
ReportFormat: llotypes.ReportFormat(d.ChannelDefinition.ReportFormat),
104102
Streams: streams,
105103
Opts: d.ChannelDefinition.Opts,
106-
Tombstone: d.ChannelDefinition.Tombstone,
107-
Source: d.ChannelDefinition.Source,
108104
}
109105
}
110106
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package llo
2+
3+
import (
4+
"encoding/hex"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"bytes"
10+
11+
"github.com/shopspring/decimal"
12+
"github.com/stretchr/testify/require"
13+
14+
llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo"
15+
)
16+
17+
// Test_LLOOutcomeProtoV1_SerializationComparison compares the serialized format
18+
// of LLOOutcomeProtoV1 between v0.1.6 and onwards
19+
func Test_LLOOutcomeProtoV1_SerializationComparison(t *testing.T) {
20+
codec := protoOutcomeCodecV1{}
21+
22+
testCases := []struct {
23+
name string
24+
outcome Outcome
25+
goldenFile string
26+
}{
27+
{
28+
name: "empty",
29+
outcome: Outcome{
30+
LifeCycleStage: "",
31+
ObservationTimestampNanoseconds: 0,
32+
ChannelDefinitions: nil,
33+
ValidAfterNanoseconds: nil,
34+
StreamAggregates: nil,
35+
},
36+
goldenFile: "empty.bin",
37+
},
38+
{
39+
name: "minimal",
40+
outcome: Outcome{
41+
LifeCycleStage: LifeCycleStageProduction,
42+
ObservationTimestampNanoseconds: 1234567890,
43+
ChannelDefinitions: map[llotypes.ChannelID]llotypes.ChannelDefinition{
44+
1: {
45+
ReportFormat: llotypes.ReportFormatJSON,
46+
Streams: []llotypes.Stream{
47+
{StreamID: 1, Aggregator: llotypes.AggregatorMedian},
48+
},
49+
Opts: []byte(`{}`),
50+
},
51+
},
52+
ValidAfterNanoseconds: map[llotypes.ChannelID]uint64{
53+
1: 1000000000,
54+
},
55+
StreamAggregates: map[llotypes.StreamID]map[llotypes.Aggregator]StreamValue{
56+
1: {
57+
llotypes.AggregatorMedian: ToDecimal(decimal.NewFromInt(123)),
58+
},
59+
},
60+
},
61+
goldenFile: "minimal.bin",
62+
},
63+
{
64+
name: "full",
65+
outcome: Outcome{
66+
LifeCycleStage: LifeCycleStageProduction,
67+
ObservationTimestampNanoseconds: 9876543210,
68+
ChannelDefinitions: map[llotypes.ChannelID]llotypes.ChannelDefinition{
69+
1: {
70+
ReportFormat: llotypes.ReportFormatJSON,
71+
Streams: []llotypes.Stream{
72+
{StreamID: 1, Aggregator: llotypes.AggregatorMedian},
73+
{StreamID: 2, Aggregator: llotypes.AggregatorQuote},
74+
},
75+
Opts: []byte(`{"foo":"bar"}`),
76+
},
77+
2: {
78+
ReportFormat: llotypes.ReportFormatJSON,
79+
Streams: []llotypes.Stream{
80+
{StreamID: 3, Aggregator: llotypes.AggregatorMedian},
81+
},
82+
Opts: []byte(`{"baz":"qux"}`),
83+
},
84+
},
85+
ValidAfterNanoseconds: map[llotypes.ChannelID]uint64{
86+
1: 5000000000,
87+
2: 6000000000,
88+
},
89+
StreamAggregates: map[llotypes.StreamID]map[llotypes.Aggregator]StreamValue{
90+
1: {
91+
llotypes.AggregatorMedian: ToDecimal(decimal.NewFromInt(12345)),
92+
},
93+
2: {
94+
llotypes.AggregatorQuote: &Quote{
95+
Bid: decimal.NewFromInt(1010),
96+
Benchmark: decimal.NewFromInt(1011),
97+
Ask: decimal.NewFromInt(1012),
98+
},
99+
},
100+
3: {
101+
llotypes.AggregatorMedian: ToDecimal(decimal.NewFromFloat(123.456)),
102+
},
103+
},
104+
},
105+
goldenFile: "full.bin",
106+
},
107+
}
108+
109+
for _, tc := range testCases {
110+
t.Run(tc.name, func(t *testing.T) {
111+
// Serialize with current code
112+
currentSerialized, err := codec.Encode(tc.outcome)
113+
require.NoError(t, err, "failed to encode outcome with current code")
114+
115+
// Read golden file from v0.1.6
116+
goldenPath := filepath.Join("testdata", "outcome_v1_serialization", "v0.1.6", tc.goldenFile)
117+
goldenBytes, err := os.ReadFile(goldenPath)
118+
if err != nil {
119+
if os.IsNotExist(err) {
120+
t.Skipf("golden file %s does not exist. Run generate_golden_files.sh to create it.", goldenPath)
121+
return
122+
}
123+
require.NoError(t, err, "failed to read golden file")
124+
}
125+
126+
// Compare byte-by-byte
127+
if !bytes.Equal(currentSerialized, goldenBytes) {
128+
t.Errorf("Serialization mismatch for test case %q\n", tc.name)
129+
t.Errorf("Current length: %d bytes\n", len(currentSerialized))
130+
t.Errorf("Golden (v0.1.6) length: %d bytes\n", len(goldenBytes))
131+
t.Errorf("Current hex: %s\n", hex.EncodeToString(currentSerialized))
132+
t.Errorf("Golden (v0.1.6) hex: %s\n", hex.EncodeToString(goldenBytes))
133+
134+
// Find first difference
135+
minLen := len(currentSerialized)
136+
if len(goldenBytes) < minLen {
137+
minLen = len(goldenBytes)
138+
}
139+
for i := 0; i < minLen; i++ {
140+
if currentSerialized[i] != goldenBytes[i] {
141+
t.Errorf("First difference at byte %d: current=0x%02x, golden=0x%02x\n", i, currentSerialized[i], goldenBytes[i])
142+
break
143+
}
144+
}
145+
if len(currentSerialized) != len(goldenBytes) {
146+
t.Errorf("Length mismatch: current has %d bytes, golden has %d bytes\n", len(currentSerialized), len(goldenBytes))
147+
}
148+
t.FailNow()
149+
}
150+
})
151+
}
152+
}

llo/plugin_codecs.pb.go

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

llo/plugin_codecs.proto

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ message LLOChannelDefinitionProto {
5656
uint32 reportFormat = 1;
5757
repeated LLOStreamDefinition streams = 2;
5858
bytes opts = 3;
59-
bool tombstone = 4;
60-
uint32 source = 5;
6159
}
6260

6361
message LLOStreamDefinition {

llo/plugin_codecs_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ func genChannelDefinition() gopter.Gen {
6363
"ReportFormat": genReportFormat(),
6464
"Streams": gen.SliceOf(genStream()),
6565
"Opts": gen.SliceOf(gen.UInt8()),
66-
"Tombstone": gen.Bool(),
67-
"Source": gen.UInt32(),
66+
"Tombstone": gen.Bool(), // Field still exists in llotypes.ChannelDefinition but is filtered out in proto
67+
"Source": gen.UInt32(), // Field still exists in llotypes.ChannelDefinition but is filtered out in proto
6868
})
6969
}
7070

llo/plugin_outcome.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,6 @@ func (p *Plugin) outcome(outctx ocr3types.OutcomeContext, query types.Query, aos
202202
// pair) and re-use the same result, in case multiple channels share the
203203
// same stream/aggregator pair.
204204
for cid, cd := range outcome.ChannelDefinitions {
205-
if cd.Tombstone {
206-
continue
207-
}
208-
209205
for _, strm := range cd.Streams {
210206
sid, agg := strm.StreamID, strm.Aggregator
211207

llo/plugin_outcome_test.go

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,11 @@ func testOutcome(t *testing.T, outcomeCodec OutcomeCodec) {
147147
assert.Equal(t, newCd, decoded.ChannelDefinitions[42])
148148
})
149149

150-
t.Run("replaces channel definition with tombstoned version and stops generating reports", func(t *testing.T) {
150+
t.Run("removes tombstoned channel from channel definitions", func(t *testing.T) {
151151
channelID := llotypes.ChannelID(42)
152152
originalCd := llotypes.ChannelDefinition{
153153
ReportFormat: llotypes.ReportFormatJSON,
154154
Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 2, Aggregator: llotypes.AggregatorMedian}},
155-
Tombstone: false,
156-
}
157-
tombstonedCd := llotypes.ChannelDefinition{
158-
ReportFormat: llotypes.ReportFormatJSON,
159-
Streams: []llotypes.Stream{{StreamID: 1, Aggregator: llotypes.AggregatorMedian}, {StreamID: 2, Aggregator: llotypes.AggregatorMedian}},
160-
Tombstone: true,
161155
}
162156

163157
// Create previous outcome with a non-tombstoned, reportable channel
@@ -182,10 +176,16 @@ func testOutcome(t *testing.T, outcomeCodec OutcomeCodec) {
182176
encodedPreviousOutcome, err := p.OutcomeCodec.Encode(previousOutcome)
183177
require.NoError(t, err)
184178

185-
// Create observations voting to update channel to tombstoned version
179+
// Mock the channel definition cache to return empty (channel is tombstoned, so not in expected definitions)
180+
// This simulates the channel being tombstoned in the source
181+
p.ChannelDefinitionCache = &mockChannelDefinitionCache{
182+
definitions: map[llotypes.ChannelID]llotypes.ChannelDefinition{},
183+
}
184+
185+
// Create observations - subtractChannelDefinitions will identify the tombstoned channel for removal
186186
obs, err := p.ObservationCodec.Encode(Observation{
187-
UpdateChannelDefinitions: map[llotypes.ChannelID]llotypes.ChannelDefinition{
188-
channelID: tombstonedCd,
187+
RemoveChannelIDs: map[llotypes.ChannelID]struct{}{
188+
channelID: {},
189189
},
190190
UnixTimestampNanoseconds: previousObsTS + uint64(1*time.Second),
191191
})
@@ -200,7 +200,7 @@ func testOutcome(t *testing.T, outcomeCodec OutcomeCodec) {
200200
})
201201
}
202202

203-
// Generate new outcome with tombstoned channel
203+
// Generate new outcome - tombstoned channel should be removed
204204
outcome, err := p.Outcome(ctx, ocr3types.OutcomeContext{
205205
PreviousOutcome: encodedPreviousOutcome,
206206
SeqNr: 3,
@@ -210,21 +210,25 @@ func testOutcome(t *testing.T, outcomeCodec OutcomeCodec) {
210210
decoded, err := p.OutcomeCodec.Decode(outcome)
211211
require.NoError(t, err)
212212

213-
// Verify channel definition was replaced with tombstoned version
214-
assert.True(t, decoded.ChannelDefinitions[channelID].Tombstone, "Channel should be tombstoned")
215-
assert.Equal(t, tombstonedCd, decoded.ChannelDefinitions[channelID])
213+
// Verify channel definition was removed (tombstoned channels are filtered out)
214+
assert.NotContains(t, decoded.ChannelDefinitions, channelID, "Tombstoned channel should be removed from ChannelDefinitions")
215+
216+
// Verify ValidAfterNanoseconds entry was also removed
217+
assert.NotContains(t, decoded.ValidAfterNanoseconds, channelID, "ValidAfterNanoseconds entry should be removed for tombstoned channel")
216218

217-
// Verify channel is no longer reportable
219+
// Verify channel is no longer reportable (channel doesn't exist)
218220
err = decoded.IsReportable(channelID, 1, uint64(100*time.Millisecond))
219221
require.NotNil(t, err)
220-
assert.Contains(t, err.Error(), "tombstone channel")
222+
assert.Contains(t, err.Error(), "no channel definition with this ID")
221223

222-
// Verify ReportableChannels excludes the tombstoned channel
224+
// Verify ReportableChannels does not include the removed channel
225+
// (removed channels don't appear in either reportable or unreportable lists)
223226
reportable, unreportable := decoded.ReportableChannels(1, uint64(100*time.Millisecond))
224-
assert.NotContains(t, reportable, channelID, "Tombstoned channel should not be in reportable list")
225-
require.Len(t, unreportable, 1)
226-
assert.Equal(t, channelID, unreportable[0].ChannelID)
227-
assert.Contains(t, unreportable[0].Error(), "tombstone channel")
227+
assert.NotContains(t, reportable, channelID, "Removed channel should not be in reportable list")
228+
// Channel should not appear in unreportable list either since it doesn't exist in ChannelDefinitions
229+
for _, err := range unreportable {
230+
assert.NotEqual(t, channelID, err.ChannelID, "Removed channel should not be in unreportable list")
231+
}
228232
})
229233

230234
t.Run("does not add channels beyond MaxOutcomeChannelDefinitionsLength", func(t *testing.T) {

0 commit comments

Comments
 (0)