Skip to content

Commit

Permalink
RMN - Migrate to contracts that use f instead of minSigners and `…
Browse files Browse the repository at this point in the history
  • Loading branch information
dimkouv authored Oct 29, 2024
1 parent b737195 commit 09d6056
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 156 deletions.
92 changes: 48 additions & 44 deletions commit/merkleroot/rmn/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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.
}

Expand All @@ -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")
}

Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 09d6056

Please sign in to comment.