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.
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 to allow round-skipping)
* Nodes gossip the proposal block proposed by the designated proposer at round.
* 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 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
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
* NewRound(height:H,round:R):
@ -33,12 +33,12 @@
* Propose(height:H,round:R):
* 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:
* After `timeoutPropose` after entering Propose. --> 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 precommits received for (H,R+1). --> goto Precommit(H,R+1)
* After any +2/3 prevotes received at (H,R+1). --> goto Prevote(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)
* Prevote(height:H,round:R):
@ -49,8 +49,8 @@
* The Prevote step ends:
* 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 any +2/3 prevotes received for (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 prevotes received at (H,R+1). --> goto Prevote(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)
* 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 <nil>. --> 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 precommits received for (H,R+1). --> goto Precommit(H,R+1)
* After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1)
* After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1)
* Commit(height:H):
* Set CommitTime = now
@ -77,7 +77,7 @@
* Wait until `StartTime` to receive straggler commits. --> goto NewRound(H,0)
* 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.
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.
@ -199,10 +199,11 @@ type RoundState struct {
Proposal *Proposal
ProposalBlock *types.Block
ProposalBlockParts *types.PartSet
LockedRound uint
LockedBlock *types.Block
LockedBlockParts *types.PartSet
Votes *HeightVoteSet
LastCommit *VoteSet // Last precommits for Height-1
LastCommit *VoteSet // Last precommits at Height-1
}
func (rs *RoundState) String() string {
@ -217,6 +218,7 @@ func (rs *RoundState) StringIndented(indent string) string {
%s Validators: %v
%s Proposal: %v
%s ProposalBlock: %v %v
%s LockedRound: %v
%s LockedBlock: %v %v
%s Votes: %v
%s LastCommit: %v
@ -227,6 +229,7 @@ func (rs *RoundState) StringIndented(indent string) string {
indent, rs.Validators.StringIndented(indent+" "),
indent, rs.Proposal,
indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(),
indent, rs.LockedRound,
indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(),
indent, rs.Votes.StringIndented(indent+" "),
indent, rs.LastCommit.StringShort(),
@ -387,6 +390,7 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) {
cs.Proposal = nil
cs.ProposalBlock = nil
cs.ProposalBlockParts = nil
cs.LockedRound = 0
cs.LockedBlock = nil
cs.LockedBlockParts = nil
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: `startTime = commitTime+timeoutCommit` from NewHeight(height)
// NOTE: cs.StartTime was already set for height.
@ -650,7 +654,7 @@ func (cs *ConsensusState) doPrevote(height uint, round uint) {
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) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
@ -719,6 +723,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
log.Info("EnterPrecommit: +2/3 prevoted for nil.")
} else {
log.Info("EnterPrecommit: +2/3 prevoted for nil. Unlocking")
cs.LockedRound = 0
cs.LockedBlock = 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 {
panic(Fmt("EnterPrecommit: +2/3 prevoted for an invalid block: %v", err))
}
cs.LockedRound = round
cs.LockedBlock = cs.ProposalBlock
cs.LockedBlockParts = cs.ProposalBlockParts
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.
// Unlock and precommit nil.
// 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()))
}
cs.LockedRound = 0
cs.LockedBlock = nil
cs.LockedBlockParts = nil
if !cs.ProposalBlockParts.HasHeader(partsHeader) {
@ -824,9 +831,11 @@ func (cs *ConsensusState) EnterCommit(height uint) {
if cs.LockedBlock.HashesTo(hash) {
cs.ProposalBlock = cs.LockedBlock
cs.ProposalBlockParts = cs.LockedBlockParts
cs.LockedRound = 0
cs.LockedBlock = nil
cs.LockedBlockParts = nil
} else {
cs.LockedRound = 0
cs.LockedBlock = nil
cs.LockedBlockParts = nil
}
@ -854,10 +863,10 @@ func (cs *ConsensusState) tryFinalizeCommit(height uint) {
hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority()
if !ok || len(hash) == 0 {
return
return // There was no +2/3 majority, or +2/3 was for <nil>.
}
if !cs.ProposalBlock.HashesTo(hash) {
return
return // We don't have the commit block.
}
go cs.FinalizeCommit(height)
}
@ -964,19 +973,21 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty
return added, err
}
if added && cs.ProposalBlockParts.IsComplete() {
// Added and completed!
var n int64
var err error
cs.ProposalBlock = binary.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), &n, &err).(*types.Block)
log.Debug("Received complete proposal", "hash", cs.ProposalBlock.Hash())
if cs.Step == RoundStepPropose && cs.isProposalComplete() {
// Move onto the next step
go cs.EnterPrevote(height, round)
} else if cs.Step == RoundStepCommit {
/// XXX How about, EnterCommit()?
// If we're waiting on the proposal block...
cs.tryFinalizeCommit(height)
}
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) {
@ -1005,74 +1016,57 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri
if added {
switch vote.Type {
case types.VoteTypePrevote:
log.Debug(Fmt("Added to prevotes: %v", cs.Votes.Prevotes(vote.Round).StringShort()))
if cs.Round < vote.Round && cs.Votes.Prevotes(vote.Round).HasTwoThirdsAny() {
// Goto to Prevote vote.Round.
go func() {
cs.EnterNewRound(height, vote.Round)
cs.EnterPrevote(height, vote.Round)
cs.EnterPrevoteWait(height, vote.Round)
}()
} else if cs.Round == vote.Round {
if cs.Votes.Prevotes(cs.Round).HasTwoThirdsMajority() {
// 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)
}()
prevotes := cs.Votes.Prevotes(vote.Round)
log.Debug(Fmt("Added to prevotes: %v", prevotes.StringShort()))
// First, unlock if prevotes is a valid POL.
if cs.LockedBlock != nil && cs.LockedRound < vote.Round {
hash, _, ok := prevotes.TwoThirdsMajority()
if ok && !cs.LockedBlock.HashesTo(hash) {
log.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
cs.LockedRound = 0
cs.LockedBlock = nil
cs.LockedBlockParts = nil
}
} 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() {
go cs.EnterPrevote(height, cs.Round)
}
}
case types.VoteTypePrecommit:
log.Debug(Fmt("Added to precommit: %v", cs.Votes.Precommits(vote.Round).StringShort()))
if cs.Round < vote.Round {
if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok {
if len(hash) == 0 {
// This is weird, shouldn't happen
log.Warn("This is weird, why did we receive +2/3 of nil precommits?")
// Skip to Precommit of vote.Round
go func() {
cs.EnterNewRound(height, 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() {
precommits := cs.Votes.Precommits(vote.Round)
log.Debug(Fmt("Added to precommit: %v", precommits.StringShort()))
if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() {
go func() {
hash, _, ok := precommits.TwoThirdsMajority()
if ok && len(hash) == 0 {
cs.EnterNewRound(height, vote.Round+1)
return
} else if cs.Round < vote.Round {
cs.EnterNewRound(height, vote.Round)
}
if ok {
cs.EnterCommit(height)
} else {
cs.EnterPrecommit(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:
panic(Fmt("Unexpected vote type %X", vote.Type)) // Should not happen.