github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/committees/cluster_committee.go (about) 1 package committees 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/flow-go/consensus/hotstuff" 7 "github.com/onflow/flow-go/consensus/hotstuff/committees/leader" 8 "github.com/onflow/flow-go/consensus/hotstuff/model" 9 "github.com/onflow/flow-go/model/flow" 10 "github.com/onflow/flow-go/model/flow/filter" 11 "github.com/onflow/flow-go/state/protocol" 12 "github.com/onflow/flow-go/storage" 13 ) 14 15 // Cluster represents the committee for a cluster of collection nodes. Cluster 16 // committees are epoch-scoped. 17 // 18 // Clusters build blocks on a cluster chain but must obtain identity table 19 // information from the main chain. Thus, block ID parameters in this DynamicCommittee 20 // implementation reference blocks on the cluster chain, which in turn reference 21 // blocks on the main chain - this implementation manages that translation. 22 type Cluster struct { 23 state protocol.State 24 payloads storage.ClusterPayloads 25 me flow.Identifier 26 // pre-computed leader selection for the full lifecycle of the cluster 27 selection *leader.LeaderSelection 28 // a filter that returns all members of the cluster committee allowed to vote 29 clusterMemberFilter flow.IdentityFilter 30 // initial set of cluster members, WITHOUT dynamic weight changes 31 // TODO: should use identity skeleton https://github.com/dapperlabs/flow-go/issues/6232 32 initialClusterMembers flow.IdentityList 33 weightThresholdForQC uint64 // computed based on initial cluster committee weights 34 weightThresholdForTO uint64 // computed based on initial cluster committee weights 35 } 36 37 var _ hotstuff.Replicas = (*Cluster)(nil) 38 var _ hotstuff.DynamicCommittee = (*Cluster)(nil) 39 40 func NewClusterCommittee( 41 state protocol.State, 42 payloads storage.ClusterPayloads, 43 cluster protocol.Cluster, 44 epoch protocol.Epoch, 45 me flow.Identifier, 46 ) (*Cluster, error) { 47 48 selection, err := leader.SelectionForCluster(cluster, epoch) 49 if err != nil { 50 return nil, fmt.Errorf("could not compute leader selection for cluster: %w", err) 51 } 52 53 totalWeight := cluster.Members().TotalWeight() 54 com := &Cluster{ 55 state: state, 56 payloads: payloads, 57 me: me, 58 selection: selection, 59 clusterMemberFilter: filter.And( 60 cluster.Members().Selector(), 61 filter.Not(filter.Ejected), 62 filter.HasWeight(true), 63 ), 64 initialClusterMembers: cluster.Members(), 65 weightThresholdForQC: WeightThresholdToBuildQC(totalWeight), 66 weightThresholdForTO: WeightThresholdToTimeout(totalWeight), 67 } 68 return com, nil 69 } 70 71 // IdentitiesByBlock returns the identities of all cluster members that are authorized to 72 // participate at the given block. The order of the identities is the canonical order. 73 func (c *Cluster) IdentitiesByBlock(blockID flow.Identifier) (flow.IdentityList, error) { 74 // blockID is a collection block not a block produced by consensus, 75 // to query the identities from protocol state, we need to use the reference block id from the payload 76 // 77 // first retrieve the cluster block payload 78 payload, err := c.payloads.ByBlockID(blockID) 79 if err != nil { 80 return nil, fmt.Errorf("could not get cluster payload: %w", err) 81 } 82 83 // an empty reference block ID indicates a root block 84 isRootBlock := payload.ReferenceBlockID == flow.ZeroID 85 86 // use the initial cluster members for root block 87 if isRootBlock { 88 return c.initialClusterMembers, nil 89 } 90 91 // otherwise use the snapshot given by the reference block 92 identities, err := c.state.AtBlockID(payload.ReferenceBlockID).Identities(c.clusterMemberFilter) 93 return identities, err 94 } 95 96 func (c *Cluster) IdentityByBlock(blockID flow.Identifier, nodeID flow.Identifier) (*flow.Identity, error) { 97 98 // first retrieve the cluster block payload 99 payload, err := c.payloads.ByBlockID(blockID) 100 if err != nil { 101 return nil, fmt.Errorf("could not get cluster payload: %w", err) 102 } 103 104 // an empty reference block ID indicates a root block 105 isRootBlock := payload.ReferenceBlockID == flow.ZeroID 106 107 // use the initial cluster members for root block 108 if isRootBlock { 109 identity, ok := c.initialClusterMembers.ByNodeID(nodeID) 110 if !ok { 111 return nil, model.NewInvalidSignerErrorf("node %v is not an authorized hotstuff participant", nodeID) 112 } 113 return identity, nil 114 } 115 116 // otherwise use the snapshot given by the reference block 117 identity, err := c.state.AtBlockID(payload.ReferenceBlockID).Identity(nodeID) 118 if protocol.IsIdentityNotFound(err) { 119 return nil, model.NewInvalidSignerErrorf("%v is not a valid node id at block %v: %w", nodeID, payload.ReferenceBlockID, err) 120 } 121 if err != nil { 122 return nil, fmt.Errorf("could not get identity for node (id=%x): %w", nodeID, err) 123 } 124 if !c.clusterMemberFilter(identity) { 125 return nil, model.NewInvalidSignerErrorf("node %v is not an authorized hotstuff cluster member", nodeID) 126 } 127 return identity, nil 128 } 129 130 // IdentitiesByEpoch returns the initial cluster members for this epoch. The view 131 // parameter is the view in the cluster consensus. Since clusters only exist for 132 // one epoch, we don't need to check the view. 133 func (c *Cluster) IdentitiesByEpoch(_ uint64) (flow.IdentityList, error) { 134 return c.initialClusterMembers, nil 135 } 136 137 // IdentityByEpoch returns the node from the initial cluster members for this epoch. 138 // The view parameter is the view in the cluster consensus. Since clusters only exist 139 // for one epoch, we don't need to check the view. 140 // 141 // Returns: 142 // - model.InvalidSignerError if nodeID was not listed by the Epoch Setup event as an 143 // authorized participant in this cluster 144 func (c *Cluster) IdentityByEpoch(_ uint64, nodeID flow.Identifier) (*flow.Identity, error) { 145 identity, ok := c.initialClusterMembers.ByNodeID(nodeID) 146 if !ok { 147 return nil, model.NewInvalidSignerErrorf("node %v is not an authorized hotstuff participant", nodeID) 148 } 149 return identity, nil 150 } 151 152 func (c *Cluster) LeaderForView(view uint64) (flow.Identifier, error) { 153 return c.selection.LeaderForView(view) 154 } 155 156 // QuorumThresholdForView returns the weight threshold required to build a QC 157 // for the given view. The view parameter is the view in the cluster consensus. 158 // Since clusters only exist for one epoch, and the weight threshold is static 159 // over the course of an epoch, we don't need to check the view. 160 // 161 // No errors are expected during normal operation. 162 func (c *Cluster) QuorumThresholdForView(_ uint64) (uint64, error) { 163 return c.weightThresholdForQC, nil 164 } 165 166 // TimeoutThresholdForView returns the minimum weight of observed timeout objects to 167 // safely immediately timeout for the current view. The view parameter is the view 168 // in the cluster consensus. Since clusters only exist for one epoch, and the weight 169 // threshold is static over the course of an epoch, we don't need to check the view. 170 // 171 // No errors are expected during normal operation. 172 func (c *Cluster) TimeoutThresholdForView(_ uint64) (uint64, error) { 173 return c.weightThresholdForTO, nil 174 } 175 176 func (c *Cluster) Self() flow.Identifier { 177 return c.me 178 } 179 180 func (c *Cluster) DKG(_ uint64) (hotstuff.DKG, error) { 181 panic("queried DKG of cluster committee") 182 }