refactor from Binary centric model to global method model

This commit is contained in:
Jae Kwon
2014-09-03 20:41:57 -07:00
parent d0ec18dc16
commit e53b148acf
23 changed files with 624 additions and 692 deletions
+37 -5
View File
@@ -1,17 +1,49 @@
## [ZombieValidators]
## Determining the order of proposers at height h:
```
Determining the order of proposers at height h:
A B C All validators A, B, and C
[+10, +5, +2] (+17) Voting power
[ 0, 0, 0] Genesis?
[ 10, 5, 2] (+17)
A [ -7, 5, 2] (-17) Round 0 proposer: A
[ 3, 10, 4] (+17)
B [ 3, -7, 4] (-17) Round 1 proposer: B
[ 13, -2, 6] (+17)
A [ -4, -2, 6] (-17) Round 2 proposer: A
[ 6, 3, 8] (+17)
C [ 6, 3, -9] (-17) Round 3 proposer: C
[ 16, 8, -7] (+17)
A [ -1, 8, -7] (-17) Round 4 proposer: A
[ 9, 13, -5] (+17)
B [ 9, -4, -5] (-17) Round 5 proposer: B
[ 19, 1, -3] (+17)
A [ 2, 1, -3] (-17) Round 6 proposer: A
........... ...
For a node, once consensus has been reached at some round R,
the moment the node sees +2/3 in votes for a proposal is when
the consensus rounds for the *next* height h+1 begins.
Round R+1 in the consensus rounds at height h+1 is the same as
round R in the consensus rounds at height h (the parent block).
We omit details of dealing with membership changes.
```
## Zombie Validators
The most likely scenario may be during an upgrade.
We'll see some validators that fail to upgrade while most have. Then, some updated validator will propose a block that appears invalid to the outdated validators. What is the outdated validator to do?
The right thing to do is to stop participating, because you have no idea what is going on, and prompt the administrator to upgrade the daemon. (Now this could be a security issue if not handled properly, so in the future we should think about upgrade security best practices). Yet say you don't, and you continue to sign blocks without really participating in the consensus rounds -- maybe voting nil each time and then signing whatever is decided on. Well for one, you've lost all ability to validate any blocks. It's a problem because if there are too many of these zombies, the network might accidentally commit a bad block -- in effect, crashing the network. So, the network wants to weed the zombies out.
It's hard catching the zombie. It can mimick whatever other validator is doing, perhaps mimicking the first one to vote during the rounds and waiting just a little longer for the final block vote. Based on my extensive experience with zombie flicks, it appears that the best way to identify a zombie is to make it perform some action that only non-zombies would understand. That's easy! Just make each version of the protocol have a special algorithm that selects a small but sufficiently large fraction of the validator population at each block, and make them perform an action (intuitively, make them raise their hadns). Eventually, it'll become the zombie's turn to do something but it won't know what to do. Or it will act out of turn. Gotcha.
The algorithm could even depend on state data, such that validators are required to keep it updated, which is a hair away from full validation. I suspect that there are more complete ways to enforce validation, but such measures may not be necessary in practice.
TODO: implement such a mechanism.
+98 -83
View File
@@ -10,11 +10,11 @@ import (
"sync/atomic"
"time"
. "github.com/tendermint/tendermint/accounts"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/p2p"
. "github.com/tendermint/tendermint/state"
)
const (
@@ -89,31 +89,44 @@ type ConsensusManager struct {
started uint32
stopped uint32
csc *ConsensusStateControl
blockStore *BlockStore
accountStore *AccountStore
mtx sync.Mutex
peerStates map[string]*PeerState
doActionCh chan RoundAction
cs *ConsensusState
blockStore *BlockStore
doActionCh chan RoundAction
mtx sync.Mutex
state *State
privValidator *PrivValidator
peerStates map[string]*PeerState
stagedProposal *BlockPartSet
stagedState *State
}
func NewConsensusManager(sw *p2p.Switch, csc *ConsensusStateControl, blockStore *BlockStore, accountStore *AccountStore) *ConsensusManager {
func NewConsensusManager(sw *p2p.Switch, state *State, blockStore *BlockStore) *ConsensusManager {
swEvents := make(chan interface{})
sw.AddEventListener("ConsensusManager.swEvents", swEvents)
csc.Update(blockStore) // Update csc with new blocks.
cs := NewConsensusState(state)
cm := &ConsensusManager{
sw: sw,
swEvents: swEvents,
quit: make(chan struct{}),
csc: csc,
blockStore: blockStore,
accountStore: accountStore,
peerStates: make(map[string]*PeerState),
doActionCh: make(chan RoundAction, 1),
sw: sw,
swEvents: swEvents,
quit: make(chan struct{}),
cs: cs,
blockStore: blockStore,
doActionCh: make(chan RoundAction, 1),
state: state,
peerStates: make(map[string]*PeerState),
}
return cm
}
// Sets our private validator account for signing votes.
func (cm *ConsensusManager) SetPrivValidator(priv *PrivValidator) {
cm.mtx.Lock()
defer cm.mtx.Unlock()
cm.privValidator = priv
}
func (cm *ConsensusManager) Start() {
if atomic.CompareAndSwapUint32(&cm.started, 0, 1) {
log.Info("Starting ConsensusManager")
@@ -166,7 +179,7 @@ func (cm *ConsensusManager) switchEventsRoutine() {
// Like, how large is it and how often can we send it?
func (cm *ConsensusManager) makeKnownBlockPartsMessage() *KnownBlockPartsMessage {
rs := cm.csc.RoundState()
rs := cm.cs.RoundState()
return &KnownBlockPartsMessage{
Height: rs.Height,
SecondsSinceStartTime: uint32(time.Now().Sub(rs.StartTime).Seconds()),
@@ -188,7 +201,7 @@ func (cm *ConsensusManager) gossipProposalRoutine() {
OUTER_LOOP:
for {
// Get round state
rs := cm.csc.RoundState()
rs := cm.cs.RoundState()
// Receive incoming message on ProposalCh
inMsg, ok := cm.sw.Receive(ProposalCh)
@@ -282,9 +295,8 @@ OUTER_LOOP:
// Signs a vote document and broadcasts it.
// hash can be nil to vote "nil"
func (cm *ConsensusManager) signAndVote(vote *Vote) error {
privValidator := cm.csc.PrivValidator()
if privValidator != nil {
err := privValidator.SignVote(vote)
if cm.privValidator != nil {
err := cm.privValidator.SignVote(vote)
if err != nil {
return err
}
@@ -294,15 +306,43 @@ func (cm *ConsensusManager) signAndVote(vote *Vote) error {
return nil
}
func (cm *ConsensusManager) isProposalValid(rs *RoundState) bool {
if !rs.BlockPartSet.IsComplete() {
return false
func (cm *ConsensusManager) stageProposal(proposal *BlockPartSet) error {
// Already staged?
cm.mtx.Lock()
if cm.stagedProposal == proposal {
cm.mtx.Unlock()
return nil
} else {
cm.mtx.Unlock()
}
err := cm.stageBlock(rs.BlockPartSet)
// Basic validation
if !proposal.IsComplete() {
return errors.New("Incomplete proposal BlockPartSet")
}
block, blockParts := blockPartSet.Block(), blockPartSet.BlockParts()
err := block.ValidateBasic()
if err != nil {
return false
return err
}
return true
// Create a copy of the state for staging
cm.mtx.Lock()
stateCopy := cm.state.Copy() // Deep copy the state before staging.
cm.mtx.Unlock()
// Commit block onto the copied state.
err := stateCopy.CommitBlock(block, block.Header.Time) // NOTE: fake commit time.
if err != nil {
return err
}
// Looks good!
cm.mtx.Lock()
cm.stagedProposal = proposal
cm.stagedState = state
cm.mtx.Unlock()
return nil
}
func (cm *ConsensusManager) constructProposal(rs *RoundState) (*Block, error) {
@@ -315,7 +355,7 @@ func (cm *ConsensusManager) constructProposal(rs *RoundState) (*Block, error) {
// We may not have received a full proposal.
func (cm *ConsensusManager) voteProposal(rs *RoundState) error {
// If we're locked, must vote that.
locked := cm.csc.LockedProposal()
locked := cm.cs.LockedProposal()
if locked != nil {
block := locked.Block()
err := cm.signAndVote(&Vote{
@@ -326,9 +366,10 @@ func (cm *ConsensusManager) voteProposal(rs *RoundState) error {
})
return err
}
// If proposal is invalid
if !cm.isProposalValid(rs) {
// Vote for nil.
// Stage proposal
err := cm.stageProposal(rs.BlockPartSet)
if err != nil {
// Vote for nil, whatever the error.
err := cm.signAndVote(&Vote{
Height: rs.Height,
Round: rs.Round,
@@ -361,13 +402,13 @@ func (cm *ConsensusManager) precommitProposal(rs *RoundState) error {
// If proposal is invalid or unknown, do nothing.
// See note on ZombieValidators to see why.
if !cm.isProposalValid(rs) {
if cm.stageProposal(rs.BlockPartSet) != nil {
return nil
}
// Lock this proposal.
// NOTE: we're unlocking any prior locks.
cm.csc.LockProposal(rs.BlockPartSet)
cm.cs.LockProposal(rs.BlockPartSet)
// Send precommit vote.
err := cm.signAndVote(&Vote{
@@ -395,11 +436,11 @@ func (cm *ConsensusManager) commitOrUnlockProposal(rs *RoundState) error {
// do not commit.
// TODO If we were just late to receive the block, when
// do we actually get it? Document it.
if !cm.isProposalValid(rs) {
if cm.stageProposal(rs.BlockPartSet) != nil {
return nil
}
// TODO: Remove?
cm.csc.LockProposal(rs.BlockPartSet)
cm.cs.LockProposal(rs.BlockPartSet)
// Vote commit.
err := cm.signAndVote(&Vote{
Height: rs.Height,
@@ -416,11 +457,11 @@ func (cm *ConsensusManager) commitOrUnlockProposal(rs *RoundState) error {
// time differences between nodes, so nodes end up drifting
// in time.
commitTime := time.Now()
cm.commitBlock(rs.BlockPartSet, commitTime)
cm.commitProposal(rs.BlockPartSet, commitTime)
return nil
} else {
// Otherwise, if a 1/3 majority if a block that isn't our locked one exists, unlock.
locked := cm.csc.LockedProposal()
locked := cm.cs.LockedProposal()
if locked != nil {
for _, hashOrNil := range rs.RoundPrecommits.OneThirdMajority() {
if hashOrNil == nil {
@@ -429,7 +470,7 @@ func (cm *ConsensusManager) commitOrUnlockProposal(rs *RoundState) error {
hash := hashOrNil.([]byte)
if !bytes.Equal(hash, locked.Block().Hash()) {
// Unlock our lock.
cm.csc.LockProposal(nil)
cm.cs.LockProposal(nil)
}
}
}
@@ -437,52 +478,27 @@ func (cm *ConsensusManager) commitOrUnlockProposal(rs *RoundState) error {
}
}
// After stageBlock(), a call to commitBlock() with the same arguments must succeed.
func (cm *ConsensusManager) stageBlock(blockPartSet *BlockPartSet) error {
func (cm *ConsensusManager) commitProposal(blockPartSet *BlockPartSet, commitTime time.Time) error {
cm.mtx.Lock()
defer cm.mtx.Unlock()
if cm.stagedProposal != blockPartSet {
panic("Unexpected stagedProposal.") // Shouldn't happen.
}
// Save to blockStore
block, blockParts := blockPartSet.Block(), blockPartSet.BlockParts()
err := block.ValidateBasic()
if err != nil {
return err
}
err = cm.blockStore.StageBlockAndParts(block, blockParts)
if err != nil {
return err
}
err = cm.csc.StageBlock(block)
if err != nil {
return err
}
err = cm.accountStore.StageBlock(block)
if err != nil {
return err
}
// NOTE: more stores may be added here for validation.
return nil
}
// after stageBlock(), a call to commitBlock() with the same arguments must succeed.
func (cm *ConsensusManager) commitBlock(blockPartSet *BlockPartSet, commitTime time.Time) error {
cm.mtx.Lock()
defer cm.mtx.Unlock()
block, blockParts := blockPartSet.Block(), blockPartSet.BlockParts()
err := cm.blockStore.SaveBlockParts(block.Height, blockParts)
if err != nil {
return err
}
err = cm.csc.CommitBlock(block, commitTime)
if err != nil {
return err
}
err = cm.accountStore.CommitBlock(block)
if err != nil {
return err
}
// What was staged becomes committed.
cm.state = cm.stagedState
cm.cs.Update(cm.state)
cm.stagedProposal = nil
cm.stagedState = nil
return nil
}
@@ -490,7 +506,7 @@ func (cm *ConsensusManager) gossipVoteRoutine() {
OUTER_LOOP:
for {
// Get round state
rs := cm.csc.RoundState()
rs := cm.cs.RoundState()
// Receive incoming message on VoteCh
inMsg, ok := cm.sw.Receive(VoteCh)
@@ -569,7 +585,7 @@ func (cm *ConsensusManager) proposeAndVoteRoutine() {
// Figure out which height/round/step we're at,
// then schedule an action for when it is due.
rs := cm.csc.RoundState()
rs := cm.cs.RoundState()
_, _, roundDuration, _, elapsedRatio := calcRoundInfo(rs.StartTime)
switch rs.Step() {
case RoundStepStart:
@@ -607,15 +623,14 @@ func (cm *ConsensusManager) proposeAndVoteRoutine() {
// We only consider transitioning to given step.
step := roundAction.XnToStep
// This is the current state.
rs := cm.csc.RoundState()
rs := cm.cs.RoundState()
if height != rs.Height || round != rs.Round {
return // Not relevant.
}
if step == RoundStepProposal && rs.Step() == RoundStepStart {
// Propose a block if I am the proposer.
privValidator := cm.csc.PrivValidator()
if privValidator != nil && rs.Proposer.Account.Id == privValidator.Id {
if cm.privValidator != nil && rs.Proposer.Account.Id == cm.privValidator.Id {
block, err := cm.constructProposal(rs)
if err != nil {
log.Error("Error attempting to construct a proposal: %v", err)
@@ -644,7 +659,7 @@ func (cm *ConsensusManager) proposeAndVoteRoutine() {
}
// Round is over. This is a special case.
// Prepare a new RoundState for the next state.
cm.csc.SetupRound(rs.Round + 1)
cm.cs.SetupRound(rs.Round + 1)
return // setAlarm() takes care of the rest.
} else {
return // Action is not relevant.
+54 -229
View File
@@ -5,288 +5,113 @@ import (
"sync"
"time"
. "github.com/tendermint/tendermint/accounts"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
. "github.com/tendermint/tendermint/common"
db_ "github.com/tendermint/tendermint/db"
. "github.com/tendermint/tendermint/state"
)
var (
consensusStateKey = []byte("consensusState")
)
/*
Determining the order of proposers at height h:
A B C All validators A, B, and C
[+10, +5, +2] (+17) Voting power
[ 0, 0, 0] Genesis?
[ 10, 5, 2] (+17)
A [ -7, 5, 2] (-17) Round 0 proposer: A
[ 3, 10, 4] (+17)
B [ 3, -7, 4] (-17) Round 1 proposer: B
[ 13, -2, 6] (+17)
A [ -4, -2, 6] (-17) Round 2 proposer: A
[ 6, 3, 8] (+17)
C [ 6, 3, -9] (-17) Round 3 proposer: C
[ 16, 8, -7] (+17)
A [ -1, 8, -7] (-17) Round 4 proposer: A
[ 9, 13, -5] (+17)
B [ 9, -4, -5] (-17) Round 5 proposer: B
[ 19, 1, -3] (+17)
A [ 2, 1, -3] (-17) Round 6 proposer: A
........... ...
For a node, once consensus has been reached at some round R,
the moment the node sees +2/3 in votes for a proposal is when
the consensus rounds for the *next* height h+1 begins.
Round R+1 in the consensus rounds at height h+1 is the same as
round R in the consensus rounds at height h (the parent block).
We omit details of dealing with membership changes.
*/
func getProposer(validators map[uint64]*Validator) (proposer *Validator) {
highestAccum := int64(0)
for _, validator := range validators {
if validator.Accum > highestAccum {
highestAccum = validator.Accum
proposer = validator
} else if validator.Accum == highestAccum {
if validator.Id < proposer.Id { // Seniority
proposer = validator
}
}
}
return
}
func incrementAccum(validators map[uint64]*Validator) {
totalDelta := int64(0)
for _, validator := range validators {
validator.Accum += int64(validator.VotingPower)
totalDelta += int64(validator.VotingPower)
}
proposer := getProposer(validators)
proposer.Accum -= totalDelta
// NOTE: sum(validators) here should be zero.
if true {
totalAccum := int64(0)
for _, validator := range validators {
totalAccum += validator.Accum
}
if totalAccum != 0 {
Panicf("Total Accum of validators did not equal 0. Got: ", totalAccum)
}
}
}
// Creates a deep copy of validators.
// Caller can then modify the resulting validators' .Accum field without
// modifying the original *Validator's.
func copyValidators(validators map[uint64]*Validator) map[uint64]*Validator {
mapCopy := map[uint64]*Validator{}
for _, val := range validators {
mapCopy[val.Id] = val.Copy()
}
return mapCopy
}
//-----------------------------------------------------------------------------
// Handles consensus state tracking across block heights.
// NOTE: When adding more fields, also reset it in Load() and CommitBlock()
type ConsensusStateControl struct {
// Tracks consensus state across block heights and rounds.
type ConsensusState struct {
mtx sync.Mutex
db db_.Db // Where we store the validators list & other data.
validatorsR0 map[uint64]*Validator // A copy of the validators at round 0
privValidator *PrivValidator // PrivValidator used to participate, if present.
accountStore *AccountStore // Account storage
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.
roundState *RoundState // The RoundState object for the current round.
commits *VoteSet // Commits for this height.
roundState *RoundState // The RoundState object for the current round.
}
func NewConsensusStateControl(db db_.Db, accountStore *AccountStore) *ConsensusStateControl {
csc := &ConsensusStateControl{
db: db,
accountStore: accountStore,
}
csc.Load()
return csc
func NewConsensusState(state *State) *ConsensusState {
cs := &ConsensusState{}
cs.Update(state)
return cs
}
// Load the current state from db.
func (csc *ConsensusStateControl) Load() {
csc.mtx.Lock()
defer csc.mtx.Unlock()
buf := csc.db.Get(consensusStateKey)
if len(buf) == 0 {
height := uint32(0)
validators := make(map[uint64]*Validator) // XXX BOOTSTRAP
startTime := time.Now() // XXX BOOTSTRAP
csc.setupHeight(height, validators, startTime)
} else {
reader := bytes.NewReader(buf)
height := Readuint32(reader)
validators := make(map[uint64]*Validator)
startTime := ReadTime(reader)
for reader.Len() > 0 {
validator := ReadValidator(reader)
validators[validator.Id] = validator
}
csc.setupHeight(height, validators, startTime.Time)
}
func (cs *ConsensusState) LockProposal(blockPartSet *BlockPartSet) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
cs.lockedProposal = blockPartSet
}
// Save the current state onto db.
// Doesn't save the round state, just initial state at round 0.
func (csc *ConsensusStateControl) Save() {
csc.mtx.Lock()
defer csc.mtx.Unlock()
var buf bytes.Buffer
UInt32(csc.height).WriteTo(&buf)
Time{csc.startTime}.WriteTo(&buf)
for _, validator := range csc.validatorsR0 {
validator.WriteTo(&buf)
}
csc.db.Set(consensusStateKey, buf.Bytes())
func (cs *ConsensusState) UnlockProposal() {
cs.mtx.Lock()
defer cs.mtx.Unlock()
cs.lockedProposal = nil
}
// Finds more blocks from blockStore and commits them.
func (csc *ConsensusStateControl) Update(blockStore *BlockStore) {
csc.mtx.Lock()
defer csc.mtx.Unlock()
for h := csc.height + 1; h <= blockStore.Height(); h++ {
block := blockStore.LoadBlock(h)
// TODO: would be better to be able to override
// the block commit time, but in the meantime,
// just use the block time as proposed by the proposer.
csc.CommitBlock(block, block.Header.Time.Time)
}
func (cs *ConsensusState) LockedProposal() *BlockPartSet {
cs.mtx.Lock()
defer cs.mtx.Unlock()
return cs.lockedProposal
}
func (csc *ConsensusStateControl) PrivValidator() *PrivValidator {
csc.mtx.Lock()
defer csc.mtx.Unlock()
return csc.privValidator
func (cs *ConsensusState) RoundState() *RoundState {
cs.mtx.Lock()
defer cs.mtx.Unlock()
return cs.roundState
}
func (csc *ConsensusStateControl) SetPrivValidator(privValidator *PrivValidator) error {
csc.mtx.Lock()
defer csc.mtx.Unlock()
if csc.privValidator != nil {
panic("ConsensusStateControl privValidator already set.")
}
csc.privValidator = privValidator
return nil
}
func (cs *ConsensusState) Update(state *State) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
// Set blockPartSet to nil to unlock.
func (csc *ConsensusStateControl) LockProposal(blockPartSet *BlockPartSet) {
csc.mtx.Lock()
defer csc.mtx.Unlock()
csc.lockedProposal = blockPartSet
}
func (csc *ConsensusStateControl) LockedProposal() *BlockPartSet {
csc.mtx.Lock()
defer csc.mtx.Unlock()
return csc.lockedProposal
}
func (csc *ConsensusStateControl) StageBlock(block *Block) error {
// XXX implement staging.
return nil
}
// NOTE: assumes that block is valid.
// NOTE: the block should be saved on the BlockStore before commiting here.
// commitTime is usually set to the system clock time (time.Now()).
func (csc *ConsensusStateControl) CommitBlock(block *Block, commitTime time.Time) error {
csc.mtx.Lock()
defer csc.mtx.Unlock()
// Ensure that block is the next block needed.
if block.Height != csc.height {
return Errorf("Cannot commit block %v to csc. Expected height %v", block, csc.height+1)
}
// Update validator.
validators := copyValidators(csc.validatorsR0)
incrementAccum(validators)
// TODO if there are new validators in the block, add them.
// XXX: it's not commitTime we want...
csc.setupHeight(block.Height+1, validators, commitTime)
// Save the state.
csc.Save()
return nil
}
func (csc *ConsensusStateControl) RoundState() *RoundState {
csc.mtx.Lock()
defer csc.mtx.Unlock()
return csc.roundState
}
func (csc *ConsensusStateControl) setupHeight(height uint32, validators map[uint64]*Validator, startTime time.Time) {
if height > 0 && height != csc.height+1 {
panic("setupHeight() cannot skip heights")
// Sanity check state.
stateHeight := state.Height()
if stateHeight > 0 && stateHeight != cs.height+1 {
Panicf("Update() expected state height of %v but found %v", cs.height+1, stateHeight)
}
// Reset the state for the next height.
csc.validatorsR0 = validators
csc.height = height
csc.lockedProposal = nil
csc.startTime = startTime
csc.commits = NewVoteSet(height, 0, VoteTypeCommit, validators)
// Reset fields based on 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.commits = NewVoteSet(stateHeight, 0, VoteTypeCommit, cs.validatorsR0)
// Setup the roundState
csc.roundState = nil
csc.setupRound(0)
cs.roundState = nil
cs.setupRound(0)
}
// If csc.roundSTate isn't at round, set up new roundState at round.
func (csc *ConsensusStateControl) SetupRound(round uint16) {
csc.mtx.Lock()
defer csc.mtx.Unlock()
if csc.roundState != nil && csc.roundState.Round >= round {
// If cs.roundSTate isn't at round, set up new roundState at round.
func (cs *ConsensusState) SetupRound(round uint16) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.roundState != nil && cs.roundState.Round >= round {
return
}
csc.setupRound(round)
cs.setupRound(round)
}
// Initialize roundState for given round.
// Involves incrementing validators for each past rand.
func (csc *ConsensusStateControl) setupRound(round uint16) {
func (cs *ConsensusState) setupRound(round uint16) {
// Increment validator accums as necessary.
// We need to start with csc.validatorsR0 or csc.roundState.Validators
// We need to start with cs.validatorsR0 or cs.roundState.Validators
var validators map[uint64]*Validator = nil
var validatorsRound uint16
if csc.roundState == nil {
if cs.roundState == nil {
// We have no roundState so we start from validatorsR0 at round 0.
validators = copyValidators(csc.validatorsR0)
validators = copyValidators(cs.validatorsR0)
validatorsRound = 0
} else {
// We have a previous roundState so we start from that.
validators = copyValidators(csc.roundState.Validators)
validatorsRound = csc.roundState.Round
validators = copyValidators(cs.roundState.Validators)
validatorsRound = cs.roundState.Round
}
// Increment all the way to round.
for r := validatorsRound; r < round; r++ {
incrementAccum(validators)
}
roundState := NewRoundState(csc.height, round, csc.startTime, validators, csc.commits)
csc.roundState = roundState
roundState := NewRoundState(cs.height, round, cs.startTime, validators, cs.commits)
cs.roundState = roundState
}
//-----------------------------------------------------------------------------
-66
View File
@@ -1,66 +0,0 @@
package consensus
import (
"io"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
//. "github.com/tendermint/tendermint/common"
db_ "github.com/tendermint/tendermint/db"
)
// Holds state for a Validator at a given height+round.
// Meant to be discarded every round of the consensus protocol.
type Validator struct {
Account
BondHeight uint32
VotingPower uint64
Accum int64
}
// Used to persist the state of ConsensusStateControl.
func ReadValidator(r io.Reader) *Validator {
return &Validator{
Account: Account{
Id: Readuint64(r),
PubKey: ReadByteSlice(r),
},
BondHeight: Readuint32(r),
VotingPower: Readuint64(r),
Accum: Readint64(r),
}
}
// Creates a new copy of the validator so we can mutate accum.
func (v *Validator) Copy() *Validator {
return &Validator{
Account: v.Account,
BondHeight: v.BondHeight,
VotingPower: v.VotingPower,
Accum: v.Accum,
}
}
// Used to persist the state of ConsensusStateControl.
func (v *Validator) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteTo(UInt64(v.Id), w, n, err)
n, err = WriteTo(v.PubKey, w, n, err)
n, err = WriteTo(UInt32(v.BondHeight), w, n, err)
n, err = WriteTo(UInt64(v.VotingPower), w, n, err)
n, err = WriteTo(Int64(v.Accum), w, n, err)
return
}
//-----------------------------------------------------------------------------
// 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
}
-207
View File
@@ -1,207 +0,0 @@
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: Readuint32(r),
Round: Readuint16(r),
Type: 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 += 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[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
}