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

View File

@ -1,19 +1,31 @@
/* /*
Consensus State Machine Overview: Consensus State Machine Overview:
* NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep). 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: To "prevote/precommit" something means to broadcast a prevote/precommit vote for something.
* 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) During NewHeight/NewRound/Propose/Prevote/Precommit:
* Nodes also gossip prevotes for the proposal's POL (proof-of-lock) round if proposed. * Nodes gossip the proposal block proposed by the designated proposer for that round.
* Upon each state transition, the height/round/step is broadcast to neighboring peers. * Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 to allow round-skipping)
* The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation. * Nodes gossip prevotes for the proposal's POL (proof-of-lock) round if proposed.
* A block contains the last block's Validation, which includes the Commit precommits. * Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup)
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), Upon each state transition, the height/round/step is broadcast to neighboring peers.
or some precommits may be for different blockhashes for the last block hash (which is fine).
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),
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): * NewRound(height:H,round:R):
* Set up new round. --> goto Propose(H,R) * Set up new round. --> goto Propose(H,R)
@ -273,15 +285,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) {
lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators) lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators)
seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight)
for idx, precommit := range seenValidation.Precommits { for idx, precommit := range seenValidation.Precommits {
precommitVote := &types.Vote{ added, _, err := lastPrecommits.AddByIndex(uint(idx), precommit)
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)
if !added || err != nil { if !added || err != nil {
panic(Fmt("Failed to reconstruct LastCommit: %v", err)) 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) { 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()) sleepDuration := cs.StartTime.Sub(time.Now())
go func() { go func() {
if sleepDuration > 0 { if 0 < sleepDuration {
time.Sleep(sleepDuration) time.Sleep(sleepDuration)
} }
cs.EnterNewRound(height, 0) cs.EnterNewRound(height, 0)
@ -346,7 +351,7 @@ func (cs *ConsensusState) IsStopped() bool {
// The round becomes 0 and cs.Step becomes RoundStepNewHeight. // The round becomes 0 and cs.Step becomes RoundStepNewHeight.
func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) {
// SANITY CHECK // 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", panic(Fmt("updateToState() expected state height of %v but found %v",
cs.Height, state.LastBlockHeight)) 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 height := state.LastBlockHeight + 1 // next desired block height
lastPrecommits := (*VoteSet)(nil) lastPrecommits := (*VoteSet)(nil)
if contiguous && cs.Votes != 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) 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: +2/3 precommits for nil from (height,round-1)
// Enter: `timeoutPrecommits` after any +2/3 precommits // Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1)
// Enter: `commitTime+timeoutCommit` from NewHeight // Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height)
// NOTE: cs.StartTime was already set for height. // NOTE: cs.StartTime was already set for height.
func (cs *ConsensusState) EnterNewRound(height uint, round uint) { func (cs *ConsensusState) EnterNewRound(height uint, round uint) {
cs.mtx.Lock() cs.mtx.Lock()
@ -453,17 +461,17 @@ func (cs *ConsensusState) EnterNewRound(height uint, round uint) {
cs.Proposal = nil cs.Proposal = nil
cs.ProposalBlock = nil cs.ProposalBlock = nil
cs.ProposalBlockParts = 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. // Immediately go to EnterPropose.
go cs.EnterPropose(height, round) go cs.EnterPropose(height, round)
} }
// Enter: from NewRound. // Enter: from NewRound(height,round).
func (cs *ConsensusState) EnterPropose(height uint, round uint) { func (cs *ConsensusState) EnterPropose(height uint, round uint) {
cs.mtx.Lock() cs.mtx.Lock()
defer cs.mtx.Unlock() 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)) log.Debug(Fmt("EnterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return return
} }
@ -486,21 +494,21 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) {
cs.EnterPrevote(height, round) 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 { if cs.privValidator == nil {
return return
} }
// See if it is our turn to propose
if !bytes.Equal(cs.Validators.Proposer().Address, cs.privValidator.Address) { 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) log.Debug("EnterPropose: Not our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator)
return
} else { } else {
log.Debug("EnterPropose: Our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator) 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 block *types.Block
var blockParts *types.PartSet var blockParts *types.PartSet
@ -514,21 +522,23 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) {
} }
// Make proposal // 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) err := cs.privValidator.SignProposal(cs.state.ChainID, proposal)
if err == nil { 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)) log.Debug(Fmt("Signed and set proposal block: %v", block))
// Set fields // Set fields
cs.Proposal = proposal cs.Proposal = proposal
cs.ProposalBlock = block cs.ProposalBlock = block
cs.ProposalBlockParts = blockParts cs.ProposalBlockParts = blockParts
} else { } 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 { func (cs *ConsensusState) isProposalComplete() bool {
if cs.Proposal == nil || cs.ProposalBlock == nil { if cs.Proposal == nil || cs.ProposalBlock == nil {
return false return false
@ -585,32 +595,30 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
return block, blockParts return block, blockParts
} }
// Enter: `timeoutPropose` after start of Propose. // Enter: `timeoutPropose` after entering Propose.
// Enter: proposal block and POL is ready. // Enter: proposal block and POL is ready.
// Enter: any +2/3 prevotes for next round. // Enter: any +2/3 prevotes for future round.
// Prevote for LockedBlock if we're locked, or ProposealBlock if valid. // Prevote for LockedBlock if we're locked, or ProposalBlock if valid.
// Otherwise vote nil. // Otherwise vote nil.
func (cs *ConsensusState) EnterPrevote(height uint, round uint) { func (cs *ConsensusState) EnterPrevote(height uint, round uint) {
cs.mtx.Lock() cs.mtx.Lock()
defer cs.mtx.Unlock() 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)) log.Debug(Fmt("EnterPrevote(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return return
} }
defer func() {
// Done EnterPrevote:
cs.Round = round
cs.Step = RoundStepPrevote
cs.newStepCh <- cs.getRoundState()
// Maybe immediately go to EnterPrevoteWait.
if cs.Votes.Prevotes(round).HasTwoThirdsAny() {
go cs.EnterPrevoteWait(height, round)
}
}()
// Sign and broadcast vote as necessary // Sign and broadcast vote as necessary
cs.doPrevote(height, round) cs.doPrevote(height, round)
// Done EnterPrevote:
cs.Round = round
cs.Step = RoundStepPrevote
cs.newStepCh <- cs.getRoundState()
/* This isn't necessary because addVote() does it for us.
if cs.Votes.Prevotes(round).HasTwoThirdsAny() {
go cs.EnterPrevoteWait(height, round)
}*/
} }
func (cs *ConsensusState) doPrevote(height uint, round uint) { 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) { func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) {
cs.mtx.Lock() cs.mtx.Lock()
defer cs.mtx.Unlock() 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)) log.Debug(Fmt("EnterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return return
} }
@ -675,7 +683,7 @@ func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) {
func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
cs.mtx.Lock() cs.mtx.Lock()
defer cs.mtx.Unlock() 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)) log.Debug(Fmt("EnterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return return
} }
@ -685,10 +693,10 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) {
cs.Round = round cs.Round = round
cs.Step = RoundStepPrecommit cs.Step = RoundStepPrecommit
cs.newStepCh <- cs.getRoundState() 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() { if cs.Votes.Precommits(round).HasTwoThirdsAny() {
go cs.EnterPrecommitWait(height, round) go cs.EnterPrecommitWait(height, round)
} }*/
}() }()
hash, partsHeader, ok := cs.Votes.Prevotes(round).TwoThirdsMajority() 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. // 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.LockedBlock = nil
cs.LockedBlockParts = nil cs.LockedBlockParts = nil
if !cs.ProposalBlockParts.HasHeader(partsHeader) { 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) { func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) {
cs.mtx.Lock() cs.mtx.Lock()
defer cs.mtx.Unlock() 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)) log.Debug(Fmt("EnterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
return return
} }
@ -784,7 +796,7 @@ func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) {
func (cs *ConsensusState) EnterCommit(height uint) { func (cs *ConsensusState) EnterCommit(height uint) {
cs.mtx.Lock() cs.mtx.Lock()
defer cs.mtx.Unlock() 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)) log.Debug(Fmt("EnterCommit(%v): Invalid args. Current step: %v/%v/%v", height, cs.Height, cs.Round, cs.Step))
return 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. // We don't care about the proposal if we're already in RoundStepCommit.
if cs.Step >= RoundStepCommit { if RoundStepCommit <= cs.Step {
return nil return nil
} }
@ -959,6 +971,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty
if cs.Step == RoundStepPropose && cs.isProposalComplete() { if cs.Step == RoundStepPropose && cs.isProposalComplete() {
go cs.EnterPrevote(height, round) go cs.EnterPrevote(height, round)
} else if cs.Step == RoundStepCommit { } else if cs.Step == RoundStepCommit {
/// XXX How about, EnterCommit()?
cs.tryFinalizeCommit(height) cs.tryFinalizeCommit(height)
} }
return true, err return true, err
@ -1011,7 +1024,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri
cs.EnterPrevoteWait(height, cs.Round) 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() { if cs.isProposalComplete() {
go cs.EnterPrevote(height, cs.Round) go cs.EnterPrevote(height, cs.Round)
} }