github.com/evdatsion/aphelion-dpos-bft@v0.32.1/types/vote_set.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/pkg/errors"
    10  
    11  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
    12  )
    13  
    14  // UNSTABLE
    15  // XXX: duplicate of p2p.ID to avoid dependence between packages.
    16  // Perhaps we can have a minimal types package containing this (and other things?)
    17  // that both `types` and `p2p` import ?
    18  type P2PID string
    19  
    20  /*
    21  	VoteSet helps collect signatures from validators at each height+round for a
    22  	predefined vote type.
    23  
    24  	We need VoteSet to be able to keep track of conflicting votes when validators
    25  	double-sign.  Yet, we can't keep track of *all* the votes seen, as that could
    26  	be a DoS attack vector.
    27  
    28  	There are two storage areas for votes.
    29  	1. voteSet.votes
    30  	2. voteSet.votesByBlock
    31  
    32  	`.votes` is the "canonical" list of votes.  It always has at least one vote,
    33  	if a vote from a validator had been seen at all.  Usually it keeps track of
    34  	the first vote seen, but when a 2/3 majority is found, votes for that get
    35  	priority and are copied over from `.votesByBlock`.
    36  
    37  	`.votesByBlock` keeps track of a list of votes for a particular block.  There
    38  	are two ways a &blockVotes{} gets created in `.votesByBlock`.
    39  	1. the first vote seen by a validator was for the particular block.
    40  	2. a peer claims to have seen 2/3 majority for the particular block.
    41  
    42  	Since the first vote from a validator will always get added in `.votesByBlock`
    43  	, all votes in `.votes` will have a corresponding entry in `.votesByBlock`.
    44  
    45  	When a &blockVotes{} in `.votesByBlock` reaches a 2/3 majority quorum, its
    46  	votes are copied into `.votes`.
    47  
    48  	All this is memory bounded because conflicting votes only get added if a peer
    49  	told us to track that block, each peer only gets to tell us 1 such block, and,
    50  	there's only a limited number of peers.
    51  
    52  	NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64.
    53  */
    54  type VoteSet struct {
    55  	chainID string
    56  	height  int64
    57  	round   int
    58  	type_   SignedMsgType
    59  	valSet  *ValidatorSet
    60  
    61  	mtx           sync.Mutex
    62  	votesBitArray *cmn.BitArray
    63  	votes         []*Vote                // Primary votes to share
    64  	sum           int64                  // Sum of voting power for seen votes, discounting conflicts
    65  	maj23         *BlockID               // First 2/3 majority seen
    66  	votesByBlock  map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes
    67  	peerMaj23s    map[P2PID]BlockID      // Maj23 for each peer
    68  }
    69  
    70  // Constructs a new VoteSet struct used to accumulate votes for given height/round.
    71  func NewVoteSet(chainID string, height int64, round int, type_ SignedMsgType, valSet *ValidatorSet) *VoteSet {
    72  	if height == 0 {
    73  		panic("Cannot make VoteSet for height == 0, doesn't make sense.")
    74  	}
    75  	return &VoteSet{
    76  		chainID:       chainID,
    77  		height:        height,
    78  		round:         round,
    79  		type_:         type_,
    80  		valSet:        valSet,
    81  		votesBitArray: cmn.NewBitArray(valSet.Size()),
    82  		votes:         make([]*Vote, valSet.Size()),
    83  		sum:           0,
    84  		maj23:         nil,
    85  		votesByBlock:  make(map[string]*blockVotes, valSet.Size()),
    86  		peerMaj23s:    make(map[P2PID]BlockID),
    87  	}
    88  }
    89  
    90  func (voteSet *VoteSet) ChainID() string {
    91  	return voteSet.chainID
    92  }
    93  
    94  func (voteSet *VoteSet) Height() int64 {
    95  	if voteSet == nil {
    96  		return 0
    97  	}
    98  	return voteSet.height
    99  }
   100  
   101  func (voteSet *VoteSet) Round() int {
   102  	if voteSet == nil {
   103  		return -1
   104  	}
   105  	return voteSet.round
   106  }
   107  
   108  func (voteSet *VoteSet) Type() byte {
   109  	if voteSet == nil {
   110  		return 0x00
   111  	}
   112  	return byte(voteSet.type_)
   113  }
   114  
   115  func (voteSet *VoteSet) Size() int {
   116  	if voteSet == nil {
   117  		return 0
   118  	}
   119  	return voteSet.valSet.Size()
   120  }
   121  
   122  // Returns added=true if vote is valid and new.
   123  // Otherwise returns err=ErrVote[
   124  //		UnexpectedStep | InvalidIndex | InvalidAddress |
   125  //		InvalidSignature | InvalidBlockHash | ConflictingVotes ]
   126  // Duplicate votes return added=false, err=nil.
   127  // Conflicting votes return added=*, err=ErrVoteConflictingVotes.
   128  // NOTE: vote should not be mutated after adding.
   129  // NOTE: VoteSet must not be nil
   130  // NOTE: Vote must not be nil
   131  func (voteSet *VoteSet) AddVote(vote *Vote) (added bool, err error) {
   132  	if voteSet == nil {
   133  		panic("AddVote() on nil VoteSet")
   134  	}
   135  	voteSet.mtx.Lock()
   136  	defer voteSet.mtx.Unlock()
   137  
   138  	return voteSet.addVote(vote)
   139  }
   140  
   141  // NOTE: Validates as much as possible before attempting to verify the signature.
   142  func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
   143  	if vote == nil {
   144  		return false, ErrVoteNil
   145  	}
   146  	valIndex := vote.ValidatorIndex
   147  	valAddr := vote.ValidatorAddress
   148  	blockKey := vote.BlockID.Key()
   149  
   150  	// Ensure that validator index was set
   151  	if valIndex < 0 {
   152  		return false, errors.Wrap(ErrVoteInvalidValidatorIndex, "Index < 0")
   153  	} else if len(valAddr) == 0 {
   154  		return false, errors.Wrap(ErrVoteInvalidValidatorAddress, "Empty address")
   155  	}
   156  
   157  	// Make sure the step matches.
   158  	if (vote.Height != voteSet.height) ||
   159  		(vote.Round != voteSet.round) ||
   160  		(vote.Type != voteSet.type_) {
   161  		return false, errors.Wrapf(ErrVoteUnexpectedStep, "Expected %d/%d/%d, but got %d/%d/%d",
   162  			voteSet.height, voteSet.round, voteSet.type_,
   163  			vote.Height, vote.Round, vote.Type)
   164  	}
   165  
   166  	// Ensure that signer is a validator.
   167  	lookupAddr, val := voteSet.valSet.GetByIndex(valIndex)
   168  	if val == nil {
   169  		return false, errors.Wrapf(ErrVoteInvalidValidatorIndex,
   170  			"Cannot find validator %d in valSet of size %d", valIndex, voteSet.valSet.Size())
   171  	}
   172  
   173  	// Ensure that the signer has the right address.
   174  	if !bytes.Equal(valAddr, lookupAddr) {
   175  		return false, errors.Wrapf(ErrVoteInvalidValidatorAddress,
   176  			"vote.ValidatorAddress (%X) does not match address (%X) for vote.ValidatorIndex (%d)\nEnsure the genesis file is correct across all validators.",
   177  			valAddr, lookupAddr, valIndex)
   178  	}
   179  
   180  	// If we already know of this vote, return false.
   181  	if existing, ok := voteSet.getVote(valIndex, blockKey); ok {
   182  		if bytes.Equal(existing.Signature, vote.Signature) {
   183  			return false, nil // duplicate
   184  		}
   185  		return false, errors.Wrapf(ErrVoteNonDeterministicSignature, "Existing vote: %v; New vote: %v", existing, vote)
   186  	}
   187  
   188  	// Check signature.
   189  	if err := vote.Verify(voteSet.chainID, val.PubKey); err != nil {
   190  		return false, errors.Wrapf(err, "Failed to verify vote with ChainID %s and PubKey %s", voteSet.chainID, val.PubKey)
   191  	}
   192  
   193  	// Add vote and get conflicting vote if any.
   194  	added, conflicting := voteSet.addVerifiedVote(vote, blockKey, val.VotingPower)
   195  	if conflicting != nil {
   196  		return added, NewConflictingVoteError(val, conflicting, vote)
   197  	}
   198  	if !added {
   199  		panic("Expected to add non-conflicting vote")
   200  	}
   201  	return added, nil
   202  }
   203  
   204  // Returns (vote, true) if vote exists for valIndex and blockKey.
   205  func (voteSet *VoteSet) getVote(valIndex int, blockKey string) (vote *Vote, ok bool) {
   206  	if existing := voteSet.votes[valIndex]; existing != nil && existing.BlockID.Key() == blockKey {
   207  		return existing, true
   208  	}
   209  	if existing := voteSet.votesByBlock[blockKey].getByIndex(valIndex); existing != nil {
   210  		return existing, true
   211  	}
   212  	return nil, false
   213  }
   214  
   215  // Assumes signature is valid.
   216  // If conflicting vote exists, returns it.
   217  func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower int64) (added bool, conflicting *Vote) {
   218  	valIndex := vote.ValidatorIndex
   219  
   220  	// Already exists in voteSet.votes?
   221  	if existing := voteSet.votes[valIndex]; existing != nil {
   222  		if existing.BlockID.Equals(vote.BlockID) {
   223  			panic("addVerifiedVote does not expect duplicate votes")
   224  		} else {
   225  			conflicting = existing
   226  		}
   227  		// Replace vote if blockKey matches voteSet.maj23.
   228  		if voteSet.maj23 != nil && voteSet.maj23.Key() == blockKey {
   229  			voteSet.votes[valIndex] = vote
   230  			voteSet.votesBitArray.SetIndex(valIndex, true)
   231  		}
   232  		// Otherwise don't add it to voteSet.votes
   233  	} else {
   234  		// Add to voteSet.votes and incr .sum
   235  		voteSet.votes[valIndex] = vote
   236  		voteSet.votesBitArray.SetIndex(valIndex, true)
   237  		voteSet.sum += votingPower
   238  	}
   239  
   240  	votesByBlock, ok := voteSet.votesByBlock[blockKey]
   241  	if ok {
   242  		if conflicting != nil && !votesByBlock.peerMaj23 {
   243  			// There's a conflict and no peer claims that this block is special.
   244  			return false, conflicting
   245  		}
   246  		// We'll add the vote in a bit.
   247  	} else {
   248  		// .votesByBlock doesn't exist...
   249  		if conflicting != nil {
   250  			// ... and there's a conflicting vote.
   251  			// We're not even tracking this blockKey, so just forget it.
   252  			return false, conflicting
   253  		}
   254  		// ... and there's no conflicting vote.
   255  		// Start tracking this blockKey
   256  		votesByBlock = newBlockVotes(false, voteSet.valSet.Size())
   257  		voteSet.votesByBlock[blockKey] = votesByBlock
   258  		// We'll add the vote in a bit.
   259  	}
   260  
   261  	// Before adding to votesByBlock, see if we'll exceed quorum
   262  	origSum := votesByBlock.sum
   263  	quorum := voteSet.valSet.TotalVotingPower()*2/3 + 1
   264  
   265  	// Add vote to votesByBlock
   266  	votesByBlock.addVerifiedVote(vote, votingPower)
   267  
   268  	// If we just crossed the quorum threshold and have 2/3 majority...
   269  	if origSum < quorum && quorum <= votesByBlock.sum {
   270  		// Only consider the first quorum reached
   271  		if voteSet.maj23 == nil {
   272  			maj23BlockID := vote.BlockID
   273  			voteSet.maj23 = &maj23BlockID
   274  			// And also copy votes over to voteSet.votes
   275  			for i, vote := range votesByBlock.votes {
   276  				if vote != nil {
   277  					voteSet.votes[i] = vote
   278  				}
   279  			}
   280  		}
   281  	}
   282  
   283  	return true, conflicting
   284  }
   285  
   286  // If a peer claims that it has 2/3 majority for given blockKey, call this.
   287  // NOTE: if there are too many peers, or too much peer churn,
   288  // this can cause memory issues.
   289  // TODO: implement ability to remove peers too
   290  // NOTE: VoteSet must not be nil
   291  func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID BlockID) error {
   292  	if voteSet == nil {
   293  		panic("SetPeerMaj23() on nil VoteSet")
   294  	}
   295  	voteSet.mtx.Lock()
   296  	defer voteSet.mtx.Unlock()
   297  
   298  	blockKey := blockID.Key()
   299  
   300  	// Make sure peer hasn't already told us something.
   301  	if existing, ok := voteSet.peerMaj23s[peerID]; ok {
   302  		if existing.Equals(blockID) {
   303  			return nil // Nothing to do
   304  		}
   305  		return fmt.Errorf("SetPeerMaj23: Received conflicting blockID from peer %v. Got %v, expected %v",
   306  			peerID, blockID, existing)
   307  	}
   308  	voteSet.peerMaj23s[peerID] = blockID
   309  
   310  	// Create .votesByBlock entry if needed.
   311  	votesByBlock, ok := voteSet.votesByBlock[blockKey]
   312  	if ok {
   313  		if votesByBlock.peerMaj23 {
   314  			return nil // Nothing to do
   315  		}
   316  		votesByBlock.peerMaj23 = true
   317  		// No need to copy votes, already there.
   318  	} else {
   319  		votesByBlock = newBlockVotes(true, voteSet.valSet.Size())
   320  		voteSet.votesByBlock[blockKey] = votesByBlock
   321  		// No need to copy votes, no votes to copy over.
   322  	}
   323  	return nil
   324  }
   325  
   326  func (voteSet *VoteSet) BitArray() *cmn.BitArray {
   327  	if voteSet == nil {
   328  		return nil
   329  	}
   330  	voteSet.mtx.Lock()
   331  	defer voteSet.mtx.Unlock()
   332  	return voteSet.votesBitArray.Copy()
   333  }
   334  
   335  func (voteSet *VoteSet) BitArrayByBlockID(blockID BlockID) *cmn.BitArray {
   336  	if voteSet == nil {
   337  		return nil
   338  	}
   339  	voteSet.mtx.Lock()
   340  	defer voteSet.mtx.Unlock()
   341  	votesByBlock, ok := voteSet.votesByBlock[blockID.Key()]
   342  	if ok {
   343  		return votesByBlock.bitArray.Copy()
   344  	}
   345  	return nil
   346  }
   347  
   348  // NOTE: if validator has conflicting votes, returns "canonical" vote
   349  func (voteSet *VoteSet) GetByIndex(valIndex int) *Vote {
   350  	if voteSet == nil {
   351  		return nil
   352  	}
   353  	voteSet.mtx.Lock()
   354  	defer voteSet.mtx.Unlock()
   355  	return voteSet.votes[valIndex]
   356  }
   357  
   358  func (voteSet *VoteSet) GetByAddress(address []byte) *Vote {
   359  	if voteSet == nil {
   360  		return nil
   361  	}
   362  	voteSet.mtx.Lock()
   363  	defer voteSet.mtx.Unlock()
   364  	valIndex, val := voteSet.valSet.GetByAddress(address)
   365  	if val == nil {
   366  		panic("GetByAddress(address) returned nil")
   367  	}
   368  	return voteSet.votes[valIndex]
   369  }
   370  
   371  func (voteSet *VoteSet) HasTwoThirdsMajority() bool {
   372  	if voteSet == nil {
   373  		return false
   374  	}
   375  	voteSet.mtx.Lock()
   376  	defer voteSet.mtx.Unlock()
   377  	return voteSet.maj23 != nil
   378  }
   379  
   380  func (voteSet *VoteSet) IsCommit() bool {
   381  	if voteSet == nil {
   382  		return false
   383  	}
   384  	if voteSet.type_ != PrecommitType {
   385  		return false
   386  	}
   387  	voteSet.mtx.Lock()
   388  	defer voteSet.mtx.Unlock()
   389  	return voteSet.maj23 != nil
   390  }
   391  
   392  func (voteSet *VoteSet) HasTwoThirdsAny() bool {
   393  	if voteSet == nil {
   394  		return false
   395  	}
   396  	voteSet.mtx.Lock()
   397  	defer voteSet.mtx.Unlock()
   398  	return voteSet.sum > voteSet.valSet.TotalVotingPower()*2/3
   399  }
   400  
   401  func (voteSet *VoteSet) HasAll() bool {
   402  	voteSet.mtx.Lock()
   403  	defer voteSet.mtx.Unlock()
   404  	return voteSet.sum == voteSet.valSet.TotalVotingPower()
   405  }
   406  
   407  // If there was a +2/3 majority for blockID, return blockID and true.
   408  // Else, return the empty BlockID{} and false.
   409  func (voteSet *VoteSet) TwoThirdsMajority() (blockID BlockID, ok bool) {
   410  	if voteSet == nil {
   411  		return BlockID{}, false
   412  	}
   413  	voteSet.mtx.Lock()
   414  	defer voteSet.mtx.Unlock()
   415  	if voteSet.maj23 != nil {
   416  		return *voteSet.maj23, true
   417  	}
   418  	return BlockID{}, false
   419  }
   420  
   421  //--------------------------------------------------------------------------------
   422  // Strings and JSON
   423  
   424  func (voteSet *VoteSet) String() string {
   425  	if voteSet == nil {
   426  		return "nil-VoteSet"
   427  	}
   428  	return voteSet.StringIndented("")
   429  }
   430  
   431  func (voteSet *VoteSet) StringIndented(indent string) string {
   432  	voteSet.mtx.Lock()
   433  	defer voteSet.mtx.Unlock()
   434  	voteStrings := make([]string, len(voteSet.votes))
   435  	for i, vote := range voteSet.votes {
   436  		if vote == nil {
   437  			voteStrings[i] = "nil-Vote"
   438  		} else {
   439  			voteStrings[i] = vote.String()
   440  		}
   441  	}
   442  	return fmt.Sprintf(`VoteSet{
   443  %s  H:%v R:%v T:%v
   444  %s  %v
   445  %s  %v
   446  %s  %v
   447  %s}`,
   448  		indent, voteSet.height, voteSet.round, voteSet.type_,
   449  		indent, strings.Join(voteStrings, "\n"+indent+"  "),
   450  		indent, voteSet.votesBitArray,
   451  		indent, voteSet.peerMaj23s,
   452  		indent)
   453  }
   454  
   455  // Marshal the VoteSet to JSON. Same as String(), just in JSON,
   456  // and without the height/round/type_ (since its already included in the votes).
   457  func (voteSet *VoteSet) MarshalJSON() ([]byte, error) {
   458  	voteSet.mtx.Lock()
   459  	defer voteSet.mtx.Unlock()
   460  	return cdc.MarshalJSON(VoteSetJSON{
   461  		voteSet.voteStrings(),
   462  		voteSet.bitArrayString(),
   463  		voteSet.peerMaj23s,
   464  	})
   465  }
   466  
   467  // More human readable JSON of the vote set
   468  // NOTE: insufficient for unmarshalling from (compressed votes)
   469  // TODO: make the peerMaj23s nicer to read (eg just the block hash)
   470  type VoteSetJSON struct {
   471  	Votes         []string          `json:"votes"`
   472  	VotesBitArray string            `json:"votes_bit_array"`
   473  	PeerMaj23s    map[P2PID]BlockID `json:"peer_maj_23s"`
   474  }
   475  
   476  // Return the bit-array of votes including
   477  // the fraction of power that has voted like:
   478  // "BA{29:xx__x__x_x___x__x_______xxx__} 856/1304 = 0.66"
   479  func (voteSet *VoteSet) BitArrayString() string {
   480  	voteSet.mtx.Lock()
   481  	defer voteSet.mtx.Unlock()
   482  	return voteSet.bitArrayString()
   483  }
   484  
   485  func (voteSet *VoteSet) bitArrayString() string {
   486  	bAString := voteSet.votesBitArray.String()
   487  	voted, total, fracVoted := voteSet.sumTotalFrac()
   488  	return fmt.Sprintf("%s %d/%d = %.2f", bAString, voted, total, fracVoted)
   489  }
   490  
   491  // Returns a list of votes compressed to more readable strings.
   492  func (voteSet *VoteSet) VoteStrings() []string {
   493  	voteSet.mtx.Lock()
   494  	defer voteSet.mtx.Unlock()
   495  	return voteSet.voteStrings()
   496  }
   497  
   498  func (voteSet *VoteSet) voteStrings() []string {
   499  	voteStrings := make([]string, len(voteSet.votes))
   500  	for i, vote := range voteSet.votes {
   501  		if vote == nil {
   502  			voteStrings[i] = "nil-Vote"
   503  		} else {
   504  			voteStrings[i] = vote.String()
   505  		}
   506  	}
   507  	return voteStrings
   508  }
   509  
   510  func (voteSet *VoteSet) StringShort() string {
   511  	if voteSet == nil {
   512  		return "nil-VoteSet"
   513  	}
   514  	voteSet.mtx.Lock()
   515  	defer voteSet.mtx.Unlock()
   516  	_, _, frac := voteSet.sumTotalFrac()
   517  	return fmt.Sprintf(`VoteSet{H:%v R:%v T:%v +2/3:%v(%v) %v %v}`,
   518  		voteSet.height, voteSet.round, voteSet.type_, voteSet.maj23, frac, voteSet.votesBitArray, voteSet.peerMaj23s)
   519  }
   520  
   521  // return the power voted, the total, and the fraction
   522  func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) {
   523  	voted, total := voteSet.sum, voteSet.valSet.TotalVotingPower()
   524  	fracVoted := float64(voted) / float64(total)
   525  	return voted, total, fracVoted
   526  }
   527  
   528  //--------------------------------------------------------------------------------
   529  // Commit
   530  
   531  // MakeCommit constructs a Commit from the VoteSet.
   532  // Panics if the vote type is not PrecommitType or if
   533  // there's no +2/3 votes for a single block.
   534  func (voteSet *VoteSet) MakeCommit() *Commit {
   535  	if voteSet.type_ != PrecommitType {
   536  		panic("Cannot MakeCommit() unless VoteSet.Type is PrecommitType")
   537  	}
   538  	voteSet.mtx.Lock()
   539  	defer voteSet.mtx.Unlock()
   540  
   541  	// Make sure we have a 2/3 majority
   542  	if voteSet.maj23 == nil {
   543  		panic("Cannot MakeCommit() unless a blockhash has +2/3")
   544  	}
   545  
   546  	// For every validator, get the precommit
   547  	commitSigs := make([]*CommitSig, len(voteSet.votes))
   548  	for i, v := range voteSet.votes {
   549  		commitSigs[i] = v.CommitSig()
   550  	}
   551  	return NewCommit(*voteSet.maj23, commitSigs)
   552  }
   553  
   554  //--------------------------------------------------------------------------------
   555  
   556  /*
   557  	Votes for a particular block
   558  	There are two ways a *blockVotes gets created for a blockKey.
   559  	1. first (non-conflicting) vote of a validator w/ blockKey (peerMaj23=false)
   560  	2. A peer claims to have a 2/3 majority w/ blockKey (peerMaj23=true)
   561  */
   562  type blockVotes struct {
   563  	peerMaj23 bool          // peer claims to have maj23
   564  	bitArray  *cmn.BitArray // valIndex -> hasVote?
   565  	votes     []*Vote       // valIndex -> *Vote
   566  	sum       int64         // vote sum
   567  }
   568  
   569  func newBlockVotes(peerMaj23 bool, numValidators int) *blockVotes {
   570  	return &blockVotes{
   571  		peerMaj23: peerMaj23,
   572  		bitArray:  cmn.NewBitArray(numValidators),
   573  		votes:     make([]*Vote, numValidators),
   574  		sum:       0,
   575  	}
   576  }
   577  
   578  func (vs *blockVotes) addVerifiedVote(vote *Vote, votingPower int64) {
   579  	valIndex := vote.ValidatorIndex
   580  	if existing := vs.votes[valIndex]; existing == nil {
   581  		vs.bitArray.SetIndex(valIndex, true)
   582  		vs.votes[valIndex] = vote
   583  		vs.sum += votingPower
   584  	}
   585  }
   586  
   587  func (vs *blockVotes) getByIndex(index int) *Vote {
   588  	if vs == nil {
   589  		return nil
   590  	}
   591  	return vs.votes[index]
   592  }
   593  
   594  //--------------------------------------------------------------------------------
   595  
   596  // Common interface between *consensus.VoteSet and types.Commit
   597  type VoteSetReader interface {
   598  	Height() int64
   599  	Round() int
   600  	Type() byte
   601  	Size() int
   602  	BitArray() *cmn.BitArray
   603  	GetByIndex(int) *Vote
   604  	IsCommit() bool
   605  }