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  }