diff options
Diffstat (limited to 'core/total-ordering_test.go')
-rw-r--r-- | core/total-ordering_test.go | 1453 |
1 files changed, 0 insertions, 1453 deletions
diff --git a/core/total-ordering_test.go b/core/total-ordering_test.go deleted file mode 100644 index 24ad646..0000000 --- a/core/total-ordering_test.go +++ /dev/null @@ -1,1453 +0,0 @@ -// 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 -// <http://www.gnu.org/licenses/>. - -package core - -import ( - "sort" - "strings" - "testing" - "time" - - "github.com/dexon-foundation/dexon-consensus/common" - "github.com/dexon-foundation/dexon-consensus/core/db" - "github.com/dexon-foundation/dexon-consensus/core/test" - "github.com/dexon-foundation/dexon-consensus/core/types" - "github.com/stretchr/testify/suite" -) - -type TotalOrderingTestSuite struct { - suite.Suite -} - -func (s *TotalOrderingTestSuite) genGenesisBlock( - vIDs types.NodeIDs, - chainID uint32, - acks common.Hashes) *types.Block { - - return &types.Block{ - ProposerID: vIDs[chainID], - ParentHash: common.Hash{}, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 0, - ChainID: chainID, - }, - Acks: common.NewSortedHashes(acks), - } -} - -func (s *TotalOrderingTestSuite) performOneRun( - to *totalOrdering, revealer test.BlockRevealer) (revealed, ordered string) { - revealer.Reset() - curRound := uint64(0) - revealedDAG := make(map[common.Hash]struct{}) - for { - // Reveal next block. - b, err := revealer.NextBlock() - if err != nil { - if err == db.ErrIterationFinished { - err = nil - break - } - } - s.Require().NoError(err) - revealed += b.Hash.String() + "," - // Perform total ordering. - s.Require().NoError(to.addBlock(&b)) - for { - blocks, mode, err := to.extractBlocks() - s.Require().NoError(err) - if len(blocks) == 0 { - break - } - for _, b := range blocks { - ordered += b.Hash.String() + "," - // Make sure the round ID is increasing, and no interleave. - s.Require().True(b.Position.Round >= curRound) - curRound = b.Position.Round - // Make sure all acking blocks are already delivered. - for _, ack := range b.Acks { - s.Require().Contains(revealedDAG, ack) - } - if mode == TotalOrderingModeFlush { - // For blocks delivered by flushing, the acking relations - // would exist in one deliver set, however, only later block - // would ack previous block, not backward. - revealedDAG[b.Hash] = struct{}{} - } - } - // For blocks not delivered by flushing, the acking relations only - // exist between deliver sets. - if mode != TotalOrderingModeFlush { - for _, b := range blocks { - revealedDAG[b.Hash] = struct{}{} - } - } - } - } - return -} - -func (s *TotalOrderingTestSuite) checkRandomResult( - revealingSequence, orderingSequence map[string]struct{}) { - // Make sure we test at least two different - // revealing sequence. - s.True(len(revealingSequence) > 1) - // Make sure all ordering are equal or prefixed - // to another one. - for orderFrom := range orderingSequence { - s.True(len(orderFrom) > 0) - for orderTo := range orderingSequence { - if orderFrom == orderTo { - continue - } - ok := strings.HasPrefix(orderFrom, orderTo) || - strings.HasPrefix(orderTo, orderFrom) - s.True(ok) - } - } -} - -func (s *TotalOrderingTestSuite) checkNotDeliver(to *totalOrdering, b *types.Block) { - err := to.addBlock(b) - s.NoError(err) - blocks, mode, err := to.extractBlocks() - s.Empty(blocks) - s.Equal(mode, TotalOrderingModeNormal) - s.Nil(err) -} - -func (s *TotalOrderingTestSuite) checkHashSequence(blocks []*types.Block, hashes common.Hashes) { - sort.Sort(hashes) - for i, h := range hashes { - s.Equal(blocks[i].Hash, h) - } -} - -func (s *TotalOrderingTestSuite) checkNotInWorkingSet( - to *totalOrdering, b *types.Block) { - - s.NotContains(to.pendings, b.Hash) - s.NotContains(to.acked, b.Hash) -} - -func (s *TotalOrderingTestSuite) TestBlockRelation() { - // This test case would verify if 'acking' and 'acked' - // accumulated correctly. - // - // The DAG used below is: - // A <- B <- C - nodes := test.GenerateRandomNodeIDs(5) - vID := nodes[0] - blockA := s.genGenesisBlock(nodes, 0, common.Hashes{}) - blockB := &types.Block{ - ProposerID: vID, - ParentHash: blockA.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{blockA.Hash}), - } - blockC := &types.Block{ - ProposerID: vID, - ParentHash: blockB.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 2, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{blockB.Hash}), - } - - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 1, - PhiRatio: 0.6, - NumChains: uint32(len(nodes)), - } - genesisTime := time.Now().UTC() - to := newTotalOrdering(genesisTime, 0, genesisConfig) - s.checkNotDeliver(to, blockA) - s.checkNotDeliver(to, blockB) - s.checkNotDeliver(to, blockC) - - // Check 'acked'. - ackedA := to.acked[blockA.Hash] - s.Require().NotNil(ackedA) - s.Len(ackedA, 2) - s.Contains(ackedA, blockB.Hash) - s.Contains(ackedA, blockC.Hash) - - ackedB := to.acked[blockB.Hash] - s.Require().NotNil(ackedB) - s.Len(ackedB, 1) - s.Contains(ackedB, blockC.Hash) - - s.Nil(to.acked[blockC.Hash]) -} - -func (s *TotalOrderingTestSuite) TestCreateAckingHeightVectorFromHeightVector() { - var ( - cache = newTotalOrderingObjectCache(5) - dirties = []int{0, 1, 2, 3, 4} - ) - // Prepare global acking status. - global := &totalOrderingCandidateInfo{ - ackedStatus: []*totalOrderingHeightRecord{ - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - }} - - // For 'not existed' record in local but exist in global, - // should be infinity. - candidate := &totalOrderingCandidateInfo{ - ackedStatus: []*totalOrderingHeightRecord{ - &totalOrderingHeightRecord{minHeight: 0, count: 2}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - }} - candidate.updateAckingHeightVector(global, 0, dirties, cache) - s.Equal(candidate.cachedHeightVector[0], uint64(0)) - s.Equal(candidate.cachedHeightVector[1], infinity) - s.Equal(candidate.cachedHeightVector[2], infinity) - s.Equal(candidate.cachedHeightVector[3], infinity) - - // For local min exceeds global's min+k-1, should be infinity - candidate = &totalOrderingCandidateInfo{ - ackedStatus: []*totalOrderingHeightRecord{ - &totalOrderingHeightRecord{minHeight: 3, count: 1}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - }} - candidate.updateAckingHeightVector(global, 2, dirties, cache) - s.Equal(candidate.cachedHeightVector[0], infinity) - candidate.updateAckingHeightVector(global, 3, dirties, cache) - s.Equal(candidate.cachedHeightVector[0], uint64(3)) - - candidate = &totalOrderingCandidateInfo{ - ackedStatus: []*totalOrderingHeightRecord{ - &totalOrderingHeightRecord{minHeight: 0, count: 3}, - &totalOrderingHeightRecord{minHeight: 0, count: 3}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - }} - candidate.updateAckingHeightVector(global, 5, dirties, cache) -} - -func (s *TotalOrderingTestSuite) TestCreateAckingNodeSetFromHeightVector() { - global := &totalOrderingCandidateInfo{ - ackedStatus: []*totalOrderingHeightRecord{ - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 5}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - }} - - local := &totalOrderingCandidateInfo{ - ackedStatus: []*totalOrderingHeightRecord{ - &totalOrderingHeightRecord{minHeight: 1, count: 2}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - &totalOrderingHeightRecord{minHeight: 0, count: 0}, - }} - s.Equal(local.getAckingNodeSetLength(global, 1, 5), uint64(1)) - s.Equal(local.getAckingNodeSetLength(global, 2, 5), uint64(1)) - s.Equal(local.getAckingNodeSetLength(global, 3, 5), uint64(0)) -} - -func (s *TotalOrderingTestSuite) TestGrade() { - // This test case just fake some internal structure used - // when performing total ordering. - var ( - nodes = test.GenerateRandomNodeIDs(5) - cache = newTotalOrderingObjectCache(5) - dirtyNodes = []int{0, 1, 2, 3, 4} - ) - ansLength := uint64(len(map[types.NodeID]struct{}{ - nodes[0]: struct{}{}, - nodes[1]: struct{}{}, - nodes[2]: struct{}{}, - nodes[3]: struct{}{}, - })) - candidate1 := newTotalOrderingCandidateInfo(common.Hash{}, cache) - candidate1.cachedHeightVector = []uint64{ - 1, infinity, infinity, infinity, infinity} - candidate2 := newTotalOrderingCandidateInfo(common.Hash{}, cache) - candidate2.cachedHeightVector = []uint64{ - 1, 1, 1, 1, infinity} - candidate3 := newTotalOrderingCandidateInfo(common.Hash{}, cache) - candidate3.cachedHeightVector = []uint64{ - 1, 1, infinity, infinity, infinity} - - candidate2.updateWinRecord( - 0, candidate1, dirtyNodes, cache, 5) - s.Equal(candidate2.winRecords[0].grade(5, 3, ansLength), 1) - candidate1.updateWinRecord( - 1, candidate2, dirtyNodes, cache, 5) - s.Equal(candidate1.winRecords[1].grade(5, 3, ansLength), 0) - candidate2.updateWinRecord( - 2, candidate3, dirtyNodes, cache, 5) - s.Equal(candidate2.winRecords[2].grade(5, 3, ansLength), -1) - candidate3.updateWinRecord( - 1, candidate2, dirtyNodes, cache, 5) - s.Equal(candidate3.winRecords[1].grade(5, 3, ansLength), 0) -} - -func (s *TotalOrderingTestSuite) TestCycleDetection() { - // Make sure we don't get hang by cycle from - // block's acks. - nodes := test.GenerateRandomNodeIDs(5) - - // create blocks with cycles in acking relation. - cycledHash := common.NewRandomHash() - b00 := s.genGenesisBlock(nodes, 0, common.Hashes{cycledHash}) - b01 := &types.Block{ - ProposerID: nodes[0], - ParentHash: b00.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{b00.Hash}), - } - b02 := &types.Block{ - ProposerID: nodes[0], - ParentHash: b01.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 2, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{b01.Hash}), - } - b03 := &types.Block{ - ProposerID: nodes[0], - ParentHash: b02.Hash, - Hash: cycledHash, - Position: types.Position{ - Height: 3, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{b02.Hash}), - } - - // Create a block acks self. - b10 := s.genGenesisBlock(nodes, 1, common.Hashes{}) - b10.Acks = append(b10.Acks, b10.Hash) - - // Make sure we won't hang when cycle exists. - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 1, - PhiRatio: 0.6, - NumChains: uint32(len(nodes)), - } - genesisTime := time.Now().UTC() - to := newTotalOrdering(genesisTime, 0, genesisConfig) - s.checkNotDeliver(to, b00) - s.checkNotDeliver(to, b01) - s.checkNotDeliver(to, b02) - - // Should not hang in this line. - s.checkNotDeliver(to, b03) - // Should not hang in this line - s.checkNotDeliver(to, b10) -} - -func (s *TotalOrderingTestSuite) TestEarlyDeliver() { - // The test scenario: - // - // o o o o o - // : : : : : <- (K - 1) layers - // o o o o o - // \ v / | - // o o - // A B - // Even when B is not received, A should - // be able to be delivered. - nodes := test.GenerateRandomNodeIDs(5) - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 2, - PhiRatio: 0.6, - NumChains: uint32(len(nodes)), - } - genesisTime := time.Now().UTC() - to := newTotalOrdering(genesisTime, 0, genesisConfig) - genNextBlock := func(b *types.Block) *types.Block { - return &types.Block{ - ProposerID: b.ProposerID, - ParentHash: b.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: b.Position.Height + 1, - ChainID: b.Position.ChainID, - }, - Acks: common.NewSortedHashes(common.Hashes{b.Hash}), - } - } - - b00 := s.genGenesisBlock(nodes, 0, common.Hashes{}) - b01 := genNextBlock(b00) - b02 := genNextBlock(b01) - - b10 := s.genGenesisBlock(nodes, 1, common.Hashes{b00.Hash}) - b11 := genNextBlock(b10) - b12 := genNextBlock(b11) - - b20 := s.genGenesisBlock(nodes, 2, common.Hashes{b00.Hash}) - b21 := genNextBlock(b20) - b22 := genNextBlock(b21) - - b30 := s.genGenesisBlock(nodes, 3, common.Hashes{b00.Hash}) - b31 := genNextBlock(b30) - b32 := genNextBlock(b31) - - // It's a valid block sequence to deliver - // to total ordering algorithm: DAG. - s.checkNotDeliver(to, b00) - s.checkNotDeliver(to, b01) - s.checkNotDeliver(to, b02) - - candidate := to.candidates[0] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, - b00.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(3)) - - s.checkNotDeliver(to, b10) - s.checkNotDeliver(to, b11) - s.checkNotDeliver(to, b12) - s.checkNotDeliver(to, b20) - s.checkNotDeliver(to, b21) - s.checkNotDeliver(to, b22) - s.checkNotDeliver(to, b30) - s.checkNotDeliver(to, b31) - - // Check the internal state before delivering. - s.Len(to.candidateChainMapping, 1) // b00 is the only candidate. - - candidate = to.candidates[0] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b00.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(3)) - s.Equal(candidate.ackedStatus[1].minHeight, b10.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(3)) - s.Equal(candidate.ackedStatus[2].minHeight, b20.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(3)) - s.Equal(candidate.ackedStatus[3].minHeight, b30.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(2)) - - s.Require().NoError(to.addBlock(b32)) - blocks, mode, err := to.extractBlocks() - s.Require().Len(blocks, 1) - s.Equal(mode, TotalOrderingModeEarly) - s.Nil(err) - s.checkHashSequence(blocks, common.Hashes{b00.Hash}) - - // Check the internal state after delivered. - s.Len(to.candidateChainMapping, 4) // b01, b10, b20, b30 are candidates. - - // Check b01. - candidate = to.candidates[0] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b01.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(2)) - - // Check b10. - candidate = to.candidates[1] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[1].minHeight, b10.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(3)) - - // Check b20. - candidate = to.candidates[2] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[2].minHeight, b20.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(3)) - - // Check b30. - candidate = to.candidates[3] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[3].minHeight, b30.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(3)) - - // Make sure b00 doesn't exist in current working set: - s.checkNotInWorkingSet(to, b00) -} - -func (s *TotalOrderingTestSuite) TestBasicCaseForK2() { - // It's a handcrafted test case. - nodes := test.GenerateRandomNodeIDs(5) - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 2, - PhiRatio: 0.6, - NumChains: uint32(len(nodes)), - } - genesisTime := time.Now().UTC() - to := newTotalOrdering(genesisTime, 0, genesisConfig) - // Setup blocks. - b00 := s.genGenesisBlock(nodes, 0, common.Hashes{}) - b10 := s.genGenesisBlock(nodes, 1, common.Hashes{}) - b20 := s.genGenesisBlock(nodes, 2, common.Hashes{b10.Hash}) - b30 := s.genGenesisBlock(nodes, 3, common.Hashes{b20.Hash}) - b40 := s.genGenesisBlock(nodes, 4, common.Hashes{}) - b11 := &types.Block{ - ProposerID: nodes[1], - ParentHash: b10.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 1, - }, - Acks: common.NewSortedHashes(common.Hashes{b10.Hash, b00.Hash}), - } - b01 := &types.Block{ - ProposerID: nodes[0], - ParentHash: b00.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{b00.Hash, b11.Hash}), - } - b21 := &types.Block{ - ProposerID: nodes[2], - ParentHash: b20.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 2, - }, - Acks: common.NewSortedHashes(common.Hashes{b20.Hash, b01.Hash}), - } - b31 := &types.Block{ - ProposerID: nodes[3], - ParentHash: b30.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 3, - }, - Acks: common.NewSortedHashes(common.Hashes{b30.Hash, b21.Hash}), - } - b02 := &types.Block{ - ProposerID: nodes[0], - ParentHash: b01.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 2, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{b01.Hash, b21.Hash}), - } - b12 := &types.Block{ - ProposerID: nodes[1], - ParentHash: b11.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 2, - ChainID: 1, - }, - Acks: common.NewSortedHashes(common.Hashes{b11.Hash, b21.Hash}), - } - b32 := &types.Block{ - ProposerID: nodes[3], - ParentHash: b31.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 2, - ChainID: 3, - }, - Acks: common.NewSortedHashes(common.Hashes{b31.Hash}), - } - b22 := &types.Block{ - ProposerID: nodes[2], - ParentHash: b21.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 2, - ChainID: 2, - }, - Acks: common.NewSortedHashes(common.Hashes{b21.Hash, b32.Hash}), - } - b23 := &types.Block{ - ProposerID: nodes[2], - ParentHash: b22.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 3, - ChainID: 2, - }, - Acks: common.NewSortedHashes(common.Hashes{b22.Hash}), - } - b03 := &types.Block{ - ProposerID: nodes[0], - ParentHash: b02.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 3, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{b02.Hash, b22.Hash}), - } - b13 := &types.Block{ - ProposerID: nodes[1], - ParentHash: b12.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 3, - ChainID: 1, - }, - Acks: common.NewSortedHashes(common.Hashes{b12.Hash, b22.Hash}), - } - b14 := &types.Block{ - ProposerID: nodes[1], - ParentHash: b13.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 4, - ChainID: 1, - }, - Acks: common.NewSortedHashes(common.Hashes{b13.Hash}), - } - b41 := &types.Block{ - ProposerID: nodes[4], - ParentHash: b40.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 4, - }, - Acks: common.NewSortedHashes(common.Hashes{b40.Hash}), - } - b42 := &types.Block{ - ProposerID: nodes[4], - ParentHash: b41.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 2, - ChainID: 4, - }, - Acks: common.NewSortedHashes(common.Hashes{b41.Hash}), - } - - s.checkNotDeliver(to, b00) - s.checkNotDeliver(to, b10) - s.checkNotDeliver(to, b11) - s.checkNotDeliver(to, b01) - s.checkNotDeliver(to, b20) - s.checkNotDeliver(to, b30) - s.checkNotDeliver(to, b21) - s.checkNotDeliver(to, b31) - s.checkNotDeliver(to, b32) - s.checkNotDeliver(to, b22) - s.checkNotDeliver(to, b12) - - // Make sure 'acked' for current precedings is correct. - acked := to.acked[b00.Hash] - s.Require().NotNil(acked) - s.Len(acked, 7) - s.Contains(acked, b01.Hash) - s.Contains(acked, b11.Hash) - s.Contains(acked, b12.Hash) - s.Contains(acked, b21.Hash) - s.Contains(acked, b22.Hash) - s.Contains(acked, b31.Hash) - s.Contains(acked, b32.Hash) - - acked = to.acked[b10.Hash] - s.Require().NotNil(acked) - s.Len(acked, 9) - s.Contains(acked, b01.Hash) - s.Contains(acked, b11.Hash) - s.Contains(acked, b12.Hash) - s.Contains(acked, b20.Hash) - s.Contains(acked, b21.Hash) - s.Contains(acked, b22.Hash) - s.Contains(acked, b30.Hash) - s.Contains(acked, b31.Hash) - s.Contains(acked, b32.Hash) - - // Make sure there are 2 candidates. - s.Require().Len(to.candidateChainMapping, 2) - - // Check b00's height vector. - candidate := to.candidates[0] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b00.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(2)) - s.Equal(candidate.ackedStatus[1].minHeight, b11.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(2)) - s.Equal(candidate.ackedStatus[2].minHeight, b21.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(2)) - s.Equal(candidate.ackedStatus[3].minHeight, b31.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(2)) - s.Equal(candidate.ackedStatus[4].count, uint64(0)) - - // Check b10's height vector. - candidate = to.candidates[1] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b01.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(1)) - s.Equal(candidate.ackedStatus[1].minHeight, b10.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(3)) - s.Equal(candidate.ackedStatus[2].minHeight, b20.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(3)) - s.Equal(candidate.ackedStatus[3].minHeight, b30.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(3)) - s.Equal(candidate.ackedStatus[4].count, uint64(0)) - - // Check the first deliver. - s.NoError(to.addBlock(b02)) - blocks, mode, err := to.extractBlocks() - s.Equal(mode, TotalOrderingModeEarly) - s.Nil(err) - s.checkHashSequence(blocks, common.Hashes{b00.Hash, b10.Hash}) - - // Make sure b00, b10 are removed from current working set. - s.checkNotInWorkingSet(to, b00) - s.checkNotInWorkingSet(to, b10) - - // Check if candidates of next round are picked correctly. - s.Len(to.candidateChainMapping, 2) - - // Check b01's height vector. - candidate = to.candidates[1] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b01.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(2)) - s.Equal(candidate.ackedStatus[1].minHeight, b11.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(2)) - s.Equal(candidate.ackedStatus[2].minHeight, b21.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(2)) - s.Equal(candidate.ackedStatus[3].minHeight, b11.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(2)) - s.Equal(candidate.ackedStatus[4].count, uint64(0)) - - // Check b20's height vector. - candidate = to.candidates[2] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b02.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(1)) - s.Equal(candidate.ackedStatus[1].minHeight, b12.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(1)) - s.Equal(candidate.ackedStatus[2].minHeight, b20.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(3)) - s.Equal(candidate.ackedStatus[3].minHeight, b30.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(3)) - s.Equal(candidate.ackedStatus[4].count, uint64(0)) - - s.checkNotDeliver(to, b13) - - // Check the second deliver. - s.NoError(to.addBlock(b03)) - blocks, mode, err = to.extractBlocks() - s.Equal(mode, TotalOrderingModeEarly) - s.Nil(err) - s.checkHashSequence(blocks, common.Hashes{b11.Hash, b20.Hash}) - - // Make sure b11, b20 are removed from current working set. - s.checkNotInWorkingSet(to, b11) - s.checkNotInWorkingSet(to, b20) - - // Add b40, b41, b42 to pending set. - s.checkNotDeliver(to, b40) - s.checkNotDeliver(to, b41) - s.checkNotDeliver(to, b42) - s.checkNotDeliver(to, b14) - - // Make sure b01, b30, b40 are candidate in next round. - s.Len(to.candidateChainMapping, 3) - candidate = to.candidates[0] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b01.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(3)) - s.Equal(candidate.ackedStatus[1].minHeight, b12.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(3)) - s.Equal(candidate.ackedStatus[2].minHeight, b21.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(2)) - s.Equal(candidate.ackedStatus[3].minHeight, b31.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(2)) - s.Equal(candidate.ackedStatus[4].count, uint64(0)) - - candidate = to.candidates[3] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].minHeight, b03.Position.Height) - s.Equal(candidate.ackedStatus[0].count, uint64(1)) - s.Equal(candidate.ackedStatus[1].minHeight, b13.Position.Height) - s.Equal(candidate.ackedStatus[1].count, uint64(2)) - s.Equal(candidate.ackedStatus[2].minHeight, b22.Position.Height) - s.Equal(candidate.ackedStatus[2].count, uint64(1)) - s.Equal(candidate.ackedStatus[3].minHeight, b30.Position.Height) - s.Equal(candidate.ackedStatus[3].count, uint64(3)) - s.Equal(candidate.ackedStatus[4].count, uint64(0)) - - candidate = to.candidates[4] - s.Require().NotNil(candidate) - s.Equal(candidate.ackedStatus[0].count, uint64(0)) - s.Equal(candidate.ackedStatus[1].count, uint64(0)) - s.Equal(candidate.ackedStatus[2].count, uint64(0)) - s.Equal(candidate.ackedStatus[3].count, uint64(0)) - s.Equal(candidate.ackedStatus[4].minHeight, b40.Position.Height) - s.Equal(candidate.ackedStatus[4].count, uint64(3)) - - // Make 'Acking Node Set' contains blocks from all chains, - // this should trigger not-early deliver. - s.NoError(to.addBlock(b23)) - blocks, mode, err = to.extractBlocks() - s.Equal(mode, TotalOrderingModeNormal) - s.Nil(err) - s.checkHashSequence(blocks, common.Hashes{b01.Hash, b30.Hash}) - - // Make sure b01, b30 not in working set - s.checkNotInWorkingSet(to, b01) - s.checkNotInWorkingSet(to, b30) - - // Make sure b21, b40 are candidates of next round. - s.Equal(to.candidateChainMapping[b21.Position.ChainID], b21.Hash) - s.Equal(to.candidateChainMapping[b40.Position.ChainID], b40.Hash) -} - -func (s *TotalOrderingTestSuite) TestBasicCaseForK0() { - // This is a relatively simple test for K=0. - // - // 0 1 2 3 4 - // ------------------- - // . . . . . - // . . . . . - // o o o <- o <- o Height: 1 - // | \ | \ | | - // v v v v - // o o o <- o Height: 0 - var ( - nodes = test.GenerateRandomNodeIDs(5) - genesisConfig = &types.Config{ - RoundInterval: 1000 * time.Second, - K: 0, - PhiRatio: 0.6, - NumChains: uint32(len(nodes)), - } - req = s.Require() - genesisTime = time.Now().UTC() - to = newTotalOrdering(genesisTime, 0, genesisConfig) - ) - // Setup blocks. - b00 := s.genGenesisBlock(nodes, 0, common.Hashes{}) - b10 := s.genGenesisBlock(nodes, 1, common.Hashes{}) - b20 := s.genGenesisBlock(nodes, 2, common.Hashes{}) - b30 := s.genGenesisBlock(nodes, 3, common.Hashes{b20.Hash}) - b01 := &types.Block{ - ProposerID: nodes[0], - ParentHash: b00.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 0, - }, - Acks: common.NewSortedHashes(common.Hashes{b00.Hash, b10.Hash}), - } - b11 := &types.Block{ - ProposerID: nodes[1], - ParentHash: b10.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 1, - }, - Acks: common.NewSortedHashes(common.Hashes{b10.Hash, b20.Hash}), - } - b21 := &types.Block{ - ProposerID: nodes[2], - ParentHash: b20.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 2, - }, - Acks: common.NewSortedHashes(common.Hashes{b20.Hash}), - } - b31 := &types.Block{ - ProposerID: nodes[3], - ParentHash: b30.Hash, - Hash: common.NewRandomHash(), - Position: types.Position{ - Height: 1, - ChainID: 3, - }, - Acks: common.NewSortedHashes(common.Hashes{b21.Hash, b30.Hash}), - } - b40 := s.genGenesisBlock(nodes, 4, common.Hashes{b31.Hash}) - - s.checkNotDeliver(to, b00) - s.checkNotDeliver(to, b10) - s.checkNotDeliver(to, b20) - s.checkNotDeliver(to, b30) - s.checkNotDeliver(to, b01) - s.checkNotDeliver(to, b11) - s.checkNotDeliver(to, b21) - s.checkNotDeliver(to, b31) - - // Check candidate status before delivering. - candidate := to.candidates[0] - req.NotNil(candidate) - req.Equal(candidate.ackedStatus[0].minHeight, b00.Position.Height) - req.Equal(candidate.ackedStatus[0].count, uint64(2)) - - candidate = to.candidates[1] - req.NotNil(candidate) - req.Equal(candidate.ackedStatus[0].minHeight, b01.Position.Height) - req.Equal(candidate.ackedStatus[0].count, uint64(1)) - req.Equal(candidate.ackedStatus[1].minHeight, b10.Position.Height) - req.Equal(candidate.ackedStatus[1].count, uint64(2)) - - candidate = to.candidates[2] - req.NotNil(candidate) - req.Equal(candidate.ackedStatus[1].minHeight, b11.Position.Height) - req.Equal(candidate.ackedStatus[1].count, uint64(1)) - req.Equal(candidate.ackedStatus[2].minHeight, b20.Position.Height) - req.Equal(candidate.ackedStatus[2].count, uint64(2)) - req.Equal(candidate.ackedStatus[3].minHeight, b30.Position.Height) - req.Equal(candidate.ackedStatus[3].count, uint64(2)) - - // This new block should trigger non-early deliver. - req.NoError(to.addBlock(b40)) - blocks, mode, err := to.extractBlocks() - req.Equal(mode, TotalOrderingModeNormal) - req.Nil(err) - s.checkHashSequence(blocks, common.Hashes{b20.Hash}) - - // Make sure b20 is no long existing in working set. - s.checkNotInWorkingSet(to, b20) - - // Make sure b10, b30 are candidates for next round. - req.Equal(to.candidateChainMapping[b00.Position.ChainID], b00.Hash) - req.Equal(to.candidateChainMapping[b10.Position.ChainID], b10.Hash) - req.Equal(to.candidateChainMapping[b30.Position.ChainID], b30.Hash) -} - -func (s *TotalOrderingTestSuite) baseTestRandomlyGeneratedBlocks( - totalOrderingConstructor func(chainNum uint32) *totalOrdering, - chainNum uint32, - ackingCountGenerator func() int, - repeat int) { - var ( - req = s.Require() - revealingSequence = make(map[string]struct{}) - orderingSequence = make(map[string]struct{}) - genesisTime = time.Now().UTC() - ) - gen := test.NewBlocksGenerator(&test.BlocksGeneratorConfig{ - NumChains: chainNum, - MinBlockTimeInterval: 250 * time.Millisecond, - }, ackingCountGenerator) - dbInst, err := db.NewMemBackedDB() - req.NoError(err) - req.NoError(gen.Generate( - 0, - genesisTime, - genesisTime.Add(20*time.Second), - dbInst)) - iter, err := dbInst.GetAllBlocks() - req.NoError(err) - // Setup a revealer that would reveal blocks forming - // valid DAGs. - revealer, err := test.NewRandomDAGBlockRevealer(iter) - req.NoError(err) - // TODO (mission): make this part run concurrently. - for i := 0; i < repeat; i++ { - revealed, ordered := s.performOneRun( - totalOrderingConstructor(chainNum), revealer) - revealingSequence[revealed] = struct{}{} - orderingSequence[ordered] = struct{}{} - } - s.checkRandomResult(revealingSequence, orderingSequence) -} - -func (s *TotalOrderingTestSuite) TestRandomlyGeneratedBlocks() { - var ( - numChains = uint32(20) - phi = float32(0.5) - repeat = 15 - genesisTime = time.Now().UTC() - ) - if testing.Short() { - numChains = 10 - phi = 0.5 - repeat = 3 - } - - ackingCountGenerators := []func() int{ - nil, // Acking frequency with normal distribution. - test.MaxAckingCountGenerator(0), // Low acking frequency. - test.MaxAckingCountGenerator(numChains), // High acking frequency. - } - - // Test based on different acking frequency. - for _, gen := range ackingCountGenerators { - // Test for K=0. - constructor := func(numChains uint32) *totalOrdering { - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 0, - PhiRatio: phi, - NumChains: numChains, - } - to := newTotalOrdering(genesisTime, 0, genesisConfig) - // Add config for next round. - s.Require().NoError(to.appendConfig(1, &types.Config{ - K: 0, - PhiRatio: 0.5, - NumChains: numChains, - })) - return to - } - s.baseTestRandomlyGeneratedBlocks(constructor, numChains, gen, repeat) - // Test for K=1. - constructor = func(numChains uint32) *totalOrdering { - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 1, - PhiRatio: phi, - NumChains: numChains, - } - to := newTotalOrdering(genesisTime, 0, genesisConfig) - // Add config for next round. - s.Require().NoError(to.appendConfig(1, &types.Config{ - K: 1, - PhiRatio: 0.5, - NumChains: numChains, - })) - return to - } - s.baseTestRandomlyGeneratedBlocks(constructor, numChains, gen, repeat) - // Test for K=2. - constructor = func(numChains uint32) *totalOrdering { - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 2, - PhiRatio: phi, - NumChains: numChains, - } - to := newTotalOrdering(genesisTime, 0, genesisConfig) - s.Require().NoError(to.appendConfig(1, &types.Config{ - K: 2, - PhiRatio: 0.5, - NumChains: numChains, - })) - return to - } - s.baseTestRandomlyGeneratedBlocks(constructor, numChains, gen, repeat) - // Test for K=3. - constructor = func(numChains uint32) *totalOrdering { - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 3, - PhiRatio: phi, - NumChains: numChains, - } - to := newTotalOrdering(genesisTime, 0, genesisConfig) - s.Require().NoError(to.appendConfig(1, &types.Config{ - K: 3, - PhiRatio: 0.5, - NumChains: numChains, - })) - return to - } - s.baseTestRandomlyGeneratedBlocks(constructor, numChains, gen, repeat) - } -} - -func (s *TotalOrderingTestSuite) baseTestForRoundChange( - repeat int, configs []*types.Config) { - var ( - req = s.Require() - genesisTime = time.Now().UTC() - ) - dbInst, err := db.NewMemBackedDB() - req.NoError(err) - // Generate DAG for rounds. - // NOTE: the last config won't be tested, just avoid panic - // when round switching. - begin := genesisTime - for roundID, config := range configs[:len(configs)-1] { - gen := test.NewBlocksGenerator( - test.NewBlocksGeneratorConfig(config), nil) - end := begin.Add(config.RoundInterval) - req.NoError(gen.Generate(uint64(roundID), begin, end, dbInst)) - begin = end - } - // Test, just dump the whole DAG to total ordering and make sure - // repeating it won't change it delivered sequence. - iter, err := dbInst.GetAllBlocks() - req.NoError(err) - revealer, err := test.NewRandomDAGBlockRevealer(iter) - req.NoError(err) - revealingSequence := make(map[string]struct{}) - orderingSequence := make(map[string]struct{}) - for i := 0; i < repeat; i++ { - to := newTotalOrdering(genesisTime, 0, configs[0]) - for roundID, config := range configs[1:] { - req.NoError(to.appendConfig(uint64(roundID+1), config)) - } - revealed, ordered := s.performOneRun(to, revealer) - revealingSequence[revealed] = struct{}{} - orderingSequence[ordered] = struct{}{} - } - s.checkRandomResult(revealingSequence, orderingSequence) -} - -func (s *TotalOrderingTestSuite) TestNumChainsChanged() { - // This test fixes K, Phi, and changes 'numChains' for each round. - fix := func(c *types.Config) *types.Config { - c.K = 1 - c.PhiRatio = 0.5 - c.MinBlockInterval = 250 * time.Millisecond - c.RoundInterval = 10 * time.Second - return c - } - var ( - repeat = 7 - configs = []*types.Config{ - fix(&types.Config{NumChains: 7}), - fix(&types.Config{NumChains: 10}), - fix(&types.Config{NumChains: 4}), - fix(&types.Config{NumChains: 13}), - fix(&types.Config{NumChains: 4}), - } - ) - s.baseTestForRoundChange(repeat, configs) -} - -func (s *TotalOrderingTestSuite) TestPhiChanged() { - // This test fixes K, numChains, and changes Phi each round. - fix := func(c *types.Config) *types.Config { - c.K = 1 - c.NumChains = 10 - c.MinBlockInterval = 250 * time.Millisecond - c.RoundInterval = 10 * time.Second - return c - } - var ( - repeat = 7 - configs = []*types.Config{ - fix(&types.Config{PhiRatio: 0.5}), - fix(&types.Config{PhiRatio: 0.7}), - fix(&types.Config{PhiRatio: 1}), - fix(&types.Config{PhiRatio: 0.5}), - fix(&types.Config{PhiRatio: 0.7}), - } - ) - s.baseTestForRoundChange(repeat, configs) -} - -func (s *TotalOrderingTestSuite) TestKChanged() { - // This test fixes phi, numChains, and changes K each round. - fix := func(c *types.Config) *types.Config { - c.NumChains = 10 - c.PhiRatio = 0.7 - c.MinBlockInterval = 250 * time.Millisecond - c.RoundInterval = 10 * time.Second - return c - } - var ( - repeat = 7 - configs = []*types.Config{ - fix(&types.Config{K: 0}), - fix(&types.Config{K: 4}), - fix(&types.Config{K: 1}), - fix(&types.Config{K: 2}), - fix(&types.Config{K: 0}), - } - ) - s.baseTestForRoundChange(repeat, configs) -} - -func (s *TotalOrderingTestSuite) TestRoundChanged() { - // This test changes everything when round changed. - fix := func(c *types.Config) *types.Config { - c.MinBlockInterval = 250 * time.Millisecond - c.RoundInterval = 10 * time.Second - return c - } - var ( - repeat = 7 - configs = []*types.Config{ - fix(&types.Config{K: 0, NumChains: 4, PhiRatio: 0.5}), - fix(&types.Config{K: 1, NumChains: 10, PhiRatio: 0.7}), - fix(&types.Config{K: 2, NumChains: 7, PhiRatio: 0.8}), - fix(&types.Config{K: 0, NumChains: 4, PhiRatio: 0.5}), - fix(&types.Config{K: 3, NumChains: 10, PhiRatio: 0.8}), - fix(&types.Config{K: 0, NumChains: 7, PhiRatio: 0.5}), - fix(&types.Config{K: 2, NumChains: 13, PhiRatio: 0.7}), - } - ) - s.baseTestForRoundChange(repeat, configs) -} - -// TestSync tests sync mode of total ordering, which is started not from genesis -// but some blocks which is on the cut of delivery set. -func (s *TotalOrderingTestSuite) TestSync() { - var ( - req = s.Require() - numChains = uint32(19) - genesisTime = time.Now().UTC() - ) - gen := test.NewBlocksGenerator(&test.BlocksGeneratorConfig{ - NumChains: numChains, - MinBlockTimeInterval: 250 * time.Millisecond, - }, nil) - dbInst, err := db.NewMemBackedDB() - req.NoError(err) - err = gen.Generate(0, genesisTime, genesisTime.Add(20*time.Second), dbInst) - req.NoError(err) - iter, err := dbInst.GetAllBlocks() - req.NoError(err) - - revealer, err := test.NewRandomDAGBlockRevealer(iter) - req.NoError(err) - - genesisConfig := &types.Config{ - RoundInterval: 1000 * time.Second, - K: 0, - PhiRatio: 0.67, - NumChains: numChains, - } - to1 := newTotalOrdering(genesisTime, 0, genesisConfig) - s.Require().NoError(to1.appendConfig(1, &types.Config{ - K: 0, - PhiRatio: 0.5, - NumChains: numChains, - })) - deliveredBlockSets1 := [][]*types.Block{} - for { - b, err := revealer.NextBlock() - if err != nil { - if err == db.ErrIterationFinished { - err = nil - break - } - } - s.Require().NoError(err) - s.Require().NoError(to1.addBlock(&b)) - bs, _, err := to1.extractBlocks() - s.Require().Nil(err) - if len(bs) > 0 { - deliveredBlockSets1 = append(deliveredBlockSets1, bs) - } - } - // Run new total ordering again. - offset := len(deliveredBlockSets1) / 2 - to2 := newTotalOrdering(genesisTime, 0, genesisConfig) - s.Require().NoError(to2.appendConfig(1, &types.Config{ - K: 0, - PhiRatio: 0.5, - NumChains: numChains, - })) - deliveredBlockSets2 := [][]*types.Block{} - for i := offset; i < len(deliveredBlockSets1); i++ { - for _, b := range deliveredBlockSets1[i] { - req.NoError(to2.addBlock(b)) - bs, _, err := to2.extractBlocks() - req.NoError(err) - if len(bs) > 0 { - deliveredBlockSets2 = append(deliveredBlockSets2, bs) - } - } - } - // Check deliver1 and deliver2. - for i := 0; i < len(deliveredBlockSets2); i++ { - req.Equal(len(deliveredBlockSets1[offset+i]), len(deliveredBlockSets2[i])) - for j := 0; j < len(deliveredBlockSets2[i]); j++ { - req.Equal(deliveredBlockSets1[offset+i][j], deliveredBlockSets2[i][j]) - } - } -} - -func (s *TotalOrderingTestSuite) TestSyncWithConfigChange() { - var ( - req = s.Require() - genesisTime = time.Now().UTC() - roundInterval = 30 * time.Second - ) - - // Configs for round change, notice configs[0] is the same as genesisConfig. - configs := []*types.Config{ - &types.Config{ - K: 0, - PhiRatio: 0.67, - NumChains: uint32(19), - RoundInterval: roundInterval, - }, - &types.Config{ - K: 2, - PhiRatio: 0.5, - NumChains: uint32(17), - RoundInterval: roundInterval, - }, - &types.Config{ - K: 0, - PhiRatio: 0.8, - NumChains: uint32(22), - RoundInterval: roundInterval, - }, - &types.Config{ - K: 3, - PhiRatio: 0.5, - NumChains: uint32(25), - RoundInterval: roundInterval, - }, - &types.Config{ - K: 1, - PhiRatio: 0.7, - NumChains: uint32(20), - RoundInterval: roundInterval, - }, - // Sometimes all generated blocks would be delivered, thus the total - // ordering module would proceed to next round. We need to prepare - // one additional configuration for that possibility. - &types.Config{ - K: 1, - PhiRatio: 0.7, - NumChains: uint32(20), - RoundInterval: roundInterval, - }, - } - - blocks := []*types.Block{} - dbInst, err := db.NewMemBackedDB() - req.NoError(err) - - for i, cfg := range configs[:len(configs)-1] { - gen := test.NewBlocksGenerator(&test.BlocksGeneratorConfig{ - NumChains: cfg.NumChains, - MinBlockTimeInterval: 250 * time.Millisecond, - }, nil) - err = gen.Generate( - uint64(i), - genesisTime.Add(time.Duration(i)*cfg.RoundInterval), - genesisTime.Add(time.Duration(i+1)*cfg.RoundInterval), - dbInst, - ) - req.NoError(err) - } - - iter, err := dbInst.GetAllBlocks() - req.NoError(err) - - revealer, err := test.NewRandomDAGBlockRevealer(iter) - req.NoError(err) - - for { - b, err := revealer.NextBlock() - if err != nil { - if err == db.ErrIterationFinished { - err = nil - break - } - } - req.NoError(err) - blocks = append(blocks, &b) - } - - to1 := newTotalOrdering(genesisTime, 0, configs[0]) - for i, cfg := range configs[1:] { - req.NoError(to1.appendConfig(uint64(i+1), cfg)) - } - - deliveredBlockSets1 := [][]*types.Block{} - deliveredBlockModes := []uint32{} - for _, b := range blocks { - req.NoError(to1.addBlock(b)) - bs, mode, err := to1.extractBlocks() - req.NoError(err) - if len(bs) > 0 { - deliveredBlockSets1 = append(deliveredBlockSets1, bs) - deliveredBlockModes = append(deliveredBlockModes, mode) - } - } - - // Find the offset that can be used in the second run of total ordering. And - // the mode of deliver set should not be "flush". - for test := 0; test < 3; test++ { - offset := len(deliveredBlockSets1) * (3 + test) / 7 - for deliveredBlockModes[offset] == TotalOrderingModeFlush { - offset++ - } - offsetRound := deliveredBlockSets1[offset][0].Position.Round - // The range of offset's round should not be the first nor the last round, - // or nothing is tested. - req.True(uint64(0) < offsetRound && offsetRound < uint64(len(configs)-1)) - - to2 := newTotalOrdering(genesisTime, 0, configs[0]) - for i, cfg := range configs[1:] { - req.NoError(to2.appendConfig(uint64(i+1), cfg)) - } - // Skip useless configs. - for i := uint64(0); i < deliveredBlockSets1[offset][0].Position.Round; i++ { - to2.switchRound() - } - // Run total ordering again from offset. - deliveredBlockSets2 := [][]*types.Block{} - for i := offset; i < len(deliveredBlockSets1); i++ { - for _, b := range deliveredBlockSets1[i] { - req.NoError(to2.addBlock(b)) - bs, _, err := to2.extractBlocks() - req.NoError(err) - if len(bs) > 0 { - deliveredBlockSets2 = append(deliveredBlockSets2, bs) - } - } - } - // Check deliver1 and deliver2. - for i := 0; i < len(deliveredBlockSets2); i++ { - req.Equal(len(deliveredBlockSets1[offset+i]), len(deliveredBlockSets2[i])) - for j := 0; j < len(deliveredBlockSets2[i]); j++ { - req.Equal(deliveredBlockSets1[offset+i][j], deliveredBlockSets2[i][j]) - } - } - } -} - -func (s *TotalOrderingTestSuite) TestModeDefinition() { - // Make sure the copied deliver mode definition is identical between - // core and test package. - s.Require().Equal(TotalOrderingModeError, test.TotalOrderingModeError) - s.Require().Equal(TotalOrderingModeNormal, test.TotalOrderingModeNormal) - s.Require().Equal(TotalOrderingModeEarly, test.TotalOrderingModeEarly) - s.Require().Equal(TotalOrderingModeFlush, test.TotalOrderingModeFlush) -} - -func TestTotalOrdering(t *testing.T) { - suite.Run(t, new(TotalOrderingTestSuite)) -} |