aboutsummaryrefslogtreecommitdiffstats
path: root/core/sequencer_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/sequencer_test.go')
-rw-r--r--core/sequencer_test.go874
1 files changed, 874 insertions, 0 deletions
diff --git a/core/sequencer_test.go b/core/sequencer_test.go
new file mode 100644
index 0000000..fa8b461
--- /dev/null
+++ b/core/sequencer_test.go
@@ -0,0 +1,874 @@
+package core
+
+import (
+ "sort"
+ "testing"
+
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type SequencerTestSuite struct {
+ suite.Suite
+}
+
+func (s *SequencerTestSuite) generateValidatorIDs(
+ count int) []types.ValidatorID {
+
+ validatorIDs := []types.ValidatorID{}
+ for i := 0; i < count; i++ {
+ validatorIDs = append(validatorIDs,
+ types.ValidatorID{Hash: common.NewRandomHash()})
+ }
+
+ return validatorIDs
+}
+
+func (s *SequencerTestSuite) genRootBlock(
+ vID types.ValidatorID, acks map[common.Hash]struct{}) *types.Block {
+
+ hash := common.NewRandomHash()
+ return &types.Block{
+ ProposerID: vID,
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: acks,
+ }
+}
+
+func (s *SequencerTestSuite) checkNotDeliver(seq *sequencer, b *types.Block) {
+ hashes, eqrly, err := seq.processBlock(b)
+ s.Empty(hashes)
+ s.False(eqrly)
+ s.Nil(err)
+}
+
+func (s *SequencerTestSuite) checkNotInWorkingSet(
+ seq *sequencer, b *types.Block) {
+
+ s.NotContains(seq.pendings, b.Hash)
+ s.NotContains(seq.acked, b.Hash)
+}
+
+func (s *SequencerTestSuite) TestBlockRelation() {
+ // This test case would verify if 'acking' and 'acked'
+ // accumulated correctly.
+ //
+ // The DAG used below is:
+ // A <- B <- C
+
+ vID := types.ValidatorID{Hash: common.NewRandomHash()}
+
+ hash := common.NewRandomHash()
+ blockA := &types.Block{
+ ProposerID: vID,
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: map[common.Hash]struct{}{},
+ }
+ blockB := &types.Block{
+ ProposerID: vID,
+ ParentHash: blockA.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ blockA.Hash: struct{}{},
+ },
+ }
+ blockC := &types.Block{
+ ProposerID: vID,
+ ParentHash: blockB.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ blockB.Hash: struct{}{},
+ },
+ }
+
+ seq := newSequencer(1, 3, 5)
+ s.checkNotDeliver(seq, blockA)
+ s.checkNotDeliver(seq, blockB)
+ s.checkNotDeliver(seq, blockC)
+
+ // Check 'acked'.
+ ackedA := seq.acked[blockA.Hash]
+ s.Require().NotNil(ackedA)
+ s.Len(ackedA, 2)
+ s.Contains(ackedA, blockB.Hash)
+ s.Contains(ackedA, blockC.Hash)
+
+ ackedB := seq.acked[blockB.Hash]
+ s.Require().NotNil(ackedB)
+ s.Len(ackedB, 1)
+ s.Contains(ackedB, blockC.Hash)
+
+ s.Nil(seq.acked[blockC.Hash])
+}
+
+func (s *SequencerTestSuite) TestCreateAckingHeightVectorFromHeightVector() {
+ validators := s.generateValidatorIDs(5)
+ global := ackingStatusVector{
+ validators[0]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ validators[1]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ validators[2]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ validators[3]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ }
+
+ // For 'not existed' record in local but exist in global,
+ // should be infinity.
+ ahv := ackingStatusVector{
+ validators[0]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 2},
+ }.getAckingHeightVector(global, 0)
+ s.Len(ahv, 4)
+ s.Equal(ahv[validators[0]], uint64(0))
+ s.Equal(ahv[validators[1]], infinity)
+ s.Equal(ahv[validators[2]], infinity)
+ s.Equal(ahv[validators[3]], infinity)
+
+ // For local min exceeds global's min+k-1, should be infinity
+ hv := ackingStatusVector{
+ validators[0]: &struct{ minHeight, count uint64 }{
+ minHeight: 3, count: 1},
+ }
+ ahv = hv.getAckingHeightVector(global, 2)
+ s.Equal(ahv[validators[0]], infinity)
+ ahv = hv.getAckingHeightVector(global, 3)
+ s.Equal(ahv[validators[0]], uint64(3))
+
+ ahv = ackingStatusVector{
+ validators[0]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 3},
+ validators[1]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 3},
+ }.getAckingHeightVector(global, 5)
+ s.Len(ahv, 0)
+}
+
+func (s *SequencerTestSuite) TestCreateAckingNodeSetFromHeightVector() {
+ validators := s.generateValidatorIDs(5)
+ global := ackingStatusVector{
+ validators[0]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ validators[1]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ validators[2]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ validators[3]: &struct{ minHeight, count uint64 }{
+ minHeight: 0, count: 5},
+ }
+
+ local := ackingStatusVector{
+ validators[0]: &struct{ minHeight, count uint64 }{
+ minHeight: 1, count: 2},
+ }
+ s.Len(local.getAckingNodeSet(global, 1), 1)
+ s.Len(local.getAckingNodeSet(global, 2), 1)
+ s.Len(local.getAckingNodeSet(global, 3), 0)
+}
+
+func (s *SequencerTestSuite) TestGrade() {
+ validators := s.generateValidatorIDs(5)
+ seq := newSequencer(1, 3, 5) // K doesn't matter when calculating preceding.
+
+ ans := map[types.ValidatorID]struct{}{
+ validators[0]: struct{}{},
+ validators[1]: struct{}{},
+ validators[2]: struct{}{},
+ validators[3]: struct{}{},
+ }
+
+ ahv1 := map[types.ValidatorID]uint64{
+ validators[0]: 1,
+ validators[1]: infinity,
+ validators[2]: infinity,
+ validators[3]: infinity,
+ }
+ ahv2 := map[types.ValidatorID]uint64{
+ validators[0]: 1,
+ validators[1]: 1,
+ validators[2]: 1,
+ validators[3]: 1,
+ }
+ ahv3 := map[types.ValidatorID]uint64{
+ validators[0]: 1,
+ validators[1]: 1,
+ validators[2]: infinity,
+ validators[3]: infinity,
+ }
+ s.Equal(seq.grade(ahv2, ahv1, ans), 1)
+ s.Equal(seq.grade(ahv1, ahv2, ans), 0)
+ s.Equal(seq.grade(ahv2, ahv3, ans), -1)
+ s.Equal(seq.grade(ahv3, ahv2, ans), 0)
+}
+
+func (s *SequencerTestSuite) TestCycleDetection() {
+ // Make sure we don't get hang by cycle from
+ // block's acks.
+ validators := s.generateValidatorIDs(5)
+
+ // create blocks with cycles in acking relation.
+ cycledHash := common.NewRandomHash()
+ hash := common.NewRandomHash()
+ b00 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: map[common.Hash]struct{}{
+ cycledHash: struct{}{},
+ },
+ }
+ b01 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b00.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b00.Hash: struct{}{},
+ },
+ }
+ b02 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b01.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b01.Hash: struct{}{},
+ },
+ }
+ b03 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b02.Hash,
+ Hash: cycledHash,
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b02.Hash: struct{}{},
+ },
+ }
+
+ // Create a block acks self.
+ hash = common.NewRandomHash()
+ b10 := &types.Block{
+ ProposerID: validators[1],
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: map[common.Hash]struct{}{
+ hash: struct{}{},
+ },
+ }
+
+ // Make sure we won't hang when cycle exists.
+ seq := newSequencer(1, 3, 5)
+ s.checkNotDeliver(seq, b00)
+ s.checkNotDeliver(seq, b01)
+ s.checkNotDeliver(seq, b02)
+
+ // Should not hang in this line.
+ s.checkNotDeliver(seq, b03)
+ // Should not hang in this line
+ s.checkNotDeliver(seq, b10)
+}
+
+func (s *SequencerTestSuite) TestNotValidDAGDetection() {
+ validators := s.generateValidatorIDs(4)
+ seq := newSequencer(1, 3, 5)
+
+ hash := common.NewRandomHash()
+ b00 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: map[common.Hash]struct{}{},
+ }
+ b01 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b00.Hash,
+ Hash: common.NewRandomHash(),
+ }
+
+ // When submit to block with lower height to sequencer,
+ // caller should receive an error.
+ s.checkNotDeliver(seq, b01)
+ _, _, err := seq.processBlock(b00)
+ s.Equal(err, ErrNotValidDAG)
+}
+
+func (s *SequencerTestSuite) 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.
+ seq := newSequencer(2, 3, 5)
+ validators := s.generateValidatorIDs(5)
+
+ genNextBlock := func(b *types.Block) *types.Block {
+ return &types.Block{
+ ProposerID: b.ProposerID,
+ ParentHash: b.Hash,
+ Hash: common.NewRandomHash(),
+ Height: b.Height + 1,
+ Acks: map[common.Hash]struct{}{
+ b.Hash: struct{}{},
+ },
+ }
+ }
+
+ b00 := s.genRootBlock(validators[0], map[common.Hash]struct{}{})
+ b01 := genNextBlock(b00)
+ b02 := genNextBlock(b01)
+
+ b10 := s.genRootBlock(validators[1], map[common.Hash]struct{}{
+ b00.Hash: struct{}{},
+ })
+ b11 := genNextBlock(b10)
+ b12 := genNextBlock(b11)
+
+ b20 := s.genRootBlock(validators[2], map[common.Hash]struct{}{
+ b00.Hash: struct{}{},
+ })
+ b21 := genNextBlock(b20)
+ b22 := genNextBlock(b21)
+
+ b30 := s.genRootBlock(validators[3], map[common.Hash]struct{}{
+ b00.Hash: struct{}{},
+ })
+ b31 := genNextBlock(b30)
+ b32 := genNextBlock(b31)
+
+ // It's a valid block sequence to deliver
+ // to total ordering algorithm: DAG.
+ s.checkNotDeliver(seq, b00)
+ s.checkNotDeliver(seq, b01)
+ s.checkNotDeliver(seq, b02)
+
+ vec := seq.candidateAckingStatusVectors[b00.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 1)
+ s.Equal(vec[validators[0]].minHeight, b00.Height)
+ s.Equal(vec[validators[0]].count, uint64(3))
+
+ s.checkNotDeliver(seq, b10)
+ s.checkNotDeliver(seq, b11)
+ s.checkNotDeliver(seq, b12)
+ s.checkNotDeliver(seq, b20)
+ s.checkNotDeliver(seq, b21)
+ s.checkNotDeliver(seq, b22)
+ s.checkNotDeliver(seq, b30)
+ s.checkNotDeliver(seq, b31)
+
+ // Check the internal state before delivering.
+ s.Len(seq.candidateAckingStatusVectors, 1) // b00 is the only candidate.
+
+ vec = seq.candidateAckingStatusVectors[b00.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 4)
+ s.Equal(vec[validators[0]].minHeight, b00.Height)
+ s.Equal(vec[validators[0]].count, uint64(3))
+ s.Equal(vec[validators[1]].minHeight, b10.Height)
+ s.Equal(vec[validators[1]].count, uint64(3))
+ s.Equal(vec[validators[2]].minHeight, b20.Height)
+ s.Equal(vec[validators[2]].count, uint64(3))
+ s.Equal(vec[validators[3]].minHeight, b30.Height)
+ s.Equal(vec[validators[3]].count, uint64(2))
+
+ hashes, early, err := seq.processBlock(b32)
+ s.Require().Len(hashes, 1)
+ s.True(early)
+ s.Nil(err)
+ s.Equal(hashes[0], b00.Hash)
+
+ // Check the internal state after delivered.
+ s.Len(seq.candidateAckingStatusVectors, 4) // b01, b10, b20, b30 are candidates.
+
+ // Check b01.
+ vec = seq.candidateAckingStatusVectors[b01.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 1)
+ s.Equal(vec[validators[0]].minHeight, b01.Height)
+ s.Equal(vec[validators[0]].count, uint64(2))
+
+ // Check b10.
+ vec = seq.candidateAckingStatusVectors[b10.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 1)
+ s.Equal(vec[validators[1]].minHeight, b10.Height)
+ s.Equal(vec[validators[1]].count, uint64(3))
+
+ // Check b20.
+ vec = seq.candidateAckingStatusVectors[b20.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 1)
+ s.Equal(vec[validators[2]].minHeight, b20.Height)
+ s.Equal(vec[validators[2]].count, uint64(3))
+
+ // Check b30.
+ vec = seq.candidateAckingStatusVectors[b30.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 1)
+ s.Equal(vec[validators[3]].minHeight, b30.Height)
+ s.Equal(vec[validators[3]].count, uint64(3))
+
+ // Make sure b00 doesn't exist in current working set:
+ s.checkNotInWorkingSet(seq, b00)
+}
+
+func (s *SequencerTestSuite) TestBasicCaseForK2() {
+ // It's a handcrafted test case.
+ seq := newSequencer(2, 3, 5)
+ validators := s.generateValidatorIDs(5)
+
+ b00 := s.genRootBlock(validators[0], map[common.Hash]struct{}{})
+ b10 := s.genRootBlock(validators[1], map[common.Hash]struct{}{})
+ b20 := s.genRootBlock(
+ validators[2], map[common.Hash]struct{}{b10.Hash: struct{}{}})
+ b30 := s.genRootBlock(
+ validators[3], map[common.Hash]struct{}{b20.Hash: struct{}{}})
+ b40 := s.genRootBlock(validators[4], map[common.Hash]struct{}{})
+ b11 := &types.Block{
+ ProposerID: validators[1],
+ ParentHash: b10.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b10.Hash: struct{}{},
+ b00.Hash: struct{}{},
+ },
+ }
+ b01 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b00.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b00.Hash: struct{}{},
+ b11.Hash: struct{}{},
+ },
+ }
+ b21 := &types.Block{
+ ProposerID: validators[2],
+ ParentHash: b20.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b20.Hash: struct{}{},
+ b01.Hash: struct{}{},
+ },
+ }
+ b31 := &types.Block{
+ ProposerID: validators[3],
+ ParentHash: b30.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b30.Hash: struct{}{},
+ b21.Hash: struct{}{},
+ },
+ }
+ b02 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b01.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b01.Hash: struct{}{},
+ b21.Hash: struct{}{},
+ },
+ }
+ b12 := &types.Block{
+ ProposerID: validators[1],
+ ParentHash: b11.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b11.Hash: struct{}{},
+ b21.Hash: struct{}{},
+ },
+ }
+ b32 := &types.Block{
+ ProposerID: validators[3],
+ ParentHash: b31.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b31.Hash: struct{}{},
+ },
+ }
+ b22 := &types.Block{
+ ProposerID: validators[2],
+ ParentHash: b21.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b21.Hash: struct{}{},
+ b32.Hash: struct{}{},
+ },
+ }
+ b23 := &types.Block{
+ ProposerID: validators[2],
+ ParentHash: b22.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b22.Hash: struct{}{},
+ },
+ }
+ b03 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b02.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b02.Hash: struct{}{},
+ b22.Hash: struct{}{},
+ },
+ }
+ b13 := &types.Block{
+ ProposerID: validators[1],
+ ParentHash: b12.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b12.Hash: struct{}{},
+ b22.Hash: struct{}{},
+ },
+ }
+ b14 := &types.Block{
+ ProposerID: validators[1],
+ ParentHash: b13.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 4,
+ Acks: map[common.Hash]struct{}{
+ b13.Hash: struct{}{},
+ },
+ }
+ b41 := &types.Block{
+ ProposerID: validators[4],
+ ParentHash: b40.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b40.Hash: struct{}{},
+ },
+ }
+ b42 := &types.Block{
+ ProposerID: validators[4],
+ ParentHash: b41.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b41.Hash: struct{}{},
+ },
+ }
+
+ s.checkNotDeliver(seq, b00)
+ s.checkNotDeliver(seq, b10)
+ s.checkNotDeliver(seq, b11)
+ s.checkNotDeliver(seq, b01)
+ s.checkNotDeliver(seq, b20)
+ s.checkNotDeliver(seq, b30)
+ s.checkNotDeliver(seq, b21)
+ s.checkNotDeliver(seq, b31)
+ s.checkNotDeliver(seq, b32)
+ s.checkNotDeliver(seq, b22)
+ s.checkNotDeliver(seq, b12)
+
+ // Make sure 'acked' for current precedings is correct.
+ acked := seq.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 = seq.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(seq.candidateAckingStatusVectors, 2)
+
+ // Check b00's height vector.
+ vec := seq.candidateAckingStatusVectors[b00.Hash]
+ s.Require().NotNil(vec)
+ s.NotContains(vec, validators[4])
+ s.Equal(vec[validators[0]].minHeight, b00.Height)
+ s.Equal(vec[validators[0]].count, uint64(2))
+ s.Equal(vec[validators[1]].minHeight, b11.Height)
+ s.Equal(vec[validators[1]].count, uint64(2))
+ s.Equal(vec[validators[2]].minHeight, b21.Height)
+ s.Equal(vec[validators[2]].count, uint64(2))
+ s.Equal(vec[validators[3]].minHeight, b31.Height)
+ s.Equal(vec[validators[3]].count, uint64(2))
+
+ // Check b10's height vector.
+ vec = seq.candidateAckingStatusVectors[b10.Hash]
+ s.Require().NotNil(vec)
+ s.NotContains(vec, validators[4])
+ s.Equal(vec[validators[0]].minHeight, b01.Height)
+ s.Equal(vec[validators[0]].count, uint64(1))
+ s.Equal(vec[validators[1]].minHeight, b10.Height)
+ s.Equal(vec[validators[1]].count, uint64(3))
+ s.Equal(vec[validators[2]].minHeight, b20.Height)
+ s.Equal(vec[validators[2]].count, uint64(3))
+ s.Equal(vec[validators[3]].minHeight, b30.Height)
+ s.Equal(vec[validators[3]].count, uint64(3))
+
+ // Check the first deliver.
+ hashes, early, err := seq.processBlock(b02)
+ s.True(early)
+ s.Nil(err)
+ expected := common.Hashes{b00.Hash, b10.Hash}
+ sort.Sort(expected)
+ s.Equal(hashes, expected)
+
+ // Make sure b00, b10 are removed from current working set.
+ s.checkNotInWorkingSet(seq, b00)
+ s.checkNotInWorkingSet(seq, b10)
+
+ // Check if candidates of next round are picked correctly.
+ s.Len(seq.candidateAckingStatusVectors, 2)
+
+ // Check b01's height vector.
+ vec = seq.candidateAckingStatusVectors[b11.Hash]
+ s.Require().NotNil(vec)
+ s.NotContains(vec, validators[4])
+ s.Equal(vec[validators[0]].minHeight, b01.Height)
+ s.Equal(vec[validators[0]].count, uint64(2))
+ s.Equal(vec[validators[1]].minHeight, b11.Height)
+ s.Equal(vec[validators[1]].count, uint64(2))
+ s.Equal(vec[validators[2]].minHeight, b21.Height)
+ s.Equal(vec[validators[2]].count, uint64(2))
+ s.Equal(vec[validators[3]].minHeight, b11.Height)
+ s.Equal(vec[validators[3]].count, uint64(2))
+
+ // Check b20's height vector.
+ vec = seq.candidateAckingStatusVectors[b20.Hash]
+ s.Require().NotNil(vec)
+ s.NotContains(vec, validators[4])
+ s.Equal(vec[validators[0]].minHeight, b02.Height)
+ s.Equal(vec[validators[0]].count, uint64(1))
+ s.Equal(vec[validators[1]].minHeight, b12.Height)
+ s.Equal(vec[validators[1]].count, uint64(1))
+ s.Equal(vec[validators[2]].minHeight, b20.Height)
+ s.Equal(vec[validators[2]].count, uint64(3))
+ s.Equal(vec[validators[3]].minHeight, b30.Height)
+ s.Equal(vec[validators[3]].count, uint64(3))
+
+ s.checkNotDeliver(seq, b13)
+
+ // Check the second deliver.
+ hashes, early, err = seq.processBlock(b03)
+ s.True(early)
+ s.Nil(err)
+ expected = common.Hashes{b11.Hash, b20.Hash}
+ sort.Sort(expected)
+ s.Equal(hashes, expected)
+
+ // Make sure b11, b20 are removed from current working set.
+ s.checkNotInWorkingSet(seq, b11)
+ s.checkNotInWorkingSet(seq, b20)
+
+ // Add b40, b41, b42 to pending set.
+ s.checkNotDeliver(seq, b40)
+ s.checkNotDeliver(seq, b41)
+ s.checkNotDeliver(seq, b42)
+ s.checkNotDeliver(seq, b14)
+
+ // Make sure b01, b30, b40 are candidate in next round.
+ s.Len(seq.candidateAckingStatusVectors, 3)
+ vec = seq.candidateAckingStatusVectors[b01.Hash]
+ s.Require().NotNil(vec)
+ s.NotContains(vec, validators[4])
+ s.Equal(vec[validators[0]].minHeight, b01.Height)
+ s.Equal(vec[validators[0]].count, uint64(3))
+ s.Equal(vec[validators[1]].minHeight, b12.Height)
+ s.Equal(vec[validators[1]].count, uint64(3))
+ s.Equal(vec[validators[2]].minHeight, b21.Height)
+ s.Equal(vec[validators[2]].count, uint64(2))
+ s.Equal(vec[validators[3]].minHeight, b31.Height)
+ s.Equal(vec[validators[3]].count, uint64(2))
+
+ vec = seq.candidateAckingStatusVectors[b30.Hash]
+ s.Require().NotNil(vec)
+ s.NotContains(vec, validators[4])
+ s.Equal(vec[validators[0]].minHeight, b03.Height)
+ s.Equal(vec[validators[0]].count, uint64(1))
+ s.Equal(vec[validators[1]].minHeight, b13.Height)
+ s.Equal(vec[validators[1]].count, uint64(2))
+ s.Equal(vec[validators[2]].minHeight, b22.Height)
+ s.Equal(vec[validators[2]].count, uint64(1))
+ s.Equal(vec[validators[3]].minHeight, b30.Height)
+ s.Equal(vec[validators[3]].count, uint64(3))
+
+ vec = seq.candidateAckingStatusVectors[b40.Hash]
+ s.Require().NotNil(vec)
+ s.NotContains(vec, validators[0])
+ s.NotContains(vec, validators[1])
+ s.NotContains(vec, validators[2])
+ s.NotContains(vec, validators[3])
+ s.Equal(vec[validators[4]].minHeight, b40.Height)
+ s.Equal(vec[validators[4]].count, uint64(3))
+
+ // Make 'Acking Node Set' contains blocks from all validators,
+ // this should trigger not-early deliver.
+ hashes, early, err = seq.processBlock(b23)
+ s.False(early)
+ s.Nil(err)
+ expected = common.Hashes{b01.Hash, b30.Hash}
+ sort.Sort(expected)
+ s.Equal(expected, hashes)
+
+ // Make sure b01, b30 not in working set
+ s.checkNotInWorkingSet(seq, b01)
+ s.checkNotInWorkingSet(seq, b30)
+
+ // Make sure b21, b40 are candidates of next round.
+ s.Contains(seq.candidateAckingStatusVectors, b21.Hash)
+ s.Contains(seq.candidateAckingStatusVectors, b40.Hash)
+}
+
+func (s *SequencerTestSuite) 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
+ seq := newSequencer(0, 3, 5)
+ validators := s.generateValidatorIDs(5)
+
+ b00 := s.genRootBlock(validators[0], map[common.Hash]struct{}{})
+ b10 := s.genRootBlock(validators[1], map[common.Hash]struct{}{})
+ b20 := s.genRootBlock(validators[2], map[common.Hash]struct{}{})
+ b30 := s.genRootBlock(validators[3], map[common.Hash]struct{}{
+ b20.Hash: struct{}{},
+ })
+ b01 := &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b00.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b00.Hash: struct{}{},
+ b10.Hash: struct{}{},
+ },
+ }
+ b11 := &types.Block{
+ ProposerID: validators[1],
+ ParentHash: b10.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b10.Hash: struct{}{},
+ b20.Hash: struct{}{},
+ },
+ }
+ b21 := &types.Block{
+ ProposerID: validators[2],
+ ParentHash: b20.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b20.Hash: struct{}{},
+ },
+ }
+ b31 := &types.Block{
+ ProposerID: validators[3],
+ ParentHash: b30.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b21.Hash: struct{}{},
+ b30.Hash: struct{}{},
+ },
+ }
+ b40 := s.genRootBlock(validators[4], map[common.Hash]struct{}{
+ b31.Hash: struct{}{},
+ })
+
+ s.checkNotDeliver(seq, b00)
+ s.checkNotDeliver(seq, b10)
+ s.checkNotDeliver(seq, b20)
+ s.checkNotDeliver(seq, b30)
+ s.checkNotDeliver(seq, b01)
+ s.checkNotDeliver(seq, b11)
+ s.checkNotDeliver(seq, b21)
+ s.checkNotDeliver(seq, b31)
+
+ // Check status before delivering.
+ vec := seq.candidateAckingStatusVectors[b00.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 1)
+ s.Equal(vec[validators[0]].minHeight, b00.Height)
+ s.Equal(vec[validators[0]].count, uint64(2))
+
+ vec = seq.candidateAckingStatusVectors[b10.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 2)
+ s.Equal(vec[validators[0]].minHeight, b01.Height)
+ s.Equal(vec[validators[0]].count, uint64(1))
+ s.Equal(vec[validators[1]].minHeight, b10.Height)
+ s.Equal(vec[validators[1]].count, uint64(2))
+
+ vec = seq.candidateAckingStatusVectors[b20.Hash]
+ s.Require().NotNil(vec)
+ s.Len(vec, 3)
+ s.Equal(vec[validators[1]].minHeight, b11.Height)
+ s.Equal(vec[validators[1]].count, uint64(1))
+ s.Equal(vec[validators[2]].minHeight, b20.Height)
+ s.Equal(vec[validators[2]].count, uint64(2))
+ s.Equal(vec[validators[3]].minHeight, b30.Height)
+ s.Equal(vec[validators[3]].count, uint64(2))
+
+ // This new block should trigger non-early deliver.
+ hashes, early, err := seq.processBlock(b40)
+ s.False(early)
+ s.Nil(err)
+ expected := common.Hashes{b20.Hash}
+ sort.Sort(expected)
+ s.Equal(expected, hashes)
+
+ // Make sure b20 is no long existing in working set.
+ s.checkNotInWorkingSet(seq, b20)
+
+ // Make sure b10, b30 are candidates for next round.
+ s.Contains(seq.candidateAckingStatusVectors, b10.Hash)
+ s.Contains(seq.candidateAckingStatusVectors, b30.Hash)
+}
+
+func TestSequencer(t *testing.T) {
+ suite.Run(t, new(SequencerTestSuite))
+}