wordings and clarifications, unnecessary code uncommenting

This commit is contained in:
Jae Kwon 2015-06-24 17:05:52 -07:00
parent 4d5fda7516
commit d05276ee87
2 changed files with 90 additions and 76 deletions

View File

@ -18,7 +18,8 @@ type RoundVoteSet struct {
Keeps track of all VoteSets from round 0 to round 'round'.
Also keeps track of up to one RoundVoteSet greater than
'round' from each peer, to facilitate fast-forward syncing.
'round' from each peer, to facilitate catchup syncing of commits.
A commit is +2/3 precommits for a block at a round,
but which round is not known in advance, so when a peer
provides a precommit for a round greater than mtx.round,
@ -32,7 +33,7 @@ type HeightVoteSet struct {
mtx sync.Mutex
round uint // max tracked round
roundVoteSets map[uint]RoundVoteSet // keys: [0...round]
peerFastForward map[string]uint // keys: peer.Key; values: round
peerCatchupRounds map[string]uint // keys: peer.Key; values: round
}
func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet {
@ -40,7 +41,7 @@ func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet {
height: height,
valSet: valSet,
roundVoteSets: make(map[uint]RoundVoteSet),
peerFastForward: make(map[string]uint),
peerCatchupRounds: make(map[string]uint),
}
hvs.addRound(0)
hvs.round = 0
@ -66,7 +67,7 @@ func (hvs *HeightVoteSet) SetRound(round uint) {
}
for r := hvs.round + 1; r <= round; r++ {
if _, ok := hvs.roundVoteSets[r]; ok {
continue // Already exists because peerFastForward.
continue // Already exists because peerCatchupRounds.
}
hvs.addRound(round)
}
@ -92,9 +93,9 @@ func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peerKey
defer hvs.mtx.Unlock()
voteSet := hvs.getVoteSet(vote.Round, vote.Type)
if voteSet == nil {
if _, ok := hvs.peerFastForward[peerKey]; !ok {
if _, ok := hvs.peerCatchupRounds[peerKey]; !ok {
hvs.addRound(vote.Round)
hvs.peerFastForward[peerKey] = vote.Round
hvs.peerCatchupRounds[peerKey] = vote.Round
} else {
// Peer has sent a vote that does not match our round,
// for more than one round. Bad peer!
@ -160,7 +161,7 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string {
voteSetString = hvs.roundVoteSets[round].Precommits.StringShort()
vsStrings = append(vsStrings, voteSetString)
}
// all other peer fast-forward rounds
// all other peer catchup rounds
for round, roundVoteSet := range hvs.roundVoteSets {
if round <= hvs.round {
continue

View File

@ -2,19 +2,31 @@
Consensus State Machine Overview:
* NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep).
* To "prevote/precommit" something means to broadcast a prevote/precommit vote for something.
* During NewHeight/NewRound/Propose/Prevote/Precommit:
NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep).
To "prevote/precommit" something means to broadcast a prevote/precommit vote for something.
During NewHeight/NewRound/Propose/Prevote/Precommit:
* Nodes gossip the proposal block proposed by the designated proposer for that round.
* Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 for catch-up)
* Nodes also gossip prevotes for the proposal's POL (proof-of-lock) round if proposed.
* Upon each state transition, the height/round/step is broadcast to neighboring peers.
* The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation.
* A block contains the last block's Validation, which includes the Commit precommits.
* Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 to allow round-skipping)
* Nodes gossip prevotes for the proposal's POL (proof-of-lock) round if proposed.
* Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup)
Upon each state transition, the height/round/step is broadcast to neighboring peers.
The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation.
A block contains the last block's Validation, which includes the Commit precommits.
While all the precommits in the Validation are from the same height & round (ordered by validator index),
some precommits may be nil (if the validator's precommit vote didn't reach the proposer in time),
some precommits may be <nil> (if the validator's precommit vote didn't reach the proposer in time),
or some precommits may be for different blockhashes for the last block hash (which is fine).
Each unlock/change-of-lock should be justifiable by an POL where +2/3 prevoted for
some block or <nil> at some round.
POL = Proof-of-Lock = +2/3 prevotes for block B or <nil> for (H,R)
lockRound < POLRound <= unlockOrChangeLockRound
* NewRound(height:H,round:R):
* Set up new round. --> goto Propose(H,R)
* NOTE: Not much happens in this step. It exists for clarity.
@ -273,15 +285,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) {
lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators)
seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight)
for idx, precommit := range seenValidation.Precommits {
precommitVote := &types.Vote{
Height: state.LastBlockHeight,
Round: seenValidation.Round(),
Type: types.VoteTypePrecommit,
BlockHash: state.LastBlockHash,
BlockParts: state.LastBlockParts,
Signature: precommit.Signature,
}
added, _, err := lastPrecommits.AddByIndex(uint(idx), precommitVote)
added, _, err := lastPrecommits.AddByIndex(uint(idx), precommit)
if !added || err != nil {
panic(Fmt("Failed to reconstruct LastCommit: %v", err))
}
@ -320,11 +324,12 @@ func (cs *ConsensusState) Start() {
}
}
// EnterNewRound(height, 0) at cs.StartTime.
func (cs *ConsensusState) scheduleRound0(height uint) {
log.Debug("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime)
//log.Debug("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime)
sleepDuration := cs.StartTime.Sub(time.Now())
go func() {
if sleepDuration > 0 {
if 0 < sleepDuration {
time.Sleep(sleepDuration)
}
cs.EnterNewRound(height, 0)
@ -346,7 +351,7 @@ func (cs *ConsensusState) IsStopped() bool {
// The round becomes 0 and cs.Step becomes RoundStepNewHeight.
func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) {
// SANITY CHECK
if contiguous && cs.Height > 0 && cs.Height != state.LastBlockHeight {
if contiguous && 0 < cs.Height && cs.Height != state.LastBlockHeight {
panic(Fmt("updateToState() expected state height of %v but found %v",
cs.Height, state.LastBlockHeight))
}
@ -357,6 +362,9 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) {
height := state.LastBlockHeight + 1 // next desired block height
lastPrecommits := (*VoteSet)(nil)
if contiguous && cs.Votes != nil {
if !cs.Votes.Precommits(cs.Round).HasTwoThirdsMajority() {
panic("updateToState(state, true) called but last Precommit round didn't have +2/3")
}
lastPrecommits = cs.Votes.Precommits(cs.Round)
}
@ -424,9 +432,9 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) {
//-----------------------------------------------------------------------------
// Enter: +2/3 precommits for nil from previous round
// Enter: `timeoutPrecommits` after any +2/3 precommits
// Enter: `commitTime+timeoutCommit` from NewHeight
// Enter: +2/3 precommits for nil from (height,round-1)
// Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1)
// Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height)
// NOTE: cs.StartTime was already set for height.
func (cs *ConsensusState) EnterNewRound(height uint, round uint) {
cs.mtx.Lock()
@ -453,17 +461,17 @@ func (cs *ConsensusState) EnterNewRound(height uint, round uint) {
cs.Proposal = nil
cs.ProposalBlock = nil
cs.ProposalBlockParts = nil
cs.Votes.SetRound(round + 1) // track next round.
cs.Votes.SetRound(round + 1) // also track next round (round+1) to allow round-skipping
// Immediately go to EnterPropose.
go cs.EnterPropose(height, round)
}
// Enter: from NewRound.
// Enter: from NewRound(height,round).
func (cs *ConsensusState) EnterPropose(height uint, round uint) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPropose) {
if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPropose <= cs.Step) {
log.Debug(Fmt("EnterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return
}
@ -486,21 +494,21 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) {
cs.EnterPrevote(height, round)
}()
// Nothing to do if it's not our turn.
// Nothing more to do if we're not a validator
if cs.privValidator == nil {
return
}
// See if it is our turn to propose
if !bytes.Equal(cs.Validators.Proposer().Address, cs.privValidator.Address) {
log.Debug("EnterPropose: Not our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator)
return
} else {
log.Debug("EnterPropose: Our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator)
cs.decideProposal(height, round)
}
}
// We are going to propose a block.
// Decides on the next proposal and sets them onto cs.Proposal*
func (cs *ConsensusState) decideProposal(height uint, round uint) {
var block *types.Block
var blockParts *types.PartSet
@ -514,21 +522,23 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) {
}
// Make proposal
proposal := NewProposal(cs.Height, cs.Round, blockParts.Header(), cs.Votes.POLRound())
proposal := NewProposal(height, round, blockParts.Header(), cs.Votes.POLRound())
err := cs.privValidator.SignProposal(cs.state.ChainID, proposal)
if err == nil {
log.Info("Signed and set proposal", "height", cs.Height, "round", cs.Round, "proposal", proposal)
log.Info("Signed and set proposal", "height", height, "round", round, "proposal", proposal)
log.Debug(Fmt("Signed and set proposal block: %v", block))
// Set fields
cs.Proposal = proposal
cs.ProposalBlock = block
cs.ProposalBlockParts = blockParts
} else {
log.Warn("EnterPropose: Error signing proposal", "height", cs.Height, "round", cs.Round, "error", err)
log.Warn("EnterPropose: Error signing proposal", "height", height, "round", round, "error", err)
}
}
// Returns true if the proposal block is complete &&
// (if POLRound was proposed, we have +2/3 prevotes from there).
func (cs *ConsensusState) isProposalComplete() bool {
if cs.Proposal == nil || cs.ProposalBlock == nil {
return false
@ -585,32 +595,30 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
return block, blockParts
}
// Enter: `timeoutPropose` after start of Propose.
// Enter: `timeoutPropose` after entering Propose.
// Enter: proposal block and POL is ready.
// Enter: any +2/3 prevotes for next round.
// Prevote for LockedBlock if we're locked, or ProposealBlock if valid.
// Enter: any +2/3 prevotes for future round.
// Prevote for LockedBlock if we're locked, or ProposalBlock if valid.
// Otherwise vote nil.
func (cs *ConsensusState) EnterPrevote(height uint, round uint) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrevote) {
if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevote <= cs.Step) {
log.Debug(Fmt("EnterPrevote(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return
}
defer func() {
// Sign and broadcast vote as necessary
cs.doPrevote(height, round)
// Done EnterPrevote:
cs.Round = round
cs.Step = RoundStepPrevote
cs.newStepCh <- cs.getRoundState()
// Maybe immediately go to EnterPrevoteWait.
/* This isn't necessary because addVote() does it for us.
if cs.Votes.Prevotes(round).HasTwoThirdsAny() {
go cs.EnterPrevoteWait(height, round)
}
}()
// Sign and broadcast vote as necessary
cs.doPrevote(height, round)
}*/
}
func (cs *ConsensusState) doPrevote(height uint, round uint) {
@ -646,7 +654,7 @@ func (cs *ConsensusState) doPrevote(height uint, round uint) {
func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrevoteWait) {
if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevoteWait <= cs.Step) {
log.Debug(Fmt("EnterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return
}
@ -675,7 +683,7 @@ func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) {
func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrecommit) {
if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommit <= cs.Step) {
log.Debug(Fmt("EnterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return
}
@ -685,10 +693,10 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
cs.Round = round
cs.Step = RoundStepPrecommit
cs.newStepCh <- cs.getRoundState()
// Maybe immediately go to EnterPrecommitWait.
/* This isn't necessary because addVote() does it for us.
if cs.Votes.Precommits(round).HasTwoThirdsAny() {
go cs.EnterPrecommitWait(height, round)
}
}*/
}()
hash, partsHeader, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
@ -741,7 +749,11 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
}
// Otherwise, we need to fetch the +2/3 prevoted block.
// We don't have the block yet so we can't lock/precommit it.
// Unlock and precommit nil.
// The +2/3 prevotes for this round is the POL for our unlock.
if cs.Votes.POLRound() < round {
panic(Fmt("This POLRound shold be %v but got %", round, cs.Votes.POLRound()))
}
cs.LockedBlock = nil
cs.LockedBlockParts = nil
if !cs.ProposalBlockParts.HasHeader(partsHeader) {
@ -756,7 +768,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrecommitWait) {
if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommitWait <= cs.Step) {
log.Debug(Fmt("EnterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return
}
@ -784,7 +796,7 @@ func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) {
func (cs *ConsensusState) EnterCommit(height uint) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Step >= RoundStepCommit {
if cs.Height != height || RoundStepCommit <= cs.Step {
log.Debug(Fmt("EnterCommit(%v): Invalid args. Current step: %v/%v/%v", height, cs.Height, cs.Round, cs.Step))
return
}
@ -912,7 +924,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error {
}
// We don't care about the proposal if we're already in RoundStepCommit.
if cs.Step >= RoundStepCommit {
if RoundStepCommit <= cs.Step {
return nil
}
@ -959,6 +971,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty
if cs.Step == RoundStepPropose && cs.isProposalComplete() {
go cs.EnterPrevote(height, round)
} else if cs.Step == RoundStepCommit {
/// XXX How about, EnterCommit()?
cs.tryFinalizeCommit(height)
}
return true, err
@ -1011,7 +1024,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri
cs.EnterPrevoteWait(height, cs.Round)
}()
}
} else if cs.Proposal != nil && cs.Proposal.POLRound >= 0 && uint(cs.Proposal.POLRound) == vote.Round {
} else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && uint(cs.Proposal.POLRound) == vote.Round {
if cs.isProposalComplete() {
go cs.EnterPrevote(height, cs.Round)
}