aboutsummaryrefslogtreecommitdiffstats
path: root/core/total-ordering_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/total-ordering_test.go')
-rw-r--r--core/total-ordering_test.go1453
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))
-}