From 08730282bd715616d5ce14dce097aab80327f3a6 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Fri, 29 May 2026 17:58:21 -0500 Subject: [PATCH 1/2] Updated hardcoded f+1 identical response aggregation to be configurable --- libs/chainconsensus/oracle/error_mode.go | 14 +- libs/chainconsensus/oracle/error_mode_test.go | 69 +++++++- libs/chainconsensus/oracle/mode.go | 8 +- libs/chainconsensus/oracle/mode_test.go | 66 ++++++++ .../chainconsensus/oracle/reporting_plugin.go | 32 ++-- .../oracle/reporting_plugin_factory.go | 13 +- .../oracle/reporting_plugin_test.go | 156 ++++++++++++++++++ libs/go.mod | 28 ++-- libs/go.sum | 68 ++++---- 9 files changed, 384 insertions(+), 70 deletions(-) create mode 100644 libs/chainconsensus/oracle/mode_test.go diff --git a/libs/chainconsensus/oracle/error_mode.go b/libs/chainconsensus/oracle/error_mode.go index e33bc15b4..c082371d3 100644 --- a/libs/chainconsensus/oracle/error_mode.go +++ b/libs/chainconsensus/oracle/error_mode.go @@ -12,10 +12,10 @@ var errInsufficientErrorOb = fmt.Errorf("insufficient number of errors") // modeForError returns a slice of common errors for a given request when: // 1. The total number of observations is at least (N+F)/2+1, and -// 2. The number of observed errors is at least F+1. -// If no single error was observed by at least F+1 nodes, it returns a slice -// of the most frequently observed errors whose combined observation count equals F+1. -func modeForError(N, F int, requestID string, aos []attributedObservation) ([][]byte, error) { +// 2. The number of observed errors is at least minMatching. +// If no single error was observed by at least minMatching nodes, it returns a slice +// of the most frequently observed errors whose combined observation count equals minMatching. +func modeForError(N, F, minMatching int, requestID string, aos []attributedObservation) ([][]byte, error) { type keyT [sha256.Size]byte counters := make(map[keyT]*counter[[]byte]) var totalNum int @@ -81,13 +81,13 @@ func modeForError(N, F int, requestID string, aos []attributedObservation) ([][] for _, c := range sortedCounters { result = append(result, c.value) count += c.count - if count >= F+1 { + if count >= minMatching { break } } - if count < F+1 { - return nil, fmt.Errorf("%w: expected %d, got %d", errInsufficientErrorOb, F+1, count) + if count < minMatching { + return nil, fmt.Errorf("%w: expected %d, got %d", errInsufficientErrorOb, minMatching, count) } return result, nil diff --git a/libs/chainconsensus/oracle/error_mode_test.go b/libs/chainconsensus/oracle/error_mode_test.go index 134cc2249..70def665a 100644 --- a/libs/chainconsensus/oracle/error_mode_test.go +++ b/libs/chainconsensus/oracle/error_mode_test.go @@ -95,7 +95,7 @@ func TestErrorMode(t *testing.T) { Observation: strToObservation(ob), } } - rawActualErrors, err := modeForError(N, tc.F, requestID, aos) + rawActualErrors, err := modeForError(N, tc.F, tc.F+1, requestID, aos) if tc.ExpectedError != "" { require.ErrorContains(t, err, tc.ExpectedError) } else { @@ -146,3 +146,70 @@ func TestErrorMode(t *testing.T) { runTest(t, newObservation) }) } + +func TestErrorMode_TwoFPlusOneThreshold(t *testing.T) { + // N=7, F=2: with minMatching=5 (2F+1), we need 5 matching errors to succeed. + const requestID = "req-2f1" + N, F := 7, 2 + minMatching := 2*F + 1 // 5 + + makeAos := func(errors []string) []attributedObservation { + aos := make([]attributedObservation, len(errors)) + for i, e := range errors { + aos[i] = attributedObservation{ + //nolint:gosec + Observer: commontypes.OracleID(i), + Observation: &types.Observation{ + Observations: map[string]*types.RequestObservation{ + requestID: {Observation: &types.RequestObservation_Error{Error: []byte(e)}}, + }, + }, + } + } + return aos + } + + t.Run("succeeds when 5 of 7 report same error", func(t *testing.T) { + errors := []string{"err", "err", "err", "err", "err", "other", "other"} + result, err := modeForError(N, F, minMatching, requestID, makeAos(errors)) + require.NoError(t, err) + require.Equal(t, []string{"err"}, func() []string { + s := make([]string, len(result)) + for i, b := range result { + s[i] = string(b) + } + return s + }()) + }) + + t.Run("fails when combined error observations don't reach minMatching", func(t *testing.T) { + // Only 4 error observations total (3 distinct errors), non-errors fill the remaining 3 slots + allObs := make([]attributedObservation, N) + errPayloads := []string{"err-a", "err-b", "err-a", "err-c"} + for i, e := range errPayloads { + allObs[i] = attributedObservation{ + //nolint:gosec + Observer: commontypes.OracleID(i), + Observation: &types.Observation{ + Observations: map[string]*types.RequestObservation{ + requestID: {Observation: &types.RequestObservation_Error{Error: []byte(e)}}, + }, + }, + } + } + // remaining 3 are non-error (EventuallyConsistent) — count toward totalNum but not error count + for i := len(errPayloads); i < N; i++ { + allObs[i] = attributedObservation{ + //nolint:gosec + Observer: commontypes.OracleID(i), + Observation: &types.Observation{ + Observations: map[string]*types.RequestObservation{ + requestID: {Observation: &types.RequestObservation_EventuallyConsistent{EventuallyConsistent: []byte("value")}}, + }, + }, + } + } + _, err := modeForError(N, F, minMatching, requestID, allObs) + require.ErrorContains(t, err, "insufficient number of errors") + }) +} diff --git a/libs/chainconsensus/oracle/mode.go b/libs/chainconsensus/oracle/mode.go index e406389f2..44c834117 100644 --- a/libs/chainconsensus/oracle/mode.go +++ b/libs/chainconsensus/oracle/mode.go @@ -20,9 +20,9 @@ type observation[keyT comparable, valueT any] struct { } // mode - returns most frequent value, if total number of observations is at least (N+F)/2+1 and -// number of values with identical keys is at least F+1. Returns error, otherwise. +// number of values with identical keys is at least minMatching. Returns error, otherwise. // If multiple values have identical number of observations, prefers value reported by oracle with the lowest oracleID. -func mode[keyT comparable, valueT any](N, F int, observations iter.Seq2[commontypes.OracleID, *observation[keyT, valueT]]) (valueT, error) { +func mode[keyT comparable, valueT any](N, F, minMatching int, observations iter.Seq2[commontypes.OracleID, *observation[keyT, valueT]]) (valueT, error) { counters := make(map[keyT]*counter[valueT]) var totalNum int for nodeID, nodeObservation := range observations { @@ -65,9 +65,9 @@ func mode[keyT comparable, valueT any](N, F int, observations iter.Seq2[commonty return zero, errors.New("unexpected state: highestCounter is nil") } - if highestCounter.count < F+1 { + if highestCounter.count < minMatching { var zero valueT - return zero, fmt.Errorf("insufficient number of identical observations: expected %d, got %d", F+1, highestCounter.count) + return zero, fmt.Errorf("insufficient number of identical observations: expected %d, got %d", minMatching, highestCounter.count) } return highestCounter.value, nil diff --git a/libs/chainconsensus/oracle/mode_test.go b/libs/chainconsensus/oracle/mode_test.go new file mode 100644 index 000000000..4526cd564 --- /dev/null +++ b/libs/chainconsensus/oracle/mode_test.go @@ -0,0 +1,66 @@ +package oracle + +import ( + "iter" + "testing" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/stretchr/testify/require" +) + +// stringObs returns an iterator over the given values, attributed to sequential oracle IDs. +// An empty string produces a nil observation (counts toward total but not toward any key). +func stringObs(values ...string) iter.Seq2[commontypes.OracleID, *observation[string, string]] { + return func(yield func(commontypes.OracleID, *observation[string, string]) bool) { + for i, v := range values { + //nolint:gosec + id := commontypes.OracleID(i) + if v == "" { + if !yield(id, nil) { + return + } + continue + } + if !yield(id, &observation[string, string]{Key: v, Value: v}) { + return + } + } + } +} + +func TestMode_DefaultFPlusOne(t *testing.T) { + // N=7, F=2 → byzQuorumSize=5, default minMatching=F+1=3 + N, F := 7, 2 + got, err := mode[string, string](N, F, F+1, stringObs("a", "a", "a", "b", "b", "c", "")) + require.NoError(t, err) + require.Equal(t, "a", got) +} + +func TestMode_DefaultFPlusOne_InsufficientMatching(t *testing.T) { + // N=7, F=2 → byzQuorumSize=5; default minMatching=F+1=3 + N, F := 7, 2 + _, err := mode[string, string](N, F, F+1, stringObs("a", "a", "b", "b", "c", "c", "d")) + require.ErrorContains(t, err, "insufficient number of identical observations: expected 3, got 2") +} + +func TestMode_TwoFPlusOne(t *testing.T) { + // N=7, F=2 → minMatching=5; 5 agree on "a" + N, F := 7, 2 + got, err := mode[string, string](N, F, 2*F+1, stringObs("a", "a", "a", "a", "a", "b", "c")) + require.NoError(t, err) + require.Equal(t, "a", got) +} + +func TestMode_TwoFPlusOne_InsufficientMatching(t *testing.T) { + // N=7, F=2 → minMatching=5; only 4 agree on "a" → fail + N, F := 7, 2 + _, err := mode[string, string](N, F, 2*F+1, stringObs("a", "a", "a", "a", "b", "b", "c")) + require.ErrorContains(t, err, "insufficient number of identical observations: expected 5, got 4") +} + +func TestMode_InsufficientTotalObservations(t *testing.T) { + // N=7, F=2 → byzQuorumSize=5; only 4 provided → fail before matching check + N, F := 7, 2 + _, err := mode[string, string](N, F, F+1, stringObs("a", "a", "a", "a")) + require.ErrorContains(t, err, "insufficient number of observations: expected 5, got 4") +} diff --git a/libs/chainconsensus/oracle/reporting_plugin.go b/libs/chainconsensus/oracle/reporting_plugin.go index c5b3f7b62..6e1fa63f9 100644 --- a/libs/chainconsensus/oracle/reporting_plugin.go +++ b/libs/chainconsensus/oracle/reporting_plugin.go @@ -37,8 +37,19 @@ var _ ocr3types.ReportingPlugin[[]byte] = (*reportingPlugin)(nil) type Config struct { ocr3types.ReportingPluginConfig - MaxBatchSize int // max number of requests that this node will try to process in a single round - MaxObservationLength int // max length of observation in bytes + MaxBatchSize int // max number of requests that this node will try to process in a single round + MaxObservationLength int // max length of observation in bytes + MinResponsesToAggregate int // minimum responses to aggregate to accept a read value; 0 means use F+1 +} + +// matchingThreshold returns the minimum number of nodes that must report identical +// observations for a read value to be accepted. When MinIdenticalObservations is +// zero the default of F+1 is used. +func (c Config) matchingThreshold() int { + if c.MinResponsesToAggregate > 0 { + return c.MinResponsesToAggregate + } + return c.F + 1 } type reportingPlugin struct { @@ -455,7 +466,7 @@ func (rp *reportingPlugin) agreeOnObservationType(requestID string, aos []attrib } } - return mode[ctypes.ObservationType, ctypes.ObservationType](rp.config.N, rp.config.F, iterator) + return mode[ctypes.ObservationType, ctypes.ObservationType](rp.config.N, rp.config.F, rp.config.matchingThreshold(), iterator) } func (rp *reportingPlugin) aggregateValue(requestID string, aos []attributedObservation) (*pb.Decimal, error) { @@ -520,17 +531,18 @@ func (rp *reportingPlugin) agreeOnAggregationMethod(requestID string, aos []attr } } - return mode[string, string](rp.config.N, rp.config.F, iterator) + return mode[string, string](rp.config.N, rp.config.F, rp.config.matchingThreshold(), iterator) } func (rp *reportingPlugin) agreeOnMissingRequestIDs(aos []attributedObservation) ([]string, error) { counter := make(map[string]int) var result []string + minMatching := rp.config.matchingThreshold() for _, ob := range aos { // MissingRequestIDs are guaranteed to be unique per observation by ValidateObservation for _, missingRequestID := range ob.Observation.MissingRequestIDs { counter[missingRequestID]++ - if counter[missingRequestID] == rp.config.F+1 { + if counter[missingRequestID] == minMatching { result = append(result, missingRequestID) } } @@ -565,7 +577,7 @@ func (rp *reportingPlugin) agreeOnEventuallyConsistentValue(requestID string, ao } } - return mode[[32]byte, []byte](rp.config.N, rp.config.F, iterator) + return mode[[32]byte, []byte](rp.config.N, rp.config.F, rp.config.matchingThreshold(), iterator) } func (rp *reportingPlugin) agreeOnHashableValue(requestID string, aos []attributedObservation) ([]byte, error) { @@ -602,7 +614,7 @@ func (rp *reportingPlugin) agreeOnHashableValue(requestID string, aos []attribut } } - return mode[[32]byte, []byte](rp.config.N, rp.config.F, iterator) + return mode[[32]byte, []byte](rp.config.N, rp.config.F, rp.config.matchingThreshold(), iterator) } func medianInt64(heights []int64) float64 { @@ -687,7 +699,7 @@ func (rp *reportingPlugin) agreeOnVolatileValue(requestID string, aos []attribut var best *volatileOutcomeCandidate for _, candidate := range candidates { - if candidate.supporters < rp.config.F+1 { + if candidate.supporters < rp.config.matchingThreshold() { continue } if best == nil || isVolatileCandidateABetter(&candidate, best) { @@ -701,7 +713,7 @@ func (rp *reportingPlugin) agreeOnVolatileValue(requestID string, aos []attribut }, nil } - errPayload, err := modeForError(rp.config.N, rp.config.F, requestID, aos) + errPayload, err := modeForError(rp.config.N, rp.config.F, rp.config.matchingThreshold(), requestID, aos) if err != nil { if errors.Is(err, errInsufficientErrorOb) { return nil, errors.New("no volatile outcome candidate reached F+1 supporters") @@ -795,7 +807,7 @@ func (rp *reportingPlugin) Outcome( Outcome: &ctypes.RequestOutcome_LockableToBlock{LockableToBlock: &emptypb.Empty{}}, }) case ctypes.ObservationType_ERROR: - requestErrors, err := modeForError(rp.config.N, rp.config.F, requestID, aos) + requestErrors, err := modeForError(rp.config.N, rp.config.F, rp.config.matchingThreshold(), requestID, aos) if err != nil { rp.logger.Infow("Could not determine request error", "requestID", requestID, "err", err) continue diff --git a/libs/chainconsensus/oracle/reporting_plugin_factory.go b/libs/chainconsensus/oracle/reporting_plugin_factory.go index 1dd636486..004c75d2b 100644 --- a/libs/chainconsensus/oracle/reporting_plugin_factory.go +++ b/libs/chainconsensus/oracle/reporting_plugin_factory.go @@ -50,12 +50,19 @@ func (rpf *ReportingPluginFactory) NewReportingPlugin( return nil, ocr3types.ReportingPluginInfo{}, fmt.Errorf("failed to read reporting plugin config: %w", err) } + //nolint:gosec // F and N values will never exceed uint32 max + // Allow MinResponsesToAggregate to be set to 0 to use the default of F+1. If set, it must be between F+1 and N. + if offchainCfg.MinResponsesToAggregate != 0 && (offchainCfg.MinResponsesToAggregate < uint32(config.F+1) || offchainCfg.MinResponsesToAggregate > uint32(config.N)) { + return nil, ocr3types.ReportingPluginInfo{}, fmt.Errorf("invalid MinResponsesToAggregate: %d; must be gte to %d (F+1) and lte to %d (N)", offchainCfg.MinResponsesToAggregate, config.F+1, config.N) + } + rpf.logger.Infof("Using reporting plugin config: %+v", offchainCfg) cfg := Config{ - ReportingPluginConfig: config, - MaxBatchSize: int(offchainCfg.MaxBatchSize), - MaxObservationLength: int(offchainCfg.MaxObservationLengthBytes), + ReportingPluginConfig: config, + MaxBatchSize: int(offchainCfg.MaxBatchSize), + MaxObservationLength: int(offchainCfg.MaxObservationLengthBytes), + MinResponsesToAggregate: int(offchainCfg.MinResponsesToAggregate), } return newReportingPlugin(cfg, rpf.logger, rpf.blocksProvider, rpf.requestsStore, rpf.metrics), ocr3types.ReportingPluginInfo{ diff --git a/libs/chainconsensus/oracle/reporting_plugin_test.go b/libs/chainconsensus/oracle/reporting_plugin_test.go index 0f8cbb1b5..eb3f8f1b1 100644 --- a/libs/chainconsensus/oracle/reporting_plugin_test.go +++ b/libs/chainconsensus/oracle/reporting_plugin_test.go @@ -22,6 +22,7 @@ import ( "github.com/smartcontractkit/capabilities/libs/chainconsensus/test" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + evmcapocr3types "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/consensus/ocr3/types" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" valuespb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb" @@ -1659,3 +1660,158 @@ func TestIsVolatileCandidateBetter(t *testing.T) { }) } } + +// TestAgreeOnEventuallyConsistentValue_TwoFPlusOne verifies that setting +// MinIdenticalObservations=2F+1 raises the matching bar for read results. +func TestAgreeOnEventuallyConsistentValue_TwoFPlusOne(t *testing.T) { + const id = "req-2f1" + // N=7, F=2 → require 5 identical (2F+1) + plugin := newReportingPlugin(Config{ + ReportingPluginConfig: ocr3types.ReportingPluginConfig{F: 2, N: 7}, + MinResponsesToAggregate: 5, + }, logger.Sugared(logger.Test(t)), nil, nil, test.GetConsensusMetrics(t)) + + makeAos := func(values []string) []attributedObservation { + aos := make([]attributedObservation, len(values)) + for i, v := range values { + //nolint:gosec + aos[i] = attributedObservation{ + Observer: commontypes.OracleID(i), + Observation: &types.Observation{ + Observations: map[string]*types.RequestObservation{ + id: {Observation: &types.RequestObservation_EventuallyConsistent{EventuallyConsistent: []byte(v)}}, + }, + }, + } + } + return aos + } + + t.Run("succeeds with 5 of 7 identical", func(t *testing.T) { + aos := makeAos([]string{"v", "v", "v", "v", "v", "other", "other"}) + got, err := plugin.agreeOnEventuallyConsistentValue(id, aos) + require.NoError(t, err) + require.Equal(t, []byte("v"), got) + }) + + t.Run("fails with only 4 of 7 identical", func(t *testing.T) { + aos := makeAos([]string{"v", "v", "v", "v", "x", "y", "z"}) + _, err := plugin.agreeOnEventuallyConsistentValue(id, aos) + require.ErrorContains(t, err, "insufficient number of identical observations: expected 5, got 4") + }) + + t.Run("default F+1 still works when MinIdenticalObservations is zero", func(t *testing.T) { + pluginDefault := newReportingPlugin(Config{ + ReportingPluginConfig: ocr3types.ReportingPluginConfig{F: 2, N: 7}, + }, logger.Sugared(logger.Test(t)), nil, nil, test.GetConsensusMetrics(t)) + // 3 of 7 matching → F+1=3, should succeed + aos := makeAos([]string{"v", "v", "v", "a", "b", "c", "d"}) + got, err := pluginDefault.agreeOnEventuallyConsistentValue(id, aos) + require.NoError(t, err) + require.Equal(t, []byte("v"), got) + }) +} + +func marshalOffchainConfig(t *testing.T, cfg *evmcapocr3types.ReportingPluginConfig) []byte { + t.Helper() + b, err := proto.Marshal(cfg) + require.NoError(t, err) + return b +} + +func TestNewReportingPlugin_MinResponsesToAggregateValidation(t *testing.T) { + const N = 4 + const F = 1 + // F+1 = 2 + + newFactory := func(t *testing.T) *ReportingPluginFactory { + t.Helper() + return NewReportingPluginFactory( + logger.Sugared(logger.Test(t)), + mocks.NewRequestsHandler(t), + mocks.NewBlocksProvider(t), + test.GetConsensusMetrics(t), + ) + } + + baseOffchainCfg := &evmcapocr3types.ReportingPluginConfig{ + MaxQueryLengthBytes: 1024 * 1024, + MaxObservationLengthBytes: 95 * 1024, + MaxOutcomeLengthBytes: uint32(ocr3types.MaxMaxOutcomeLength), + MaxReportLengthBytes: uint32(ocr3types.MaxMaxReportLength), + MaxReportCount: uint32(ocr3types.MaxMaxReportCount), + MaxBatchSize: 200, + } + + t.Run("below F+1 is rejected", func(t *testing.T) { + cfg := proto.Clone(baseOffchainCfg).(*evmcapocr3types.ReportingPluginConfig) + cfg.MinResponsesToAggregate = 1 // less than F+1=2 + + _, _, err := newFactory(t).NewReportingPlugin(t.Context(), ocr3types.ReportingPluginConfig{ + F: F, + N: N, + OffchainConfig: marshalOffchainConfig(t, cfg), + }) + require.ErrorContains(t, err, "invalid MinResponsesToAggregate") + }) + + t.Run("zero is allowed (default case)", func(t *testing.T) { + cfg := proto.Clone(baseOffchainCfg).(*evmcapocr3types.ReportingPluginConfig) + cfg.MinResponsesToAggregate = 0 + + _, _, err := newFactory(t).NewReportingPlugin(t.Context(), ocr3types.ReportingPluginConfig{ + F: F, + N: N, + OffchainConfig: marshalOffchainConfig(t, cfg), + }) + require.NoError(t, err) + }) + + t.Run("above N is rejected", func(t *testing.T) { + cfg := proto.Clone(baseOffchainCfg).(*evmcapocr3types.ReportingPluginConfig) + cfg.MinResponsesToAggregate = N + 1 + + _, _, err := newFactory(t).NewReportingPlugin(t.Context(), ocr3types.ReportingPluginConfig{ + F: F, + N: N, + OffchainConfig: marshalOffchainConfig(t, cfg), + }) + require.ErrorContains(t, err, "invalid MinResponsesToAggregate") + }) + + t.Run("equal to F+1 is accepted", func(t *testing.T) { + cfg := proto.Clone(baseOffchainCfg).(*evmcapocr3types.ReportingPluginConfig) + cfg.MinResponsesToAggregate = F + 1 + + _, _, err := newFactory(t).NewReportingPlugin(t.Context(), ocr3types.ReportingPluginConfig{ + F: F, + N: N, + OffchainConfig: marshalOffchainConfig(t, cfg), + }) + require.NoError(t, err) + }) + + t.Run("equal to N is accepted", func(t *testing.T) { + cfg := proto.Clone(baseOffchainCfg).(*evmcapocr3types.ReportingPluginConfig) + cfg.MinResponsesToAggregate = N + + _, _, err := newFactory(t).NewReportingPlugin(t.Context(), ocr3types.ReportingPluginConfig{ + F: F, + N: N, + OffchainConfig: marshalOffchainConfig(t, cfg), + }) + require.NoError(t, err) + }) + + t.Run("value between F+1 and N is accepted", func(t *testing.T) { + cfg := proto.Clone(baseOffchainCfg).(*evmcapocr3types.ReportingPluginConfig) + cfg.MinResponsesToAggregate = 3 // F+1=2, N=4 + + _, _, err := newFactory(t).NewReportingPlugin(t.Context(), ocr3types.ReportingPluginConfig{ + F: F, + N: N, + OffchainConfig: marshalOffchainConfig(t, cfg), + }) + require.NoError(t, err) + }) +} diff --git a/libs/go.mod b/libs/go.mod index 7c7e3202b..fcfaecbc3 100644 --- a/libs/go.mod +++ b/libs/go.mod @@ -8,8 +8,8 @@ require ( github.com/google/uuid v1.6.0 github.com/hashicorp/go-plugin v1.8.0 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260511105412-c0c74c7c893e - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260420204255-a3f3bdd56877 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260529220618-3961b1465372 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260526195338-adcf8013a1b7 github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.43.0 @@ -74,14 +74,14 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect - github.com/smartcontractkit/chain-selectors v1.0.89 // indirect - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect + github.com/smartcontractkit/chain-selectors v1.0.101 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260518142424-bacfb6ba4146 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260205130626-db2a2aab956b // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e // indirect @@ -90,6 +90,7 @@ require ( github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect @@ -108,13 +109,14 @@ require ( go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.49.0 // indirect - golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/net v0.52.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect - golang.org/x/time v0.14.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/libs/go.sum b/libs/go.sum index c62c85485..0c9fc815b 100644 --- a/libs/go.sum +++ b/libs/go.sum @@ -187,15 +187,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -204,16 +204,16 @@ github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cj github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/smartcontractkit/chain-selectors v1.0.89 h1:L9oWZGqQXWyTPnC6ODXgu3b0DFyLmJ9eHv+uJrE9IZY= -github.com/smartcontractkit/chain-selectors v1.0.89/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260511105412-c0c74c7c893e h1:ohdyUY4e3I0n2xt6ahFpcdFsV7wzEoMFLSQ4sU4yGmQ= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260511105412-c0c74c7c893e/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= +github.com/smartcontractkit/chain-selectors v1.0.101 h1:TF4ma9h3QeyIZ8XoEmgI5lrUvZfzHAz8tfR0pV0+GCA= +github.com/smartcontractkit/chain-selectors v1.0.101/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260529220618-3961b1465372 h1:ZvJn7kXgVZ2ByeLGFUTzsdXICXbjoe++AjeQW221pkE= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260529220618-3961b1465372/go.mod h1:jueIfDkkRexwGgLbVB7vGCZlNtd383zuwi4uHHwcbqc= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260518142424-bacfb6ba4146 h1:PlkA7NGpBm5sc2P//crDFgMIQ0qsQhKcpjWV7Qzwqz8= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260518142424-bacfb6ba4146/go.mod h1:HmUyH2oD9m+GRpKq7q3vuRnm1F2Uczf/Nd1v3ipMSK8= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260420204255-a3f3bdd56877 h1:6UueUIbck1Ogarm9rm/9TS6b09mKgMmx+YE8XFg63AQ= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260420204255-a3f3bdd56877/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260526195338-adcf8013a1b7 h1:iljEJss3WOwcsMkWy72Yn2zvjw7Gyxc+RXL7r8YKM6g= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260526195338-adcf8013a1b7/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260205130626-db2a2aab956b h1:36knUpKHHAZ86K4FGWXtx8i/EQftGdk2bqCoEu/Cha8= @@ -251,6 +251,8 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 h1:w3zlHYETbDwXyWHZlyyR58ZC39XGi8rAhkBgUgJ9d5w= +go.opentelemetry.io/contrib/bridges/prometheus v0.68.0/go.mod h1:GR/mClR2nn7vE8RLwxKjoBNg+QtgdDhRzxVa93koy5o= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= @@ -301,22 +303,24 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -326,8 +330,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -353,19 +357,19 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= -golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260519152614-eab6ae52b5e2 h1:2EucmYlcIsc8Y6aLj+kX90Y00hmjqLNlw935kc13R2k= +golang.org/x/telemetry v0.0.0-20260519152614-eab6ae52b5e2/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -374,8 +378,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 44cb501e73175681198b1bb511521266b46db98c Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 3 Jun 2026 19:02:56 -0500 Subject: [PATCH 2/2] Fixed tests --- libs/chainconsensus/oracle/error_mode_test.go | 22 ++++++++++++++++--- libs/chainconsensus/oracle/mode_test.go | 15 ++++++++----- .../oracle/reporting_plugin_test.go | 9 +++++--- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/libs/chainconsensus/oracle/error_mode_test.go b/libs/chainconsensus/oracle/error_mode_test.go index b5cccfd2d..29b460fc2 100644 --- a/libs/chainconsensus/oracle/error_mode_test.go +++ b/libs/chainconsensus/oracle/error_mode_test.go @@ -176,10 +176,11 @@ func TestErrorMode_TwoFPlusOneThreshold(t *testing.T) { return aos } - t.Run("succeeds when 5 of 7 report same error", func(t *testing.T) { + t.Run("succeeds when minMatching number of identical errors are reported", func(t *testing.T) { errors := []string{"err", "err", "err", "err", "err", "other", "other"} - result, err := modeForError(N, F, minMatching, requestID, makeAos(errors)) + result, actualCount, err := modeForError(N, F, minMatching, requestID, makeAos(errors)) require.NoError(t, err) + require.Equal(t, 5, actualCount) require.Equal(t, []string{"err"}, func() []string { s := make([]string, len(result)) for i, b := range result { @@ -189,6 +190,20 @@ func TestErrorMode_TwoFPlusOneThreshold(t *testing.T) { }()) }) + t.Run("succeeds when minMatching number of different errors are reported", func(t *testing.T) { + errors := []string{"err-a", "err-a", "err-b", "err-b", "err-c", "err-d", "err-e"} + result, actualCount, err := modeForError(N, F, minMatching, requestID, makeAos(errors)) + require.NoError(t, err) + require.Equal(t, 5, actualCount) + require.Equal(t, []string{"err-a", "err-b", "err-c"}, func() []string { + s := make([]string, len(result)) + for i, b := range result { + s[i] = string(b) + } + return s + }()) + }) + t.Run("fails when combined error observations don't reach minMatching", func(t *testing.T) { // Only 4 error observations total (3 distinct errors), non-errors fill the remaining 3 slots allObs := make([]attributedObservation, N) @@ -216,7 +231,8 @@ func TestErrorMode_TwoFPlusOneThreshold(t *testing.T) { }, } } - _, err := modeForError(N, F, minMatching, requestID, allObs) + _, actualCount, err := modeForError(N, F, minMatching, requestID, allObs) require.ErrorContains(t, err, "insufficient number of errors") + require.Equal(t, 4, actualCount) }) } diff --git a/libs/chainconsensus/oracle/mode_test.go b/libs/chainconsensus/oracle/mode_test.go index 4526cd564..6c1a7202a 100644 --- a/libs/chainconsensus/oracle/mode_test.go +++ b/libs/chainconsensus/oracle/mode_test.go @@ -31,36 +31,41 @@ func stringObs(values ...string) iter.Seq2[commontypes.OracleID, *observation[st func TestMode_DefaultFPlusOne(t *testing.T) { // N=7, F=2 → byzQuorumSize=5, default minMatching=F+1=3 N, F := 7, 2 - got, err := mode[string, string](N, F, F+1, stringObs("a", "a", "a", "b", "b", "c", "")) + got, actualCount, err := mode[string, string](N, F, F+1, stringObs("a", "a", "a", "b", "b", "c", "")) require.NoError(t, err) require.Equal(t, "a", got) + require.Equal(t, 3, actualCount) } func TestMode_DefaultFPlusOne_InsufficientMatching(t *testing.T) { // N=7, F=2 → byzQuorumSize=5; default minMatching=F+1=3 N, F := 7, 2 - _, err := mode[string, string](N, F, F+1, stringObs("a", "a", "b", "b", "c", "c", "d")) + _, actualCount, err := mode[string, string](N, F, F+1, stringObs("a", "a", "b", "b", "c", "c", "d")) require.ErrorContains(t, err, "insufficient number of identical observations: expected 3, got 2") + require.Equal(t, 2, actualCount) } func TestMode_TwoFPlusOne(t *testing.T) { // N=7, F=2 → minMatching=5; 5 agree on "a" N, F := 7, 2 - got, err := mode[string, string](N, F, 2*F+1, stringObs("a", "a", "a", "a", "a", "b", "c")) + got, actualCount, err := mode[string, string](N, F, 2*F+1, stringObs("a", "a", "a", "a", "a", "b", "c")) require.NoError(t, err) require.Equal(t, "a", got) + require.Equal(t, 5, actualCount) } func TestMode_TwoFPlusOne_InsufficientMatching(t *testing.T) { // N=7, F=2 → minMatching=5; only 4 agree on "a" → fail N, F := 7, 2 - _, err := mode[string, string](N, F, 2*F+1, stringObs("a", "a", "a", "a", "b", "b", "c")) + _, actualCount, err := mode[string, string](N, F, 2*F+1, stringObs("a", "a", "a", "a", "b", "b", "c")) require.ErrorContains(t, err, "insufficient number of identical observations: expected 5, got 4") + require.Equal(t, 4, actualCount) } func TestMode_InsufficientTotalObservations(t *testing.T) { // N=7, F=2 → byzQuorumSize=5; only 4 provided → fail before matching check N, F := 7, 2 - _, err := mode[string, string](N, F, F+1, stringObs("a", "a", "a", "a")) + _, actualCount, err := mode[string, string](N, F, F+1, stringObs("a", "a", "a", "a")) require.ErrorContains(t, err, "insufficient number of observations: expected 5, got 4") + require.Equal(t, 0, actualCount) } diff --git a/libs/chainconsensus/oracle/reporting_plugin_test.go b/libs/chainconsensus/oracle/reporting_plugin_test.go index 101c70bdd..61d2e0adf 100644 --- a/libs/chainconsensus/oracle/reporting_plugin_test.go +++ b/libs/chainconsensus/oracle/reporting_plugin_test.go @@ -1711,15 +1711,17 @@ func TestAgreeOnEventuallyConsistentValue_TwoFPlusOne(t *testing.T) { t.Run("succeeds with 5 of 7 identical", func(t *testing.T) { aos := makeAos([]string{"v", "v", "v", "v", "v", "other", "other"}) - got, err := plugin.agreeOnEventuallyConsistentValue(id, aos) + got, actualCount, err := plugin.agreeOnEventuallyConsistentValue(id, aos) require.NoError(t, err) require.Equal(t, []byte("v"), got) + require.Equal(t, 5, actualCount) }) t.Run("fails with only 4 of 7 identical", func(t *testing.T) { aos := makeAos([]string{"v", "v", "v", "v", "x", "y", "z"}) - _, err := plugin.agreeOnEventuallyConsistentValue(id, aos) + _, actualCount, err := plugin.agreeOnEventuallyConsistentValue(id, aos) require.ErrorContains(t, err, "insufficient number of identical observations: expected 5, got 4") + require.Equal(t, 4, actualCount) }) t.Run("default F+1 still works when MinIdenticalObservations is zero", func(t *testing.T) { @@ -1728,9 +1730,10 @@ func TestAgreeOnEventuallyConsistentValue_TwoFPlusOne(t *testing.T) { }, logger.Sugared(logger.Test(t)), nil, nil, test.GetConsensusMetrics(t)) // 3 of 7 matching → F+1=3, should succeed aos := makeAos([]string{"v", "v", "v", "a", "b", "c", "d"}) - got, err := pluginDefault.agreeOnEventuallyConsistentValue(id, aos) + got, actualCount, err := pluginDefault.agreeOnEventuallyConsistentValue(id, aos) require.NoError(t, err) require.Equal(t, []byte("v"), got) + require.Equal(t, 3, actualCount) }) }