github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/committees/consensus_committee.go (about) 1 package committees 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 8 "github.com/koko1123/flow-go-1/consensus/hotstuff" 9 "github.com/koko1123/flow-go-1/consensus/hotstuff/committees/leader" 10 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 11 "github.com/koko1123/flow-go-1/model/flow" 12 "github.com/koko1123/flow-go-1/model/flow/filter" 13 "github.com/koko1123/flow-go-1/state/protocol" 14 "github.com/koko1123/flow-go-1/state/protocol/seed" 15 ) 16 17 var errSelectionNotComputed = fmt.Errorf("leader selection for epoch not yet computed") 18 19 // Consensus represents the main committee for consensus nodes. The consensus 20 // committee persists across epochs. 21 type Consensus struct { 22 mu sync.RWMutex 23 state protocol.State // the protocol state 24 me flow.Identifier // the node ID of this node 25 leaders map[uint64]*leader.LeaderSelection // pre-computed leader selection for each epoch 26 } 27 28 var _ hotstuff.Committee = (*Consensus)(nil) 29 30 func NewConsensusCommittee(state protocol.State, me flow.Identifier) (*Consensus, error) { 31 32 com := &Consensus{ 33 state: state, 34 me: me, 35 leaders: make(map[uint64]*leader.LeaderSelection), 36 } 37 38 final := state.Final() 39 40 // pre-compute leader selection for current epoch 41 current := final.Epochs().Current() 42 _, err := com.prepareLeaderSelection(current) 43 if err != nil { 44 return nil, fmt.Errorf("could not add leader for current epoch: %w", err) 45 } 46 47 // Pre-compute leader selection for previous epoch, if it exists. 48 // 49 // This ensures we always know about leader selection for at least one full 50 // epoch into the past, ensuring we are able to not only determine the leader 51 // for block proposals we receive, but also adjudicate consensus-related 52 // challenges up to one epoch into the past. 53 previous := final.Epochs().Previous() 54 _, err = previous.Counter() 55 // if there is no previous epoch, return the committee as-is 56 if errors.Is(err, protocol.ErrNoPreviousEpoch) { 57 return com, nil 58 } 59 if err != nil { 60 return nil, fmt.Errorf("could not get previous epoch: %w", err) 61 } 62 63 _, err = com.prepareLeaderSelection(previous) 64 if err != nil { 65 return nil, fmt.Errorf("could not add leader for previous epoch: %w", err) 66 } 67 68 return com, nil 69 } 70 71 // Identities returns the identities of all authorized consensus participants at the given block. 72 // The order of the identities is the canonical order. 73 func (c *Consensus) Identities(blockID flow.Identifier) (flow.IdentityList, error) { 74 il, err := c.state.AtBlockID(blockID).Identities(filter.IsVotingConsensusCommitteeMember) 75 return il, err 76 } 77 78 func (c *Consensus) Identity(blockID flow.Identifier, nodeID flow.Identifier) (*flow.Identity, error) { 79 identity, err := c.state.AtBlockID(blockID).Identity(nodeID) 80 if err != nil { 81 if protocol.IsIdentityNotFound(err) { 82 return nil, model.NewInvalidSignerErrorf("id %v is not a valid node id: %w", nodeID, err) 83 } 84 return nil, fmt.Errorf("could not get identity for node ID %x: %w", nodeID, err) 85 } 86 if !filter.IsVotingConsensusCommitteeMember(identity) { 87 return nil, model.NewInvalidSignerErrorf("node %v is not an authorized hotstuff voting participant", nodeID) 88 } 89 return identity, nil 90 } 91 92 // LeaderForView returns the node ID of the leader for the given view. Returns 93 // the following errors: 94 // - epoch containing the requested view has not been set up (protocol.ErrNextEpochNotSetup) 95 // - epoch is too far in the past (leader.InvalidViewError) 96 // - any other error indicates an unexpected internal error 97 func (c *Consensus) LeaderForView(view uint64) (flow.Identifier, error) { 98 99 // try to retrieve the leader from a pre-computed LeaderSelection 100 id, err := c.precomputedLeaderForView(view) 101 if err == nil { 102 return id, nil 103 } 104 if !errors.Is(err, errSelectionNotComputed) { 105 return flow.ZeroID, err 106 } 107 // we only reach the following code, if we got a errSelectionNotComputed 108 109 // STEP 2 - we haven't yet computed leader selection for an epoch containing 110 // the requested view. We compute leader selection for the current and previous 111 // epoch (w.r.t. the finalized head) at initialization then compute leader 112 // selection for the next epoch when we encounter any view for which we don't 113 // know the leader. The series of epochs we have computed leaders for is 114 // strictly consecutive, meaning we know the leader for all views V where: 115 // 116 // oldestEpoch.firstView <= V <= newestEpoch.finalView 117 // 118 // Thus, the requested view is either before oldestEpoch.firstView or after 119 // newestEpoch.finalView. 120 // 121 // CASE 1: V < oldestEpoch.firstView 122 // If the view is before the first view we've computed the leader for, this 123 // represents an invalid query because we only guarantee the protocol state 124 // will contain epoch information for the current, previous, and next epoch - 125 // such a query must be for a view within an epoch at least TWO epochs before 126 // the current epoch when we started up. This is considered an invalid query. 127 // 128 // CASE 2: V > newestEpoch.finalView 129 // If the view is after the last view we've computed the leader for, we 130 // assume the view is within the next epoch (w.r.t. the finalized head). 131 // This assumption is equivalent to assuming that we build at least one 132 // block in every epoch, which is anyway a requirement for valid epochs. 133 // 134 epochs := c.state.Final().Epochs() 135 next := epochs.Next() 136 137 // TMP: EMERGENCY EPOCH CHAIN CONTINUATION [EECC] 138 // 139 // If we reach this code-path, it means we are about to propose or vote 140 // for the first block in the next epoch. If that epoch has not been 141 // committed or set up, rather than stopping consensus, this intervention 142 // will create a new fallback leader selection for the next epoch containing 143 // 6 months worth of views, so that consensus will have leaders specified 144 // for the duration of the current spork, without any epoch transitions. 145 // 146 _, err = next.DKG() // either of the following errors indicates that we have transitioned into EECC 147 if errors.Is(err, protocol.ErrEpochNotCommitted) || errors.Is(err, protocol.ErrNextEpochNotSetup) { 148 current := epochs.Current() 149 150 currentCounter, err := current.Counter() 151 if err != nil { 152 return flow.ZeroID, fmt.Errorf("could not get next epoch currentCounter: %w", err) 153 } 154 identities, err := current.InitialIdentities() 155 if err != nil { 156 return flow.ZeroID, fmt.Errorf("could not get epoch initial identities: %w", err) 157 } 158 // Get the random source 159 // CAUTION: this is re-using the same leader selection random source from the now-ending epoch 160 randomSeed, err := current.RandomSource() 161 if err != nil { 162 return flow.ZeroID, fmt.Errorf("could not get epoch seed: %w", err) 163 } 164 currentFinalView, err := current.FinalView() 165 if err != nil { 166 return flow.ZeroID, fmt.Errorf("could not get epoch first view: %w", err) 167 } 168 169 // we will inject a fallback leader selection in place of the next epoch 170 counter := currentCounter + 1 171 // the fallback leader selection begins after the final view of the current epoch 172 firstView := currentFinalView + 1 173 174 // create random number generator from the seed and customizer 175 rng, err := seed.PRGFromRandomSource(randomSeed, seed.ProtocolConsensusLeaderSelection) 176 if err != nil { 177 return flow.ZeroID, fmt.Errorf("could not create rng from seed: %w", err) 178 } 179 180 selection, err := leader.ComputeLeaderSelection( 181 firstView, 182 rng, 183 int(firstView+leader.EstimatedSixMonthOfViews), // the fallback epoch lasts until the next spork 184 identities.Filter(filter.IsVotingConsensusCommitteeMember), 185 ) 186 if err != nil { 187 return flow.ZeroID, fmt.Errorf("could not compute epoch fallback leader selection: %w", err) 188 } 189 c.mu.Lock() 190 c.leaders[counter] = selection 191 c.mu.Unlock() 192 return selection.LeaderForView(view) 193 } 194 if err != nil { 195 return flow.ZeroID, fmt.Errorf("unexpected error in EECC logic while retrieving DKG data: %w", err) 196 } 197 198 // HAPPY PATH logic 199 selection, err := c.prepareLeaderSelection(next) 200 if err != nil { 201 return flow.ZeroID, fmt.Errorf("could not compute leader selection for next epoch: %w", err) 202 } 203 204 return selection.LeaderForView(view) 205 } 206 207 func (c *Consensus) Self() flow.Identifier { 208 return c.me 209 } 210 211 func (c *Consensus) DKG(blockID flow.Identifier) (hotstuff.DKG, error) { 212 return c.state.AtBlockID(blockID).Epochs().Current().DKG() 213 } 214 215 // precomputedLeaderForView retrieves the leader from the precomputed 216 // LeaderSelection in `c.leaders` 217 // Error returns: 218 // - errSelectionNotComputed [sentinel error] if there is no Epoch for view stored in `c.leaders` 219 // - unspecific error in case of unexpected problems and bugs 220 func (c *Consensus) precomputedLeaderForView(view uint64) (flow.Identifier, error) { 221 c.mu.RLock() 222 defer c.mu.RUnlock() 223 224 // STEP 1 - look for an epoch matching this view for which we have already 225 // pre-computed leader selection. Epochs last ~500k views, so we find the 226 // epoch here 99.99% of the time. Since epochs are long-lived, it is fine 227 // for this to be linear in the number of epochs we have observed. 228 for _, selection := range c.leaders { 229 230 // try retrieving the leader 231 leaderID, err := selection.LeaderForView(view) 232 // if the view is out of range, try the next epoch 233 if leader.IsInvalidViewError(err) { 234 continue 235 } 236 if err != nil { 237 return flow.ZeroID, fmt.Errorf("could not get leader: %w", err) 238 } 239 240 return leaderID, nil 241 } 242 243 return flow.ZeroID, errSelectionNotComputed 244 } 245 246 // prepareLeaderSelection pre-computes and stores the leader selection for the 247 // given epoch. Computing leader selection for the same epoch multiple times 248 // is a no-op. 249 // 250 // Returns the leader selection for the given epoch. 251 func (c *Consensus) prepareLeaderSelection(epoch protocol.Epoch) (*leader.LeaderSelection, error) { 252 c.mu.Lock() 253 defer c.mu.Unlock() 254 255 counter, err := epoch.Counter() 256 if err != nil { 257 return nil, fmt.Errorf("could not get counter for current epoch: %w", err) 258 } 259 // this is a no-op if we have already computed leaders for this epoch 260 selection, exists := c.leaders[counter] 261 if exists { 262 return selection, nil 263 } 264 265 selection, err = leader.SelectionForConsensus(epoch) 266 if err != nil { 267 return nil, fmt.Errorf("could not get leader selection for current epoch: %w", err) 268 } 269 c.leaders[counter] = selection 270 271 // now prune any old epochs, if we have exceeded our maximum of 3 272 // if we have fewer than 3 epochs, this is a no-op 273 274 // find the maximum counter, including the epoch we just computed 275 max := uint64(0) 276 for counter := range c.leaders { 277 if counter > max { 278 max = counter 279 } 280 } 281 282 // remove any epochs which aren't within the most recent 3 283 for counter := range c.leaders { 284 if counter+3 <= max { 285 delete(c.leaders, counter) 286 } 287 } 288 289 return selection, nil 290 }