diff options
Diffstat (limited to 'core/test/blocks-generator_test.go')
-rw-r--r-- | core/test/blocks-generator_test.go | 255 |
1 files changed, 205 insertions, 50 deletions
diff --git a/core/test/blocks-generator_test.go b/core/test/blocks-generator_test.go index 1baa9c5..0477664 100644 --- a/core/test/blocks-generator_test.go +++ b/core/test/blocks-generator_test.go @@ -20,6 +20,7 @@ package test import ( "sort" "testing" + "time" "github.com/dexon-foundation/dexon-consensus-core/common" "github.com/dexon-foundation/dexon-consensus-core/core/blockdb" @@ -27,27 +28,29 @@ import ( "github.com/stretchr/testify/suite" ) -type BlocksGeneratorTestCase struct { +type BlocksGeneratorTestSuite struct { suite.Suite } -func (s *BlocksGeneratorTestCase) TestGenerate() { +func (s *BlocksGeneratorTestSuite) TestGenerate() { // This test case is to make sure the generated blocks are legimate. var ( - chainNum = uint32(19) - blockNum = 50 + config = &BlocksGeneratorConfig{ + NumChains: 19, + MinBlockTimeInterval: 50 * time.Millisecond, + MaxBlockTimeInterval: 400 * time.Millisecond, + } + gen = NewBlocksGenerator(config, nil, stableRandomHash) + req = s.Require() + beginTime = time.Now().UTC() + endTime = beginTime.Add(time.Minute) ) - gen := NewBlocksGenerator(nil, stableRandomHash) db, err := blockdb.NewMemBackedBlockDB() - s.Require().Nil(err) - - keys, err := gen.Generate(chainNum, blockNum, nil, db) - s.Require().Nil(err) - s.Require().Len(keys, int(chainNum)) - + req.NoError(err) + req.NoError(gen.Generate(1, beginTime, endTime, db)) // Load all blocks in that database for further checking. iter, err := db.GetAll() - s.Require().Nil(err) + req.NoError(err) blocksByNode := make(map[types.NodeID][]*types.Block) blocksByHash := make(map[common.Hash]*types.Block) for { @@ -55,34 +58,33 @@ func (s *BlocksGeneratorTestCase) TestGenerate() { if err == blockdb.ErrIterationFinished { break } - s.Nil(err) - + req.NoError(err) // TODO(mission): Make sure each block is correctly signed once // we have a way to access core.hashBlock. - s.Require().NotEqual(block.Hash, common.Hash{}) - s.Require().NotEmpty(block.Signature) - + req.NotEqual(block.Hash, common.Hash{}) + req.NotEmpty(block.Signature) + req.Equal(block.Position.Round, uint64(1)) blocksByNode[block.ProposerID] = append(blocksByNode[block.ProposerID], &block) sort.Sort(types.ByHeight(blocksByNode[block.ProposerID])) 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 blocksByNode { lastAckingHeights := map[types.NodeID]uint64{} - s.Require().NotEmpty(blocks) - + req.NotEmpty(blocks) // Check genesis block. genesisBlock := blocks[0] - s.Equal(genesisBlock.ParentHash, common.Hash{}) - s.Equal(genesisBlock.Position.Height, uint64(0)) - s.Empty(genesisBlock.Acks) - + 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 @@ -90,9 +92,8 @@ func (s *BlocksGeneratorTestCase) TestGenerate() { if ack == block.ParentHash { parentAcked = true } - ackedBlock := blocksByHash[ack] - s.Require().NotNil(ackedBlock) + req.NotNil(ackedBlock) prevAckingHeight, exists := lastAckingHeights[ackedBlock.ProposerID] if exists { @@ -103,61 +104,70 @@ func (s *BlocksGeneratorTestCase) TestGenerate() { // // Because we iterate blocks slice from 1, // we need to add 1 to the index. - s.Equal(block.Position.Height, uint64(index+1)) + req.Equal(block.Position.Height, uint64(index+1)) } - s.True(parentAcked) + 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 *BlocksGeneratorTestCase) TestGenerateWithMaxAckCount() { +func (s *BlocksGeneratorTestSuite) TestGenerateWithMaxAckCount() { var ( - chainNum = uint32(13) - blockNum = 50 - gen = NewBlocksGenerator(nil, stableRandomHash) + config = &BlocksGeneratorConfig{ + NumChains: 13, + MinBlockTimeInterval: 0, + MaxBlockTimeInterval: 500 * time.Millisecond, + } req = s.Require() totalAckingCount = 0 totalBlockCount = 0 + genesisTime = time.Now().UTC() ) - // Generate with 0 acks. db, err := blockdb.NewMemBackedBlockDB() - req.Nil(err) - keys, err := gen.Generate( - chainNum, blockNum, MaxAckingCountGenerator(0), db) - req.Nil(err) - req.Len(keys, int(chainNum)) + req.NoError(err) + gen := NewBlocksGenerator( + config, MaxAckingCountGenerator(0), stableRandomHash) + req.NoError(gen.Generate( + 0, + genesisTime, + genesisTime.Add(50*time.Second), + db)) // Load blocks to check their acking count. iter, err := db.GetAll() - req.Nil(err) + req.NoError(err) for { block, err := iter.Next() if err == blockdb.ErrIterationFinished { break } - req.Nil(err) + req.NoError(err) if block.IsGenesis() { continue } req.Len(block.Acks, 1) } - // Generate with acks as many as possible. db, err = blockdb.NewMemBackedBlockDB() - req.Nil(err) - keys, err = gen.Generate( - chainNum, blockNum, MaxAckingCountGenerator(chainNum), db) - req.Nil(err) - req.Len(keys, int(chainNum)) + req.NoError(err) + gen = NewBlocksGenerator( + config, MaxAckingCountGenerator(config.NumChains), stableRandomHash) + req.NoError(gen.Generate( + 0, + genesisTime, + genesisTime.Add(50*time.Second), + db)) // Load blocks to verify the average acking count. iter, err = db.GetAll() - req.Nil(err) + req.NoError(err) for { block, err := iter.Next() if err == blockdb.ErrIterationFinished { break } - req.Nil(err) + req.NoError(err) if block.IsGenesis() { continue } @@ -165,9 +175,154 @@ func (s *BlocksGeneratorTestCase) TestGenerateWithMaxAckCount() { totalBlockCount++ } req.NotZero(totalBlockCount) - req.True((totalAckingCount / totalBlockCount) >= int(chainNum/2)) + 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: 0, + MaxBlockTimeInterval: 500 * time.Millisecond, + } + req = s.Require() + genesisTime = time.Now().UTC() + endTime = genesisTime.Add(100 * time.Second) + ) + gen := NewBlocksGenerator(config, nil, stableRandomHash) + db, err := blockdb.NewMemBackedBlockDB() + req.NoError(err) + req.NoError(gen.Generate( + 0, + genesisTime, + endTime, + db)) + tips, err := gen.findTips(0, db) + 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 blockdb. + // - if those rounds are continuous, they should be concated. + var ( + req = s.Require() + genesisTime = time.Now().UTC() + ) + db, err := blockdb.NewMemBackedBlockDB() + req.NoError(err) + // Generate round 0 blocks. + gen := NewBlocksGenerator(&BlocksGeneratorConfig{ + NumChains: 4, + MinBlockTimeInterval: 0, + MaxBlockTimeInterval: 500 * time.Millisecond, + }, nil, stableRandomHash) + req.NoError(gen.Generate( + 0, + genesisTime, + genesisTime.Add(10*time.Second), + db)) + tips0, err := gen.findTips(0, db) + req.NoError(err) + req.Len(tips0, 4) + // Generate round 1 blocks. + gen = NewBlocksGenerator(&BlocksGeneratorConfig{ + NumChains: 10, + MinBlockTimeInterval: 0, + MaxBlockTimeInterval: 500 * time.Millisecond, + }, nil, stableRandomHash) + req.NoError(gen.Generate( + 1, + genesisTime.Add(10*time.Second), + genesisTime.Add(20*time.Second), + db)) + tips1, err := gen.findTips(1, db) + req.NoError(err) + req.Len(tips1, 10) + // Generate round 2 blocks. + gen = NewBlocksGenerator(&BlocksGeneratorConfig{ + NumChains: 7, + MinBlockTimeInterval: 0, + MaxBlockTimeInterval: 500 * time.Millisecond, + }, nil, stableRandomHash) + req.NoError(gen.Generate( + 2, + genesisTime.Add(20*time.Second), + genesisTime.Add(30*time.Second), + db)) + tips2, err := gen.findTips(2, db) + req.NoError(err) + req.Len(tips2, 7) + // Check results, make sure tips0, tips1 are acked by correct blocks. + iter, err := db.GetAll() + req.NoError(err) + revealer, err := NewRandomRevealer(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.Next() + if err != nil { + if err == blockdb.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.Next() + if err != nil { + if err == blockdb.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(BlocksGeneratorTestCase)) + suite.Run(t, new(BlocksGeneratorTestSuite)) } |