Unlocking from POL complete

This commit is contained in:
Jae Kwon 2015-06-24 18:51:14 -07:00
parent d05276ee87
commit 44216ab481
1 changed files with 71 additions and 77 deletions

View File

@ -7,8 +7,8 @@
To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. To "prevote/precommit" something means to broadcast a prevote/precommit vote for something.
During NewHeight/NewRound/Propose/Prevote/Precommit: During NewHeight/NewRound/Propose/Prevote/Precommit:
* Nodes gossip the proposal block proposed by the designated proposer for that round. * Nodes gossip the proposal block proposed by the designated proposer at round.
* Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 to allow round-skipping) * Nodes gossip prevotes/precommits at 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 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) * Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup)
@ -24,7 +24,7 @@
Each unlock/change-of-lock should be justifiable by an POL where +2/3 prevoted for Each unlock/change-of-lock should be justifiable by an POL where +2/3 prevoted for
some block or <nil> at some round. some block or <nil> at some round.
POL = Proof-of-Lock = +2/3 prevotes for block B or <nil> for (H,R) POL = Proof-of-Lock = +2/3 prevotes for block B (or +2/3 prevotes for <nil>) at (H,R)
lockRound < POLRound <= unlockOrChangeLockRound lockRound < POLRound <= unlockOrChangeLockRound
* NewRound(height:H,round:R): * NewRound(height:H,round:R):
@ -33,12 +33,12 @@
* Propose(height:H,round:R): * Propose(height:H,round:R):
* Upon entering Propose: * Upon entering Propose:
* The designated proposer proposes a block for (H,R). * The designated proposer proposes a block at (H,R).
* The Propose step ends: * The Propose step ends:
* After `timeoutPropose` after entering Propose. --> goto Prevote(H,R) * After `timeoutPropose` after entering Propose. --> goto Prevote(H,R)
* After receiving proposal block and all POL prevotes. --> goto Prevote(H,R) * After receiving proposal block and all POL prevotes. --> goto Prevote(H,R)
* After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) * After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1)
* After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) * After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1)
* After +2/3 precommits received for a particular block. --> goto Commit(H) * After +2/3 precommits received for a particular block. --> goto Commit(H)
* Prevote(height:H,round:R): * Prevote(height:H,round:R):
@ -49,8 +49,8 @@
* The Prevote step ends: * The Prevote step ends:
* After +2/3 prevotes for a particular block or <nil>. --> goto Precommit(H,R) * After +2/3 prevotes for a particular block or <nil>. --> goto Precommit(H,R)
* After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto Precommit(H,R) * After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto Precommit(H,R)
* After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) * After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1)
* After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) * After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1)
* After +2/3 precommits received for a particular block. --> goto Commit(H) * After +2/3 precommits received for a particular block. --> goto Commit(H)
* Precommit(height:H,round:R): * Precommit(height:H,round:R):
@ -64,8 +64,8 @@
* After +2/3 precommits for a particular block. --> goto Commit(H) * After +2/3 precommits for a particular block. --> goto Commit(H)
* After +2/3 precommits for <nil>. --> goto NewRound(H,R+1) * After +2/3 precommits for <nil>. --> goto NewRound(H,R+1)
* After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto NewRound(H,R+1) * After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto NewRound(H,R+1)
* After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) * After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1)
* After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) * After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1)
* Commit(height:H): * Commit(height:H):
* Set CommitTime = now * Set CommitTime = now
@ -77,7 +77,7 @@
* Wait until `StartTime` to receive straggler commits. --> goto NewRound(H,0) * Wait until `StartTime` to receive straggler commits. --> goto NewRound(H,0)
* Proof of Safety: * Proof of Safety:
If a good validator commits at round R, it's because it saw +2/3 of precommits for round R. If a good validator commits at round R, it's because it saw +2/3 of precommits at round R.
This implies that (assuming tolerance bounds) +1/3 of honest nodes are still locked at round R+1. This implies that (assuming tolerance bounds) +1/3 of honest nodes are still locked at round R+1.
These locked validators will remain locked until they see +2/3 prevote for something These locked validators will remain locked until they see +2/3 prevote for something
else, but this won't happen because +1/3 are locked and honest. else, but this won't happen because +1/3 are locked and honest.
@ -199,10 +199,11 @@ type RoundState struct {
Proposal *Proposal Proposal *Proposal
ProposalBlock *types.Block ProposalBlock *types.Block
ProposalBlockParts *types.PartSet ProposalBlockParts *types.PartSet
LockedRound uint
LockedBlock *types.Block LockedBlock *types.Block
LockedBlockParts *types.PartSet LockedBlockParts *types.PartSet
Votes *HeightVoteSet Votes *HeightVoteSet
LastCommit *VoteSet // Last precommits for Height-1 LastCommit *VoteSet // Last precommits at Height-1
} }
func (rs *RoundState) String() string { func (rs *RoundState) String() string {
@ -217,6 +218,7 @@ func (rs *RoundState) StringIndented(indent string) string {
%s Validators: %v %s Validators: %v
%s Proposal: %v %s Proposal: %v
%s ProposalBlock: %v %v %s ProposalBlock: %v %v
%s LockedRound: %v
%s LockedBlock: %v %v %s LockedBlock: %v %v
%s Votes: %v %s Votes: %v
%s LastCommit: %v %s LastCommit: %v
@ -227,6 +229,7 @@ func (rs *RoundState) StringIndented(indent string) string {
indent, rs.Validators.StringIndented(indent+" "), indent, rs.Validators.StringIndented(indent+" "),
indent, rs.Proposal, indent, rs.Proposal,
indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(), indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(),
indent, rs.LockedRound,
indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(),
indent, rs.Votes.StringIndented(indent+" "), indent, rs.Votes.StringIndented(indent+" "),
indent, rs.LastCommit.StringShort(), indent, rs.LastCommit.StringShort(),
@ -387,6 +390,7 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) {
cs.Proposal = nil cs.Proposal = nil
cs.ProposalBlock = nil cs.ProposalBlock = nil
cs.ProposalBlockParts = nil cs.ProposalBlockParts = nil
cs.LockedRound = 0
cs.LockedBlock = nil cs.LockedBlock = nil
cs.LockedBlockParts = nil cs.LockedBlockParts = nil
cs.Votes = NewHeightVoteSet(height, validators) cs.Votes = NewHeightVoteSet(height, validators)
@ -432,7 +436,7 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Enter: +2/3 precommits for nil from (height,round-1) // Enter: +2/3 precommits for nil at (height,round-1)
// Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1) // Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1)
// Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height) // Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height)
// NOTE: cs.StartTime was already set for height. // NOTE: cs.StartTime was already set for height.
@ -650,7 +654,7 @@ func (cs *ConsensusState) doPrevote(height uint, round uint) {
return return
} }
// Enter: any +2/3 prevotes for next round. // Enter: any +2/3 prevotes at next round.
func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) {
cs.mtx.Lock() cs.mtx.Lock()
defer cs.mtx.Unlock() defer cs.mtx.Unlock()
@ -719,6 +723,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
log.Info("EnterPrecommit: +2/3 prevoted for nil.") log.Info("EnterPrecommit: +2/3 prevoted for nil.")
} else { } else {
log.Info("EnterPrecommit: +2/3 prevoted for nil. Unlocking") log.Info("EnterPrecommit: +2/3 prevoted for nil. Unlocking")
cs.LockedRound = 0
cs.LockedBlock = nil cs.LockedBlock = nil
cs.LockedBlockParts = nil cs.LockedBlockParts = nil
} }
@ -742,6 +747,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
if err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts); err != nil { if err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts); err != nil {
panic(Fmt("EnterPrecommit: +2/3 prevoted for an invalid block: %v", err)) panic(Fmt("EnterPrecommit: +2/3 prevoted for an invalid block: %v", err))
} }
cs.LockedRound = round
cs.LockedBlock = cs.ProposalBlock cs.LockedBlock = cs.ProposalBlock
cs.LockedBlockParts = cs.ProposalBlockParts cs.LockedBlockParts = cs.ProposalBlockParts
cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader) cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader)
@ -751,9 +757,10 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
// Otherwise, we need to fetch the +2/3 prevoted block. // Otherwise, we need to fetch the +2/3 prevoted block.
// Unlock and precommit nil. // Unlock and precommit nil.
// The +2/3 prevotes for this round is the POL for our unlock. // The +2/3 prevotes for this round is the POL for our unlock.
if cs.Votes.POLRound() < round { if cs.Votes.POLRound() < int(round) {
panic(Fmt("This POLRound shold be %v but got %", round, cs.Votes.POLRound())) panic(Fmt("This POLRound shold be %v but got %", round, cs.Votes.POLRound()))
} }
cs.LockedRound = 0
cs.LockedBlock = nil cs.LockedBlock = nil
cs.LockedBlockParts = nil cs.LockedBlockParts = nil
if !cs.ProposalBlockParts.HasHeader(partsHeader) { if !cs.ProposalBlockParts.HasHeader(partsHeader) {
@ -824,9 +831,11 @@ func (cs *ConsensusState) EnterCommit(height uint) {
if cs.LockedBlock.HashesTo(hash) { if cs.LockedBlock.HashesTo(hash) {
cs.ProposalBlock = cs.LockedBlock cs.ProposalBlock = cs.LockedBlock
cs.ProposalBlockParts = cs.LockedBlockParts cs.ProposalBlockParts = cs.LockedBlockParts
cs.LockedRound = 0
cs.LockedBlock = nil cs.LockedBlock = nil
cs.LockedBlockParts = nil cs.LockedBlockParts = nil
} else { } else {
cs.LockedRound = 0
cs.LockedBlock = nil cs.LockedBlock = nil
cs.LockedBlockParts = nil cs.LockedBlockParts = nil
} }
@ -854,10 +863,10 @@ func (cs *ConsensusState) tryFinalizeCommit(height uint) {
hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority()
if !ok || len(hash) == 0 { if !ok || len(hash) == 0 {
return return // There was no +2/3 majority, or +2/3 was for <nil>.
} }
if !cs.ProposalBlock.HashesTo(hash) { if !cs.ProposalBlock.HashesTo(hash) {
return return // We don't have the commit block.
} }
go cs.FinalizeCommit(height) go cs.FinalizeCommit(height)
} }
@ -964,19 +973,21 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty
return added, err return added, err
} }
if added && cs.ProposalBlockParts.IsComplete() { if added && cs.ProposalBlockParts.IsComplete() {
// Added and completed!
var n int64 var n int64
var err error var err error
cs.ProposalBlock = binary.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), &n, &err).(*types.Block) cs.ProposalBlock = binary.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), &n, &err).(*types.Block)
log.Debug("Received complete proposal", "hash", cs.ProposalBlock.Hash()) log.Debug("Received complete proposal", "hash", cs.ProposalBlock.Hash())
if cs.Step == RoundStepPropose && cs.isProposalComplete() { if cs.Step == RoundStepPropose && cs.isProposalComplete() {
// Move onto the next step
go cs.EnterPrevote(height, round) go cs.EnterPrevote(height, round)
} else if cs.Step == RoundStepCommit { } else if cs.Step == RoundStepCommit {
/// XXX How about, EnterCommit()? // If we're waiting on the proposal block...
cs.tryFinalizeCommit(height) cs.tryFinalizeCommit(height)
} }
return true, err return true, err
} }
return true, nil return added, nil
} }
func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) {
@ -1005,74 +1016,57 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri
if added { if added {
switch vote.Type { switch vote.Type {
case types.VoteTypePrevote: case types.VoteTypePrevote:
log.Debug(Fmt("Added to prevotes: %v", cs.Votes.Prevotes(vote.Round).StringShort())) prevotes := cs.Votes.Prevotes(vote.Round)
if cs.Round < vote.Round && cs.Votes.Prevotes(vote.Round).HasTwoThirdsAny() { log.Debug(Fmt("Added to prevotes: %v", prevotes.StringShort()))
// Goto to Prevote vote.Round. // First, unlock if prevotes is a valid POL.
go func() { if cs.LockedBlock != nil && cs.LockedRound < vote.Round {
cs.EnterNewRound(height, vote.Round) hash, _, ok := prevotes.TwoThirdsMajority()
cs.EnterPrevote(height, vote.Round) if ok && !cs.LockedBlock.HashesTo(hash) {
cs.EnterPrevoteWait(height, vote.Round) log.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
}() cs.LockedRound = 0
} else if cs.Round == vote.Round { cs.LockedBlock = nil
if cs.Votes.Prevotes(cs.Round).HasTwoThirdsMajority() { cs.LockedBlockParts = nil
// Goto Precommit, whether for block or nil.
go cs.EnterPrecommit(height, cs.Round)
} else if cs.Votes.Prevotes(cs.Round).HasTwoThirdsAny() {
// Goto PrevoteWait
go func() {
cs.EnterPrevote(height, cs.Round)
cs.EnterPrevoteWait(height, cs.Round)
}()
} }
} else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && uint(cs.Proposal.POLRound) == vote.Round { }
if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() {
// Round-skip over to PrevoteWait or goto Precommit.
go func() {
if cs.Round < vote.Round {
cs.EnterNewRound(height, vote.Round)
}
if prevotes.HasTwoThirdsMajority() {
cs.EnterPrecommit(height, vote.Round)
} else {
cs.EnterPrevote(height, vote.Round)
cs.EnterPrevoteWait(height, vote.Round)
}
}()
} else if cs.Proposal != nil &&
0 <= cs.Proposal.POLRound && uint(cs.Proposal.POLRound) == vote.Round {
// If the proposal is now complete, enter prevote of cs.Round.
if cs.isProposalComplete() { if cs.isProposalComplete() {
go cs.EnterPrevote(height, cs.Round) go cs.EnterPrevote(height, cs.Round)
} }
} }
case types.VoteTypePrecommit: case types.VoteTypePrecommit:
log.Debug(Fmt("Added to precommit: %v", cs.Votes.Precommits(vote.Round).StringShort())) precommits := cs.Votes.Precommits(vote.Round)
if cs.Round < vote.Round { log.Debug(Fmt("Added to precommit: %v", precommits.StringShort()))
if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok { if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() {
if len(hash) == 0 { go func() {
// This is weird, shouldn't happen hash, _, ok := precommits.TwoThirdsMajority()
log.Warn("This is weird, why did we receive +2/3 of nil precommits?") if ok && len(hash) == 0 {
// Skip to Precommit of vote.Round cs.EnterNewRound(height, vote.Round+1)
go func() { return
cs.EnterNewRound(height, vote.Round) } else if cs.Round < vote.Round {
cs.EnterPrecommit(height, vote.Round)
cs.EnterPrecommitWait(height, vote.Round)
}()
} else {
// If hash is block, goto Commit
go func() {
cs.EnterNewRound(height, vote.Round)
cs.EnterCommit(height)
}()
}
} else if cs.Votes.Precommits(vote.Round).HasTwoThirdsAny() {
// Skip to Precommit of vote.Round
go func() {
cs.EnterNewRound(height, vote.Round) cs.EnterNewRound(height, vote.Round)
}
if ok {
cs.EnterCommit(height)
} else {
cs.EnterPrecommit(height, vote.Round) cs.EnterPrecommit(height, vote.Round)
cs.EnterPrecommitWait(height, vote.Round) cs.EnterPrecommitWait(height, vote.Round)
}()
}
} else if cs.Round == vote.Round {
if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok {
if len(hash) == 0 {
// If hash is nil, goto NewRound
go cs.EnterNewRound(height, cs.Round+1)
} else {
// If hash is block, goto Commit
go cs.EnterCommit(height)
} }
} else if cs.Votes.Precommits(cs.Round).HasTwoThirdsAny() { }()
// Goto PrecommitWait
go func() {
cs.EnterPrecommit(height, cs.Round)
cs.EnterPrecommitWait(height, cs.Round)
}()
}
} }
default: default:
panic(Fmt("Unexpected vote type %X", vote.Type)) // Should not happen. panic(Fmt("Unexpected vote type %X", vote.Type)) // Should not happen.