github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/model/flow/factory/cluster_list.go (about)

     1  package factory
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/flow-go/model/flow"
     7  )
     8  
     9  // NewClusterList creates a new cluster list based on the given cluster assignment and the provided list of identities.
    10  // The implementation enforces the following protocol rules and errors in case they are violated:
    11  //
    12  //	(a) input `collectors` only contains collector nodes with positive weight
    13  //	(b) collectors have unique node IDs
    14  //	(c) each collector is assigned exactly to one cluster and is only listed once within that cluster
    15  //
    16  // Furthermore, for each cluster (i.e. element in `assignments`) we enforce:
    17  //
    18  //	(d) cluster contains at least one collector (i.e. is not empty)
    19  //	(e) cluster is composed of known nodes, i.e. for each nodeID in `assignments` an IdentitySkeleton is given in `collectors`
    20  //	(f) cluster assignment lists the nodes in canonical ordering
    21  //
    22  // The caller must ensure each assignment contains identities ordered in canonical order, so that
    23  // each cluster in the returned cluster list is ordered in canonical order as well. If not,
    24  // an error will be returned.
    25  // This is a side-effect-free function. Any error return indicates that the input violate protocol rules.
    26  func NewClusterList(assignments flow.AssignmentList, collectors flow.IdentitySkeletonList) (flow.ClusterList, error) {
    27  	// build a lookup for all the identities by node identifier
    28  	lookup := collectors.Lookup()
    29  	for _, collector := range collectors { // enforce (a): `collectors` only contains collector nodes with positive weight
    30  		if collector.Role != flow.RoleCollection {
    31  			return nil, fmt.Errorf("node %v is not a collector", collector.NodeID)
    32  		}
    33  		if collector.InitialWeight == 0 {
    34  			return nil, fmt.Errorf("node %v has zero weight", collector.NodeID)
    35  		}
    36  		lookup[collector.NodeID] = collector
    37  	}
    38  	if len(lookup) != len(collectors) { // enforce (b): collectors have unique node IDs
    39  		return nil, fmt.Errorf("duplicate collector in list")
    40  	}
    41  
    42  	// assignments only contains the NodeIDs for each cluster. In the following, we substitute them with the respective IdentitySkeletons.
    43  	clusters := make(flow.ClusterList, 0, len(assignments))
    44  	for i, participants := range assignments {
    45  		cluster := make(flow.IdentitySkeletonList, 0, len(participants))
    46  		if len(participants) == 0 { // enforce (d): each cluster contains at least one collector (i.e. is not empty)
    47  			return nil, fmt.Errorf("particpants in assignment list is empty, cluster index %v", i)
    48  		}
    49  
    50  		prev := participants[0] // for checking that cluster participants are listed in canonical order
    51  		for i, participantID := range participants {
    52  			participant, found := lookup[participantID] // enforce (e): for each nodeID in assignments an IdentitySkeleton is given in `collectors`
    53  			if !found {
    54  				return nil, fmt.Errorf("could not find collector identity (%x)", participantID)
    55  			}
    56  			cluster = append(cluster, participant)
    57  			delete(lookup, participantID) // enforce (c) part 1: reject repeated assignment of the same node
    58  
    59  			if i > 0 { // enforce (f): canonical ordering
    60  				if !flow.IsIdentifierCanonical(prev, participantID) {
    61  					return nil, fmt.Errorf("the assignments is not sorted in canonical order in cluster index %v, prev %v, next %v",
    62  						i, prev, participantID)
    63  				}
    64  			}
    65  			prev = participantID
    66  		}
    67  
    68  		clusters = append(clusters, cluster)
    69  	}
    70  
    71  	if len(lookup) != 0 { // enforce (c) part 2: every collector was assigned
    72  		return nil, fmt.Errorf("missing collector assignments (%s)", lookup)
    73  	}
    74  
    75  	return clusters, nil
    76  }