Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions chain_capabilities/evm/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ require (
github.com/ethereum/go-ethereum v1.17.0
github.com/google/go-cmp v0.7.0
github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260615195421-fb87220e503f
github.com/smartcontractkit/capabilities/libs v0.0.0-20260609124022-2749e4a32bfb
github.com/smartcontractkit/capabilities/libs v0.0.0-20260630163841-b72e3c0829c7
github.com/smartcontractkit/chain-selectors v1.0.104
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260619153749-934b00c44d37
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260630163725-715aee90ca96
github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260410162948-2dca02f24e98
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1
github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260410144512-ca02ad6ed16a
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260618082634-432eb85805e7
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260622152157-c8e129347b8b
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/otel v1.43.0
go.uber.org/zap v1.27.1
Expand Down Expand Up @@ -90,7 +90,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/smartcontractkit/chainlink-common/keystore v1.1.1-0.20260529092756-a94bc8ce96d6 // indirect
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 // indirect
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 // indirect
github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57 // indirect
github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260401162955-be2bc6b5264b // indirect
github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect
Expand Down
16 changes: 8 additions & 8 deletions chain_capabilities/evm/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions integration_tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ require (
github.com/smartcontractkit/capabilities/http_trigger v0.0.0-00010101000000-000000000000
github.com/smartcontractkit/capabilities/loadtestwritetarget v0.0.0-00010101000000-000000000000
github.com/smartcontractkit/chain-selectors v1.0.104
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260619153749-934b00c44d37
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260630163725-715aee90ca96
github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260609161557-8ceae53b8ab1
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260618082634-432eb85805e7
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260622152157-c8e129347b8b
github.com/smartcontractkit/chainlink-protos/workflows/go v0.0.0-20260528221400-84746b70eeeb
github.com/smartcontractkit/chainlink/v2 v2.29.1-cre-beta.0.0.20260609174137-e2407e0bdd98
github.com/smartcontractkit/cre-sdk-go v1.5.0
Expand Down Expand Up @@ -304,7 +304,7 @@ require (
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect
github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260615195421-fb87220e503f // indirect
github.com/smartcontractkit/capabilities/libs v0.0.0-20260609124022-2749e4a32bfb // indirect
github.com/smartcontractkit/capabilities/libs v0.0.0-20260630163841-b72e3c0829c7 // indirect
github.com/smartcontractkit/chainlink-aptos v0.0.0-20260507123701-77fc93b573bb // indirect
github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect
github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 // indirect
Expand All @@ -314,7 +314,7 @@ require (
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc // indirect
github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd // indirect
github.com/smartcontractkit/chainlink-common/keystore v1.2.0 // indirect
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 // indirect
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 // indirect
github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a // indirect
github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260403151002-2c91155b5501 // indirect
github.com/smartcontractkit/chainlink-feeds v0.1.2-0.20250227211209-7cd000095135 // indirect
Expand Down
16 changes: 8 additions & 8 deletions integration_tests/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions libs/chainconsensus/oracle/error_mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ 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.
// 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.
// The returned int is the count of the most frequently observed error(s) (not necessarily identical).
func modeForError(N, F int, requestID string, aos []attributedObservation) ([][]byte, int, error) {
func modeForError(N, F, minMatching int, requestID string, aos []attributedObservation) ([][]byte, int, error) {
type keyT [sha256.Size]byte
counters := make(map[keyT]*counter[[]byte])
var totalNum int
Expand Down Expand Up @@ -82,13 +82,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, count, fmt.Errorf("%w: expected %d, got %d", errInsufficientErrorOb, F+1, count)
if count < minMatching {
return nil, count, fmt.Errorf("%w: expected %d, got %d", errInsufficientErrorOb, minMatching, count)
}

return result, count, nil
Expand Down
85 changes: 84 additions & 1 deletion libs/chainconsensus/oracle/error_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestErrorMode(t *testing.T) {
Observation: strToObservation(ob),
}
}
rawActualErrors, actualCount, err := modeForError(N, tc.F, requestID, aos)
rawActualErrors, actualCount, err := modeForError(N, tc.F, tc.F+1, requestID, aos)
if tc.ExpectedError != "" {
require.ErrorContains(t, err, tc.ExpectedError)
} else {
Expand Down Expand Up @@ -153,3 +153,86 @@ 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 minMatching number of identical errors are reported", func(t *testing.T) {
errors := []string{"err", "err", "err", "err", "err", "other", "other"}
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 {
s[i] = string(b)
}
return s
}())
})

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)
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")}},
},
},
}
}
_, actualCount, err := modeForError(N, F, minMatching, requestID, allObs)
require.ErrorContains(t, err, "insufficient number of errors")
require.Equal(t, 4, actualCount)
})
}
Loading
Loading