diff options
Diffstat (limited to 'core/test')
-rw-r--r-- | core/test/app.go | 158 | ||||
-rw-r--r-- | core/test/app_test.go | 142 | ||||
-rw-r--r-- | core/test/utils.go | 9 |
3 files changed, 293 insertions, 16 deletions
diff --git a/core/test/app.go b/core/test/app.go index 55ba7c5..aedcba9 100644 --- a/core/test/app.go +++ b/core/test/app.go @@ -18,44 +18,79 @@ package test import ( + "fmt" + "sync" "time" "github.com/dexon-foundation/dexon-consensus-core/common" ) +var ( + // ErrEmptyDeliverSequence means there is no delivery event in this App + // instance. + ErrEmptyDeliverSequence = fmt.Errorf("emptry deliver sequence") + // ErrMismatchBlockHashSequence means the delivering sequence between two App + // instances are different. + ErrMismatchBlockHashSequence = fmt.Errorf("mismatch block hash sequence") + // ErrMismatchConsensusTime means the consensus timestamp between two blocks + // with the same hash from two App instances are different. + ErrMismatchConsensusTime = fmt.Errorf("mismatch consensus time") + // ErrApplicationIntegrityFailed means the internal datum in a App instance + // is not integrated. + ErrApplicationIntegrityFailed = fmt.Errorf("application integrity failed") + // ErrConsensusTimestampOutOfOrder means the later delivered block has + // consensus timestamp older than previous block. + ErrConsensusTimestampOutOfOrder = fmt.Errorf( + "consensus timestamp out of order") + // ErrDeliveredBlockNotAcked means some block delivered (confirmed) but + // not strongly acked. + ErrDeliveredBlockNotAcked = fmt.Errorf("delivered block not acked") + // ErrMismatchTotalOrderingAndDelivered mean the sequence of total ordering + // and delivered are different. + ErrMismatchTotalOrderingAndDelivered = fmt.Errorf( + "mismatch total ordering and delivered sequence") +) + +type totalOrderDeliver struct { + BlockHashes common.Hashes + Early bool +} + // App implements Application interface for testing purpose. type App struct { - Acked map[common.Hash]struct{} - TotalOrdered []*struct { - BlockHashes common.Hashes - Early bool - } - Delivered map[common.Hash]time.Time + Acked map[common.Hash]struct{} + ackedLock sync.RWMutex + TotalOrdered []*totalOrderDeliver + totalOrderedLock sync.RWMutex + Delivered map[common.Hash]time.Time + DeliverSequence common.Hashes + deliveredLock sync.RWMutex } // NewApp constructs a TestApp instance. func NewApp() *App { return &App{ - Acked: make(map[common.Hash]struct{}), - TotalOrdered: []*struct { - BlockHashes common.Hashes - Early bool - }{}, - Delivered: make(map[common.Hash]time.Time), + Acked: make(map[common.Hash]struct{}), + TotalOrdered: []*totalOrderDeliver{}, + Delivered: make(map[common.Hash]time.Time), + DeliverSequence: common.Hashes{}, } } // StronglyAcked implements Application interface. func (app *App) StronglyAcked(blockHash common.Hash) { + app.ackedLock.Lock() + defer app.ackedLock.Unlock() + app.Acked[blockHash] = struct{}{} } // TotalOrderingDeliver implements Application interface. func (app *App) TotalOrderingDeliver(blockHashes common.Hashes, early bool) { - app.TotalOrdered = append(app.TotalOrdered, &struct { - BlockHashes common.Hashes - Early bool - }{ + app.totalOrderedLock.Lock() + defer app.totalOrderedLock.Unlock() + + app.TotalOrdered = append(app.TotalOrdered, &totalOrderDeliver{ BlockHashes: blockHashes, Early: early, }) @@ -63,5 +98,96 @@ func (app *App) TotalOrderingDeliver(blockHashes common.Hashes, early bool) { // DeliverBlock implements Application interface. func (app *App) DeliverBlock(blockHash common.Hash, timestamp time.Time) { + app.deliveredLock.Lock() + defer app.deliveredLock.Unlock() + app.Delivered[blockHash] = timestamp + app.DeliverSequence = append(app.DeliverSequence, blockHash) +} + +// Compare performs these checks against another App instance +// and return erros if not passed: +// - deliver sequence by comparing block hashes. +// - consensus timestamp of each block are equal. +func (app *App) Compare(other *App) error { + app.deliveredLock.RLock() + defer app.deliveredLock.RUnlock() + other.deliveredLock.RLock() + defer other.deliveredLock.RUnlock() + + minLength := len(app.DeliverSequence) + if minLength > len(other.DeliverSequence) { + minLength = len(other.DeliverSequence) + } + if minLength == 0 { + return ErrEmptyDeliverSequence + } + for idx, h := range app.DeliverSequence[:minLength] { + hOther := other.DeliverSequence[idx] + if hOther != h { + return ErrMismatchBlockHashSequence + } + if app.Delivered[h] != other.Delivered[h] { + return ErrMismatchConsensusTime + } + } + return nil +} + +// Verify checks the integrity of date received by this App instance. +func (app *App) Verify() error { + app.deliveredLock.RLock() + defer app.deliveredLock.RUnlock() + + if len(app.DeliverSequence) == 0 { + return ErrEmptyDeliverSequence + } + if len(app.DeliverSequence) != len(app.Delivered) { + return ErrApplicationIntegrityFailed + } + + app.ackedLock.RLock() + defer app.ackedLock.RUnlock() + + prevTime := time.Time{} + for _, h := range app.DeliverSequence { + // Make sure delivered block is strongly acked. + if _, acked := app.Acked[h]; !acked { + return ErrDeliveredBlockNotAcked + } + t, exists := app.Delivered[h] + if !exists { + return ErrApplicationIntegrityFailed + } + // Make sure the consensus time is incremental. + ok := prevTime.Before(t) || prevTime.Equal(t) + if !ok { + return ErrConsensusTimestampOutOfOrder + } + prevTime = t + } + // Make sure the order of delivered and total ordering are the same by + // comparing the concated string. + app.totalOrderedLock.RLock() + defer app.totalOrderedLock.RUnlock() + + hashSequenceIdx := 0 +Loop: + for _, totalOrderDeliver := range app.TotalOrdered { + for _, h := range totalOrderDeliver.BlockHashes { + if hashSequenceIdx >= len(app.DeliverSequence) { + break Loop + } + if h != app.DeliverSequence[hashSequenceIdx] { + return ErrMismatchTotalOrderingAndDelivered + } + hashSequenceIdx++ + } + } + if hashSequenceIdx != len(app.DeliverSequence) { + // The count of delivered blocks should be larger than those delivered + // by total ordering. + return ErrMismatchTotalOrderingAndDelivered + } + return nil } diff --git a/core/test/app_test.go b/core/test/app_test.go new file mode 100644 index 0000000..285773e --- /dev/null +++ b/core/test/app_test.go @@ -0,0 +1,142 @@ +package test + +import ( + "testing" + "time" + + "github.com/dexon-foundation/dexon-consensus-core/common" + "github.com/stretchr/testify/suite" +) + +type AppTestSuite struct { + suite.Suite + + to1, to2, to3 *totalOrderDeliver +} + +func (s *AppTestSuite) SetupSuite() { + s.to1 = &totalOrderDeliver{ + BlockHashes: common.Hashes{ + common.NewRandomHash(), + common.NewRandomHash(), + }, + Early: false, + } + s.to2 = &totalOrderDeliver{ + BlockHashes: common.Hashes{ + common.NewRandomHash(), + common.NewRandomHash(), + common.NewRandomHash(), + }, + Early: false, + } + s.to3 = &totalOrderDeliver{ + BlockHashes: common.Hashes{ + common.NewRandomHash(), + }, + Early: false, + } +} + +func (s *AppTestSuite) setupAppByTotalOrderDeliver( + app *App, to *totalOrderDeliver) { + + for _, h := range to.BlockHashes { + app.StronglyAcked(h) + } + app.TotalOrderingDeliver(to.BlockHashes, to.Early) + for _, h := range to.BlockHashes { + // To make it simpler, use the index of hash sequence + // as the time. + s.deliverBlockWithTimeFromSequenceLength(app, h) + } +} + +func (s *AppTestSuite) deliverBlockWithTimeFromSequenceLength( + app *App, hash common.Hash) { + + app.DeliverBlock( + hash, + time.Time{}.Add(time.Duration(len(app.DeliverSequence))*time.Second)) +} + +func (s *AppTestSuite) TestCompare() { + req := s.Require() + + app1 := NewApp() + s.setupAppByTotalOrderDeliver(app1, s.to1) + s.setupAppByTotalOrderDeliver(app1, s.to2) + s.setupAppByTotalOrderDeliver(app1, s.to3) + // An App with different deliver sequence. + app2 := NewApp() + s.setupAppByTotalOrderDeliver(app2, s.to1) + s.setupAppByTotalOrderDeliver(app2, s.to2) + hash := common.NewRandomHash() + app2.StronglyAcked(hash) + app2.TotalOrderingDeliver(common.Hashes{hash}, false) + s.deliverBlockWithTimeFromSequenceLength(app2, hash) + req.Equal(ErrMismatchBlockHashSequence, app1.Compare(app2)) + // An App with different consensus time for the same block. + app3 := NewApp() + s.setupAppByTotalOrderDeliver(app3, s.to1) + s.setupAppByTotalOrderDeliver(app3, s.to2) + for _, h := range s.to3.BlockHashes { + app3.StronglyAcked(h) + } + app3.TotalOrderingDeliver(s.to3.BlockHashes, s.to3.Early) + wrongTime := time.Time{}.Add( + time.Duration(len(app3.DeliverSequence)) * time.Second) + wrongTime = wrongTime.Add(1 * time.Second) + app3.DeliverBlock(s.to3.BlockHashes[0], wrongTime) + req.Equal(ErrMismatchConsensusTime, app1.Compare(app3)) + req.Equal(ErrMismatchConsensusTime, app3.Compare(app1)) + // An App without any delivered blocks. + app4 := NewApp() + req.Equal(ErrEmptyDeliverSequence, app4.Compare(app1)) + req.Equal(ErrEmptyDeliverSequence, app1.Compare(app4)) +} + +func (s *AppTestSuite) TestVerify() { + req := s.Require() + + // An OK App instance. + app1 := NewApp() + s.setupAppByTotalOrderDeliver(app1, s.to1) + s.setupAppByTotalOrderDeliver(app1, s.to2) + s.setupAppByTotalOrderDeliver(app1, s.to3) + req.Nil(app1.Verify()) + // A delivered block without strongly ack + app1.DeliverBlock(common.NewRandomHash(), time.Time{}) + req.Equal(ErrDeliveredBlockNotAcked, app1.Verify()) + // The consensus time is out of order. + app2 := NewApp() + s.setupAppByTotalOrderDeliver(app2, s.to1) + for _, h := range s.to2.BlockHashes { + app2.StronglyAcked(h) + } + app2.TotalOrderingDeliver(s.to2.BlockHashes, s.to2.Early) + app2.DeliverBlock(s.to2.BlockHashes[0], time.Time{}) + req.Equal(ErrConsensusTimestampOutOfOrder, app2.Verify()) + // A delivered block is not found in total ordering delivers. + app3 := NewApp() + s.setupAppByTotalOrderDeliver(app3, s.to1) + hash := common.NewRandomHash() + app3.StronglyAcked(hash) + s.deliverBlockWithTimeFromSequenceLength(app3, hash) + req.Equal(ErrMismatchTotalOrderingAndDelivered, app3.Verify()) + // A delivered block is not found in total ordering delivers. + app4 := NewApp() + s.setupAppByTotalOrderDeliver(app4, s.to1) + for _, h := range s.to2.BlockHashes { + app4.StronglyAcked(h) + } + app4.TotalOrderingDeliver(s.to2.BlockHashes, s.to2.Early) + hash = common.NewRandomHash() + app4.StronglyAcked(hash) + app4.TotalOrderingDeliver(common.Hashes{hash}, false) + s.deliverBlockWithTimeFromSequenceLength(app4, hash) +} + +func TestApp(t *testing.T) { + suite.Run(t, new(AppTestSuite)) +} diff --git a/core/test/utils.go b/core/test/utils.go index eae12a0..35fbdd5 100644 --- a/core/test/utils.go +++ b/core/test/utils.go @@ -29,3 +29,12 @@ func stableRandomHash(blockConv types.BlockConverter) (common.Hash, error) { } return common.NewRandomHash(), nil } + +// GenerateRandomValidatorIDs generates randomly a slices of types.ValidatorID. +func GenerateRandomValidatorIDs(validatorCount int) (vIDs types.ValidatorIDs) { + vIDs = types.ValidatorIDs{} + for i := 0; i < validatorCount; i++ { + vIDs = append(vIDs, types.ValidatorID{Hash: common.NewRandomHash()}) + } + return +} |