// Copyright 2018 The dexon-consensus Authors // This file is part of the dexon-consensus library. // // The dexon-consensus library is free software: you can redistribute it // and/or modify it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 of the License, // or (at your option) any later version. // // The dexon-consensus library is distributed in the hope that it will be // useful, but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser // General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the dexon-consensus library. If not, see // . package core import ( "testing" "github.com/stretchr/testify/suite" "github.com/dexon-foundation/dexon-consensus/common" "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa" "github.com/dexon-foundation/dexon-consensus/core/types" "github.com/dexon-foundation/dexon-consensus/core/utils" ) type AgreementStateTestSuite struct { suite.Suite ID types.NodeID signers map[types.NodeID]*utils.Signer voteChan chan *types.Vote blockChan chan common.Hash confirmChan chan common.Hash block map[common.Hash]*types.Block } type agreementStateTestReceiver struct { s *AgreementStateTestSuite leader *leaderSelector } func (r *agreementStateTestReceiver) VerifyPartialSignature(*types.Vote) bool { return true } func (r *agreementStateTestReceiver) ProposeVote(vote *types.Vote) { r.s.voteChan <- vote } func (r *agreementStateTestReceiver) ProposeBlock() common.Hash { block := r.s.proposeBlock(r.leader) r.s.blockChan <- block.Hash return block.Hash } func (r *agreementStateTestReceiver) ConfirmBlock(block common.Hash, _ map[types.NodeID]*types.Vote) { r.s.confirmChan <- block } func (r *agreementStateTestReceiver) PullBlocks(common.Hashes) {} func (r *agreementStateTestReceiver) ReportForkVote(v1, v2 *types.Vote) {} func (r *agreementStateTestReceiver) ReportForkBlock(b1, b2 *types.Block) {} func (s *AgreementStateTestSuite) proposeBlock( leader *leaderSelector) *types.Block { block := &types.Block{ ProposerID: s.ID, Position: types.Position{Height: types.GenesisHeight}, Hash: common.NewRandomHash(), } s.Require().NoError(s.signers[s.ID].SignCRS(block, leader.hashCRS)) s.block[block.Hash] = block return block } func (s *AgreementStateTestSuite) prepareVote( nID types.NodeID, voteType types.VoteType, blockHash common.Hash, period uint64) ( vote *types.Vote) { vote = types.NewVote(voteType, blockHash, period) s.Require().NoError(s.signers[nID].SignVote(vote)) return } func (s *AgreementStateTestSuite) SetupTest() { prvKey, err := ecdsa.NewPrivateKey() s.Require().NoError(err) s.ID = types.NewNodeID(prvKey.PublicKey()) s.signers = map[types.NodeID]*utils.Signer{ s.ID: utils.NewSigner(prvKey), } s.voteChan = make(chan *types.Vote, 100) s.blockChan = make(chan common.Hash, 100) s.confirmChan = make(chan common.Hash, 100) s.block = make(map[common.Hash]*types.Block) } func (s *AgreementStateTestSuite) newAgreement(numNode int) *agreement { logger := &common.NullLogger{} leader := newLeaderSelector(func(*types.Block) (bool, error) { return true, nil }, logger) notarySet := make(map[types.NodeID]struct{}) for i := 0; i < numNode-1; i++ { prvKey, err := ecdsa.NewPrivateKey() s.Require().NoError(err) nID := types.NewNodeID(prvKey.PublicKey()) notarySet[nID] = struct{}{} s.signers[nID] = utils.NewSigner(prvKey) } notarySet[s.ID] = struct{}{} agreement := newAgreement( s.ID, &agreementStateTestReceiver{ s: s, leader: leader, }, leader, s.signers[s.ID], logger, ) agreement.restart(notarySet, utils.GetBAThreshold(&types.Config{ NotarySetSize: uint32(len(notarySet)), }), types.Position{Height: types.GenesisHeight}, types.NodeID{}, common.NewRandomHash()) return agreement } func (s *AgreementStateTestSuite) TestFastStateLeader() { a := s.newAgreement(4) state := newFastState(a.data) s.Equal(stateFast, state.state()) s.Equal(0, state.clocks()) // Proposing a new block if it's leader. a.data.period = 1 a.data.isLeader = true newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.blockChan, 1) proposedBlock := <-s.blockChan s.NotEqual(common.Hash{}, proposedBlock) s.Require().NoError(a.processBlock(s.block[proposedBlock])) s.Require().Len(s.voteChan, 1) proposedVote := <-s.voteChan s.Equal(proposedBlock, proposedVote.BlockHash) s.Equal(types.VoteFast, proposedVote.Type) s.Equal(stateFastVote, newState.state()) } func (s *AgreementStateTestSuite) TestFastStateNotLeader() { a := s.newAgreement(4) state := newFastState(a.data) s.Equal(stateFast, state.state()) s.Equal(0, state.clocks()) // Not proposing any block if it's not leader. a.data.period = 1 a.data.isLeader = false newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.blockChan, 0) s.Equal(stateFastVote, newState.state()) } func (s *AgreementStateTestSuite) TestFastVoteState() { a := s.newAgreement(4) state := newFastVoteState(a.data) s.Equal(stateFastVote, state.state()) s.Equal(3, state.clocks()) // The vote proposed is not implemented inside state. a.data.period = 1 newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.voteChan, 0) s.Equal(stateInitial, newState.state()) } func (s *AgreementStateTestSuite) TestInitialState() { a := s.newAgreement(4) state := newInitialState(a.data) s.Equal(stateInitial, state.state()) s.Equal(0, state.clocks()) // Proposing a new block. a.data.period = 1 newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.blockChan, 1) proposedBlock := <-s.blockChan s.NotEqual(common.Hash{}, proposedBlock) s.Require().NoError(a.processBlock(s.block[proposedBlock])) s.Equal(statePreCommit, newState.state()) } func (s *AgreementStateTestSuite) TestPreCommitState() { a := s.newAgreement(4) state := newPreCommitState(a.data) s.Equal(statePreCommit, state.state()) s.Equal(2, state.clocks()) blocks := make([]*types.Block, 3) for i := range blocks { blocks[i] = s.proposeBlock(a.data.leader) prv, err := ecdsa.NewPrivateKey() s.Require().NoError(err) blocks[i].ProposerID = types.NewNodeID(prv.PublicKey()) s.Require().NoError(utils.NewSigner(prv).SignCRS( blocks[i], a.data.leader.hashCRS)) s.Require().NoError(a.processBlock(blocks[i])) } // If lockvalue == null, propose preCom-vote for the leader block. a.data.lockValue = types.NullBlockHash a.data.period = 1 newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.voteChan, 1) vote := <-s.voteChan s.Equal(types.VotePreCom, vote.Type) s.NotEqual(types.SkipBlockHash, vote.BlockHash) s.Equal(stateCommit, newState.state()) // If lockvalue == SKIP, propose preCom-vote for the leader block. a.data.lockValue = types.SkipBlockHash a.data.period = 2 newState, err = state.nextState() s.Require().NoError(err) s.Require().Len(s.voteChan, 1) vote = <-s.voteChan s.Equal(types.VotePreCom, vote.Type) s.NotEqual(types.SkipBlockHash, vote.BlockHash) s.Equal(stateCommit, newState.state()) // Else, preCom-vote on lockValue. a.data.period = 3 hash := common.NewRandomHash() a.data.lockValue = hash newState, err = state.nextState() s.Require().NoError(err) s.Require().Len(s.voteChan, 1) vote = <-s.voteChan s.Equal(types.VotePreCom, vote.Type) s.Equal(hash, vote.BlockHash) s.Equal(stateCommit, newState.state()) } func (s *AgreementStateTestSuite) TestCommitState() { a := s.newAgreement(4) state := newCommitState(a.data) s.Equal(stateCommit, state.state()) s.Equal(2, state.clocks()) // Commit on lock value. a.data.period = 1 a.data.lockValue = common.NewRandomHash() newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.voteChan, 1) vote := <-s.voteChan s.Equal(types.VoteCom, vote.Type) s.Equal(a.data.lockValue, vote.BlockHash) s.Equal(stateForward, newState.state()) } func (s *AgreementStateTestSuite) TestForwardState() { a := s.newAgreement(4) state := newForwardState(a.data) s.Equal(stateForward, state.state()) s.Equal(4, state.clocks()) newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.voteChan, 0) s.Equal(statePullVote, newState.state()) } func (s *AgreementStateTestSuite) TestPullVoteState() { a := s.newAgreement(4) state := newPullVoteState(a.data) s.Equal(statePullVote, state.state()) s.Equal(4, state.clocks()) newState, err := state.nextState() s.Require().NoError(err) s.Require().Len(s.voteChan, 0) s.Equal(statePullVote, newState.state()) } func TestAgreementState(t *testing.T) { suite.Run(t, new(AgreementStateTestSuite)) }