mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-19 06:31:57 +00:00
draft of consensus+state code, compiles.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package consensus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
. "github.com/tendermint/tendermint/blocks"
|
||||
. "github.com/tendermint/tendermint/state"
|
||||
)
|
||||
|
||||
// Helper for keeping track of block parts.
|
||||
type BlockPartSet struct {
|
||||
mtx sync.Mutex
|
||||
signer *Account
|
||||
height uint32
|
||||
round uint16 // Not used
|
||||
total uint16
|
||||
numParts uint16
|
||||
parts []*BlockPart
|
||||
|
||||
_block *Block // cache
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidBlockPartSignature = errors.New("Invalid block part signature") // Peer gave us a fake part
|
||||
ErrInvalidBlockPartConflict = errors.New("Invalid block part conflict") // Signer signed conflicting parts
|
||||
)
|
||||
|
||||
// Signer may be nil if signer is unknown beforehand.
|
||||
func NewBlockPartSet(height uint32, round uint16, signer *Account) *BlockPartSet {
|
||||
return &BlockPartSet{
|
||||
signer: signer,
|
||||
height: height,
|
||||
round: round,
|
||||
}
|
||||
}
|
||||
|
||||
// In the case where the signer wasn't known prior to NewBlockPartSet(),
|
||||
// user should call SetSigner() prior to AddBlockPart().
|
||||
func (bps *BlockPartSet) SetSigner(signer *Account) {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
if bps.signer != nil {
|
||||
panic("BlockPartSet signer already set.")
|
||||
}
|
||||
bps.signer = signer
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) BlockParts() []*BlockPart {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
return bps.parts
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) BitArray() []byte {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
if bps.parts == nil {
|
||||
return nil
|
||||
}
|
||||
bitArray := make([]byte, (len(bps.parts)+7)/8)
|
||||
for i, part := range bps.parts {
|
||||
if part != nil {
|
||||
bitArray[i/8] |= 1 << uint(i%8)
|
||||
}
|
||||
}
|
||||
return bitArray
|
||||
}
|
||||
|
||||
// If the part isn't valid, returns an error.
|
||||
// err can be ErrInvalidBlockPart[Conflict|Signature]
|
||||
func (bps *BlockPartSet) AddBlockPart(part *BlockPart) (added bool, err error) {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
|
||||
// If part is invalid, return an error.
|
||||
/* XXX
|
||||
err = part.ValidateWithSigner(bps.signer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
*/
|
||||
|
||||
if bps.parts == nil {
|
||||
// First received part for this round.
|
||||
bps.parts = make([]*BlockPart, part.Total)
|
||||
bps.total = uint16(part.Total)
|
||||
bps.parts[int(part.Index)] = part
|
||||
bps.numParts++
|
||||
return true, nil
|
||||
} else {
|
||||
// Check part.Index and part.Total
|
||||
if uint16(part.Index) >= bps.total {
|
||||
return false, ErrInvalidBlockPartConflict
|
||||
}
|
||||
if uint16(part.Total) != bps.total {
|
||||
return false, ErrInvalidBlockPartConflict
|
||||
}
|
||||
// Check for existing parts.
|
||||
existing := bps.parts[part.Index]
|
||||
if existing != nil {
|
||||
if bytes.Equal(existing.Bytes, part.Bytes) {
|
||||
// Ignore duplicate
|
||||
return false, nil
|
||||
} else {
|
||||
return false, ErrInvalidBlockPartConflict
|
||||
}
|
||||
} else {
|
||||
bps.parts[int(part.Index)] = part
|
||||
bps.numParts++
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) IsComplete() bool {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
return bps.total > 0 && bps.total == bps.numParts
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) Block() *Block {
|
||||
if !bps.IsComplete() {
|
||||
return nil
|
||||
}
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
if bps._block == nil {
|
||||
blockBytes := []byte{}
|
||||
for _, part := range bps.parts {
|
||||
blockBytes = append(blockBytes, part.Bytes...)
|
||||
}
|
||||
var n int64
|
||||
var err error
|
||||
block := ReadBlock(bytes.NewReader(blockBytes), &n, &err)
|
||||
bps._block = block
|
||||
}
|
||||
return bps._block
|
||||
}
|
||||
+35
-34
@@ -320,7 +320,7 @@ func (cm *ConsensusManager) stageProposal(proposal *BlockPartSet) error {
|
||||
if !proposal.IsComplete() {
|
||||
return errors.New("Incomplete proposal BlockPartSet")
|
||||
}
|
||||
block, blockParts := blockPartSet.Block(), blockPartSet.BlockParts()
|
||||
block := proposal.Block()
|
||||
err := block.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -332,7 +332,7 @@ func (cm *ConsensusManager) stageProposal(proposal *BlockPartSet) error {
|
||||
cm.mtx.Unlock()
|
||||
|
||||
// Commit block onto the copied state.
|
||||
err := stateCopy.CommitBlock(block, block.Header.Time) // NOTE: fake commit time.
|
||||
err = stateCopy.CommitBlock(block, block.Header.Time) // NOTE: fake commit time.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -340,7 +340,7 @@ func (cm *ConsensusManager) stageProposal(proposal *BlockPartSet) error {
|
||||
// Looks good!
|
||||
cm.mtx.Lock()
|
||||
cm.stagedProposal = proposal
|
||||
cm.stagedState = state
|
||||
cm.stagedState = stateCopy
|
||||
cm.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -379,7 +379,7 @@ func (cm *ConsensusManager) voteProposal(rs *RoundState) error {
|
||||
return err
|
||||
}
|
||||
// Vote for block.
|
||||
err := cm.signAndVote(&Vote{
|
||||
err = cm.signAndVote(&Vote{
|
||||
Height: rs.Height,
|
||||
Round: rs.Round,
|
||||
Type: VoteTypeBare,
|
||||
@@ -788,25 +788,26 @@ func (ps *PeerState) ApplyVoteRankMessage(msg *VoteRankMessage) error {
|
||||
// Messages
|
||||
|
||||
const (
|
||||
msgTypeUnknown = Byte(0x00)
|
||||
msgTypeBlockPart = Byte(0x10)
|
||||
msgTypeKnownBlockParts = Byte(0x11)
|
||||
msgTypeVote = Byte(0x20)
|
||||
msgTypeVoteRank = Byte(0x21)
|
||||
msgTypeUnknown = byte(0x00)
|
||||
msgTypeBlockPart = byte(0x10)
|
||||
msgTypeKnownBlockParts = byte(0x11)
|
||||
msgTypeVote = byte(0x20)
|
||||
msgTypeVoteRank = byte(0x21)
|
||||
)
|
||||
|
||||
// TODO: check for unnecessary extra bytes at the end.
|
||||
func decodeMessage(bz ByteSlice) (msg interface{}) {
|
||||
func decodeMessage(bz []byte) (msg interface{}) {
|
||||
n, err := new(int64), new(error)
|
||||
// log.Debug("decoding msg bytes: %X", bz)
|
||||
switch Byte(bz[0]) {
|
||||
switch bz[0] {
|
||||
case msgTypeBlockPart:
|
||||
return readBlockPartMessage(bytes.NewReader(bz[1:]))
|
||||
return readBlockPartMessage(bytes.NewReader(bz[1:]), n, err)
|
||||
case msgTypeKnownBlockParts:
|
||||
return readKnownBlockPartsMessage(bytes.NewReader(bz[1:]))
|
||||
return readKnownBlockPartsMessage(bytes.NewReader(bz[1:]), n, err)
|
||||
case msgTypeVote:
|
||||
return ReadVote(bytes.NewReader(bz[1:]))
|
||||
return ReadVote(bytes.NewReader(bz[1:]), n, err)
|
||||
case msgTypeVoteRank:
|
||||
return readVoteRankMessage(bytes.NewReader(bz[1:]))
|
||||
return readVoteRankMessage(bytes.NewReader(bz[1:]), n, err)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -818,15 +819,15 @@ type BlockPartMessage struct {
|
||||
BlockPart *BlockPart
|
||||
}
|
||||
|
||||
func readBlockPartMessage(r io.Reader) *BlockPartMessage {
|
||||
func readBlockPartMessage(r io.Reader, n *int64, err *error) *BlockPartMessage {
|
||||
return &BlockPartMessage{
|
||||
BlockPart: ReadBlockPart(r),
|
||||
BlockPart: ReadBlockPart(r, n, err),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *BlockPartMessage) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(msgTypeBlockPart, w, n, err)
|
||||
n, err = WriteTo(m.BlockPart, w, n, err)
|
||||
WriteByte(w, msgTypeBlockPart, &n, &err)
|
||||
WriteBinary(w, m.BlockPart, &n, &err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -839,22 +840,22 @@ func (m *BlockPartMessage) String() string {
|
||||
type KnownBlockPartsMessage struct {
|
||||
Height uint32
|
||||
SecondsSinceStartTime uint32
|
||||
BlockPartsBitArray ByteSlice
|
||||
BlockPartsBitArray []byte
|
||||
}
|
||||
|
||||
func readKnownBlockPartsMessage(r io.Reader) *KnownBlockPartsMessage {
|
||||
func readKnownBlockPartsMessage(r io.Reader, n *int64, err *error) *KnownBlockPartsMessage {
|
||||
return &KnownBlockPartsMessage{
|
||||
Height: Readuint32(r),
|
||||
SecondsSinceStartTime: Readuint32(r),
|
||||
BlockPartsBitArray: ReadByteSlice(r),
|
||||
Height: ReadUInt32(r, n, err),
|
||||
SecondsSinceStartTime: ReadUInt32(r, n, err),
|
||||
BlockPartsBitArray: ReadByteSlice(r, n, err),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *KnownBlockPartsMessage) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(msgTypeKnownBlockParts, w, n, err)
|
||||
n, err = WriteTo(UInt32(m.Height), w, n, err)
|
||||
n, err = WriteTo(UInt32(m.SecondsSinceStartTime), w, n, err)
|
||||
n, err = WriteTo(m.BlockPartsBitArray, w, n, err)
|
||||
WriteByte(w, msgTypeKnownBlockParts, &n, &err)
|
||||
WriteUInt32(w, m.Height, &n, &err)
|
||||
WriteUInt32(w, m.SecondsSinceStartTime, &n, &err)
|
||||
WriteByteSlice(w, m.BlockPartsBitArray, &n, &err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -871,17 +872,17 @@ type VoteRankMessage struct {
|
||||
Rank uint8
|
||||
}
|
||||
|
||||
func readVoteRankMessage(r io.Reader) *VoteRankMessage {
|
||||
func readVoteRankMessage(r io.Reader, n *int64, err *error) *VoteRankMessage {
|
||||
return &VoteRankMessage{
|
||||
ValidatorId: Readuint64(r),
|
||||
Rank: Readuint8(r),
|
||||
ValidatorId: ReadUInt64(r, n, err),
|
||||
Rank: ReadUInt8(r, n, err),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *VoteRankMessage) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(msgTypeVoteRank, w, n, err)
|
||||
n, err = WriteTo(UInt64(m.ValidatorId), w, n, err)
|
||||
n, err = WriteTo(UInt8(m.Rank), w, n, err)
|
||||
WriteByte(w, msgTypeVoteRank, &n, &err)
|
||||
WriteUInt64(w, m.ValidatorId, &n, &err)
|
||||
WriteUInt8(w, m.Rank, &n, &err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package consensus
|
||||
|
||||
import (
|
||||
db_ "github.com/tendermint/tendermint/db"
|
||||
. "github.com/tendermint/tendermint/state"
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// TODO: Ensure that double signing never happens via an external persistent check.
|
||||
type PrivValidator struct {
|
||||
PrivAccount
|
||||
db *db_.LevelDB
|
||||
}
|
||||
|
||||
// Modifies the vote object in memory.
|
||||
// Double signing results in an error.
|
||||
func (pv *PrivValidator) SignVote(vote *Vote) error {
|
||||
return nil
|
||||
}
|
||||
+23
-26
@@ -1,12 +1,9 @@
|
||||
package consensus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/blocks"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
. "github.com/tendermint/tendermint/state"
|
||||
)
|
||||
@@ -18,12 +15,12 @@ var (
|
||||
// Tracks consensus state across block heights and rounds.
|
||||
type ConsensusState struct {
|
||||
mtx sync.Mutex
|
||||
height uint32 // Height we are working on.
|
||||
validatorsR0 map[uint64]*Validator // A copy of the validators at round 0
|
||||
lockedProposal *BlockPartSet // A BlockPartSet of the locked proposal.
|
||||
startTime time.Time // Start of round 0 for this height.
|
||||
commits *VoteSet // Commits for this height.
|
||||
roundState *RoundState // The RoundState object for the current round.
|
||||
height uint32 // Height we are working on.
|
||||
validatorsR0 *ValidatorSet // A copy of the validators at round 0
|
||||
lockedProposal *BlockPartSet // A BlockPartSet of the locked proposal.
|
||||
startTime time.Time // Start of round 0 for this height.
|
||||
commits *VoteSet // Commits for this height.
|
||||
roundState *RoundState // The RoundState object for the current round.
|
||||
}
|
||||
|
||||
func NewConsensusState(state *State) *ConsensusState {
|
||||
@@ -70,7 +67,7 @@ func (cs *ConsensusState) Update(state *State) {
|
||||
cs.height = stateHeight
|
||||
cs.validatorsR0 = state.Validators().Copy() // NOTE: immutable.
|
||||
cs.lockedProposal = nil
|
||||
cs.startTime = state.commitTime // XXX is this what we want?
|
||||
cs.startTime = state.CommitTime() // XXX is this what we want?
|
||||
cs.commits = NewVoteSet(stateHeight, 0, VoteTypeCommit, cs.validatorsR0)
|
||||
|
||||
// Setup the roundState
|
||||
@@ -94,20 +91,20 @@ func (cs *ConsensusState) SetupRound(round uint16) {
|
||||
func (cs *ConsensusState) setupRound(round uint16) {
|
||||
// Increment validator accums as necessary.
|
||||
// We need to start with cs.validatorsR0 or cs.roundState.Validators
|
||||
var validators map[uint64]*Validator = nil
|
||||
var validators *ValidatorSet
|
||||
var validatorsRound uint16
|
||||
if cs.roundState == nil {
|
||||
// We have no roundState so we start from validatorsR0 at round 0.
|
||||
validators = copyValidators(cs.validatorsR0)
|
||||
validators = cs.validatorsR0.Copy()
|
||||
validatorsRound = 0
|
||||
} else {
|
||||
// We have a previous roundState so we start from that.
|
||||
validators = copyValidators(cs.roundState.Validators)
|
||||
validators = cs.roundState.Validators.Copy()
|
||||
validatorsRound = cs.roundState.Round
|
||||
}
|
||||
// Increment all the way to round.
|
||||
for r := validatorsRound; r < round; r++ {
|
||||
incrementAccum(validators)
|
||||
validators.IncrementAccum()
|
||||
}
|
||||
|
||||
roundState := NewRoundState(cs.height, round, cs.startTime, validators, cs.commits)
|
||||
@@ -128,25 +125,25 @@ const (
|
||||
|
||||
// RoundState encapsulates all the state needed to engage in the consensus protocol.
|
||||
type RoundState struct {
|
||||
Height uint32 // Immutable
|
||||
Round uint16 // Immutable
|
||||
StartTime time.Time // Time in which consensus started for this height.
|
||||
Expires time.Time // Time after which this round is expired.
|
||||
Proposer *Validator // The proposer to propose a block for this round.
|
||||
Validators map[uint64]*Validator // All validators with modified accumPower for this round.
|
||||
BlockPartSet *BlockPartSet // All block parts received for this round.
|
||||
RoundBareVotes *VoteSet // All votes received for this round.
|
||||
RoundPrecommits *VoteSet // All precommits received for this round.
|
||||
Commits *VoteSet // A shared object for all commit votes of this height.
|
||||
Height uint32 // Immutable
|
||||
Round uint16 // Immutable
|
||||
StartTime time.Time // Time in which consensus started for this height.
|
||||
Expires time.Time // Time after which this round is expired.
|
||||
Proposer *Validator // The proposer to propose a block for this round.
|
||||
Validators *ValidatorSet // All validators with modified accumPower for this round.
|
||||
BlockPartSet *BlockPartSet // All block parts received for this round.
|
||||
RoundBareVotes *VoteSet // All votes received for this round.
|
||||
RoundPrecommits *VoteSet // All precommits received for this round.
|
||||
Commits *VoteSet // A shared object for all commit votes of this height.
|
||||
|
||||
mtx sync.Mutex
|
||||
step uint8 // mutable
|
||||
}
|
||||
|
||||
func NewRoundState(height uint32, round uint16, startTime time.Time,
|
||||
validators map[uint64]*Validator, commits *VoteSet) *RoundState {
|
||||
validators *ValidatorSet, commits *VoteSet) *RoundState {
|
||||
|
||||
proposer := getProposer(validators)
|
||||
proposer := validators.GetProposer()
|
||||
blockPartSet := NewBlockPartSet(height, round, &(proposer.Account))
|
||||
roundBareVotes := NewVoteSet(height, round, VoteTypeBare, validators)
|
||||
roundPrecommits := NewVoteSet(height, round, VoteTypePrecommit, validators)
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package consensus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/blocks"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
. "github.com/tendermint/tendermint/state"
|
||||
)
|
||||
|
||||
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, n *int64, err *error) *Vote {
|
||||
return &Vote{
|
||||
Height: ReadUInt32(r, n, err),
|
||||
Round: ReadUInt16(r, n, err),
|
||||
Type: ReadByte(r, n, err),
|
||||
Hash: ReadByteSlice(r, n, err),
|
||||
Signature: ReadSignature(r, n, err),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Vote) WriteTo(w io.Writer) (n int64, err error) {
|
||||
WriteUInt32(w, v.Height, &n, &err)
|
||||
WriteUInt16(w, v.Round, &n, &err)
|
||||
WriteByte(w, v.Type, &n, &err)
|
||||
WriteByteSlice(w, v.Hash, &n, &err)
|
||||
WriteBinary(w, v.Signature, &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 *ValidatorSet
|
||||
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 *ValidatorSet) *VoteSet {
|
||||
totalVotingPower := uint64(0)
|
||||
for _, val := range validators.Map() {
|
||||
totalVotingPower += val.VotingPower
|
||||
}
|
||||
return &VoteSet{
|
||||
height: height,
|
||||
round: round,
|
||||
type_: type_,
|
||||
validators: validators,
|
||||
votes: make(map[uint64]*Vote, validators.Size()),
|
||||
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.Get(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[vote.SignerId]; ok {
|
||||
if bytes.Equal(existingVote.Hash, vote.Hash) {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, ErrVoteConflictingSignature
|
||||
}
|
||||
}
|
||||
vs.votes[vote.SignerId] = vote
|
||||
vs.votesByHash[string(vote.Hash)] += val.VotingPower
|
||||
vs.totalVotes += 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*2 + 2) / 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 + 2) / 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
|
||||
}
|
||||
Reference in New Issue
Block a user