tendermint/consensus/vote_set.go

278 lines
6.9 KiB
Go
Raw Normal View History

2014-09-14 15:37:32 -07:00
package consensus
import (
"bytes"
2014-10-15 20:15:38 -07:00
"fmt"
"strings"
2014-09-14 15:37:32 -07:00
"sync"
2014-10-30 03:32:09 -07:00
. "github.com/tendermint/tendermint/binary"
2014-09-14 15:37:32 -07:00
. "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/state"
2014-09-14 15:37:32 -07:00
)
// VoteSet helps collect signatures from validators at each height+round
// for a predefined vote type.
// Note that there three kinds of votes: prevotes, precommits, and commits.
2014-09-14 15:37:32 -07:00
// A commit of prior rounds can be added added in lieu of votes/precommits.
// TODO: test majority calculations etc.
// NOTE: assumes that the sum total of voting power does not exceed MaxUInt64.
2014-09-14 15:37:32 -07:00
type VoteSet struct {
height uint32
round uint16
type_ byte
2014-10-30 03:32:09 -07:00
mtx sync.Mutex
vset *state.ValidatorSet
votes map[uint64]*Vote
votesBitArray BitArray
votesByBlock map[string]uint64 // string(blockHash)+string(blockParts) -> vote sum.
totalVotes uint64
maj23Hash []byte
maj23Parts PartSetHeader
maj23Exists bool
2014-09-14 15:37:32 -07:00
}
// Constructs a new VoteSet struct used to accumulate votes for each round.
func NewVoteSet(height uint32, round uint16, type_ byte, vset *state.ValidatorSet) *VoteSet {
2014-09-14 15:37:32 -07:00
if type_ == VoteTypeCommit && round != 0 {
panic("Expected round 0 for commit vote set")
}
return &VoteSet{
2014-10-30 03:32:09 -07:00
height: height,
round: round,
type_: type_,
vset: vset,
votes: make(map[uint64]*Vote, vset.Size()),
votesBitArray: NewBitArray(vset.Size()),
votesByBlock: make(map[string]uint64),
totalVotes: 0,
2014-09-14 15:37:32 -07:00
}
}
// True if added, false if not.
// Returns ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature]
// NOTE: vote should not be mutated after adding.
2014-10-15 20:15:38 -07:00
func (vs *VoteSet) Add(vote *Vote) (bool, error) {
2014-09-14 15:37:32 -07:00
vs.mtx.Lock()
defer vs.mtx.Unlock()
// Make sure the step matches. (or that vote is commit && round < vs.round)
2014-09-14 15:37:32 -07:00
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, ErrVoteUnexpectedStep
2014-09-14 15:37:32 -07:00
}
// Ensure that signer is a validator.
2014-10-11 21:27:58 -07:00
_, val := vs.vset.GetById(vote.SignerId)
2014-09-14 15:37:32 -07:00
if val == nil {
return false, ErrVoteInvalidAccount
}
// Check signature.
2014-10-07 23:11:04 -07:00
if !val.Verify(vote) {
2014-09-14 15:37:32 -07:00
// 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
2014-10-11 21:27:58 -07:00
voterIndex, val := vs.vset.GetById(vote.SignerId)
if val == nil {
2014-09-14 15:37:32 -07:00
return false, ErrVoteInvalidAccount
}
vs.votesBitArray.SetIndex(uint(voterIndex), true)
2014-10-30 03:32:09 -07:00
blockKey := string(vote.BlockHash) + string(BinaryBytes(vote.BlockParts))
totalBlockHashVotes := vs.votesByBlock[blockKey] + val.VotingPower
vs.votesByBlock[blockKey] = totalBlockHashVotes
2014-09-14 15:37:32 -07:00
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 {
2014-10-30 03:32:09 -07:00
vs.maj23Hash = vote.BlockHash
vs.maj23Parts = vote.BlockParts
vs.maj23Exists = true
2014-09-14 15:37:32 -07:00
}
return true, nil
}
// Assumes that commits VoteSet is valid.
2014-10-15 20:15:38 -07:00
func (vs *VoteSet) AddFromCommits(commits *VoteSet) {
2014-09-14 15:37:32 -07:00
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()
}
2014-10-30 03:32:09 -07:00
func (vs *VoteSet) GetByIndex(index uint) *Vote {
vs.mtx.Lock()
defer vs.mtx.Unlock()
id, val := vs.vset.GetByIndex(index)
if val == nil {
panic("GetByIndex(index) returned nil")
}
return vs.votes[id]
}
func (vs *VoteSet) GetById(id uint64) *Vote {
2014-09-14 15:37:32 -07:00
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
}
func (vs *VoteSet) HasTwoThirdsMajority() bool {
if vs == nil {
return false
}
vs.mtx.Lock()
defer vs.mtx.Unlock()
2014-10-30 03:32:09 -07:00
return vs.maj23Exists
}
2014-09-14 15:37:32 -07:00
// Returns either a blockhash (or nil) that received +2/3 majority.
// If there exists no such majority, returns (nil, false).
2014-10-30 03:32:09 -07:00
func (vs *VoteSet) TwoThirdsMajority() (hash []byte, parts PartSetHeader, ok bool) {
2014-09-14 15:37:32 -07:00
vs.mtx.Lock()
defer vs.mtx.Unlock()
2014-10-30 03:32:09 -07:00
if vs.maj23Exists {
return vs.maj23Hash, vs.maj23Parts, true
2014-10-21 23:30:18 -07:00
} else {
2014-10-30 03:32:09 -07:00
return nil, PartSetHeader{}, false
2014-09-14 15:37:32 -07:00
}
}
func (vs *VoteSet) MakePOL() *POL {
if vs.type_ != VoteTypePrevote {
panic("Cannot MakePOL() unless VoteSet.Type is VoteTypePrevote")
}
2014-09-14 15:37:32 -07:00
vs.mtx.Lock()
defer vs.mtx.Unlock()
2014-10-30 03:32:09 -07:00
if !vs.maj23Exists {
2014-09-14 15:37:32 -07:00
return nil
}
pol := &POL{
2014-10-30 03:32:09 -07:00
Height: vs.height,
Round: vs.round,
BlockHash: vs.maj23Hash,
BlockParts: vs.maj23Parts,
2014-09-14 15:37:32 -07:00
}
for _, vote := range vs.votes {
2014-10-30 03:32:09 -07:00
if !bytes.Equal(vote.BlockHash, vs.maj23Hash) {
continue
}
if !vote.BlockParts.Equals(vs.maj23Parts) {
continue
}
if vote.Type == VoteTypePrevote {
pol.Votes = append(pol.Votes, vote.Signature)
} else if vote.Type == VoteTypeCommit {
pol.Commits = append(pol.Commits, RoundSignature{vote.Round, vote.Signature})
} else {
Panicf("Unexpected vote type %X", vote.Type)
2014-09-14 15:37:32 -07:00
}
}
return pol
}
2014-10-15 20:15:38 -07:00
func (vs *VoteSet) MakeValidation() Validation {
if vs.type_ != VoteTypeCommit {
panic("Cannot MakeValidation() unless VoteSet.Type is VoteTypePrevote")
}
vs.mtx.Lock()
defer vs.mtx.Unlock()
2014-10-30 03:32:09 -07:00
if len(vs.maj23Hash) == 0 {
panic("Cannot MakeValidation() unless a blockhash has +2/3")
}
2014-10-30 03:32:09 -07:00
rsigs := make([]RoundSignature, vs.vset.Size())
vs.vset.Iterate(func(index uint, val *state.Validator) bool {
vote := vs.votes[val.Id]
if vote == nil {
return false
}
2014-10-30 03:32:09 -07:00
if !bytes.Equal(vote.BlockHash, vs.maj23Hash) {
return false
}
if !vote.BlockParts.Equals(vs.maj23Parts) {
return false
}
rsigs[index] = RoundSignature{vote.Round, vote.Signature}
return false
})
return Validation{
2014-10-30 03:32:09 -07:00
Commits: rsigs,
}
}
2014-10-15 20:15:38 -07:00
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))
counter := 0
2014-10-15 20:15:38 -07:00
for _, vote := range vs.votes {
voteStrings[counter] = vote.String()
counter++
2014-10-15 20:15:38 -07:00
}
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)
}
func (vs *VoteSet) Description() string {
if vs == nil {
return "nil-VoteSet"
}
vs.mtx.Lock()
defer vs.mtx.Unlock()
return fmt.Sprintf(`VoteSet{H:%v R:%v T:%v %v}`,
vs.height, vs.round, vs.type_, vs.votesBitArray)
}