aboutsummaryrefslogblamecommitdiffstats
path: root/core/test/blocks-generator_test.go
blob: bcbd7491f23f8d11c3ace5e1b947e4b8e4289aad (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 test

import (
    "sort"
    "testing"
    "time"

    "github.com/dexon-foundation/dexon-consensus/common"
    "github.com/dexon-foundation/dexon-consensus/core/db"
    "github.com/dexon-foundation/dexon-consensus/core/types"
    "github.com/stretchr/testify/suite"
)

type BlocksGeneratorTestSuite struct {
    suite.Suite
}

func (s *BlocksGeneratorTestSuite) TestGenerate() {
    // This test case is to make sure the generated blocks are legimate.
    var (
        config = &BlocksGeneratorConfig{
            NumChains:            19,
            MinBlockTimeInterval: 200 * time.Millisecond,
        }
        gen       = NewBlocksGenerator(config, nil)
        req       = s.Require()
        beginTime = time.Now().UTC()
        endTime   = beginTime.Add(time.Minute)
    )
    dbInst, err := db.NewMemBackedDB()
    req.NoError(err)
    req.NoError(gen.Generate(1, beginTime, endTime, dbInst))
    // Load all blocks in that database for further checking.
    iter, err := dbInst.GetAllBlocks()
    req.NoError(err)
    blocksByChain := make(map[uint32][]*types.Block)
    blocksByHash := make(map[common.Hash]*types.Block)
    for {
        block, err := iter.NextBlock()
        if err == db.ErrIterationFinished {
            break
        }
        req.NoError(err)
        // TODO(mission): Make sure each block is correctly signed once
        //                we have a way to access core.hashBlock.
        req.NotEqual(block.Hash, common.Hash{})
        if !block.IsEmpty() {
            req.NotEmpty(block.Signature)
        }
        req.Equal(block.Position.Round, uint64(1))
        blocksByChain[block.Position.ChainID] =
            append(blocksByChain[block.Position.ChainID], &block)
        sort.Sort(types.ByPosition(blocksByChain[block.Position.ChainID]))
        blocksByHash[block.Hash] = &block
    }
    // Make sure these two rules are hold for these blocks:
    //  - No backward acking: the later block should only ack new blocks
    //                        compared to its parent block.
    //  - Parent Ack: always ack its parent block.
    //  - Timestamp: timestamp are increasing, and with valid interval to
    //               previous block.
    //  - The last block of each chain should pass endTime.
    //  - No Acks in genesis bloc
    for _, blocks := range blocksByChain {
        lastAckingHeights := map[uint32]uint64{}
        req.NotEmpty(blocks)
        // Check genesis block.
        genesisBlock := blocks[0]
        req.Equal(genesisBlock.ParentHash, common.Hash{})
        req.Equal(genesisBlock.Position.Height, uint64(0))
        req.Empty(genesisBlock.Acks)
        // Check normal blocks.
        for index, block := range blocks[1:] {
            parentAcked := false
            for _, ack := range block.Acks {
                if ack == block.ParentHash {
                    parentAcked = true
                }
                ackedBlock := blocksByHash[ack]
                req.NotNil(ackedBlock)
                prevAckingHeight, exists :=
                    lastAckingHeights[ackedBlock.Position.ChainID]
                if exists {
                    s.True(prevAckingHeight < ackedBlock.Position.Height)
                }
                lastAckingHeights[ackedBlock.Position.ChainID] =
                    ackedBlock.Position.Height
                // Block Height should always incremental by 1.
                //
                // Because we iterate blocks slice from 1,
                // we need to add 1 to the index.
                req.Equal(block.Position.Height, uint64(index+1))
            }
            req.True(parentAcked)
        }
        // The block time of the last block should be after end time.
        req.True(blocks[len(blocks)-1].Timestamp.After(endTime))
    }
}

func (s *BlocksGeneratorTestSuite) TestGenerateWithMaxAckCount() {
    var (
        config = &BlocksGeneratorConfig{
            NumChains:            13,
            MinBlockTimeInterval: 250 * time.Millisecond,
        }
        req              = s.Require()
        totalAckingCount = 0
        totalBlockCount  = 0
        genesisTime      = time.Now().UTC()
    )
    // Generate with 0 acks.
    dbInst, err := db.NewMemBackedDB()
    req.NoError(err)
    gen := NewBlocksGenerator(config, MaxAckingCountGenerator(0))
    req.NoError(gen.Generate(
        0,
        genesisTime,
        genesisTime.Add(50*time.Second),
        dbInst))
    // Load blocks to check their acking count.
    iter, err := dbInst.GetAllBlocks()
    req.NoError(err)
    for {
        block, err := iter.NextBlock()
        if err == db.ErrIterationFinished {
            break
        }
        req.NoError(err)
        if block.IsGenesis() {
            continue
        }
        req.Len(block.Acks, 1)
    }
    // Generate with acks as many as possible.
    dbInst, err = db.NewMemBackedDB()
    req.NoError(err)
    gen = NewBlocksGenerator(config, MaxAckingCountGenerator(config.NumChains))
    req.NoError(gen.Generate(
        0,
        genesisTime,
        genesisTime.Add(50*time.Second),
        dbInst))
    // Load blocks to verify the average acking count.
    iter, err = dbInst.GetAllBlocks()
    req.NoError(err)
    for {
        block, err := iter.NextBlock()
        if err == db.ErrIterationFinished {
            break
        }
        req.NoError(err)
        if block.IsGenesis() {
            continue
        }
        totalAckingCount += len(block.Acks)
        totalBlockCount++
    }
    req.NotZero(totalBlockCount)
    req.True((totalAckingCount / totalBlockCount) >= int(config.NumChains/2))
}

// TestFindTips make sure findTips works as expected.
func (s *BlocksGeneratorTestSuite) TestFindTips() {
    var (
        config = &BlocksGeneratorConfig{
            NumChains:            10,
            MinBlockTimeInterval: 250 * time.Millisecond,
        }
        req         = s.Require()
        genesisTime = time.Now().UTC()
        endTime     = genesisTime.Add(100 * time.Second)
    )
    gen := NewBlocksGenerator(config, nil)
    dbInst, err := db.NewMemBackedDB()
    req.NoError(err)
    req.NoError(gen.Generate(
        0,
        genesisTime,
        endTime,
        dbInst))
    tips, err := gen.findTips(0, dbInst)
    req.NoError(err)
    req.Len(tips, int(config.NumChains))
    for _, b := range tips {
        req.True(b.Timestamp.After(endTime))
    }
}

func (s *BlocksGeneratorTestSuite) TestConcateBlocksFromRounds() {
    // This test case run these steps:
    //  - generate blocks by round but sharing one db.
    //  - if those rounds are continuous, they should be concated.
    var (
        req         = s.Require()
        genesisTime = time.Now().UTC()
    )
    dbInst, err := db.NewMemBackedDB()
    req.NoError(err)
    // Generate round 0 blocks.
    gen := NewBlocksGenerator(&BlocksGeneratorConfig{
        NumChains:            4,
        MinBlockTimeInterval: 250 * time.Millisecond,
    }, MaxAckingCountGenerator(4))
    req.NoError(gen.Generate(
        0,
        genesisTime,
        genesisTime.Add(10*time.Second),
        dbInst))
    tips0, err := gen.findTips(0, dbInst)
    req.NoError(err)
    req.Len(tips0, 4)
    // Generate round 1 blocks.
    gen = NewBlocksGenerator(&BlocksGeneratorConfig{
        NumChains:            10,
        MinBlockTimeInterval: 250 * time.Millisecond,
    }, MaxAckingCountGenerator(10))
    req.NoError(gen.Generate(
        1,
        genesisTime.Add(10*time.Second),
        genesisTime.Add(20*time.Second),
        dbInst))
    tips1, err := gen.findTips(1, dbInst)
    req.NoError(err)
    req.Len(tips1, 10)
    // Generate round 2 blocks.
    gen = NewBlocksGenerator(&BlocksGeneratorConfig{
        NumChains:            7,
        MinBlockTimeInterval: 250 * time.Millisecond,
    }, MaxAckingCountGenerator(7))
    req.NoError(gen.Generate(
        2,
        genesisTime.Add(20*time.Second),
        genesisTime.Add(30*time.Second),
        dbInst))
    tips2, err := gen.findTips(2, dbInst)
    req.NoError(err)
    req.Len(tips2, 7)
    // Check results, make sure tips0, tips1 are acked by correct blocks.
    iter, err := dbInst.GetAllBlocks()
    req.NoError(err)
    revealer, err := NewRandomBlockRevealer(iter)
    req.NoError(err)
    removeTip := func(tips map[uint32]*types.Block, b *types.Block) {
        toRemove := []uint32{}
        for chainID, tip := range tips {
            if b.ParentHash == tip.Hash {
                req.Equal(b.Position.Height, tip.Position.Height+1)
                req.Equal(b.Position.Round, tip.Position.Round+1)
                req.True(b.IsAcking(tip.Hash))
                toRemove = append(toRemove, chainID)
            }
        }
        for _, ID := range toRemove {
            delete(tips, ID)
        }
    }
    // Make sure all tips are acked by loading blocks from db
    // and check them one by one.
    for {
        b, err := revealer.NextBlock()
        if err != nil {
            if err == db.ErrIterationFinished {
                err = nil
                break
            }
            req.NoError(err)
        }
        switch b.Position.Round {
        case 1:
            removeTip(tips0, &b)
        case 2:
            removeTip(tips1, &b)
        }
    }
    req.Empty(tips0)
    req.Len(tips1, 3)
    req.Contains(tips1, uint32(7))
    req.Contains(tips1, uint32(8))
    req.Contains(tips1, uint32(9))
    // Check the acking frequency of last round, it might be wrong.
    totalBlockCount := 0
    totalAckCount := 0
    revealer.Reset()
    for {
        b, err := revealer.NextBlock()
        if err != nil {
            if err == db.ErrIterationFinished {
                err = nil
                break
            }
            req.NoError(err)
        }
        if b.Position.Round != 2 {
            continue
        }
        totalBlockCount++
        totalAckCount += len(b.Acks)
    }
    // At least all blocks can ack some non-parent block.
    req.True(totalAckCount/totalBlockCount >= 2)
}

func TestBlocksGenerator(t *testing.T) {
    suite.Run(t, new(BlocksGeneratorTestSuite))
}