package consensus import ( "bytes" "errors" "fmt" "io" "sync" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/blocks" "github.com/tendermint/tendermint/config" ) const ( VoteTypeBare = byte(0x00) VoteTypePrecommit = byte(0x01) VoteTypeCommit = byte(0x02) ) var ( ErrVoteUnexpectedPhase = errors.New("Unexpected phase") ErrVoteInvalidAccount = errors.New("Invalid round vote account") ErrVoteInvalidSignature = errors.New("Invalid round vote signature") ErrVoteInvalidHash = errors.New("Invalid hash") ErrVoteConflictingSignature = errors.New("Conflicting round vote signature") ) // Represents a bare, precommit, or commit vote for proposals. type Vote struct { Height uint32 Round uint16 Type byte Hash []byte // empty if vote is nil. Signature } func ReadVote(r io.Reader) *Vote { return &Vote{ Height: uint32(ReadUInt32(r)), Round: uint16(ReadUInt16(r)), Type: byte(ReadByte(r)), Hash: ReadByteSlice(r), Signature: ReadSignature(r), } } func (v *Vote) WriteTo(w io.Writer) (n int64, err error) { n, err = WriteTo(UInt32(v.Height), w, n, err) n, err = WriteTo(UInt16(v.Round), w, n, err) n, err = WriteTo(Byte(v.Type), w, n, err) n, err = WriteTo(ByteSlice(v.Hash), w, n, err) n, err = WriteTo(v.Signature, w, n, err) return } // This is the byteslice that validators should sign to signify a vote // for the given proposal at given height & round. // If hash is nil, the vote is a nil vote. func (v *Vote) GetDocument() []byte { switch v.Type { case VoteTypeBare: if len(v.Hash) == 0 { doc := fmt.Sprintf("%v://consensus/%v/%v/b\nnil", config.Config.Network, v.Height, v.Round) return []byte(doc) } else { doc := fmt.Sprintf("%v://consensus/%v/%v/b\n%v", config.Config.Network, v.Height, v.Round, CalcBlockURI(v.Height, v.Hash)) return []byte(doc) } case VoteTypePrecommit: if len(v.Hash) == 0 { doc := fmt.Sprintf("%v://consensus/%v/%v/p\nnil", config.Config.Network, v.Height, v.Round) return []byte(doc) } else { doc := fmt.Sprintf("%v://consensus/%v/%v/p\n%v", config.Config.Network, v.Height, v.Round, CalcBlockURI(v.Height, v.Hash)) return []byte(doc) } case VoteTypeCommit: if len(v.Hash) == 0 { panic("Commit hash cannot be nil") } else { doc := fmt.Sprintf("%v://consensus/%v/c\n%v", config.Config.Network, v.Height, // omit round info CalcBlockURI(v.Height, v.Hash)) return []byte(doc) } default: panic("Unknown vote type") } } //----------------------------------------------------------------------------- // VoteSet helps collect signatures from validators at each height+round // for a predefined vote type. type VoteSet struct { mtx sync.Mutex height uint32 round uint16 type_ byte validators map[uint64]*Validator votes map[uint64]*Vote votesByHash map[string]uint64 totalVotes uint64 totalVotingPower uint64 } // Constructs a new VoteSet struct used to accumulate votes for each round. func NewVoteSet(height uint32, round uint16, type_ byte, validators map[uint64]*Validator) *VoteSet { totalVotingPower := uint64(0) for _, val := range validators { totalVotingPower += uint64(val.VotingPower) } return &VoteSet{ height: height, round: round, type_: type_, validators: validators, votes: make(map[uint64]*Vote, len(validators)), votesByHash: make(map[string]uint64), totalVotes: 0, totalVotingPower: totalVotingPower, } } // True if added, false if not. // Returns ErrVote[UnexpectedPhase|InvalidAccount|InvalidSignature|InvalidHash|ConflictingSignature] func (vs *VoteSet) AddVote(vote *Vote) (bool, error) { vs.mtx.Lock() defer vs.mtx.Unlock() // Make sure the phase matches. if vote.Height != vs.height || vote.Round != vs.round || vote.Type != vs.type_ { return false, ErrVoteUnexpectedPhase } val := vs.validators[uint64(vote.SignerId)] // Ensure that signer is a validator. if val == nil { return false, ErrVoteInvalidAccount } // Check signature. if !val.Verify(vote.GetDocument(), vote.Signature.Bytes) { // Bad signature. return false, ErrVoteInvalidSignature } // If vote already exists, return false. if existingVote, ok := vs.votes[uint64(vote.SignerId)]; ok { if bytes.Equal(existingVote.Hash, vote.Hash) { return false, nil } else { return false, ErrVoteConflictingSignature } } vs.votes[uint64(vote.SignerId)] = vote vs.votesByHash[string(vote.Hash)] += uint64(val.VotingPower) vs.totalVotes += uint64(val.VotingPower) return true, nil } // 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, ok bool) { vs.mtx.Lock() defer vs.mtx.Unlock() twoThirdsMajority := (vs.totalVotingPower*uint64(2) + uint64(2)) / uint64(3) if vs.totalVotes < twoThirdsMajority { return nil, false } for hash, votes := range vs.votesByHash { if votes >= twoThirdsMajority { if hash == "" { return nil, true } else { return []byte(hash), true } } } return nil, false } // Returns blockhashes (or nil) that received a +1/3 majority. // If there exists no such majority, returns nil. func (vs *VoteSet) OneThirdMajority() (hashes []interface{}) { vs.mtx.Lock() defer vs.mtx.Unlock() oneThirdMajority := (vs.totalVotingPower + uint64(2)) / uint64(3) if vs.totalVotes < oneThirdMajority { return nil } for hash, votes := range vs.votesByHash { if votes >= oneThirdMajority { if hash == "" { hashes = append(hashes, nil) } else { hashes = append(hashes, []byte(hash)) } } } return hashes }