|
@ -234,7 +234,7 @@ type ConsensusState struct { |
|
|
|
|
|
|
|
|
peerMsgQueue chan msgInfo // serializes msgs affecting state (proposals, block parts, votes)
|
|
|
peerMsgQueue chan msgInfo // serializes msgs affecting state (proposals, block parts, votes)
|
|
|
internalMsgQueue chan msgInfo // like peerMsgQueue but for our own proposals, parts, votes
|
|
|
internalMsgQueue chan msgInfo // like peerMsgQueue but for our own proposals, parts, votes
|
|
|
timeoutTicker *time.Ticker // ticker for timeouts
|
|
|
|
|
|
|
|
|
timeoutTicker TimeoutTicker // ticker for timeouts
|
|
|
tickChan chan timeoutInfo // start the timeoutTicker in the timeoutRoutine
|
|
|
tickChan chan timeoutInfo // start the timeoutTicker in the timeoutRoutine
|
|
|
tockChan chan timeoutInfo // timeouts are relayed on tockChan to the receiveRoutine
|
|
|
tockChan chan timeoutInfo // timeouts are relayed on tockChan to the receiveRoutine
|
|
|
timeoutParams *TimeoutParams // parameters and functions for timeout intervals
|
|
|
timeoutParams *TimeoutParams // parameters and functions for timeout intervals
|
|
@ -252,6 +252,40 @@ type ConsensusState struct { |
|
|
setProposal func(proposal *types.Proposal) error |
|
|
setProposal func(proposal *types.Proposal) error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func NewTimeoutTicker() TimeoutTicker { |
|
|
|
|
|
return &timeoutTicker{ticker: new(time.Ticker)} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type TimeoutTicker interface { |
|
|
|
|
|
Chan() <-chan time.Time // on which to receive a timeout
|
|
|
|
|
|
Stop() // stop the timer
|
|
|
|
|
|
Reset(ti timeoutInfo) // reset the timer
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type timeoutTicker struct { |
|
|
|
|
|
ticker *time.Ticker |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (t *timeoutTicker) Chan() <-chan time.Time { |
|
|
|
|
|
return t.ticker.C |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (t *timeoutTicker) Stop() { |
|
|
|
|
|
t.ticker.Stop() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (t *timeoutTicker) Reset(ti timeoutInfo) { |
|
|
|
|
|
t.ticker = time.NewTicker(ti.Duration) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func skipTimeoutCommit(ti timeoutInfo) bool { |
|
|
|
|
|
if ti.Step == RoundStepNewHeight && |
|
|
|
|
|
ti.Duration == time.Duration(0) { |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
func NewConsensusState(config cfg.Config, state *sm.State, proxyAppConn proxy.AppConnConsensus, blockStore *bc.BlockStore, mempool *mempl.Mempool) *ConsensusState { |
|
|
func NewConsensusState(config cfg.Config, state *sm.State, proxyAppConn proxy.AppConnConsensus, blockStore *bc.BlockStore, mempool *mempl.Mempool) *ConsensusState { |
|
|
cs := &ConsensusState{ |
|
|
cs := &ConsensusState{ |
|
|
config: config, |
|
|
config: config, |
|
@ -260,7 +294,7 @@ func NewConsensusState(config cfg.Config, state *sm.State, proxyAppConn proxy.Ap |
|
|
mempool: mempool, |
|
|
mempool: mempool, |
|
|
peerMsgQueue: make(chan msgInfo, msgQueueSize), |
|
|
peerMsgQueue: make(chan msgInfo, msgQueueSize), |
|
|
internalMsgQueue: make(chan msgInfo, msgQueueSize), |
|
|
internalMsgQueue: make(chan msgInfo, msgQueueSize), |
|
|
timeoutTicker: new(time.Ticker), |
|
|
|
|
|
|
|
|
timeoutTicker: NewTimeoutTicker(), |
|
|
tickChan: make(chan timeoutInfo, tickTockBufferSize), |
|
|
tickChan: make(chan timeoutInfo, tickTockBufferSize), |
|
|
tockChan: make(chan timeoutInfo, tickTockBufferSize), |
|
|
tockChan: make(chan timeoutInfo, tickTockBufferSize), |
|
|
timeoutParams: InitTimeoutParamsFromConfig(config), |
|
|
timeoutParams: InitTimeoutParamsFromConfig(config), |
|
@ -321,6 +355,13 @@ func (cs *ConsensusState) SetPrivValidator(priv PrivValidator) { |
|
|
cs.privValidator = priv |
|
|
cs.privValidator = priv |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Set the local timer
|
|
|
|
|
|
func (cs *ConsensusState) SetTimeoutTicker(timeoutTicker TimeoutTicker) { |
|
|
|
|
|
cs.mtx.Lock() |
|
|
|
|
|
defer cs.mtx.Unlock() |
|
|
|
|
|
cs.timeoutTicker = timeoutTicker |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
func (cs *ConsensusState) LoadCommit(height int) *types.Commit { |
|
|
func (cs *ConsensusState) LoadCommit(height int) *types.Commit { |
|
|
cs.mtx.Lock() |
|
|
cs.mtx.Lock() |
|
|
defer cs.mtx.Unlock() |
|
|
defer cs.mtx.Unlock() |
|
@ -562,7 +603,6 @@ func (cs *ConsensusState) updateToState(state *sm.State) { |
|
|
} else { |
|
|
} else { |
|
|
cs.StartTime = cs.timeoutParams.Commit(cs.CommitTime) |
|
|
cs.StartTime = cs.timeoutParams.Commit(cs.CommitTime) |
|
|
} |
|
|
} |
|
|
cs.CommitTime = time.Time{} |
|
|
|
|
|
cs.Validators = validators |
|
|
cs.Validators = validators |
|
|
cs.Proposal = nil |
|
|
cs.Proposal = nil |
|
|
cs.ProposalBlock = nil |
|
|
cs.ProposalBlock = nil |
|
@ -613,6 +653,12 @@ func (cs *ConsensusState) timeoutRoutine() { |
|
|
continue |
|
|
continue |
|
|
} else if newti.Round == ti.Round { |
|
|
} else if newti.Round == ti.Round { |
|
|
if ti.Step > 0 && newti.Step <= ti.Step { |
|
|
if ti.Step > 0 && newti.Step <= ti.Step { |
|
|
|
|
|
// if we got here because we have all the votes,
|
|
|
|
|
|
// fire the tock now instead of waiting for the timeout
|
|
|
|
|
|
if skipTimeoutCommit(newti) { |
|
|
|
|
|
cs.timeoutTicker.Stop() |
|
|
|
|
|
go func(t timeoutInfo) { cs.tockChan <- t }(newti) |
|
|
|
|
|
} |
|
|
continue |
|
|
continue |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -628,8 +674,8 @@ func (cs *ConsensusState) timeoutRoutine() { |
|
|
|
|
|
|
|
|
log.Debug("Scheduling timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) |
|
|
log.Debug("Scheduling timeout", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) |
|
|
cs.timeoutTicker.Stop() |
|
|
cs.timeoutTicker.Stop() |
|
|
cs.timeoutTicker = time.NewTicker(ti.Duration) |
|
|
|
|
|
case <-cs.timeoutTicker.C: |
|
|
|
|
|
|
|
|
cs.timeoutTicker.Reset(ti) |
|
|
|
|
|
case <-cs.timeoutTicker.Chan(): |
|
|
log.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) |
|
|
log.Info("Timed out", "dur", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step) |
|
|
cs.timeoutTicker.Stop() |
|
|
cs.timeoutTicker.Stop() |
|
|
// go routine here gaurantees timeoutRoutine doesn't block.
|
|
|
// go routine here gaurantees timeoutRoutine doesn't block.
|
|
@ -681,17 +727,9 @@ func (cs *ConsensusState) receiveRoutine(maxSteps int) { |
|
|
cs.handleTimeout(ti, rs) |
|
|
cs.handleTimeout(ti, rs) |
|
|
case <-cs.Quit: |
|
|
case <-cs.Quit: |
|
|
|
|
|
|
|
|
// drain the internalMsgQueue in case we eg. signed a proposal but it didn't hit the wal
|
|
|
|
|
|
FLUSH: |
|
|
|
|
|
for { |
|
|
|
|
|
select { |
|
|
|
|
|
case mi = <-cs.internalMsgQueue: |
|
|
|
|
|
cs.wal.Save(mi) |
|
|
|
|
|
cs.handleMsg(mi, rs) |
|
|
|
|
|
default: |
|
|
|
|
|
break FLUSH |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// NOTE: the internalMsgQueue may have signed messages from our
|
|
|
|
|
|
// priv_val that haven't hit the WAL, but its ok because
|
|
|
|
|
|
// priv_val tracks LastSig
|
|
|
|
|
|
|
|
|
// close wal now that we're done writing to it
|
|
|
// close wal now that we're done writing to it
|
|
|
if cs.wal != nil { |
|
|
if cs.wal != nil { |
|
@ -758,7 +796,7 @@ func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs RoundState) { |
|
|
switch ti.Step { |
|
|
switch ti.Step { |
|
|
case RoundStepNewHeight: |
|
|
case RoundStepNewHeight: |
|
|
// NewRound event fired from enterNewRound.
|
|
|
// NewRound event fired from enterNewRound.
|
|
|
// XXX: should we fire timeout here?
|
|
|
|
|
|
|
|
|
// XXX: should we fire timeout here (for timeout commit)?
|
|
|
cs.enterNewRound(ti.Height, 0) |
|
|
cs.enterNewRound(ti.Height, 0) |
|
|
case RoundStepPropose: |
|
|
case RoundStepPropose: |
|
|
types.FireEventTimeoutPropose(cs.evsw, cs.RoundStateEvent()) |
|
|
types.FireEventTimeoutPropose(cs.evsw, cs.RoundStateEvent()) |
|
@ -1171,6 +1209,7 @@ func (cs *ConsensusState) enterCommit(height int, commitRound int) { |
|
|
// keep cs.Round the same, commitRound points to the right Precommits set.
|
|
|
// keep cs.Round the same, commitRound points to the right Precommits set.
|
|
|
cs.updateRoundStep(cs.Round, RoundStepCommit) |
|
|
cs.updateRoundStep(cs.Round, RoundStepCommit) |
|
|
cs.CommitRound = commitRound |
|
|
cs.CommitRound = commitRound |
|
|
|
|
|
cs.CommitTime = time.Now() |
|
|
cs.newStep() |
|
|
cs.newStep() |
|
|
|
|
|
|
|
|
// Maybe finalize immediately.
|
|
|
// Maybe finalize immediately.
|
|
@ -1416,6 +1455,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, |
|
|
log.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "csHeight", cs.Height) |
|
|
log.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "csHeight", cs.Height) |
|
|
|
|
|
|
|
|
// A precommit for the previous height?
|
|
|
// A precommit for the previous height?
|
|
|
|
|
|
// These come in while we wait timeoutCommit
|
|
|
if vote.Height+1 == cs.Height { |
|
|
if vote.Height+1 == cs.Height { |
|
|
if !(cs.Step == RoundStepNewHeight && vote.Type == types.VoteTypePrecommit) { |
|
|
if !(cs.Step == RoundStepNewHeight && vote.Type == types.VoteTypePrecommit) { |
|
|
// TODO: give the reason ..
|
|
|
// TODO: give the reason ..
|
|
@ -1426,7 +1466,15 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, |
|
|
if added { |
|
|
if added { |
|
|
log.Info(Fmt("Added to lastPrecommits: %v", cs.LastCommit.StringShort())) |
|
|
log.Info(Fmt("Added to lastPrecommits: %v", cs.LastCommit.StringShort())) |
|
|
types.FireEventVote(cs.evsw, types.EventDataVote{vote}) |
|
|
types.FireEventVote(cs.evsw, types.EventDataVote{vote}) |
|
|
|
|
|
|
|
|
|
|
|
if cs.LastCommit.HasAll() { |
|
|
|
|
|
// if we have all the votes now,
|
|
|
|
|
|
// schedule the timeoutCommit to happen right away
|
|
|
|
|
|
// NOTE: this won't apply if only one validator
|
|
|
|
|
|
cs.scheduleTimeout(time.Duration(0), cs.Height, 0, RoundStepNewHeight) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -1487,7 +1535,6 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, |
|
|
cs.enterNewRound(height, vote.Round) |
|
|
cs.enterNewRound(height, vote.Round) |
|
|
cs.enterPrecommit(height, vote.Round) |
|
|
cs.enterPrecommit(height, vote.Round) |
|
|
cs.enterPrecommitWait(height, vote.Round) |
|
|
cs.enterPrecommitWait(height, vote.Round) |
|
|
//}()
|
|
|
|
|
|
} |
|
|
} |
|
|
default: |
|
|
default: |
|
|
PanicSanity(Fmt("Unexpected vote type %X", vote.Type)) // Should not happen.
|
|
|
PanicSanity(Fmt("Unexpected vote type %X", vote.Type)) // Should not happen.
|
|
|