From eccdddbff92c1588e628f874d73ae557351c76f7 Mon Sep 17 00:00:00 2001 From: Jimmy Hu Date: Wed, 31 Oct 2018 12:51:54 +0800 Subject: core: Add a repeat vote state. (#280) --- core/agreement-state.go | 44 ++++++++++++++++++++++++++++++++++++-------- core/agreement-state_test.go | 30 ++++++++++++++++++++++++++---- core/agreement_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/core/agreement-state.go b/core/agreement-state.go index 56c6c27..77195ac 100644 --- a/core/agreement-state.go +++ b/core/agreement-state.go @@ -19,7 +19,6 @@ package core import ( "fmt" - "math" "github.com/dexon-foundation/dexon-consensus-core/common" "github.com/dexon-foundation/dexon-consensus-core/core/types" @@ -40,6 +39,7 @@ const ( statePreCommit stateCommit stateForward + stateRepeatVote ) var nullBlockHash = common.Hash{} @@ -127,26 +127,54 @@ func (s *commitState) nextState() (agreementState, error) { } else { hash = skipBlockHash } - s.a.recv.ProposeVote(&types.Vote{ + vote := &types.Vote{ Type: types.VoteCom, BlockHash: hash, Period: s.a.period, - }) - return newForwardState(s.a), nil + } + s.a.recv.ProposeVote(vote) + return newForwardState(s.a, vote), nil } // ----- ForwardState ----- type forwardState struct { - a *agreementData + a *agreementData + vote *types.Vote } -func newForwardState(a *agreementData) *forwardState { - return &forwardState{a: a} +func newForwardState(a *agreementData, vote *types.Vote) *forwardState { + return &forwardState{ + a: a, + vote: vote, + } } func (s *forwardState) state() agreementStateType { return stateForward } -func (s *forwardState) clocks() int { return math.MaxInt32 } +func (s *forwardState) clocks() int { return 4 } func (s *forwardState) nextState() (agreementState, error) { + return newRepeatVoteState(s.a, s.vote), nil +} + +// ----- RepeatVoteState ----- +// repeateVoteState is a special state to ensure the assumption in the consensus +// algorithm that every vote will eventually arrive for all nodes. +type repeatVoteState struct { + a *agreementData + vote *types.Vote +} + +func newRepeatVoteState(a *agreementData, vote *types.Vote) *repeatVoteState { + return &repeatVoteState{ + a: a, + vote: vote, + } +} + +func (s *repeatVoteState) state() agreementStateType { return stateRepeatVote } +func (s *repeatVoteState) clocks() int { return 4 } + +func (s *repeatVoteState) nextState() (agreementState, error) { + s.a.recv.ProposeVote(s.vote) return s, nil } diff --git a/core/agreement-state_test.go b/core/agreement-state_test.go index 429e124..862ac3e 100644 --- a/core/agreement-state_test.go +++ b/core/agreement-state_test.go @@ -233,14 +233,36 @@ func (s *AgreementStateTestSuite) TestCommitState() { func (s *AgreementStateTestSuite) TestForwardState() { a := s.newAgreement(4) - state := newForwardState(a.data) + vote := &types.Vote{ + BlockHash: common.NewRandomHash(), + } + state := newForwardState(a.data, vote) s.Equal(stateForward, state.state()) - s.True(state.clocks() > 100000) + s.Equal(4, state.clocks()) - // nextState() should return instantly without doing anything. - _, err := state.nextState() + newState, err := state.nextState() s.Require().Nil(err) s.Require().Len(s.voteChan, 0) + s.Equal(stateRepeatVote, newState.state()) +} + +func (s *AgreementStateTestSuite) TestRepeatVoteState() { + a := s.newAgreement(4) + vote := &types.Vote{ + BlockHash: common.NewRandomHash(), + } + state := newRepeatVoteState(a.data, vote) + s.Equal(stateRepeatVote, state.state()) + s.Equal(4, state.clocks()) + + for i := 0; i < 5; i++ { + newState, err := state.nextState() + s.Require().Nil(err) + s.Require().Len(s.voteChan, 1) + proposedVote := <-s.voteChan + s.Equal(vote, proposedVote) + s.Equal(stateRepeatVote, newState.state()) + } } func TestAgreementState(t *testing.T) { diff --git a/core/agreement_test.go b/core/agreement_test.go index e4e05cd..0e3814e 100644 --- a/core/agreement_test.go +++ b/core/agreement_test.go @@ -183,6 +183,49 @@ func (s *AgreementTestSuite) TestSimpleConfirm() { s.Equal(blockHash, confirmBlock) } +func (s *AgreementTestSuite) TestPartitionOnCommitVote() { + a := s.newAgreement(4) + // InitialState + a.nextState() + // PreCommitState + s.Require().Len(s.blockChan, 1) + blockHash := <-s.blockChan + block, exist := s.block[blockHash] + s.Require().True(exist) + s.Require().NoError(a.processBlock(block)) + s.Require().Len(s.voteChan, 1) + vote := <-s.voteChan + s.Equal(types.VoteInit, vote.Type) + s.Equal(blockHash, vote.BlockHash) + a.nextState() + // CommitState + s.Require().Len(s.voteChan, 1) + vote = <-s.voteChan + s.Equal(types.VotePreCom, vote.Type) + s.Equal(blockHash, vote.BlockHash) + for nID := range s.auths { + v := s.copyVote(vote, nID) + s.Require().NoError(a.processVote(v)) + } + a.nextState() + // ForwardState + s.Require().Len(s.voteChan, 1) + vote = <-s.voteChan + s.Equal(types.VoteCom, vote.Type) + s.Equal(blockHash, vote.BlockHash) + s.Equal(blockHash, a.data.lockValue) + s.Equal(uint64(1), a.data.lockRound) + // RepeateVoteState + a.nextState() + // The agreement does not receive others commit vote, it will keep re-sending. + for i := 0; i < 5; i++ { + a.nextState() + s.Require().Len(s.voteChan, 1) + proposedVote := <-s.voteChan + s.Equal(vote, proposedVote) + } +} + func (s *AgreementTestSuite) TestFastForwardCond1() { votes := 0 a := s.newAgreement(4) -- cgit v1.2.3