From 09d605658a80f119a89efadd3480ffd82db4b537 Mon Sep 17 00:00:00 2001 From: dimitris Date: Tue, 29 Oct 2024 14:42:58 +0200 Subject: [PATCH] RMN - Migrate to contracts that use `f` instead of `minSigners` and `minObservers` (#270) https://github.com/smartcontractkit/chainlink/pull/14817 --- commit/merkleroot/rmn/controller.go | 92 +++++++------- commit/merkleroot/rmn/controller_test.go | 137 +++++++++++++++------ commit/merkleroot/rmn/types/config.go | 23 ++-- commit/merkleroot/rmn/types/config_test.go | 10 +- commit/report.go | 13 +- internal/libs/testhelpers/common.go | 2 +- internal/reader/rmn_remote.go | 4 +- mocks/internal_/reader/rmn_remote.go | 22 ++-- mocks/pkg/reader/rmn_home.go | 22 ++-- pkg/reader/ccip.go | 11 +- pkg/reader/rmn_home.go | 23 ++-- pkg/reader/rmn_home_test.go | 20 +-- 12 files changed, 223 insertions(+), 156 deletions(-) diff --git a/commit/merkleroot/rmn/controller.go b/commit/merkleroot/rmn/controller.go index 6e438f5ee..9a7688c06 100644 --- a/commit/merkleroot/rmn/controller.go +++ b/commit/merkleroot/rmn/controller.go @@ -22,6 +22,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-ccip/internal/plugincommon/consensus" + typconv "github.com/smartcontractkit/chainlink-ccip/internal/libs/typeconv" "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot/rmn/rmnpb" @@ -36,7 +38,7 @@ var ( // ErrNothingToDo is returned when there are no source chains with enough RMN nodes. ErrNothingToDo = errors.New("nothing to observe from the existing RMN nodes, make " + - "sure RMN is enabled, nodes configured correctly and minObservers value is correct") + "sure RMN is enabled, nodes configured correctly and F value is correct") // ErrInsufficientObservationResponses is returned when we don't get enough observation responses to cover // all the observation requests. @@ -174,19 +176,21 @@ func (c *controller) ComputeReportSignatures( } } - minObserversMap, err := c.rmnHomeReader.GetMinObservers(rmnRemoteCfg.ConfigDigest) + homeFMap, err := c.rmnHomeReader.GetF(rmnRemoteCfg.ConfigDigest) if err != nil { - return nil, fmt.Errorf("get min observers: %w", err) + return nil, fmt.Errorf("get home F: %w", err) } // Filter out the lane update requests for chains without enough RMN nodes supporting them. for chain, l := range updatesPerChain { - if _, exists := minObserversMap[cciptypes.ChainSelector(chain)]; !exists { - return nil, fmt.Errorf("no min observers for chain %d", chain) + homeChainF, exists := homeFMap[cciptypes.ChainSelector(chain)] + if !exists { + return nil, fmt.Errorf("no home F for chain %d", chain) } - if l.RmnNodes.Cardinality() < minObserversMap[cciptypes.ChainSelector(chain)] { + + if consensus.Threshold(l.RmnNodes.Cardinality()) < consensus.FPlus1(homeChainF) { c.lggr.Warnw("chain skipped, not enough RMN nodes to support it", "chain", chain, - "minObservers", minObserversMap[cciptypes.ChainSelector(chain)], + "homeF", homeFMap[cciptypes.ChainSelector(chain)], "nodes", l.RmnNodes.ToSlice(), ) delete(updatesPerChain, chain) @@ -203,7 +207,7 @@ func (c *controller) ComputeReportSignatures( destChain, updatesPerChain, rmnRemoteCfg.ConfigDigest, - minObserversMap, + homeFMap, rmnNodeInfo) if err != nil { return nil, fmt.Errorf("get rmn signed observations: %w", err) @@ -247,29 +251,29 @@ func (c *controller) Close() error { return c.peerClient.Close() } -// getRmnSignedObservations guarantees to return at least #minObservers signed observations for each source chain. +// getRmnSignedObservations guarantees to return at least F+1 signed observations for each source chain. func (c *controller) getRmnSignedObservations( ctx context.Context, destChain *rmnpb.LaneDest, updateRequestsPerChain map[uint64]updateRequestWithMeta, configDigest cciptypes.Bytes32, - minObserversMap map[cciptypes.ChainSelector]int, + homeFMap map[cciptypes.ChainSelector]int, rmnNodeInfo map[rmntypes.NodeID]rmntypes.HomeNodeInfo, ) ([]rmnSignedObservationWithMeta, error) { requestedNodes := make(map[uint64]mapset.Set[rmntypes.NodeID]) // sourceChain -> requested rmnNodeIDs requestsPerNode := make(map[rmntypes.NodeID][]*rmnpb.FixedDestLaneUpdateRequest) // grouped requests for each node - // For each lane update request send an observation request to at most 'minObservers' number of rmn nodes. - // At this point we can safely assume that we have at least #minObservers supporting each source chain. + // For each lane update request send an observation request to at most 'F+1' number of rmn nodes. + // At this point we can safely assume that we have at least F+1 supporting each source chain. for sourceChain, updateRequest := range updateRequestsPerChain { requestedNodes[sourceChain] = mapset.NewSet[rmntypes.NodeID]() - minObservers, exist := minObserversMap[cciptypes.ChainSelector(sourceChain)] + homeChainF, exist := homeFMap[cciptypes.ChainSelector(sourceChain)] if !exist { - return nil, fmt.Errorf("no min observers for chain %d", sourceChain) + return nil, fmt.Errorf("no home F for chain %d", sourceChain) } for nodeID := range updateRequest.RmnNodes.Iter() { - if requestedNodes[sourceChain].Cardinality() >= minObservers { + if consensus.Threshold(requestedNodes[sourceChain].Cardinality()) >= consensus.FPlus1(homeChainF) { break // We have enough initial observers for this source chain. } @@ -284,14 +288,14 @@ func (c *controller) getRmnSignedObservations( requestIDs := c.sendObservationRequests(destChain, requestsPerNode, rmnNodeInfo) signedObservations, err := c.listenForRmnObservationResponses( - ctx, destChain, requestIDs, updateRequestsPerChain, requestedNodes, configDigest, minObserversMap, rmnNodeInfo) + ctx, destChain, requestIDs, updateRequestsPerChain, requestedNodes, configDigest, homeFMap, rmnNodeInfo) if err != nil { return nil, fmt.Errorf("listen for rmn observation responses: %w", err) } // Sanity check that we got enough signed observations for every source chain. // In practice this should never happen, an error must have been received earlier. - if !gotSufficientObservationResponses(c.lggr, updateRequestsPerChain, signedObservations, minObserversMap) { + if !gotSufficientObservationResponses(c.lggr, updateRequestsPerChain, signedObservations, homeFMap) { return nil, fmt.Errorf("not enough signed observations after sanity check") } @@ -367,7 +371,7 @@ func (c *controller) listenForRmnObservationResponses( lursPerChain map[uint64]updateRequestWithMeta, requestedNodes map[uint64]mapset.Set[rmntypes.NodeID], configDigest cciptypes.Bytes32, - minObserversMap map[cciptypes.ChainSelector]int, + homeFMap map[cciptypes.ChainSelector]int, rmnNodeInfo map[rmntypes.NodeID]rmntypes.HomeNodeInfo, ) ([]rmnSignedObservationWithMeta, error) { c.lggr.Infow("listening for RMN observation responses", "requestIDs", requestIDs.String()) @@ -411,7 +415,7 @@ func (c *controller) listenForRmnObservationResponses( c.lggr, lursPerChain, rmnObservationResponses, - minObserversMap) + homeFMap) if allChainsHaveEnoughResponses { c.lggr.Infof("all chains have enough observation responses with matching roots") return rmnObservationResponses, nil @@ -448,12 +452,12 @@ func (c *controller) listenForRmnObservationResponses( } // gotSufficientObservationResponses checks if we got enough observation responses for each source chain. -// Enough meaning that we got at least #minObservers observing the same merkle root for a target chain. +// Enough meaning that we got at least F+1 observing the same merkle root for a target chain. func gotSufficientObservationResponses( lggr logger.Logger, updateRequests map[uint64]updateRequestWithMeta, rmnObservationResponses []rmnSignedObservationWithMeta, - minObserversMap map[cciptypes.ChainSelector]int, + homeFMap map[cciptypes.ChainSelector]int, ) bool { merkleRootsCount := make(map[uint64]map[cciptypes.Bytes32]int) for _, signedObs := range rmnObservationResponses { @@ -466,20 +470,20 @@ func gotSufficientObservationResponses( } for sourceChain := range updateRequests { - // make sure we got at least #minObservers observing the same merkle root for a target chain. + // make sure we got at least F+1 observing the same merkle root for a target chain. countsPerRoot, ok := merkleRootsCount[sourceChain] if !ok || len(countsPerRoot) == 0 { return false } - minObservers, exists := minObserversMap[cciptypes.ChainSelector(sourceChain)] + homeChainF, exists := homeFMap[cciptypes.ChainSelector(sourceChain)] if !exists { - lggr.Errorw("no min observers for chain", "chain", sourceChain) + lggr.Errorw("no F for chain", "chain", sourceChain) return false } values := maps.Values(countsPerRoot) sort.Slice(values, func(i, j int) bool { return values[i] < values[j] }) - if values[len(values)-1] < minObservers { + if consensus.Threshold(values[len(values)-1]) < consensus.FPlus1(homeChainF) { return false } } @@ -569,7 +573,7 @@ func (c *controller) getRmnReportSignatures( // from the same node. // // e.g. - // The following nodes support the following chains and minObservers=2: + // The following nodes support the following chains and F=1: // node1: [1] node2:[1,2,3] node3:[1,2,3] // // node1: getObservations(1) -> never_responds @@ -580,12 +584,12 @@ func (c *controller) getRmnReportSignatures( // At this point it is also possible that the signed observations contain // different roots for the same source chain and interval. - minObservers, err := c.rmnHomeReader.GetMinObservers(rmnRemoteCfg.ConfigDigest) + homeChainF, err := c.rmnHomeReader.GetF(rmnRemoteCfg.ConfigDigest) if err != nil { - return nil, fmt.Errorf("get min observers: %w", err) + return nil, fmt.Errorf("get home reader F: %w", err) } - rootsPerChain, err := selectRoots(rmnSignedObservations, minObservers) + rootsPerChain, err := selectRoots(rmnSignedObservations, homeChainF) if err != nil { return nil, fmt.Errorf("get most voted roots from observations: %w", err) } @@ -640,12 +644,12 @@ func (c *controller) getRmnReportSignatures( }, AttributedSignedObservations: transformAndSortObservations(rmnSignedObservations), } - minSigners := int(rmnRemoteCfg.MinSigners) + remoteF := int(rmnRemoteCfg.F) signers := rmnRemoteCfg.Signers requestIDs, signersRequested, err := c.sendReportSignatureRequest( reportSigReq, signers, - minSigners, + remoteF, rmnNodeInfo) if err != nil { return nil, fmt.Errorf("send report signature request: %w", err) @@ -658,7 +662,7 @@ func (c *controller) getRmnReportSignatures( reportSigReq, signersRequested, signers, - minSigners, + remoteF, rmnNodeInfo) if err != nil { return nil, fmt.Errorf("listen for rmn report signatures: %w", err) @@ -702,10 +706,10 @@ func transformAndSortObservations( } // selectsRoots selects the roots from the signed observations. -// If there are more than one valid roots based on the provided minObservers it returns an error. +// If there are more than one valid roots based on the provided F it returns an error. func selectRoots( observations []rmnSignedObservationWithMeta, - minObservers map[cciptypes.ChainSelector]int, + homeFMap map[cciptypes.ChainSelector]int, ) (map[cciptypes.ChainSelector]cciptypes.Bytes32, error) { votesPerRoot := make(map[cciptypes.ChainSelector]map[cciptypes.Bytes32]int) for _, so := range observations { @@ -719,15 +723,15 @@ func selectRoots( selectedRoots := make(map[cciptypes.ChainSelector]cciptypes.Bytes32) for chain, votes := range votesPerRoot { - minObserversForChain, exists := minObservers[chain] + homeF, exists := homeFMap[chain] if !exists { - return nil, fmt.Errorf("no min observers for chain %d", chain) + return nil, fmt.Errorf("no home F for chain %d", chain) } var selectedRoot cciptypes.Bytes32 for root, vote := range votes { - if vote < minObserversForChain { + if consensus.Threshold(vote) < consensus.FPlus1(homeF) { continue } @@ -747,21 +751,21 @@ func selectRoots( return selectedRoots, nil } -// sendReportSignatureRequest sends the report signature request to #minSigners random RMN nodes. +// sendReportSignatureRequest sends the report signature request to #remoteF+1 random RMN nodes. // If not enough requests were sent, it returns an error. func (c *controller) sendReportSignatureRequest( reportSigReq *rmnpb.ReportSignatureRequest, remoteSigners []rmntypes.RemoteSignerInfo, - minSigners int, + remoteF int, rmnNodeInfo map[rmntypes.NodeID]rmntypes.HomeNodeInfo, ) ( requestIDs mapset.Set[uint64], signersRequested mapset.Set[rmntypes.NodeID], err error) { requestIDs = mapset.NewSet[uint64]() signersRequested = mapset.NewSet[rmntypes.NodeID]() - // Send the report signature request to at least minSigners + // Send the report signature request to at least #remoteF+1 for _, node := range randomShuffle(remoteSigners) { - if requestIDs.Cardinality() >= minSigners { + if consensus.Threshold(requestIDs.Cardinality()) >= consensus.FPlus1(remoteF) { break } @@ -790,7 +794,7 @@ func (c *controller) sendReportSignatureRequest( signersRequested.Add(rmntypes.NodeID(node.NodeIndex)) } - if requestIDs.Cardinality() < minSigners { + if consensus.Threshold(requestIDs.Cardinality()) < consensus.FPlus1(remoteF) { return requestIDs, signersRequested, fmt.Errorf("not able to send to enough report signers") } return requestIDs, signersRequested, nil @@ -811,7 +815,7 @@ func (c *controller) listenForRmnReportSignatures( reportSigReq *rmnpb.ReportSignatureRequest, signersRequested mapset.Set[rmntypes.NodeID], signers []rmntypes.RemoteSignerInfo, - minSigners int, + remoteF int, rmnNodeInfo map[rmntypes.NodeID]rmntypes.HomeNodeInfo, ) ([]*rmnpb.EcdsaSignature, error) { tReportsInitialRequest := time.NewTimer(c.reportsInitialRequestTimerDuration) @@ -841,7 +845,7 @@ func (c *controller) listenForRmnReportSignatures( reportSigs = append(reportSigs, *reportSig) } - if len(reportSigs) >= minSigners { + if consensus.Threshold(len(reportSigs)) >= consensus.FPlus1(remoteF) { c.lggr.Infof("got enough RMN report signatures") return sortAndParseReportSigs(reportSigs), nil } diff --git a/commit/merkleroot/rmn/controller_test.go b/commit/merkleroot/rmn/controller_test.go index cce2dfc21..af10377b4 100644 --- a/commit/merkleroot/rmn/controller_test.go +++ b/commit/merkleroot/rmn/controller_test.go @@ -19,8 +19,6 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" - ragep2ptypes "github.com/smartcontractkit/libocr/ragep2p/types" - readerpkg_mock "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/reader" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -52,7 +50,7 @@ type testSetup struct { updateRequests []*rmnpb.FixedDestLaneUpdateRequest rmnHomeMock *readerpkg_mock.MockRMNHome remoteRMNCfg rmntypes.RemoteConfig - minObservers int + homeF int rmnNodes []rmntypes.HomeNodeInfo } @@ -64,12 +62,12 @@ func Test_selectRoots(t *testing.T) { testCases := []struct { name string observations []rmnSignedObservationWithMeta - minObservers map[cciptypes.ChainSelector]int + homeF map[cciptypes.ChainSelector]int expErr bool expRoots map[cciptypes.ChainSelector]cciptypes.Bytes32 }{ { - name: "happy path", + name: "happy path F+1 observations", observations: []rmnSignedObservationWithMeta{ { SignedObservation: &rmnpb.SignedObservation{ @@ -83,12 +81,43 @@ func Test_selectRoots(t *testing.T) { }, }, }, + { + SignedObservation: &rmnpb.SignedObservation{ + Observation: &rmnpb.Observation{ + FixedDestLaneUpdates: []*rmnpb.FixedDestLaneUpdate{ + { + LaneSource: &rmnpb.LaneSource{SourceChainSelector: uint64(chainS1)}, + Root: root1[:], + }, + }, + }, + }, + }, }, - minObservers: map[cciptypes.ChainSelector]int{chainS1: 1}, + homeF: map[cciptypes.ChainSelector]int{chainS1: 1}, expRoots: map[cciptypes.ChainSelector]cciptypes.Bytes32{ chainS1: root1, }, }, + { + name: "F observations instead of minimum F+1", + observations: []rmnSignedObservationWithMeta{ + { + SignedObservation: &rmnpb.SignedObservation{ + Observation: &rmnpb.Observation{ + FixedDestLaneUpdates: []*rmnpb.FixedDestLaneUpdate{ + { + LaneSource: &rmnpb.LaneSource{SourceChainSelector: uint64(chainS1)}, + Root: root1[:], + }, + }, + }, + }, + }, + }, + homeF: map[cciptypes.ChainSelector]int{chainS1: 1}, + expErr: true, + }, { name: "zero valid roots", observations: []rmnSignedObservationWithMeta{ @@ -105,8 +134,8 @@ func Test_selectRoots(t *testing.T) { }, }, }, - minObservers: map[cciptypes.ChainSelector]int{chainS1: 2}, // <----- - expErr: true, + homeF: map[cciptypes.ChainSelector]int{chainS1: 2}, // <----- + expErr: true, }, { name: "observers not defined", @@ -124,11 +153,12 @@ func Test_selectRoots(t *testing.T) { }, }, }, - minObservers: map[cciptypes.ChainSelector]int{}, // <------- - expErr: true, + homeF: map[cciptypes.ChainSelector]int{}, // <------- + expErr: true, }, { - name: "more than one roots but one of them less than f", + name: "more than one roots but one of them less than F+1", + //nolint:dupl // to be fixed observations: []rmnSignedObservationWithMeta{ { SignedObservation: &rmnpb.SignedObservation{ @@ -154,6 +184,18 @@ func Test_selectRoots(t *testing.T) { }, }, }, + { + SignedObservation: &rmnpb.SignedObservation{ + Observation: &rmnpb.Observation{ + FixedDestLaneUpdates: []*rmnpb.FixedDestLaneUpdate{ + { + LaneSource: &rmnpb.LaneSource{SourceChainSelector: uint64(chainS1)}, + Root: root1[:], + }, + }, + }, + }, + }, { SignedObservation: &rmnpb.SignedObservation{ Observation: &rmnpb.Observation{ @@ -167,13 +209,14 @@ func Test_selectRoots(t *testing.T) { }, }, }, - minObservers: map[cciptypes.ChainSelector]int{chainS1: 2}, + homeF: map[cciptypes.ChainSelector]int{chainS1: 2}, expRoots: map[cciptypes.ChainSelector]cciptypes.Bytes32{ chainS1: root1, }, }, { name: "more than one valid roots", + //nolint:dupl // to be fixed observations: []rmnSignedObservationWithMeta{ { SignedObservation: &rmnpb.SignedObservation{ @@ -211,15 +254,27 @@ func Test_selectRoots(t *testing.T) { }, }, }, + { + SignedObservation: &rmnpb.SignedObservation{ + Observation: &rmnpb.Observation{ + FixedDestLaneUpdates: []*rmnpb.FixedDestLaneUpdate{ + { + LaneSource: &rmnpb.LaneSource{SourceChainSelector: uint64(chainS1)}, + Root: root2[:], + }, + }, + }, + }, + }, }, - minObservers: map[cciptypes.ChainSelector]int{chainS1: 1}, - expErr: true, + homeF: map[cciptypes.ChainSelector]int{chainS1: 1}, + expErr: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - roots, err := selectRoots(tc.observations, tc.minObservers) + roots, err := selectRoots(tc.observations, tc.homeF) if tc.expErr { assert.Error(t, err) return @@ -239,7 +294,7 @@ func TestClient_ComputeReportSignatures(t *testing.T) { peerClient := newMockPeerClient(resChan) rmnHomeReaderMock := readerpkg_mock.NewMockRMNHome(t) - const numNodes = 4 + const numNodes = 8 rmnNodes := make([]rmntypes.HomeNodeInfo, numNodes) for i := 0; i < numNodes; i++ { // deterministically create a public key by seeding with a 32char string. @@ -248,7 +303,7 @@ func TestClient_ComputeReportSignatures(t *testing.T) { require.NoError(t, err) rmnNodes[i] = rmntypes.HomeNodeInfo{ ID: rmntypes.NodeID(i + 1), - PeerID: ragep2ptypes.PeerID([32]byte{1, 2, 3}), + PeerID: [32]byte{1, 2, 3}, SupportedSourceChains: mapset.NewSet(chainS1, chainS2), OffchainPublicKey: &publicKey, } @@ -278,7 +333,7 @@ func TestClient_ComputeReportSignatures(t *testing.T) { rmnRemoteCfg := rmntypes.RemoteConfig{ ContractAddress: []byte{1, 2, 3}, ConfigDigest: cciptypes.Bytes32{0x1, 0x2, 0x3}, - MinSigners: 2, + F: 2, Signers: []rmntypes.RemoteSignerInfo{ { OnchainPublicKey: []byte{1, 2, 3}, @@ -311,7 +366,7 @@ func TestClient_ComputeReportSignatures(t *testing.T) { updateRequests: updateRequests, rmnHomeMock: rmnHomeReaderMock, remoteRMNCfg: rmnRemoteCfg, - minObservers: 2, + homeF: 2, rmnNodes: rmnNodes, } } @@ -324,7 +379,7 @@ func TestClient_ComputeReportSignatures(t *testing.T) { t.Run("empty lane update request", func(t *testing.T) { ts := newTestSetup(t) - ts.rmnHomeMock.On("GetMinObservers", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return( + ts.rmnHomeMock.On("GetF", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return( map[cciptypes.ChainSelector]int{chainS1: 2, chainS2: 2}, nil) ts.rmnHomeMock.On("GetRMNNodesInfo", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return(ts.rmnNodes, nil) @@ -342,18 +397,20 @@ func TestClient_ComputeReportSignatures(t *testing.T) { ts := newTestSetup(t) ts.rmnHomeMock.On("GetRMNNodesInfo", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return(ts.rmnNodes, nil) - ts.rmnHomeMock.On("GetMinObservers", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return( + ts.rmnHomeMock.On("GetF", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return( map[cciptypes.ChainSelector]int{chainS1: 2, chainS2: 2, chainD1: 2}, nil) go func() { requestIDs, requestedChains := ts.waitForObservationRequestsToBeSent( - ts.peerClient, ts.minObservers) + ts.peerClient, ts.homeF) ts.nodesRespondToTheObservationRequests( ts.peerClient, requestIDs, requestedChains, ts.remoteRMNCfg.ConfigDigest, destChain) requestIDs = ts.waitForReportSignatureRequestsToBeSent( - t, ts.peerClient, int(ts.remoteRMNCfg.MinSigners), - ts.minObservers) + t, ts.peerClient, + int(ts.remoteRMNCfg.F)+1, + ts.homeF, + ) ts.nodesRespondToTheSignatureRequests(ts.peerClient, requestIDs) }() @@ -366,7 +423,7 @@ func TestClient_ComputeReportSignatures(t *testing.T) { ) assert.NoError(t, err) assert.Len(t, sigs.LaneUpdates, len(ts.updateRequests)) - assert.Len(t, sigs.Signatures, int(ts.remoteRMNCfg.MinSigners)) + assert.Len(t, sigs.Signatures, int(ts.remoteRMNCfg.F+1)) // Make sure signature are in ascending signer address order for i := 1; i < len(sigs.Signatures); i++ { assert.True(t, sigs.Signatures[i].R[0] > sigs.Signatures[i-1].R[0]) @@ -381,29 +438,33 @@ func TestClient_ComputeReportSignatures(t *testing.T) { ts.rmnController.reportsInitialRequestTimerDuration = time.Nanosecond ts.rmnHomeMock.On("GetRMNNodesInfo", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return(ts.rmnNodes, nil) - ts.rmnHomeMock.On("GetMinObservers", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return( + ts.rmnHomeMock.On("GetF", cciptypes.Bytes32{0x1, 0x2, 0x3}).Return( map[cciptypes.ChainSelector]int{chainS1: 2, chainS2: 2}, nil) go func() { requestIDs, requestedChains := ts.waitForObservationRequestsToBeSent( - ts.peerClient, ts.minObservers) + ts.peerClient, ts.homeF) // requests should be sent to at least two nodes - assert.GreaterOrEqual(t, len(requestIDs), ts.minObservers) - assert.GreaterOrEqual(t, len(requestedChains), ts.minObservers) + assert.GreaterOrEqual(t, len(requestIDs), ts.homeF) + assert.GreaterOrEqual(t, len(requestedChains), ts.homeF) ts.nodesRespondToTheObservationRequests( ts.peerClient, requestIDs, requestedChains, ts.remoteRMNCfg.ConfigDigest, destChain) time.Sleep(time.Millisecond) requestIDs = ts.waitForReportSignatureRequestsToBeSent( - t, ts.peerClient, len(ts.remoteRMNCfg.Signers), ts.minObservers) + t, + ts.peerClient, + len(ts.remoteRMNCfg.Signers), // wait until all signers have responded + ts.homeF, + ) time.Sleep(time.Millisecond) t.Logf("requestIDs: %v", requestIDs) - // requests should be sent to all nodes, since we hit the timer timeout - assert.Equal(t, len(requestIDs), len(ts.remoteRMNCfg.Signers)) + // requests should be sent to more than F+1 nodes, since we hit the timer timeout + assert.Greater(t, len(requestIDs), int(ts.remoteRMNCfg.F)+1) ts.nodesRespondToTheSignatureRequests(ts.peerClient, requestIDs) }() @@ -416,13 +477,13 @@ func TestClient_ComputeReportSignatures(t *testing.T) { ) assert.NoError(t, err) assert.Len(t, sigs.LaneUpdates, len(ts.updateRequests)) - assert.Len(t, sigs.Signatures, int(ts.remoteRMNCfg.MinSigners)) + assert.Len(t, sigs.Signatures, int(ts.remoteRMNCfg.F+1)) }) } func (ts *testSetup) waitForObservationRequestsToBeSent( rmnClient *mockPeerClient, - minObservers int, + homeF int, ) (map[rmntypes.NodeID]uint64, map[rmntypes.NodeID]mapset.Set[uint64]) { requestIDs := make(map[rmntypes.NodeID]uint64) requestedChains := make(map[rmntypes.NodeID]mapset.Set[uint64]) @@ -438,7 +499,7 @@ func (ts *testSetup) waitForObservationRequestsToBeSent( } } } - if requestsPerChain[uint64(chainS1)] >= minObservers && requestsPerChain[uint64(chainS2)] >= minObservers { + if requestsPerChain[uint64(chainS1)] >= homeF+1 && requestsPerChain[uint64(chainS2)] >= homeF+1 { for nodeID, reqs := range recvReqs { requestIDs[nodeID] = reqs[0].RequestId requestedChains[nodeID] = mapset.NewSet[uint64]() @@ -521,12 +582,12 @@ func (ts *testSetup) waitForReportSignatureRequestsToBeSent( t *testing.T, rmnClient *mockPeerClient, expectedResponses int, - minObservers int, + homeF int, ) map[rmntypes.NodeID]uint64 { requestIDs := make(map[rmntypes.NodeID]uint64) // plugin now has received the observation responses and should send // the report requests to the nodes, wait for them to be received by the nodes - // should a total of minSigners requests each one containing the observation requests + // should a total of #remoteF requests each one containing the observation requests for { time.Sleep(time.Millisecond) @@ -542,7 +603,7 @@ func (ts *testSetup) waitForReportSignatureRequestsToBeSent( if req.GetReportSignatureRequest() == nil { continue } - assert.True(t, len(req.GetReportSignatureRequest().AttributedSignedObservations) >= minObservers) + assert.Greater(t, len(req.GetReportSignatureRequest().AttributedSignedObservations), homeF) aos := req.GetReportSignatureRequest().AttributedSignedObservations diff --git a/commit/merkleroot/rmn/types/config.go b/commit/merkleroot/rmn/types/config.go index 716fe9e73..a9d4daa8d 100644 --- a/commit/merkleroot/rmn/types/config.go +++ b/commit/merkleroot/rmn/types/config.go @@ -14,10 +14,10 @@ type NodeID uint32 // HomeConfig contains the configuration fetched from the RMNHome contract. type HomeConfig struct { - Nodes []HomeNodeInfo - SourceChainMinObservers map[cciptypes.ChainSelector]int - ConfigDigest cciptypes.Bytes32 - OffchainConfig cciptypes.Bytes // The raw offchain config + Nodes []HomeNodeInfo + SourceChainF map[cciptypes.ChainSelector]int + ConfigDigest cciptypes.Bytes32 + OffchainConfig cciptypes.Bytes // The raw offchain config } // HomeNodeInfo contains information about a node from the RMNHome contract. @@ -30,19 +30,20 @@ type HomeNodeInfo struct { // RemoteConfig contains the configuration fetched from the RMNRemote contract. type RemoteConfig struct { - ContractAddress cciptypes.UnknownAddress `json:"contractAddress"` - ConfigDigest cciptypes.Bytes32 `json:"configDigest"` - Signers []RemoteSignerInfo `json:"signers"` - MinSigners uint64 `json:"minSigners"` - ConfigVersion uint32 `json:"configVersion"` - RmnReportVersion cciptypes.Bytes32 `json:"rmnReportVersion"` // e.g., keccak256("RMN_V1_6_ANY2EVM_REPORT") + ContractAddress cciptypes.UnknownAddress `json:"contractAddress"` + ConfigDigest cciptypes.Bytes32 `json:"configDigest"` + Signers []RemoteSignerInfo `json:"signers"` + // F defines the max number of faulty RMN nodes; F+1 signers are required to verify a report. + F uint64 `json:"f"` // previously: MinSigners + ConfigVersion uint32 `json:"configVersion"` + RmnReportVersion cciptypes.Bytes32 `json:"rmnReportVersion"` // e.g., keccak256("RMN_V1_6_ANY2EVM_REPORT") } func (r RemoteConfig) IsEmpty() bool { return len(r.ContractAddress) == 0 && r.ConfigDigest == (cciptypes.Bytes32{}) && len(r.Signers) == 0 && - r.MinSigners == 0 && + r.F == 0 && r.ConfigVersion == 0 && r.RmnReportVersion == (cciptypes.Bytes32{}) } diff --git a/commit/merkleroot/rmn/types/config_test.go b/commit/merkleroot/rmn/types/config_test.go index e51ab83dd..ab793ccfd 100644 --- a/commit/merkleroot/rmn/types/config_test.go +++ b/commit/merkleroot/rmn/types/config_test.go @@ -41,9 +41,9 @@ func TestRMNRemoteConfig_IsEmpty(t *testing.T) { expected: false, }, { - name: "Config with only MinSigners", + name: "Config with only F", config: RemoteConfig{ - MinSigners: 1, + F: 1, }, expected: false, }, @@ -67,7 +67,7 @@ func TestRMNRemoteConfig_IsEmpty(t *testing.T) { ContractAddress: cciptypes.UnknownAddress{1, 2, 3}, ConfigDigest: cciptypes.Bytes32{1}, Signers: []RemoteSignerInfo{{}, {}}, - MinSigners: 2, + F: 2, ConfigVersion: 1, RmnReportVersion: cciptypes.Bytes32{1}, }, @@ -79,7 +79,7 @@ func TestRMNRemoteConfig_IsEmpty(t *testing.T) { ContractAddress: nil, ConfigDigest: cciptypes.Bytes32{1}, Signers: []RemoteSignerInfo{{}, {}}, - MinSigners: 2, + F: 2, ConfigVersion: 1, RmnReportVersion: cciptypes.Bytes32{1}, }, @@ -91,7 +91,7 @@ func TestRMNRemoteConfig_IsEmpty(t *testing.T) { ContractAddress: cciptypes.UnknownAddress{}, ConfigDigest: cciptypes.Bytes32{1}, Signers: []RemoteSignerInfo{{}, {}}, - MinSigners: 2, + F: 2, ConfigVersion: 1, RmnReportVersion: cciptypes.Bytes32{1}, }, diff --git a/commit/report.go b/commit/report.go index 7dd56b632..ffd664f2e 100644 --- a/commit/report.go +++ b/commit/report.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot" + "github.com/smartcontractkit/chainlink-ccip/internal/plugincommon/consensus" "github.com/smartcontractkit/chainlink-ccip/pkg/consts" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" ) @@ -16,8 +17,8 @@ import ( // ReportInfo is the info data that will be sent with the along with the report // It will be used to determine if the report should be accepted or not type ReportInfo struct { - // MinSigners is the minimum number of RMN signatures required for the report to be accepted - MinSigners uint64 `json:"minSigners"` + // RemoteF Max number of faulty RMN nodes; f+1 signers are required to verify a report. + RemoteF uint64 `json:"remoteF"` } func (ri ReportInfo) Encode() ([]byte, error) { @@ -68,7 +69,7 @@ func (p *Plugin) Reports( // Prepare the info data reportInfo := ReportInfo{ - MinSigners: outcome.MerkleRootOutcome.RMNRemoteCfg.MinSigners, + RemoteF: outcome.MerkleRootOutcome.RMNRemoteCfg.F, } // Serialize reportInfo to []byte @@ -104,9 +105,9 @@ func (p *Plugin) ShouldAcceptAttestedReport( if p.offchainCfg.RMNEnabled && len(decodedReport.MerkleRoots) > 0 && - len(decodedReport.RMNSignatures) < int(reportInfo.MinSigners) { - p.lggr.Infow("skipping report with insufficient RMN signatures %d < %d", - len(decodedReport.RMNSignatures), reportInfo.MinSigners) + consensus.Threshold(len(decodedReport.RMNSignatures)) < consensus.FPlus1(int(reportInfo.RemoteF)) { + p.lggr.Infow("skipping report with insufficient RMN signatures %d < %d+1", + len(decodedReport.RMNSignatures), reportInfo.RemoteF) return false, nil } diff --git a/internal/libs/testhelpers/common.go b/internal/libs/testhelpers/common.go index 297e63371..20e834d66 100644 --- a/internal/libs/testhelpers/common.go +++ b/internal/libs/testhelpers/common.go @@ -42,7 +42,7 @@ func CreateRMNRemoteCfg() rmntypes.RemoteConfig { NodeIndex: rand.RandomUint64(), }, }, - MinSigners: rand.RandomUint64(), + F: rand.RandomUint64(), ConfigVersion: rand.RandomUint32(), RmnReportVersion: rand.RandomReportVersion(), } diff --git a/internal/reader/rmn_remote.go b/internal/reader/rmn_remote.go index 4ebc806b8..c146930c0 100644 --- a/internal/reader/rmn_remote.go +++ b/internal/reader/rmn_remote.go @@ -7,7 +7,7 @@ import ( ) type RMNRemote interface { - GetMinSigners() uint64 + GetF() uint64 GetSignersInfo() []rmntypes.RemoteSignerInfo GetRmnReportVersion() string GetRmnRemoteContractAddress() string @@ -24,7 +24,7 @@ func NewRMNRemotePoller() RMNRemote { } } -func (r *RmnRemotePoller) GetMinSigners() uint64 { +func (r *RmnRemotePoller) GetF() uint64 { panic("implement me") } diff --git a/mocks/internal_/reader/rmn_remote.go b/mocks/internal_/reader/rmn_remote.go index 83549f884..9337567fb 100644 --- a/mocks/internal_/reader/rmn_remote.go +++ b/mocks/internal_/reader/rmn_remote.go @@ -22,12 +22,12 @@ func (_m *MockRMNRemote) EXPECT() *MockRMNRemote_Expecter { return &MockRMNRemote_Expecter{mock: &_m.Mock} } -// GetMinSigners provides a mock function with given fields: -func (_m *MockRMNRemote) GetMinSigners() uint64 { +// GetF provides a mock function with given fields: +func (_m *MockRMNRemote) GetF() uint64 { ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for GetMinSigners") + panic("no return value specified for GetF") } var r0 uint64 @@ -40,29 +40,29 @@ func (_m *MockRMNRemote) GetMinSigners() uint64 { return r0 } -// MockRMNRemote_GetMinSigners_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMinSigners' -type MockRMNRemote_GetMinSigners_Call struct { +// MockRMNRemote_GetF_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetF' +type MockRMNRemote_GetF_Call struct { *mock.Call } -// GetMinSigners is a helper method to define mock.On call -func (_e *MockRMNRemote_Expecter) GetMinSigners() *MockRMNRemote_GetMinSigners_Call { - return &MockRMNRemote_GetMinSigners_Call{Call: _e.mock.On("GetMinSigners")} +// GetF is a helper method to define mock.On call +func (_e *MockRMNRemote_Expecter) GetF() *MockRMNRemote_GetF_Call { + return &MockRMNRemote_GetF_Call{Call: _e.mock.On("GetF")} } -func (_c *MockRMNRemote_GetMinSigners_Call) Run(run func()) *MockRMNRemote_GetMinSigners_Call { +func (_c *MockRMNRemote_GetF_Call) Run(run func()) *MockRMNRemote_GetF_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *MockRMNRemote_GetMinSigners_Call) Return(_a0 uint64) *MockRMNRemote_GetMinSigners_Call { +func (_c *MockRMNRemote_GetF_Call) Return(_a0 uint64) *MockRMNRemote_GetF_Call { _c.Call.Return(_a0) return _c } -func (_c *MockRMNRemote_GetMinSigners_Call) RunAndReturn(run func() uint64) *MockRMNRemote_GetMinSigners_Call { +func (_c *MockRMNRemote_GetF_Call) RunAndReturn(run func() uint64) *MockRMNRemote_GetF_Call { _c.Call.Return(run) return _c } diff --git a/mocks/pkg/reader/rmn_home.go b/mocks/pkg/reader/rmn_home.go index e5177b8fa..825cad5cf 100644 --- a/mocks/pkg/reader/rmn_home.go +++ b/mocks/pkg/reader/rmn_home.go @@ -129,12 +129,12 @@ func (_c *MockRMNHome_GetAllConfigDigests_Call) RunAndReturn(run func() (ccipocr return _c } -// GetMinObservers provides a mock function with given fields: configDigest -func (_m *MockRMNHome) GetMinObservers(configDigest ccipocr3.Bytes32) (map[ccipocr3.ChainSelector]int, error) { +// GetF provides a mock function with given fields: configDigest +func (_m *MockRMNHome) GetF(configDigest ccipocr3.Bytes32) (map[ccipocr3.ChainSelector]int, error) { ret := _m.Called(configDigest) if len(ret) == 0 { - panic("no return value specified for GetMinObservers") + panic("no return value specified for GetF") } var r0 map[ccipocr3.ChainSelector]int @@ -159,30 +159,30 @@ func (_m *MockRMNHome) GetMinObservers(configDigest ccipocr3.Bytes32) (map[ccipo return r0, r1 } -// MockRMNHome_GetMinObservers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMinObservers' -type MockRMNHome_GetMinObservers_Call struct { +// MockRMNHome_GetF_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetF' +type MockRMNHome_GetF_Call struct { *mock.Call } -// GetMinObservers is a helper method to define mock.On call +// GetF is a helper method to define mock.On call // - configDigest ccipocr3.Bytes32 -func (_e *MockRMNHome_Expecter) GetMinObservers(configDigest interface{}) *MockRMNHome_GetMinObservers_Call { - return &MockRMNHome_GetMinObservers_Call{Call: _e.mock.On("GetMinObservers", configDigest)} +func (_e *MockRMNHome_Expecter) GetF(configDigest interface{}) *MockRMNHome_GetF_Call { + return &MockRMNHome_GetF_Call{Call: _e.mock.On("GetF", configDigest)} } -func (_c *MockRMNHome_GetMinObservers_Call) Run(run func(configDigest ccipocr3.Bytes32)) *MockRMNHome_GetMinObservers_Call { +func (_c *MockRMNHome_GetF_Call) Run(run func(configDigest ccipocr3.Bytes32)) *MockRMNHome_GetF_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(ccipocr3.Bytes32)) }) return _c } -func (_c *MockRMNHome_GetMinObservers_Call) Return(_a0 map[ccipocr3.ChainSelector]int, _a1 error) *MockRMNHome_GetMinObservers_Call { +func (_c *MockRMNHome_GetF_Call) Return(_a0 map[ccipocr3.ChainSelector]int, _a1 error) *MockRMNHome_GetF_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockRMNHome_GetMinObservers_Call) RunAndReturn(run func(ccipocr3.Bytes32) (map[ccipocr3.ChainSelector]int, error)) *MockRMNHome_GetMinObservers_Call { +func (_c *MockRMNHome_GetF_Call) RunAndReturn(run func(ccipocr3.Bytes32) (map[ccipocr3.ChainSelector]int, error)) *MockRMNHome_GetF_Call { _c.Call.Return(run) return _c } diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index aa49baa86..3ede7f439 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -650,7 +650,7 @@ func (r *ccipChainReader) GetRMNRemoteConfig( ContractAddress: rmnRemoteAddress, ConfigDigest: cciptypes.Bytes32(vc.Config.RMNHomeContractConfigDigest), Signers: signers, - MinSigners: vc.Config.MinSigners, + F: vc.Config.F, ConfigVersion: vc.Version, RmnReportVersion: header.DigestHeader, }, nil @@ -1075,13 +1075,12 @@ type offRampStaticChainConfig struct { NonceManager []byte `json:"nonceManager"` } -// offRampDynamicChainConfig maps to DynamicChainConfig in OffRamp.sol +// offRampDynamicChainConfig maps to DynamicConfig in OffRamp.sol type offRampDynamicChainConfig struct { FeeQuoter []byte `json:"feeQuoter"` PermissionLessExecutionThresholdSeconds uint32 `json:"permissionLessExecutionThresholdSeconds"` - MaxTokenTransferGas uint32 `json:"maxTokenTransferGas"` - MaxPoolReleaseOrMintGas uint32 `json:"maxPoolReleaseOrMintGas"` - MessageValidator []byte `json:"messageValidator"` + IsRMNVerificationDisabled bool `json:"isRMNVerificationDisabled"` + MessageInterceptor []byte `json:"messageInterceptor"` } //nolint:unused // it will be used soon // TODO: Remove nolint @@ -1254,7 +1253,7 @@ type signer struct { type config struct { RMNHomeContractConfigDigest []byte `json:"rmnHomeContractConfigDigest"` Signers []signer `json:"signers"` - MinSigners uint64 `json:"minSigners"` + F uint64 `json:"f"` // previously: MinSigners } // versionedConfig is used to parse the response from the RMNRemote contract's getVersionedConfig method. diff --git a/pkg/reader/rmn_home.go b/pkg/reader/rmn_home.go index 65456452b..f2e3a44be 100644 --- a/pkg/reader/rmn_home.go +++ b/pkg/reader/rmn_home.go @@ -37,8 +37,9 @@ type RMNHome interface { GetRMNNodesInfo(configDigest cciptypes.Bytes32) ([]rmntypes.HomeNodeInfo, error) // IsRMNHomeConfigDigestSet checks if the configDigest is set in the RMNHome contract IsRMNHomeConfigDigestSet(configDigest cciptypes.Bytes32) bool - // GetMinObservers gets the minimum number of observers required for each chain in the given configDigest - GetMinObservers(configDigest cciptypes.Bytes32) (map[cciptypes.ChainSelector]int, error) + // GetF gets the F value for each source chain in the given configDigest. + // Maximum number of faulty observers; F+1 observers required to agree on an observation for a source chain. + GetF(configDigest cciptypes.Bytes32) (map[cciptypes.ChainSelector]int, error) // GetOffChainConfig gets the offchain config for the given configDigest GetOffChainConfig(configDigest cciptypes.Bytes32) (cciptypes.Bytes, error) // GetAllConfigDigests gets the active and candidate RMNHomeConfigs @@ -193,14 +194,14 @@ func (r *rmnHomePoller) IsRMNHomeConfigDigestSet(configDigest cciptypes.Bytes32) return ok } -func (r *rmnHomePoller) GetMinObservers(configDigest cciptypes.Bytes32) (map[cciptypes.ChainSelector]int, error) { +func (r *rmnHomePoller) GetF(configDigest cciptypes.Bytes32) (map[cciptypes.ChainSelector]int, error) { r.mutex.RLock() defer r.mutex.RUnlock() _, ok := r.rmnHomeState.rmnHomeConfig[configDigest] if !ok { return nil, fmt.Errorf("configDigest %s not found in RMNHomeConfig", configDigest) } - return r.rmnHomeState.rmnHomeConfig[configDigest].SourceChainMinObservers, nil + return r.rmnHomeState.rmnHomeConfig[configDigest].SourceChainF, nil } func (r *rmnHomePoller) GetOffChainConfig(configDigest cciptypes.Bytes32) (cciptypes.Bytes, error) { @@ -292,10 +293,10 @@ func convertOnChainConfigToRMNHomeChainConfig( } } - minObservers := make(map[cciptypes.ChainSelector]int) + homeFMap := make(map[cciptypes.ChainSelector]int) for _, chain := range versionedConfig.DynamicConfig.SourceChains { - minObservers[chain.ChainSelector] = int(chain.MinObservers) + homeFMap[chain.ChainSelector] = int(chain.F) for j := 0; j < len(nodes); j++ { isObserver, err := IsNodeObserver(chain, j, len(nodes)) if err != nil { @@ -309,10 +310,10 @@ func convertOnChainConfigToRMNHomeChainConfig( } rmnHomeConfigs[versionedConfig.ConfigDigest] = rmntypes.HomeConfig{ - Nodes: nodes, - SourceChainMinObservers: minObservers, - ConfigDigest: versionedConfig.ConfigDigest, - OffchainConfig: versionedConfig.DynamicConfig.OffchainConfig, + Nodes: nodes, + SourceChainF: homeFMap, + ConfigDigest: versionedConfig.ConfigDigest, + OffchainConfig: versionedConfig.DynamicConfig.OffchainConfig, } } return rmnHomeConfigs @@ -379,7 +380,7 @@ type Node struct { // SourceChain mirrors RMNHome.sol's SourceChain struct type SourceChain struct { ChainSelector cciptypes.ChainSelector `json:"chainSelector"` - MinObservers uint64 `json:"minObservers"` + F uint64 `json:"f"` // previously: MinObservers ObserverNodesBitmap *big.Int `json:"observerNodesBitmap"` } diff --git a/pkg/reader/rmn_home_test.go b/pkg/reader/rmn_home_test.go index 8a1adb4f3..f2c09ce28 100644 --- a/pkg/reader/rmn_home_test.go +++ b/pkg/reader/rmn_home_test.go @@ -250,7 +250,7 @@ func Test_RMNHomePollingWorking(t *testing.T) { require.NotEmpty(t, offchainConfig) } - minObsMap, err := configPoller.GetMinObservers(config.ConfigDigest) + minObsMap, err := configPoller.GetF(config.ConfigDigest) if isEmpty { require.Error(t, err) require.Empty(t, minObsMap) @@ -285,7 +285,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Node is observer", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 3, + F: 3, ObserverNodesBitmap: big.NewInt(7), // 111 in binary }, nodeIndex: 1, @@ -297,7 +297,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Node is not observer", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 3, + F: 3, ObserverNodesBitmap: big.NewInt(5), // 101 in binary }, nodeIndex: 1, @@ -309,7 +309,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Node index out of range (high)", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 3, + F: 3, ObserverNodesBitmap: big.NewInt(7), // 111 in binary }, nodeIndex: 3, @@ -321,7 +321,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Negative node index", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 3, + F: 3, ObserverNodesBitmap: big.NewInt(7), // 111 in binary }, nodeIndex: -1, @@ -333,7 +333,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Invalid bitmap (out of bounds)", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 3, + F: 3, ObserverNodesBitmap: big.NewInt(8), // 1000 in binary }, nodeIndex: 0, @@ -345,7 +345,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Zero total nodes", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 3, + F: 3, ObserverNodesBitmap: big.NewInt(1), }, nodeIndex: 0, @@ -357,7 +357,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Total nodes exceeds 256", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 3, + F: 3, ObserverNodesBitmap: big.NewInt(1), }, nodeIndex: 0, @@ -369,7 +369,7 @@ func TestIsNodeObserver(t *testing.T) { name: "Last valid node is observer", sourceChain: SourceChain{ ChainSelector: cciptypes.ChainSelector(1), - MinObservers: 1, + F: 1, ObserverNodesBitmap: new(big.Int).SetBit(big.NewInt(0), 255, 1), // Only the 256th bit is set }, nodeIndex: 255, @@ -409,7 +409,7 @@ func createTestRMNHomeConfigs( SourceChains: []SourceChain{ { ChainSelector: cciptypes.ChainSelector(id), - MinObservers: uint64(id), + F: uint64(id), ObserverNodesBitmap: big.NewInt(int64(id)), }, },