diff --git a/commit/plugin_e2e_test.go b/commit/plugin_e2e_test.go index 82e1665de..6abbec74b 100644 --- a/commit/plugin_e2e_test.go +++ b/commit/plugin_e2e_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-ccip/internal/libs/mathslib" "github.com/smartcontractkit/chainlink-ccip/internal/libs/testhelpers/rand" @@ -721,7 +723,11 @@ func setupNode(params SetupNodeParams, nodeID commontypes.OracleID) nodeSetup { homeChainReader.EXPECT().GetFChain().Return(fChain, nil) homeChainReader.EXPECT(). GetOCRConfigs(mock.Anything, params.donID, consts.PluginTypeCommit). - Return([]reader.OCR3ConfigWithMeta{{}}, nil).Maybe() + Return(reader.ActiveAndCandidate{ + ActiveConfig: reader.OCR3ConfigWithMeta{ + ConfigDigest: params.reportingCfg.ConfigDigest, + }, + }, nil).Maybe() for peerID, supportedChains := range supportedChainsForPeer { homeChainReader.EXPECT().GetSupportedChainsForPeer(peerID).Return(supportedChains, nil).Maybe() @@ -825,6 +831,8 @@ func defaultNodeParams(t *testing.T) SetupNodeParams { lggr := logger.Test(t) donID := uint32(1) + rb := rand.RandomBytes32() + digest := ocrtypes.ConfigDigest(rb[:]) require.Equal(t, len(oracleIDs), len(peerIDs)) @@ -864,7 +872,7 @@ func defaultNodeParams(t *testing.T) SetupNodeParams { PriceFeedChainSelector: sourceChain1, } - reportingCfg := ocr3types.ReportingPluginConfig{F: 1} + reportingCfg := ocr3types.ReportingPluginConfig{F: 1, ConfigDigest: digest} params := SetupNodeParams{ ctx: ctx, diff --git a/commit/report.go b/commit/report.go index 78d7745f4..7586ddf21 100644 --- a/commit/report.go +++ b/commit/report.go @@ -133,7 +133,7 @@ func (p *Plugin) ShouldTransmitAcceptedReport( } // we only transmit reports if we are the "active" instance. - // we can check this by reading the OCR conigs home chain. + // we can check this by reading the OCR configs from the home chain. isCandidate, err := p.isCandidateInstance(ctx) if err != nil { return false, fmt.Errorf("isCandidateInstance: %w", err) @@ -171,5 +171,5 @@ func (p *Plugin) isCandidateInstance(ctx context.Context) (bool, error) { return false, fmt.Errorf("failed to get ocr configs from home chain: %w", err) } - return len(ocrConfigs) == 2 && ocrConfigs[1].ConfigDigest == p.reportingCfg.ConfigDigest, nil + return ocrConfigs.CandidateConfig.ConfigDigest == p.reportingCfg.ConfigDigest, nil } diff --git a/commit/report_test.go b/commit/report_test.go index 87b61cf7e..ce8614cde 100644 --- a/commit/report_test.go +++ b/commit/report_test.go @@ -1,18 +1,30 @@ package commit import ( + "fmt" "testing" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/smartcontractkit/chainlink-ccip/internal/libs/testhelpers/rand" + reader_mock "github.com/smartcontractkit/chainlink-ccip/mocks/internal_/reader" + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-ccip/commit/chainfee" "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot" rmntypes "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot/rmn/types" "github.com/smartcontractkit/chainlink-ccip/commit/tokenprice" "github.com/smartcontractkit/chainlink-ccip/internal/mocks" + "github.com/smartcontractkit/chainlink-ccip/internal/reader" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" ) @@ -153,3 +165,128 @@ func TestPluginReports_InvalidOutcome(t *testing.T) { _, err := p.Reports(tests.Context(t), 0, []byte("invalid json")) require.Error(t, err) } + +func Test_IsCandidateCheck(t *testing.T) { + rb := rand.RandomBytes32() + digest := types.ConfigDigest(rb[:]) + donID := uint32(3) + allTests := []struct { + name string + makePlugin func(t *testing.T, hc *reader_mock.MockHomeChain) *Plugin + makeHomeChain func(t *testing.T) *reader_mock.MockHomeChain + wantOutput bool + wantError bool + }{ + { + name: "Should return true if digest matches", + makePlugin: func(t *testing.T, hc *reader_mock.MockHomeChain) *Plugin { + p := &Plugin{ + homeChain: hc, + reportingCfg: ocr3types.ReportingPluginConfig{ + ConfigDigest: digest, + }, + } + return p + }, + makeHomeChain: func(t *testing.T) *reader_mock.MockHomeChain { + h := reader_mock.NewMockHomeChain(t) + h.On("GetOCRConfigs", mock.Anything, mock.Anything, consts.PluginTypeCommit). + Return(reader.ActiveAndCandidate{ + ActiveConfig: reader.OCR3ConfigWithMeta{}, + CandidateConfig: reader.OCR3ConfigWithMeta{ + ConfigDigest: digest, + }, + }, nil) + return h + }, + wantOutput: true, + wantError: false, + }, + { + name: "Should return false if digest doesn't match", + makePlugin: func(t *testing.T, hc *reader_mock.MockHomeChain) *Plugin { + p := &Plugin{ + homeChain: hc, + reportingCfg: ocr3types.ReportingPluginConfig{ + ConfigDigest: types.ConfigDigest(rand.RandomBytes32()), + }, + } + return p + }, + makeHomeChain: func(t *testing.T) *reader_mock.MockHomeChain { + h := reader_mock.NewMockHomeChain(t) + h.On("GetOCRConfigs", mock.Anything, mock.Anything, consts.PluginTypeCommit). + Return(reader.ActiveAndCandidate{ + ActiveConfig: reader.OCR3ConfigWithMeta{}, + CandidateConfig: reader.OCR3ConfigWithMeta{ + ConfigDigest: digest, + }, + }, nil) + return h + }, + wantOutput: false, + wantError: false, + }, + { + name: "Should work as expected without candidate instance", + makePlugin: func(t *testing.T, hc *reader_mock.MockHomeChain) *Plugin { + p := &Plugin{ + homeChain: hc, + reportingCfg: ocr3types.ReportingPluginConfig{ + ConfigDigest: types.ConfigDigest(rand.RandomBytes32()), + }, + } + return p + }, + makeHomeChain: func(t *testing.T) *reader_mock.MockHomeChain { + h := reader_mock.NewMockHomeChain(t) + h.On("GetOCRConfigs", mock.Anything, mock.Anything, consts.PluginTypeCommit). + Return(reader.ActiveAndCandidate{ + ActiveConfig: reader.OCR3ConfigWithMeta{}, + CandidateConfig: reader.OCR3ConfigWithMeta{}, + }, nil) + return h + }, + wantOutput: false, + wantError: false, + }, + { + name: "Should throw error if donID doesn't exist", + makePlugin: func(t *testing.T, hc *reader_mock.MockHomeChain) *Plugin { + p := &Plugin{ + homeChain: hc, + donID: donID, + reportingCfg: ocr3types.ReportingPluginConfig{ + ConfigDigest: types.ConfigDigest(rand.RandomBytes32()), + }, + } + return p + }, + makeHomeChain: func(t *testing.T) *reader_mock.MockHomeChain { + h := reader_mock.NewMockHomeChain(t) + h.On("GetOCRConfigs", mock.Anything, donID, consts.PluginTypeCommit). + Return(reader.ActiveAndCandidate{ + ActiveConfig: reader.OCR3ConfigWithMeta{}, + CandidateConfig: reader.OCR3ConfigWithMeta{}, + }, fmt.Errorf("DonID does not exist")) + return h + }, + wantOutput: false, + wantError: true, + }, + } + for _, tt := range allTests { + t.Run(tt.name, func(t *testing.T) { + ctx := tests.Context(t) + hc := tt.makeHomeChain(t) + p := tt.makePlugin(t, hc) + actualOutput, actualError := p.isCandidateInstance(ctx) + assert.Equal(t, tt.wantOutput, actualOutput) + if tt.wantError { + require.Error(t, actualError) + } else { + require.NoError(t, actualError) + } + }) + } +} diff --git a/execute/plugin.go b/execute/plugin.go index d808894c6..e6c8adc98 100644 --- a/execute/plugin.go +++ b/execute/plugin.go @@ -300,15 +300,15 @@ func (p *Plugin) ShouldTransmitAcceptedReport( return false, nil } - // we only transmit reports if we are the "blue" instance. - // we can check this by reading the OCR conigs home chain. - isGreen, err := p.isGreenInstance(ctx) + // we only transmit reports if we are the "active" instance. + // we can check this by reading the OCR configs home chain. + isCandidate, err := p.isCandidateInstance(ctx) if err != nil { - return false, fmt.Errorf("ShouldTransmitAcceptedReport.isGreenInstance: %w", err) + return false, fmt.Errorf("ShouldTransmitAcceptedReport.isCandidateInstance: %w", err) } - if isGreen { - p.lggr.Debugw("not the blue instance, skipping report transmission", + if isCandidate { + p.lggr.Debugw("not the active instance, skipping report transmission", "myDigest", p.reportingCfg.ConfigDigest.Hex()) return false, nil } @@ -324,13 +324,13 @@ func (p *Plugin) ShouldTransmitAcceptedReport( return true, nil } -func (p *Plugin) isGreenInstance(ctx context.Context) (bool, error) { +func (p *Plugin) isCandidateInstance(ctx context.Context) (bool, error) { ocrConfigs, err := p.homeChain.GetOCRConfigs(ctx, p.donID, consts.PluginTypeExecute) if err != nil { return false, fmt.Errorf("failed to get ocr configs from home chain: %w", err) } - return len(ocrConfigs) == 2 && ocrConfigs[1].ConfigDigest == p.reportingCfg.ConfigDigest, nil + return ocrConfigs.CandidateConfig.ConfigDigest == p.reportingCfg.ConfigDigest, nil } func (p *Plugin) Close() error { diff --git a/execute/plugin_test.go b/execute/plugin_test.go index 26e0de762..49b08aed8 100644 --- a/execute/plugin_test.go +++ b/execute/plugin_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink-ccip/internal/libs/testhelpers/rand" + mapset "github.com/deckarep/golang-set/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -24,11 +26,10 @@ import ( "github.com/smartcontractkit/chainlink-ccip/execute/exectypes" "github.com/smartcontractkit/chainlink-ccip/internal/libs/slicelib" dt "github.com/smartcontractkit/chainlink-ccip/internal/plugincommon/discovery/discoverytypes" + "github.com/smartcontractkit/chainlink-ccip/internal/reader" reader_mock "github.com/smartcontractkit/chainlink-ccip/mocks/internal_/reader" readerpkg_mock "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/reader" codec_mocks "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-ccip/pkg/consts" - "github.com/smartcontractkit/chainlink-ccip/pkg/reader" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" plugintypes2 "github.com/smartcontractkit/chainlink-ccip/plugintypes" ) @@ -531,12 +532,16 @@ func TestPlugin_ShouldTransmitAcceptReport_Ineligible(t *testing.T) { func TestPlugin_ShouldTransmitAcceptReport_DecodeFailure(t *testing.T) { const donID = uint32(1) + rb := rand.RandomBytes32() + digest := types.ConfigDigest(rb[:]) homeChain := reader_mock.NewMockHomeChain(t) homeChain.On("GetSupportedChainsForPeer", mock.Anything).Return(mapset.NewSet(cciptypes.ChainSelector(1)), nil) - homeChain. - EXPECT(). - GetOCRConfigs(mock.Anything, donID, consts.PluginTypeExecute). - Return([]reader.OCR3ConfigWithMeta{{}}, nil) + homeChain.On("GetOCRConfigs", mock.Anything, mock.Anything, mock.Anything). + Return(reader.ActiveAndCandidate{ + ActiveConfig: reader.OCR3ConfigWithMeta{ + ConfigDigest: digest, + }, + }, nil) codec := codec_mocks.NewMockExecutePluginCodec(t) codec.On("Decode", mock.Anything, mock.Anything). Return(cciptypes.ExecutePluginReport{}, fmt.Errorf("test error")) @@ -545,7 +550,7 @@ func TestPlugin_ShouldTransmitAcceptReport_DecodeFailure(t *testing.T) { donID: donID, lggr: logger.Test(t), destChain: 1, - reportingCfg: ocr3types.ReportingPluginConfig{OracleID: 2}, + reportingCfg: ocr3types.ReportingPluginConfig{OracleID: 2, ConfigDigest: digest}, reportCodec: codec, homeChain: homeChain, oracleIDToP2pID: map[commontypes.OracleID]libocrtypes.PeerID{ @@ -560,13 +565,17 @@ func TestPlugin_ShouldTransmitAcceptReport_DecodeFailure(t *testing.T) { func TestPlugin_ShouldTransmitAcceptReport_Success(t *testing.T) { const donID = uint32(1) + rb := rand.RandomBytes32() + digest := types.ConfigDigest(rb[:]) lggr, logs := logger.TestObserved(t, zapcore.DebugLevel) homeChain := reader_mock.NewMockHomeChain(t) homeChain.On("GetSupportedChainsForPeer", mock.Anything).Return(mapset.NewSet(cciptypes.ChainSelector(1)), nil) - homeChain. - EXPECT(). - GetOCRConfigs(mock.Anything, donID, consts.PluginTypeExecute). - Return([]reader.OCR3ConfigWithMeta{{}}, nil) + homeChain.On("GetOCRConfigs", mock.Anything, mock.Anything, mock.Anything). + Return(reader.ActiveAndCandidate{ + ActiveConfig: reader.OCR3ConfigWithMeta{ + ConfigDigest: digest, + }, + }, nil) codec := codec_mocks.NewMockExecutePluginCodec(t) codec.On("Decode", mock.Anything, mock.Anything). Return(cciptypes.ExecutePluginReport{}, nil) @@ -575,7 +584,7 @@ func TestPlugin_ShouldTransmitAcceptReport_Success(t *testing.T) { donID: donID, lggr: lggr, destChain: 1, - reportingCfg: ocr3types.ReportingPluginConfig{OracleID: 2}, + reportingCfg: ocr3types.ReportingPluginConfig{OracleID: 2, ConfigDigest: digest}, reportCodec: codec, homeChain: homeChain, oracleIDToP2pID: map[commontypes.OracleID]libocrtypes.PeerID{ diff --git a/execute/test_utils.go b/execute/test_utils.go index a4ec2abd6..e1530449e 100644 --- a/execute/test_utils.go +++ b/execute/test_utils.go @@ -2,6 +2,7 @@ package execute import ( "context" + crand "crypto/rand" "encoding/binary" "net/http" "net/http/httptest" @@ -14,6 +15,7 @@ import ( "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" libocrtypes "github.com/smartcontractkit/libocr/ragep2p/types" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" @@ -288,10 +290,12 @@ func (it *IntTest) newNode( N int, ) nodeSetup { reportCodec := mocks.NewExecutePluginJSONReportCodec() - + b := make([]byte, 32) + _, _ = crand.Read(b) rCfg := ocr3types.ReportingPluginConfig{ - N: N, - OracleID: commontypes.OracleID(id), + N: N, + OracleID: commontypes.OracleID(id), + ConfigDigest: ocrtypes.ConfigDigest(b), } node1 := NewPlugin( diff --git a/internal/reader/home_chain.go b/internal/reader/home_chain.go index 1ab2a311e..cae8a865e 100644 --- a/internal/reader/home_chain.go +++ b/internal/reader/home_chain.go @@ -35,7 +35,7 @@ type HomeChain interface { // GetFChain Gets the FChain value for each chain GetFChain() (map[cciptypes.ChainSelector]int, error) // GetOCRConfigs Gets the OCR3Configs for a given donID and pluginType - GetOCRConfigs(ctx context.Context, donID uint32, pluginType uint8) ([]OCR3ConfigWithMeta, error) + GetOCRConfigs(ctx context.Context, donID uint32, pluginType uint8) (ActiveAndCandidate, error) services.Service } @@ -220,10 +220,9 @@ func (r *homeChainPoller) GetFChain() (map[cciptypes.ChainSelector]int, error) { func (r *homeChainPoller) GetOCRConfigs( ctx context.Context, donID uint32, pluginType uint8, -) ([]OCR3ConfigWithMeta, error) { +) (ActiveAndCandidate, error) { var ( - ocrConfigs []OCR3ConfigWithMeta - allConfigs ActiveAndCandidate + activeAndCandidate ActiveAndCandidate ) err := r.homeChainReader.GetLatestValue( @@ -233,26 +232,18 @@ func (r *homeChainPoller) GetOCRConfigs( map[string]any{ "donId": donID, "pluginType": pluginType, - }, &allConfigs) + }, &activeAndCandidate) if err != nil { - return nil, fmt.Errorf("error fetching OCR configs: %w", err) + return ActiveAndCandidate{}, fmt.Errorf("error fetching OCR configs: %w", err) } r.lggr.Infow( "GetOCRConfigs", - "activeConfig", allConfigs.ActiveConfig, - "candidateConfig", allConfigs.CandidateConfig, + "activeConfig", activeAndCandidate.ActiveConfig, + "candidateConfig", activeAndCandidate.CandidateConfig, ) - if allConfigs.ActiveConfig.ConfigDigest != [32]byte{} { - ocrConfigs = append(ocrConfigs, allConfigs.ActiveConfig) - } - - if allConfigs.CandidateConfig.ConfigDigest != [32]byte{} { - ocrConfigs = append(ocrConfigs, allConfigs.CandidateConfig) - } - - return ocrConfigs, nil + return activeAndCandidate, nil } func (r *homeChainPoller) Close() error { diff --git a/internal/reader/home_chain_test.go b/internal/reader/home_chain_test.go index 056ededc7..3fd86119f 100644 --- a/internal/reader/home_chain_test.go +++ b/internal/reader/home_chain_test.go @@ -205,9 +205,10 @@ func Test_HomeChainPoller_GetOCRConfig(t *testing.T) { configs, err := configPoller.GetOCRConfigs(context.Background(), donID, pluginType) require.NoError(t, err) - require.Len(t, configs, 1) - require.Equal(t, uint8(1), configs[0].Config.PluginType) - require.Equal(t, cciptypes.ChainSelector(1), configs[0].Config.ChainSelector) - require.Equal(t, uint8(1), configs[0].Config.FRoleDON) - require.Equal(t, []byte("offramp"), configs[0].Config.OfframpAddress) + require.NotEqual(t, [32]byte{}, configs.ActiveConfig.ConfigDigest) + require.Equal(t, [32]byte{}, configs.CandidateConfig.ConfigDigest) + require.Equal(t, uint8(1), configs.ActiveConfig.Config.PluginType) + require.Equal(t, cciptypes.ChainSelector(1), configs.ActiveConfig.Config.ChainSelector) + require.Equal(t, uint8(1), configs.ActiveConfig.Config.FRoleDON) + require.Equal(t, []byte("offramp"), configs.ActiveConfig.Config.OfframpAddress) } diff --git a/mocks/internal_/reader/home_chain.go b/mocks/internal_/reader/home_chain.go index 767e57dc7..9b88f966c 100644 --- a/mocks/internal_/reader/home_chain.go +++ b/mocks/internal_/reader/home_chain.go @@ -302,24 +302,22 @@ func (_c *MockHomeChain_GetKnownCCIPChains_Call) RunAndReturn(run func() (mapset } // GetOCRConfigs provides a mock function with given fields: ctx, donID, pluginType -func (_m *MockHomeChain) GetOCRConfigs(ctx context.Context, donID uint32, pluginType uint8) ([]reader.OCR3ConfigWithMeta, error) { +func (_m *MockHomeChain) GetOCRConfigs(ctx context.Context, donID uint32, pluginType uint8) (reader.ActiveAndCandidate, error) { ret := _m.Called(ctx, donID, pluginType) if len(ret) == 0 { panic("no return value specified for GetOCRConfigs") } - var r0 []reader.OCR3ConfigWithMeta + var r0 reader.ActiveAndCandidate var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8) ([]reader.OCR3ConfigWithMeta, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8) (reader.ActiveAndCandidate, error)); ok { return rf(ctx, donID, pluginType) } - if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8) []reader.OCR3ConfigWithMeta); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint32, uint8) reader.ActiveAndCandidate); ok { r0 = rf(ctx, donID, pluginType) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]reader.OCR3ConfigWithMeta) - } + r0 = ret.Get(0).(reader.ActiveAndCandidate) } if rf, ok := ret.Get(1).(func(context.Context, uint32, uint8) error); ok { @@ -351,12 +349,12 @@ func (_c *MockHomeChain_GetOCRConfigs_Call) Run(run func(ctx context.Context, do return _c } -func (_c *MockHomeChain_GetOCRConfigs_Call) Return(_a0 []reader.OCR3ConfigWithMeta, _a1 error) *MockHomeChain_GetOCRConfigs_Call { +func (_c *MockHomeChain_GetOCRConfigs_Call) Return(_a0 reader.ActiveAndCandidate, _a1 error) *MockHomeChain_GetOCRConfigs_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockHomeChain_GetOCRConfigs_Call) RunAndReturn(run func(context.Context, uint32, uint8) ([]reader.OCR3ConfigWithMeta, error)) *MockHomeChain_GetOCRConfigs_Call { +func (_c *MockHomeChain_GetOCRConfigs_Call) RunAndReturn(run func(context.Context, uint32, uint8) (reader.ActiveAndCandidate, error)) *MockHomeChain_GetOCRConfigs_Call { _c.Call.Return(run) return _c } diff --git a/pkg/reader/home_chain.go b/pkg/reader/home_chain.go index 269eb863e..c08e37de0 100644 --- a/pkg/reader/home_chain.go +++ b/pkg/reader/home_chain.go @@ -17,6 +17,8 @@ type ChainConfigInfo = reader_internal.ChainConfigInfo type OCR3ConfigWithMeta = reader_internal.OCR3ConfigWithMeta +type ActiveAndCandidate = reader_internal.ActiveAndCandidate + type OCR3Config = reader_internal.OCR3Config type OCR3Node = reader_internal.OCR3Node