|
|
- 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
- }
|