From c20fbed2f75cd8f3f3e4d3f2618f27839b7cd822 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 24 Jan 2019 15:33:47 +0100 Subject: [PATCH] [WIP] fix halting issue (#3197) fix halting issue --- consensus/state.go | 12 +++---- consensus/state_test.go | 65 ++++++++++++++++++++++++++++++++++ consensus/types/round_state.go | 39 ++++++++++---------- 3 files changed, 91 insertions(+), 25 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 26b074179..c69dfb874 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -94,7 +94,6 @@ type ConsensusState struct { // internal state mtx sync.RWMutex cstypes.RoundState - triggeredTimeoutPrecommit bool state sm.State // State until height-1. // state changes may be triggered by: msgs from peers, @@ -732,6 +731,7 @@ func (cs *ConsensusState) handleTxsAvailable() { cs.mtx.Lock() defer cs.mtx.Unlock() // we only need to do this for round 0 + cs.enterNewRound(cs.Height, 0) cs.enterPropose(cs.Height, 0) } @@ -782,7 +782,7 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) { cs.ProposalBlockParts = nil } cs.Votes.SetRound(round + 1) // also track next round (round+1) to allow round-skipping - cs.triggeredTimeoutPrecommit = false + cs.TriggeredTimeoutPrecommit = false cs.eventBus.PublishEventNewRound(cs.NewRoundEvent()) cs.metrics.Rounds.Set(float64(round)) @@ -1128,12 +1128,12 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { func (cs *ConsensusState) enterPrecommitWait(height int64, round int) { logger := cs.Logger.With("height", height, "round", round) - if cs.Height != height || round < cs.Round || (cs.Round == round && cs.triggeredTimeoutPrecommit) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.TriggeredTimeoutPrecommit) { logger.Debug( fmt.Sprintf( "enterPrecommitWait(%v/%v): Invalid args. "+ - "Current state is Height/Round: %v/%v/, triggeredTimeoutPrecommit:%v", - height, round, cs.Height, cs.Round, cs.triggeredTimeoutPrecommit)) + "Current state is Height/Round: %v/%v/, TriggeredTimeoutPrecommit:%v", + height, round, cs.Height, cs.Round, cs.TriggeredTimeoutPrecommit)) return } if !cs.Votes.Precommits(round).HasTwoThirdsAny() { @@ -1143,7 +1143,7 @@ func (cs *ConsensusState) enterPrecommitWait(height int64, round int) { defer func() { // Done enterPrecommitWait: - cs.triggeredTimeoutPrecommit = true + cs.TriggeredTimeoutPrecommit = true cs.newStep() }() diff --git a/consensus/state_test.go b/consensus/state_test.go index 40103e472..10c04fbc5 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -1279,6 +1279,71 @@ func TestCommitFromPreviousRound(t *testing.T) { ensureNewRound(newRoundCh, height+1, 0) } +type fakeTxNotifier struct { + ch chan struct{} +} + +func (n *fakeTxNotifier) TxsAvailable() <-chan struct{} { + return n.ch +} + +func (n *fakeTxNotifier) Notify() { + n.ch <- struct{}{} +} + +func TestStartNextHeightCorrectly(t *testing.T) { + cs1, vss := randConsensusState(4) + cs1.config.SkipTimeoutCommit = false + cs1.txNotifier = &fakeTxNotifier{ch: make(chan struct{})} + + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + newBlockHeader := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) + + // start round and wait for propose and prevote + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + theBlockHash := rs.ProposalBlock.Hash() + theBlockParts := rs.ProposalBlockParts.Header() + + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], theBlockHash) + + signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + + ensurePrecommit(voteCh, height, round) + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) + + rs = cs1.GetRoundState() + + // add precommits + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2) + signAddVotes(cs1, types.PrecommitType, theBlockHash, theBlockParts, vs3) + signAddVotes(cs1, types.PrecommitType, theBlockHash, theBlockParts, vs4) + + ensureNewBlockHeader(newBlockHeader, height, theBlockHash) + + rs = cs1.GetRoundState() + assert.True(t, rs.TriggeredTimeoutPrecommit) + + cs1.txNotifier.(*fakeTxNotifier).Notify() + + ensureNewTimeout(timeoutProposeCh, height+1, round, cs1.config.TimeoutPropose.Nanoseconds()) + rs = cs1.GetRoundState() + assert.False(t, rs.TriggeredTimeoutPrecommit, "triggeredTimeoutPrecommit should be false at the beginning of each round") +} + //------------------------------------------------------------------------------------------ // SlashingSuite // TODO: Slashing diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index 418f73a8e..eab13b6c7 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -65,25 +65,26 @@ func (rs RoundStepType) String() string { // NOTE: Not thread safe. Should only be manipulated by functions downstream // of the cs.receiveRoutine type RoundState struct { - Height int64 `json:"height"` // Height we are working on - Round int `json:"round"` - Step RoundStepType `json:"step"` - StartTime time.Time `json:"start_time"` - CommitTime time.Time `json:"commit_time"` // Subjective time when +2/3 precommits for Block at Round were found - Validators *types.ValidatorSet `json:"validators"` - Proposal *types.Proposal `json:"proposal"` - ProposalBlock *types.Block `json:"proposal_block"` - ProposalBlockParts *types.PartSet `json:"proposal_block_parts"` - LockedRound int `json:"locked_round"` - LockedBlock *types.Block `json:"locked_block"` - LockedBlockParts *types.PartSet `json:"locked_block_parts"` - ValidRound int `json:"valid_round"` // Last known round with POL for non-nil valid block. - ValidBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above. - ValidBlockParts *types.PartSet `json:"valid_block_parts"` // Last known block parts of POL metnioned above. - Votes *HeightVoteSet `json:"votes"` - CommitRound int `json:"commit_round"` // - LastCommit *types.VoteSet `json:"last_commit"` // Last precommits at Height-1 - LastValidators *types.ValidatorSet `json:"last_validators"` + Height int64 `json:"height"` // Height we are working on + Round int `json:"round"` + Step RoundStepType `json:"step"` + StartTime time.Time `json:"start_time"` + CommitTime time.Time `json:"commit_time"` // Subjective time when +2/3 precommits for Block at Round were found + Validators *types.ValidatorSet `json:"validators"` + Proposal *types.Proposal `json:"proposal"` + ProposalBlock *types.Block `json:"proposal_block"` + ProposalBlockParts *types.PartSet `json:"proposal_block_parts"` + LockedRound int `json:"locked_round"` + LockedBlock *types.Block `json:"locked_block"` + LockedBlockParts *types.PartSet `json:"locked_block_parts"` + ValidRound int `json:"valid_round"` // Last known round with POL for non-nil valid block. + ValidBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above. + ValidBlockParts *types.PartSet `json:"valid_block_parts"` // Last known block parts of POL metnioned above. + Votes *HeightVoteSet `json:"votes"` + CommitRound int `json:"commit_round"` // + LastCommit *types.VoteSet `json:"last_commit"` // Last precommits at Height-1 + LastValidators *types.ValidatorSet `json:"last_validators"` + TriggeredTimeoutPrecommit bool `json:"triggered_timeout_precommit"` } // Compressed version of the RoundState for use in RPC