github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/votecollector/vote_cache.go (about) 1 package votecollector 2 3 import ( 4 "errors" 5 "sync" 6 7 "github.com/onflow/flow-go/consensus/hotstuff" 8 "github.com/onflow/flow-go/consensus/hotstuff/model" 9 "github.com/onflow/flow-go/model/flow" 10 ) 11 12 var ( 13 // RepeatedVoteErr is emitted, when we receive a vote for the same block 14 // from the same voter multiple times. This error does _not_ indicate 15 // equivocation. 16 RepeatedVoteErr = errors.New("duplicated vote") 17 ) 18 19 // voteContainer container stores the vote and in index representing 20 // the order in which the votes were received 21 type voteContainer struct { 22 *model.Vote 23 index int 24 } 25 26 // VotesCache maintains a _concurrency safe_ cache of votes for one particular 27 // view. The cache memorizes the order in which the votes were received. Votes 28 // are de-duplicated based on the following rules: 29 // - Vor each voter (i.e. SignerID), we store the _first_ vote v0. 30 // - For any subsequent vote v, we check whether v.BlockID == v0.BlockID. 31 // If this is the case, we consider the vote a duplicate and drop it. 32 // If v and v0 have different BlockIDs, the voter is equivocating and 33 // we return a model.DoubleVoteError 34 type VotesCache struct { 35 lock sync.RWMutex 36 view uint64 37 votes map[flow.Identifier]voteContainer // signerID -> first vote 38 voteConsumers []hotstuff.VoteConsumer 39 } 40 41 // NewVotesCache instantiates a VotesCache for the given view 42 func NewVotesCache(view uint64) *VotesCache { 43 return &VotesCache{ 44 view: view, 45 votes: make(map[flow.Identifier]voteContainer), 46 } 47 } 48 49 func (vc *VotesCache) View() uint64 { return vc.view } 50 51 // AddVote stores a vote in the cache. The following errors are expected during 52 // normal operations: 53 // - nil: if the vote was successfully added 54 // - model.DoubleVoteError is returned if the voter is equivocating 55 // (i.e. voting in the same view for different blocks). 56 // - RepeatedVoteErr is returned when adding a vote for the same block from 57 // the same voter multiple times. 58 // - IncompatibleViewErr is returned if the vote is for a different view. 59 // 60 // When AddVote returns an error, the vote is _not_ stored. 61 func (vc *VotesCache) AddVote(vote *model.Vote) error { 62 if vote.View != vc.view { 63 return VoteForIncompatibleViewError 64 } 65 vc.lock.Lock() 66 defer vc.lock.Unlock() 67 68 // De-duplicated votes based on the following rules: 69 // * Vor each voter (i.e. SignerID), we store the _first_ vote v0. 70 // * For any subsequent vote v, we check whether v.BlockID == v0.BlockID. 71 // If this is the case, we consider the vote a duplicate and drop it. 72 // If v and v0 have different BlockIDs, the voter is equivocating and 73 // we return a model.DoubleVoteError 74 firstVote, exists := vc.votes[vote.SignerID] 75 if exists { 76 if firstVote.BlockID != vote.BlockID { 77 return model.NewDoubleVoteErrorf(firstVote.Vote, vote, "detected vote equivocation at view: %d", vc.view) 78 } 79 return RepeatedVoteErr 80 } 81 82 // previously unknown vote: (1) store and (2) forward to consumers 83 vc.votes[vote.SignerID] = voteContainer{vote, len(vc.votes)} 84 for _, consumer := range vc.voteConsumers { 85 consumer(vote) 86 } 87 return nil 88 } 89 90 // GetVote returns the stored vote for the given `signerID`. Returns: 91 // - (vote, true) if a vote from signerID is known 92 // - (false, nil) no vote from signerID is known 93 func (vc *VotesCache) GetVote(signerID flow.Identifier) (*model.Vote, bool) { 94 vc.lock.RLock() 95 container, exists := vc.votes[signerID] // if signerID is unknown, its `Vote` pointer is nil 96 vc.lock.RUnlock() 97 return container.Vote, exists 98 } 99 100 // Size returns the number of cached votes 101 func (vc *VotesCache) Size() int { 102 vc.lock.RLock() 103 s := len(vc.votes) 104 vc.lock.RUnlock() 105 return s 106 } 107 108 // RegisterVoteConsumer registers a VoteConsumer. Upon registration, the cache 109 // feeds all cached votes into the consumer in the order they arrived. 110 // CAUTION: a consumer _must_ be non-blocking and consume the votes without 111 // noteworthy delay. Otherwise, consensus speed is impacted. 112 // 113 // Expected usage patter: During happy-path operations, the block arrives in a 114 // timely manner. Hence, we expect that only a few votes are cached when a 115 // consumer is registered. For the purpose of forensics, we might register a 116 // consumer later, when already lots of votes are cached. However, this should 117 // be a rare occurrence (we except moderate performance overhead in this case). 118 func (vc *VotesCache) RegisterVoteConsumer(consumer hotstuff.VoteConsumer) { 119 vc.lock.Lock() 120 defer vc.lock.Unlock() 121 122 vc.voteConsumers = append(vc.voteConsumers, consumer) 123 for _, vote := range vc.all() { // feed the consumer with the cached votes 124 consumer(vote) // non-blocking per API contract 125 } 126 } 127 128 // All returns all currently cached votes. Concurrency safe. 129 func (vc *VotesCache) All() []*model.Vote { 130 vc.lock.Lock() 131 defer vc.lock.Unlock() 132 return vc.all() 133 } 134 135 // all returns all currently cached votes. NOT concurrency safe 136 func (vc *VotesCache) all() []*model.Vote { 137 orderedVotes := make([]*model.Vote, len(vc.votes)) 138 for _, v := range vc.votes { 139 orderedVotes[v.index] = v.Vote 140 } 141 return orderedVotes 142 }