aboutsummaryrefslogblamecommitdiffstats
path: root/core/total-ordering_test.go
blob: 24ad646f2c7887733e090c71170832f173e6cc9e (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                    
  
                                                                        
                                                                               


                                                                              
                                                                         


                                                                          

                                                                           
                                                      

                                  



              
                 
                 
              
 
                                                            
                                                             

                                                                


                                           
                                    


                   
                                                 
                           
                       
                                          
 
                            
                                          

                                                   
                                         

                                         
                  
                                                   


         
                                               
                                                                                    

                             
                                                     

                                     
                                              
                               
                                                           






                                                 





                                                               
                         
                                                  




















                                                                                                    
                         
























                                                                      
                                                                                     


                                               
                       
                                              


                  






                                                                                                 

                                                      
 

                                          

 
                                                      




                                                              


                                                              



                                                   
                                         

                                   
                  
                                                                         




                                                   
                                         

                                   
                  
                                                                         

         




                                                  
         
                                       
                                                             


                                     

                         
                                       




                                       
                                       



                                       
                                    

 
                                                                                 
             

                                                        
         

                                              





                                                                           
                  


                                                                 
                                                 





                                                                           
                  
                                                                     



                                                           

                                                                     
                                                





                                                                           
                  
                                                                     
                                                          
                                                                     
                                                           

                                                





                                                                           
                  
                                                                     

 
                                                                            
                                              





                                                                           


                                             





                                                                           
                  


                                                                      

 
                                              

                                                                
             


                                                           
         




                                                          
           








                                                                         
 
                                   
                                                    
                                                                   
                                   
                                                    
                                                                   
                                   
                                                    
                                                                    
                                   
                                                    
                                                                   

 
                                                       

                                                    
                                              


                                                        
                                                                     
                            
                                     

                                                   
                                         

                                   
                  
                                                                      

                            
                                     

                                                   
                                         

                                   
                  
                                                                      

                            
                                     

                                       
                                         

                                   
                  
                                                                      


                                    
                                                           
                                             

                                                     




                                                  
         
                                       
                                                             


                                  

                                        
                                  
                                       
                                  

 
                                                     









                                                 
                                              




                                                  
         
                                       
                                                             




                                                           
                                                 

                                                               
                          
                                                                            


                 
                                                           


                                
                                                                   


                                
                                                                   


                                
                                                                   




                                                 


                                  
 
                                     
                                     
                                                   
                                    
                                                          
 







                                  

                                                      
                                                                        
 
                                    
                                     







                                                                        
 

                                               
                                  
                                             
                  
                                                            

                                                    
                                                                                

                     
                                    
                                     

                                                                        

                     
                                    
                                     

                                                                        

                     
                                    
                                     

                                                                        

                     
                                    
                                     

                                                                        

                                                              
                                       

 
                                                       
                                        
                                              




                                                  
         
                                       
                                                             
                        




                                                                   
                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                      

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                      

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                      

                            
                                     

                                                   
                                         

                                   
                  
                                                                      

                            
                                     

                                                   
                                         

                                   
                  
                                                                      

         










                                  

                                                               
                                   









                                   
                                  












                                            
                                                    

                                     
                                     
                                     








                                                                        

                                     
                                    
                                     








                                                                        

                                   

                                               
                                             
                  
                                                                      

                                                                   

                                       

                                                                  
                                          

                                     
                                    
                                     








                                                                        

                                     
                                    
                                     








                                                                        
 
                                  

                                    

                                              
                                             
                  
                                                                      

                                                                   

                                       

                                            



                                  

                                                               

                                          
                                     










                                                                        
                                     










                                                                        
                                     







                                                                        
                                                 

                                              
                                              
                  
                                                                      

                                                

                                       

                                                           

                                                                         

 
                                                       









                                                    
             
                                                             




                                                          
                 

                                              
                                                                             
         
                        



                                                                   
                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                                

                            
                                     

                                                   
                                         

                                   
                  
                                                                      

                            
                                     

                                                   
                                         

                                   
                  
                                                                                
         
                                                                   
 







                                  
 
                                                    
                                     
                             

                                                                          
 
                                    
                             





                                                                          
                             





                                                                          

                                                           

                                               
                                                
                    
                                                            

                                                            
                                       

                                                            


                                                                           

 
                                                                 

                                                                      
                                        
                     

                                               

                                                             
                                                    
         

                                                                   
                                                             
                                
                                          




                                                

                                          
                        

                                                            
                                                             
                        
                                                           
                                     

                                                                     


                                                        
                                                                

 
                                                                
             



                                              
         
                            
                              
                         

                          
 
                                              
                                                                                                      

                                                                                  
         



                                                    
                                                                      




                                                                  
                         
                                                                             






                                                                             
                 
                                                                                      
                                
                                                                     




                                                                  
                         
                                                                             






                                                                             
                 
                                                                                      
                                
                                                                     




                                                                  
                         
                                                                             





                                                                             
                 
                                                                                      
                                
                                                                     




                                                                  
                         
                                                                             





                                                                             
                 
                                                                                      
         

 





                                                        
                                          






                                                                  
                                                                   
                                                      
                                                                              



                                                                        
                                          
                        
                                                             



                                                      
                                                                  














                                                                               
                                                           




















                                                                    
                                                           




















                                                                    
                                                           


















                                                           
                                                           

















                                                                               


                                                                                






                                                                   
                                                             
               
                                          
                        
                                                                                   
                        
                                          

                        
                                                             

                        




                                                  
         
                                                              




                                                              
                                                 
             
                                              
                               
                                                           




                                         

                                                     

                                    
                                                                             


                                        
                                              
                                                              




                                                              


                                                            

                                                         

                                        
                                                                                     



                                       














                                                                                              































                                                                                    








                                                                                    


                                  
                                          

                        
                                                      


                                                                           
                       



                                                                              
                               



                                
                                          

                        
                                                             


                        
                                              
                               
                                                           







                                           
                                                           






                                                               

                                                    


















                                                                                          
                                                                   










                                                                                            

                                                                 











                                                                                                      



                 








                                                                                

                                                 
 
// 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))
}