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 }