package consensus import ( "bytes" "fmt" "strings" "sync" "time" . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/state" ) // VoteSet helps collect signatures from validators at each height+round // for a predefined vote type. // Note that there three kinds of votes: (bare) votes, precommits, and commits. // A commit of prior rounds can be added added in lieu of votes/precommits. // TODO: test majority calculations etc. type VoteSet struct { height uint32 round uint16 type_ byte mtx sync.Mutex vset *state.ValidatorSet votes map[uint64]*Vote votesBitArray BitArray votesByBlockHash map[string]uint64 totalVotes uint64 twoThirdsMajority []byte twoThirdsCommitTime time.Time } // Constructs a new VoteSet struct used to accumulate votes for each round. func NewVoteSet(height uint32, round uint16, type_ byte, vset *state.ValidatorSet) *VoteSet { if type_ == VoteTypeCommit && round != 0 { panic("Expected round 0 for commit vote set") } return &VoteSet{ height: height, round: round, type_: type_, vset: vset, votes: make(map[uint64]*Vote, vset.Size()), votesBitArray: NewBitArray(vset.Size()), votesByBlockHash: make(map[string]uint64), totalVotes: 0, } } // True if added, false if not. // Returns ErrVote[UnexpectedPhase|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] // NOTE: vote should be mutated after adding. func (vs *VoteSet) Add(vote *Vote) (bool, error) { vs.mtx.Lock() defer vs.mtx.Unlock() // Make sure the phase matches. (or that vote is commit && round < vs.round) if vote.Height != vs.height || (vote.Type != VoteTypeCommit && vote.Round != vs.round) || (vote.Type != VoteTypeCommit && vote.Type != vs.type_) || (vote.Type == VoteTypeCommit && vs.type_ != VoteTypeCommit && vote.Round >= vs.round) { return false, ErrVoteUnexpectedPhase } // Ensure that signer is a validator. _, val := vs.vset.GetById(vote.SignerId) if val == nil { return false, ErrVoteInvalidAccount } // Check signature. if !val.Verify(vote) { // Bad signature. return false, ErrVoteInvalidSignature } return vs.addVote(vote) } func (vs *VoteSet) addVote(vote *Vote) (bool, error) { // If vote already exists, return false. if existingVote, ok := vs.votes[vote.SignerId]; ok { if bytes.Equal(existingVote.BlockHash, vote.BlockHash) { return false, nil } else { return false, ErrVoteConflictingSignature } } // Add vote. vs.votes[vote.SignerId] = vote voterIndex, val := vs.vset.GetById(vote.SignerId) if val == nil { return false, ErrVoteInvalidAccount } vs.votesBitArray.SetIndex(uint(voterIndex), true) totalBlockHashVotes := vs.votesByBlockHash[string(vote.BlockHash)] + val.VotingPower vs.votesByBlockHash[string(vote.BlockHash)] = totalBlockHashVotes vs.totalVotes += val.VotingPower // If we just nudged it up to two thirds majority, add it. if totalBlockHashVotes > vs.vset.TotalVotingPower()*2/3 && (totalBlockHashVotes-val.VotingPower) <= vs.vset.TotalVotingPower()*2/3 { vs.twoThirdsMajority = vote.BlockHash vs.twoThirdsCommitTime = time.Now() } return true, nil } // Assumes that commits VoteSet is valid. func (vs *VoteSet) AddFromCommits(commits *VoteSet) { commitVotes := commits.AllVotes() for _, commit := range commitVotes { if commit.Round < vs.round { vs.addVote(commit) } } } func (vs *VoteSet) BitArray() BitArray { vs.mtx.Lock() defer vs.mtx.Unlock() return vs.votesBitArray.Copy() } func (vs *VoteSet) GetVote(id uint64) *Vote { vs.mtx.Lock() defer vs.mtx.Unlock() return vs.votes[id] } func (vs *VoteSet) AllVotes() []*Vote { vs.mtx.Lock() defer vs.mtx.Unlock() votes := []*Vote{} for _, vote := range vs.votes { votes = append(votes, vote) } return votes } // Returns either a blockhash (or nil) that received +2/3 majority. // If there exists no such majority, returns (nil, false). func (vs *VoteSet) TwoThirdsMajority() (hash []byte, commitTime time.Time, ok bool) { vs.mtx.Lock() defer vs.mtx.Unlock() if vs.twoThirdsCommitTime.IsZero() { return nil, time.Time{}, false } return vs.twoThirdsMajority, vs.twoThirdsCommitTime, true } func (vs *VoteSet) MakePOL() *POL { vs.mtx.Lock() defer vs.mtx.Unlock() if vs.twoThirdsCommitTime.IsZero() { return nil } majHash := vs.twoThirdsMajority // hash may be nil. pol := &POL{ Height: vs.height, Round: vs.round, BlockHash: majHash, } for _, vote := range vs.votes { if bytes.Equal(vote.BlockHash, majHash) { if vote.Type == VoteTypeBare { pol.Votes = append(pol.Votes, vote.Signature) } else if vote.Type == VoteTypeCommit { pol.Commits = append(pol.Votes, vote.Signature) pol.CommitRounds = append(pol.CommitRounds, vote.Round) } else { Panicf("Unexpected vote type %X", vote.Type) } } } return pol } func (vs *VoteSet) String() string { return vs.StringWithIndent("") } func (vs *VoteSet) StringWithIndent(indent string) string { vs.mtx.Lock() defer vs.mtx.Unlock() voteStrings := make([]string, len(vs.votes)) for _, vote := range vs.votes { voteStrings[vote.SignerId] = vote.String() } return fmt.Sprintf(`VoteSet{ %s H:%v R:%v T:%v %s %v %s %v %s}`, indent, vs.height, vs.round, vs.type_, indent, strings.Join(voteStrings, "\n"+indent+" "), indent, vs.votesBitArray, indent) }