aboutsummaryrefslogtreecommitdiffstats
path: root/core/consensus_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/consensus_test.go')
-rw-r--r--core/consensus_test.go305
1 files changed, 305 insertions, 0 deletions
diff --git a/core/consensus_test.go b/core/consensus_test.go
new file mode 100644
index 0000000..7ce06e9
--- /dev/null
+++ b/core/consensus_test.go
@@ -0,0 +1,305 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core 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-core 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-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "sort"
+ "testing"
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus-core/blockdb"
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/test"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type ConsensusTestSuite struct {
+ suite.Suite
+}
+
+func (s *ConsensusTestSuite) prepareGenesisBlock(
+ proposerID types.ValidatorID,
+ gov Governance) *types.Block {
+
+ hash := common.NewRandomHash()
+ block := &types.Block{
+ ProposerID: proposerID,
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: make(map[common.Hash]struct{}),
+ Timestamps: make(map[types.ValidatorID]time.Time),
+ }
+ for vID := range gov.GetValidatorSet() {
+ block.Timestamps[vID] = time.Time{}
+ }
+ block.Timestamps[proposerID] = time.Now().UTC()
+ return block
+}
+
+func (s *ConsensusTestSuite) prepareConsensus(gov *test.Gov) (
+ *test.App, *Consensus) {
+
+ app := test.NewApp()
+ db, err := blockdb.NewMemBackedBlockDB()
+ s.Require().Nil(err)
+ con := NewConsensus(app, gov, db)
+ return app, con
+}
+
+func (s *ConsensusTestSuite) TestSimpleDeliverBlock() {
+ // This test scenario:
+ // o o o o <- this layer makes older blocks strongly acked.
+ // |x|x|x| <- lots of acks.
+ // o | o o <- this layer would be sent to total ordering.
+ // |\|/|-|
+ // | o | | <- the only block which is acked by all other blocks
+ // |/|\|\| at the same height.
+ // o o o o <- genesis blocks
+ // 0 1 2 3 <- index of validator ID
+ //
+ // This test case only works for Total Ordering with K=0.
+ var (
+ minInterval = 50 * time.Millisecond
+ gov = test.NewGov(4, 1000)
+ req = s.Require()
+ validators []types.ValidatorID
+ )
+
+ for vID := range gov.GetValidatorSet() {
+ validators = append(validators, vID)
+ }
+
+ // Setup core.Consensus and test.App.
+ objs := map[types.ValidatorID]*struct {
+ app *test.App
+ con *Consensus
+ }{}
+ for _, vID := range validators {
+ app, con := s.prepareConsensus(gov)
+ objs[vID] = &struct {
+ app *test.App
+ con *Consensus
+ }{app, con}
+ }
+ // It's a helper function to emit one block
+ // to all core.Consensus objects.
+ broadcast := func(b *types.Block) {
+ for _, obj := range objs {
+ req.Nil(obj.con.ProcessBlock(b))
+ }
+ }
+ // Genesis blocks
+ b00 := s.prepareGenesisBlock(validators[0], gov)
+ time.Sleep(minInterval)
+ b10 := s.prepareGenesisBlock(validators[1], gov)
+ time.Sleep(minInterval)
+ b20 := s.prepareGenesisBlock(validators[2], gov)
+ time.Sleep(minInterval)
+ b30 := s.prepareGenesisBlock(validators[3], gov)
+ broadcast(b00)
+ broadcast(b10)
+ broadcast(b20)
+ broadcast(b30)
+ // Setup b11.
+ time.Sleep(minInterval)
+ b11 := &types.Block{
+ ProposerID: validators[1],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[1]].con.PrepareBlock(b11))
+ req.Len(b11.Acks, 4)
+ req.Contains(b11.Acks, b00.Hash)
+ req.Contains(b11.Acks, b10.Hash)
+ req.Contains(b11.Acks, b20.Hash)
+ req.Contains(b11.Acks, b30.Hash)
+ broadcast(b11)
+ // Setup b01.
+ time.Sleep(minInterval)
+ b01 := &types.Block{
+ ProposerID: validators[0],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[0]].con.PrepareBlock(b01))
+ req.Len(b01.Acks, 4)
+ req.Contains(b01.Acks, b11.Hash)
+ // Setup b21.
+ time.Sleep(minInterval)
+ b21 := &types.Block{
+ ProposerID: validators[2],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[2]].con.PrepareBlock(b21))
+ req.Len(b21.Acks, 4)
+ req.Contains(b21.Acks, b11.Hash)
+ // Setup b31.
+ time.Sleep(minInterval)
+ b31 := &types.Block{
+ ProposerID: validators[3],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[3]].con.PrepareBlock(b31))
+ req.Len(b31.Acks, 4)
+ req.Contains(b31.Acks, b11.Hash)
+ // Broadcast other height=1 blocks.
+ broadcast(b01)
+ broadcast(b21)
+ broadcast(b31)
+ // Setup height=2 blocks.
+ // Setup b02.
+ time.Sleep(minInterval)
+ b02 := &types.Block{
+ ProposerID: validators[0],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[0]].con.PrepareBlock(b02))
+ req.Len(b02.Acks, 3)
+ req.Contains(b02.Acks, b01.Hash)
+ req.Contains(b02.Acks, b21.Hash)
+ req.Contains(b02.Acks, b31.Hash)
+ // Setup b12.
+ time.Sleep(minInterval)
+ b12 := &types.Block{
+ ProposerID: validators[1],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[1]].con.PrepareBlock(b12))
+ req.Len(b12.Acks, 4)
+ req.Contains(b12.Acks, b01.Hash)
+ req.Contains(b12.Acks, b11.Hash)
+ req.Contains(b12.Acks, b21.Hash)
+ req.Contains(b12.Acks, b31.Hash)
+ // Setup b22.
+ time.Sleep(minInterval)
+ b22 := &types.Block{
+ ProposerID: validators[2],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[2]].con.PrepareBlock(b22))
+ req.Len(b22.Acks, 3)
+ req.Contains(b22.Acks, b01.Hash)
+ req.Contains(b22.Acks, b21.Hash)
+ req.Contains(b22.Acks, b31.Hash)
+ // Setup b32.
+ time.Sleep(minInterval)
+ b32 := &types.Block{
+ ProposerID: validators[3],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(objs[validators[3]].con.PrepareBlock(b32))
+ req.Len(b32.Acks, 3)
+ req.Contains(b32.Acks, b01.Hash)
+ req.Contains(b32.Acks, b21.Hash)
+ req.Contains(b32.Acks, b31.Hash)
+ // Broadcast blocks at height=2.
+ broadcast(b02)
+ broadcast(b12)
+ broadcast(b22)
+ broadcast(b32)
+
+ // Verify the cached status of each app.
+ verify := func(app *test.App) {
+ // Check blocks that are strongly acked.
+ req.Contains(app.Acked, b00.Hash)
+ req.Contains(app.Acked, b10.Hash)
+ req.Contains(app.Acked, b20.Hash)
+ req.Contains(app.Acked, b30.Hash)
+ req.Contains(app.Acked, b01.Hash)
+ req.Contains(app.Acked, b11.Hash)
+ req.Contains(app.Acked, b21.Hash)
+ req.Contains(app.Acked, b31.Hash)
+ // Genesis blocks are delivered by total ordering as a set.
+ delivered0 := common.Hashes{b00.Hash, b10.Hash, b20.Hash, b30.Hash}
+ sort.Sort(delivered0)
+ req.Len(app.TotalOrdered, 2)
+ req.Equal(app.TotalOrdered[0].BlockHashes, delivered0)
+ req.False(app.TotalOrdered[0].Early)
+ // b11 is the sencond set delivered by total ordering.
+ delivered1 := common.Hashes{b11.Hash}
+ sort.Sort(delivered1)
+ req.Equal(app.TotalOrdered[1].BlockHashes, delivered1)
+ req.False(app.TotalOrdered[1].Early)
+ // Check generated timestamps.
+ req.Contains(app.Delivered, b00.Hash)
+ req.Contains(app.Delivered, b10.Hash)
+ req.Contains(app.Delivered, b20.Hash)
+ req.Contains(app.Delivered, b30.Hash)
+ req.Contains(app.Delivered, b11.Hash)
+ // Check timestamps, there is no direct way to know which block is
+ // selected as main chain, we can only detect it by making sure
+ // its ConsensusTimestamp is not interpolated.
+ t, err := getMedianTime(b11)
+ req.Nil(err)
+ req.Equal(t, app.Delivered[b11.Hash])
+ }
+ for _, obj := range objs {
+ verify(obj.app)
+ }
+}
+
+func (s *ConsensusTestSuite) TestPrepareBlock() {
+ // This test case would test these steps:
+ // - Add all genesis blocks into lattice.
+ // - Make sure Consensus.PrepareBlock would attempt to ack
+ // all genesis blocks.
+ // - Add the prepared block into lattice.
+ // - Make sure Consensus.PrepareBlock would only attempt to
+ // ack the prepared block.
+ var (
+ gov = test.NewGov(4, 1000)
+ req = s.Require()
+ validators []types.ValidatorID
+ )
+ for vID := range gov.GetValidatorSet() {
+ validators = append(validators, vID)
+ }
+ _, con := s.prepareConsensus(gov)
+ b00 := s.prepareGenesisBlock(validators[0], gov)
+ b10 := s.prepareGenesisBlock(validators[1], gov)
+ b20 := s.prepareGenesisBlock(validators[2], gov)
+ b30 := s.prepareGenesisBlock(validators[3], gov)
+ req.Nil(con.ProcessBlock(b00))
+ req.Nil(con.ProcessBlock(b10))
+ req.Nil(con.ProcessBlock(b20))
+ req.Nil(con.ProcessBlock(b30))
+ b11 := &types.Block{
+ ProposerID: validators[1],
+ Hash: common.NewRandomHash(),
+ }
+ // Sleep to make sure 'now' is slower than b10's timestamp.
+ time.Sleep(100 * time.Millisecond)
+ req.Nil(con.PrepareBlock(b11))
+ // Make sure we would assign 'now' to the timestamp belongs to
+ // the proposer.
+ req.True(
+ b11.Timestamps[validators[1]].Sub(
+ b10.Timestamps[validators[1]]) > 100*time.Millisecond)
+ req.Nil(con.ProcessBlock(b11))
+ b12 := &types.Block{
+ ProposerID: validators[1],
+ Hash: common.NewRandomHash(),
+ }
+ req.Nil(con.PrepareBlock(b12))
+ req.Len(b12.Acks, 1)
+ req.Contains(b12.Acks, b11.Hash)
+}
+
+func TestConsensus(t *testing.T) {
+ suite.Run(t, new(ConsensusTestSuite))
+}