From d3107b56cbef1f05baddb64880c3e97d7eda87a4 Mon Sep 17 00:00:00 2001 From: Mission Liao Date: Wed, 15 Aug 2018 23:10:21 +0800 Subject: test: add test.Scheduler (#58) When simulating execution of core.Consensus by passing packets through golang-channel or real-socket, we need to utilize time.Sleep and time.Now to simulate the required network/proposing latency. It's problematic when we try to test a simulation with long network latency. Instead, Scheduler would try to execute the event with minimum timestamp, thus time.Sleep is replaced with Scheduler.nextTick, and time.Now is replaced with Event.Time. Changes: - Add test.Scheduler. - Add test.Stopper interface to provide encapsulate different stop conditions for scheduler. - Add a reference implementation for test.Stopper, it will stop scheduler when all validators confirmed X blocks proposed from themselves. - Add a test scenario on core.Consensus that all validators are not byzantine. --- core/test/app.go | 12 +++ core/test/governance.go | 21 +++++ core/test/interface.go | 29 +++++- core/test/scheduler-event.go | 76 +++++++++++++++ core/test/scheduler.go | 214 +++++++++++++++++++++++++++++++++++++++++++ core/test/scheduler_test.go | 175 +++++++++++++++++++++++++++++++++++ core/test/stopper.go | 86 +++++++++++++++++ core/test/stopper_test.go | 103 +++++++++++++++++++++ 8 files changed, 711 insertions(+), 5 deletions(-) create mode 100644 core/test/scheduler-event.go create mode 100644 core/test/scheduler.go create mode 100644 core/test/scheduler_test.go create mode 100644 core/test/stopper.go create mode 100644 core/test/stopper_test.go (limited to 'core') diff --git a/core/test/app.go b/core/test/app.go index aedcba9..5c438a7 100644 --- a/core/test/app.go +++ b/core/test/app.go @@ -191,3 +191,15 @@ Loop: } return nil } + +// Check provides a backdoor to check status of App with reader lock. +func (app *App) Check(checker func(*App)) { + app.ackedLock.RLock() + defer app.ackedLock.RUnlock() + app.totalOrderedLock.RLock() + defer app.totalOrderedLock.RUnlock() + app.deliveredLock.RLock() + defer app.deliveredLock.RUnlock() + + checker(app) +} diff --git a/core/test/governance.go b/core/test/governance.go index a4e86d1..58ab582 100644 --- a/core/test/governance.go +++ b/core/test/governance.go @@ -18,12 +18,20 @@ package test import ( + "fmt" + "github.com/dexon-foundation/dexon-consensus-core/core/types" "github.com/dexon-foundation/dexon-consensus-core/crypto" "github.com/dexon-foundation/dexon-consensus-core/crypto/eth" "github.com/shopspring/decimal" ) +var ( + // ErrPrivateKeyNotExists means caller request private key for an + // unknown validator ID. + ErrPrivateKeyNotExists = fmt.Errorf("private key not exists") +) + // Governance is an implementation of Goverance for testing purpose. type Governance struct { BlockProposingInterval int @@ -79,3 +87,16 @@ func (g *Governance) GetConfigurationChangeEvent( epoch int) []types.ConfigurationChangeEvent { return nil } + +// GetPrivateKey return the private key for that validator, this function +// is a test utility and not a general core.Governance interface. +func (g *Governance) GetPrivateKey( + vID types.ValidatorID) (key crypto.PrivateKey, err error) { + + key, exists := g.PrivateKeys[vID] + if !exists { + err = ErrPrivateKeyNotExists + return + } + return +} diff --git a/core/test/interface.go b/core/test/interface.go index 6c7b22e..0e963fd 100644 --- a/core/test/interface.go +++ b/core/test/interface.go @@ -1,13 +1,13 @@ // 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 +// 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 +// 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. // @@ -17,7 +17,10 @@ package test -import "github.com/dexon-foundation/dexon-consensus-core/blockdb" +import ( + "github.com/dexon-foundation/dexon-consensus-core/blockdb" + "github.com/dexon-foundation/dexon-consensus-core/core/types" +) // Revealer defines the interface to reveal a group // of pre-generated blocks. @@ -27,3 +30,19 @@ type Revealer interface { // Reset the revealing. Reset() } + +// Stopper defines an interface for Scheduler to tell when to stop execution. +type Stopper interface { + // ShouldStop is provided with the ID of the handler just finishes an event. + // It's thread-safe to access internal/shared state of the handler at this + // moment. + // The Stopper should check state of that handler and return 'true' + // if the execution could be stopped. + ShouldStop(vID types.ValidatorID) bool +} + +// EventHandler defines an interface to handle a Scheduler event. +type EventHandler interface { + // Handle the event belongs to this handler, and return derivated events. + Handle(*Event) []*Event +} diff --git a/core/test/scheduler-event.go b/core/test/scheduler-event.go new file mode 100644 index 0000000..60411b4 --- /dev/null +++ b/core/test/scheduler-event.go @@ -0,0 +1,76 @@ +// 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 +// . + +package test + +import ( + "time" + + "github.com/dexon-foundation/dexon-consensus-core/core/types" +) + +// Event defines a scheduler event. +type Event struct { + // ValidatorID is the ID of handler that this event deginated to. + ValidatorID types.ValidatorID + // Time is the expected execution time of this event. + Time time.Time + // ExecError record the error when handling this event. + ExecError error + // Payload is application specific data carried by this event. + Payload interface{} + // ParentTime is the time of parent event, this field is essential when + // we need to calculate the latency the handler assigned. + ParentTime time.Time + // ExecInterval is the latency to execute this event + ExecInterval time.Duration +} + +// eventQueue implements heap.Interface. +type eventQueue []*Event + +func (eq eventQueue) Len() int { return len(eq) } + +func (eq eventQueue) Less(i, j int) bool { + return eq[i].Time.Before(eq[j].Time) +} + +func (eq eventQueue) Swap(i, j int) { + eq[i], eq[j] = eq[j], eq[i] +} + +func (eq *eventQueue) Push(x interface{}) { + *eq = append(*eq, x.(*Event)) +} + +func (eq *eventQueue) Pop() interface{} { + pos := len(*eq) - 1 + item := (*eq)[pos] + *eq = (*eq)[0:pos] + return item +} + +// NewEvent is the constructor for Event. +func NewEvent( + vID types.ValidatorID, when time.Time, payload interface{}) *Event { + + return &Event{ + ValidatorID: vID, + Time: when, + Payload: payload, + } +} diff --git a/core/test/scheduler.go b/core/test/scheduler.go new file mode 100644 index 0000000..023d09f --- /dev/null +++ b/core/test/scheduler.go @@ -0,0 +1,214 @@ +// 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 +// . + +package test + +import ( + "container/heap" + "context" + "fmt" + "sync" + "time" + + "github.com/dexon-foundation/dexon-consensus-core/core/types" +) + +var ( + // ErrSchedulerAlreadyStarted means callers attempt to insert some + // seed events after calling 'Run'. + ErrSchedulerAlreadyStarted = fmt.Errorf("scheduler already started") + // errNilEventWhenNotified is an internal error which means a worker routine + // can't get an event when notified. + errNilEventWhenNotified = fmt.Errorf("nil event when notified") +) + +type schedulerHandlerRecord struct { + handler EventHandler + lock sync.Mutex +} + +// Scheduler is an event scheduler. +type Scheduler struct { + events eventQueue + eventsLock sync.Mutex + history []*Event + historyLock sync.RWMutex + isStarted bool + handlers map[types.ValidatorID]*schedulerHandlerRecord + handlersLock sync.RWMutex + eventNotification chan struct{} + ctx context.Context + cancelFunc context.CancelFunc + stopper Stopper +} + +// NewScheduler constructs an Scheduler instance. +func NewScheduler(stopper Stopper) *Scheduler { + ctx, cancel := context.WithCancel(context.Background()) + return &Scheduler{ + events: eventQueue{}, + history: []*Event{}, + handlers: make(map[types.ValidatorID]*schedulerHandlerRecord), + eventNotification: make(chan struct{}, 100000), + ctx: ctx, + cancelFunc: cancel, + stopper: stopper, + } +} + +// Run would run the scheduler. If you need strict incrememtal execution order +// of events based on their 'Time' field, assign 'numWorkers' as 1. If you need +// faster execution, assign 'numWorkers' a larger number. +func (sch *Scheduler) Run(numWorkers int) { + var wg sync.WaitGroup + + sch.isStarted = true + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go sch.workerRoutine(&wg) + } + // Blocks until all routines are finished. + wg.Wait() +} + +// Seed is used to provide the scheduler some seed events. +func (sch *Scheduler) Seed(e *Event) error { + sch.eventsLock.Lock() + defer sch.eventsLock.Unlock() + + if sch.isStarted { + return ErrSchedulerAlreadyStarted + } + sch.addEvent(e) + return nil +} + +// RegisterEventHandler register an event handler by providing ID of +// corresponding validator. +func (sch *Scheduler) RegisterEventHandler( + vID types.ValidatorID, + handler EventHandler) { + + sch.handlersLock.Lock() + defer sch.handlersLock.Unlock() + + sch.handlers[vID] = &schedulerHandlerRecord{handler: handler} +} + +// nextTick would pick the oldest event from eventQueue. +func (sch *Scheduler) nextTick() (e *Event) { + sch.eventsLock.Lock() + defer sch.eventsLock.Unlock() + + if len(sch.events) == 0 { + return nil + } + return heap.Pop(&sch.events).(*Event) +} + +// addEvent is an helper function to add events into eventQueue sorted by +// their 'Time' field. +func (sch *Scheduler) addEvent(e *Event) { + // Perform sorted insertion. + heap.Push(&sch.events, e) + sch.eventNotification <- struct{}{} +} + +// CloneExecutionHistory returns a cloned event execution history. +func (sch *Scheduler) CloneExecutionHistory() (cloned []*Event) { + sch.historyLock.RLock() + defer sch.historyLock.RUnlock() + + cloned = make([]*Event, len(sch.history)) + copy(cloned, sch.history) + return +} + +// workerRoutine is the mainloop when handling events. +func (sch *Scheduler) workerRoutine(wg *sync.WaitGroup) { + defer wg.Done() + + handleEvent := func(e *Event) { + // Find correspond handler record. + hRec := func(vID types.ValidatorID) *schedulerHandlerRecord { + sch.handlersLock.RLock() + defer sch.handlersLock.RUnlock() + + return sch.handlers[vID] + }(e.ValidatorID) + + newEvents := func() []*Event { + // This lock makes sure there would be no concurrent access + // against each handler. + hRec.lock.Lock() + defer hRec.lock.Unlock() + + // Handle incoming event, and record its execution time. + beforeExecution := time.Now().UTC() + newEvents := hRec.handler.Handle(e) + e.ExecInterval = time.Now().UTC().Sub(beforeExecution) + // It's safe to check status of that validator under 'hRec.lock'. + if sch.stopper.ShouldStop(e.ValidatorID) { + sch.cancelFunc() + } + // Include the execution interval of parent event to the expected time + // to execute child events. + for _, newEvent := range newEvents { + newEvent.ParentTime = e.Time + newEvent.Time = newEvent.Time.Add(e.ExecInterval) + } + return newEvents + }() + // Record executed events as history. + func() { + sch.historyLock.Lock() + defer sch.historyLock.Unlock() + + sch.history = append(sch.history, e) + }() + // Add derivated events back to event queue. + func() { + sch.eventsLock.Lock() + defer sch.eventsLock.Unlock() + + for _, newEvent := range newEvents { + sch.addEvent(newEvent) + } + }() + } + +Done: + for { + // We favor scheduler-shutdown signal than other events. + select { + case <-sch.ctx.Done(): + break Done + default: + } + // Block until new event arrival or scheduler shutdown. + select { + case <-sch.eventNotification: + e := sch.nextTick() + if e == nil { + panic(errNilEventWhenNotified) + } + handleEvent(e) + case <-sch.ctx.Done(): + break Done + } + } +} diff --git a/core/test/scheduler_test.go b/core/test/scheduler_test.go new file mode 100644 index 0000000..c67240f --- /dev/null +++ b/core/test/scheduler_test.go @@ -0,0 +1,175 @@ +// 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 +// . + +package test + +import ( + "sync" + "testing" + "time" + + "github.com/dexon-foundation/dexon-consensus-core/common" + "github.com/dexon-foundation/dexon-consensus-core/core/types" + "github.com/stretchr/testify/suite" +) + +type SchedulerTestSuite struct { + suite.Suite +} + +type simpleStopper struct { + lock sync.Mutex + touched map[types.ValidatorID]int + touchedCount int +} + +func newSimpleStopper( + validators []types.ValidatorID, touchedCount int) *simpleStopper { + + touched := make(map[types.ValidatorID]int) + for _, vID := range validators { + touched[vID] = 0 + } + return &simpleStopper{ + touched: touched, + touchedCount: touchedCount, + } +} + +func (stopper *simpleStopper) ShouldStop(vID types.ValidatorID) bool { + stopper.lock.Lock() + defer stopper.lock.Unlock() + + stopper.touched[vID] = stopper.touched[vID] + 1 + for _, count := range stopper.touched { + if count < stopper.touchedCount { + return false + } + } + return true +} + +type simpleHandler struct { + count int + vID types.ValidatorID +} + +func (handler *simpleHandler) Handle(e *Event) (events []*Event) { + if e.ValidatorID == handler.vID { + handler.count++ + } + return +} + +type fixedLatencyHandler struct { + vID types.ValidatorID +} + +func (handler *fixedLatencyHandler) Handle(e *Event) (events []*Event) { + // Simulate execution time. + time.Sleep(500 * time.Millisecond) + return []*Event{&Event{ + ValidatorID: handler.vID, + Time: e.Time.Add(800 * time.Millisecond), + }} +} + +func (s *SchedulerTestSuite) TestEventSequence() { + // This test case makes sure the event sequence is correctly increment + // by their timestamps in 'Time' field. + var ( + sch = NewScheduler(nil) + req = s.Require() + ) + + req.NotNil(sch) + now := time.Now() + req.Nil(sch.Seed(&Event{Time: now.Add(100 * time.Second), Payload: 1})) + req.Nil(sch.Seed(&Event{Time: now.Add(99 * time.Second), Payload: 2})) + req.Nil(sch.Seed(&Event{Time: now.Add(98 * time.Second), Payload: 3})) + req.Nil(sch.Seed(&Event{Time: now.Add(97 * time.Second), Payload: 4})) + req.Nil(sch.Seed(&Event{Time: now.Add(96 * time.Second), Payload: 5})) + + req.Equal(sch.nextTick().Payload.(int), 5) + req.Equal(sch.nextTick().Payload.(int), 4) + req.Equal(sch.nextTick().Payload.(int), 3) + req.Equal(sch.nextTick().Payload.(int), 2) + req.Equal(sch.nextTick().Payload.(int), 1) + req.Nil(sch.nextTick()) +} + +func (s *SchedulerTestSuite) TestBasicRound() { + // This test case makes sure these facts: + // - event is dispatched by validatorID attached to each handler. + // - stopper can stop the execution when condition is met. + var ( + req = s.Require() + validators = GenerateRandomValidatorIDs(3) + stopper = newSimpleStopper(validators, 2) + sch = NewScheduler(stopper) + handlers = make(map[types.ValidatorID]*simpleHandler) + ) + + for _, vID := range validators { + handler := &simpleHandler{vID: vID} + handlers[vID] = handler + sch.RegisterEventHandler(vID, handler) + req.Nil(sch.Seed(&Event{ValidatorID: vID})) + req.Nil(sch.Seed(&Event{ValidatorID: vID})) + } + sch.Run(10) + // Verify result. + for _, h := range handlers { + req.Equal(h.count, 2) + } +} + +func (s *SchedulerTestSuite) TestChildEvent() { + // This test case makes sure these fields of child events are + // assigned correctly. + var ( + req = s.Require() + vID = types.ValidatorID{Hash: common.NewRandomHash()} + stopper = newSimpleStopper(types.ValidatorIDs{vID}, 3) + handler = &fixedLatencyHandler{vID: vID} + sch = NewScheduler(stopper) + ) + + sch.RegisterEventHandler(vID, handler) + req.Nil(sch.Seed(&Event{ + ValidatorID: vID, + Time: time.Now().UTC(), + })) + sch.Run(1) + // Verify result. + history := sch.CloneExecutionHistory() + req.Len(history, 3) + curEvent := history[0] + for _, e := range history[1:] { + // Make sure the time difference between events are more than + // 1.3 second. + req.True(e.Time.Sub(curEvent.Time) >= 1300*time.Millisecond) + // Make sure ParentTime field is set and is equal to parent event's + // time. + req.Equal(e.ParentTime, curEvent.Time) + curEvent = e + } +} + +func TestScheduler(t *testing.T) { + suite.Run(t, new(SchedulerTestSuite)) +} diff --git a/core/test/stopper.go b/core/test/stopper.go new file mode 100644 index 0000000..da4d205 --- /dev/null +++ b/core/test/stopper.go @@ -0,0 +1,86 @@ +// 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 +// . + +package test + +import ( + "sync" + + "github.com/dexon-foundation/dexon-consensus-core/blockdb" + "github.com/dexon-foundation/dexon-consensus-core/core/types" +) + +// StopByConfirmedBlocks would make sure each validators confirms +// at least X blocks proposed by itself. +type StopByConfirmedBlocks struct { + apps map[types.ValidatorID]*App + dbs map[types.ValidatorID]blockdb.BlockDatabase + lastCheckDelivered map[types.ValidatorID]int + confirmedBlocks map[types.ValidatorID]int + blockCount int + lock sync.Mutex +} + +// NewStopByConfirmedBlocks construct an StopByConfirmedBlocks instance. +func NewStopByConfirmedBlocks( + blockCount int, + apps map[types.ValidatorID]*App, + dbs map[types.ValidatorID]blockdb.BlockDatabase) *StopByConfirmedBlocks { + + confirmedBlocks := make(map[types.ValidatorID]int) + for vID := range apps { + confirmedBlocks[vID] = 0 + } + return &StopByConfirmedBlocks{ + apps: apps, + dbs: dbs, + lastCheckDelivered: make(map[types.ValidatorID]int), + confirmedBlocks: confirmedBlocks, + blockCount: blockCount, + } +} + +// ShouldStop implements Stopper interface. +func (s *StopByConfirmedBlocks) ShouldStop(vID types.ValidatorID) bool { + s.lock.Lock() + defer s.lock.Unlock() + + // Accumulate confirmed blocks proposed by this validator in this round. + lastChecked := s.lastCheckDelivered[vID] + currentConfirmedBlocks := s.confirmedBlocks[vID] + db := s.dbs[vID] + s.apps[vID].Check(func(app *App) { + for _, h := range app.DeliverSequence[lastChecked:] { + b, err := db.Get(h) + if err != nil { + panic(err) + } + if b.ProposerID == vID { + currentConfirmedBlocks++ + } + } + s.lastCheckDelivered[vID] = len(app.DeliverSequence) + }) + s.confirmedBlocks[vID] = currentConfirmedBlocks + // Check if all validators confirmed at least 'blockCount' blocks. + for _, v := range s.confirmedBlocks { + if v < s.blockCount { + return false + } + } + return true +} diff --git a/core/test/stopper_test.go b/core/test/stopper_test.go new file mode 100644 index 0000000..2abd503 --- /dev/null +++ b/core/test/stopper_test.go @@ -0,0 +1,103 @@ +// 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 +// . + +package test + +import ( + "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/types" + "github.com/stretchr/testify/suite" +) + +type StopperTestSuite struct { + suite.Suite +} + +func (s *StopperTestSuite) TestStopByConfirmedBlocks() { + // This test case makes sure this stopper would stop when + // all validators confirmed at least 'x' count of blocks produced + // by themselves. + var ( + req = s.Require() + ) + + apps := make(map[types.ValidatorID]*App) + dbs := make(map[types.ValidatorID]blockdb.BlockDatabase) + validators := GenerateRandomValidatorIDs(2) + db, err := blockdb.NewMemBackedBlockDB() + req.Nil(err) + for _, vID := range validators { + apps[vID] = NewApp() + dbs[vID] = db + } + deliver := func(blocks []*types.Block) { + hashes := common.Hashes{} + for _, b := range blocks { + hashes = append(hashes, b.Hash) + req.Nil(db.Put(*b)) + } + for _, vID := range validators { + app := apps[vID] + for _, h := range hashes { + app.StronglyAcked(h) + } + app.TotalOrderingDeliver(hashes, false) + for _, h := range hashes { + app.DeliverBlock(h, time.Time{}) + } + } + } + stopper := NewStopByConfirmedBlocks(2, apps, dbs) + b00 := &types.Block{ + ProposerID: validators[0], + Hash: common.NewRandomHash(), + } + deliver([]*types.Block{b00}) + b10 := &types.Block{ + ProposerID: validators[1], + Hash: common.NewRandomHash(), + } + b11 := &types.Block{ + ProposerID: validators[1], + ParentHash: b10.Hash, + Hash: common.NewRandomHash(), + } + deliver([]*types.Block{b10, b11}) + req.False(stopper.ShouldStop(validators[1])) + b12 := &types.Block{ + ProposerID: validators[1], + ParentHash: b11.Hash, + Hash: common.NewRandomHash(), + } + deliver([]*types.Block{b12}) + req.False(stopper.ShouldStop(validators[1])) + b01 := &types.Block{ + ProposerID: validators[0], + ParentHash: b00.Hash, + Hash: common.NewRandomHash(), + } + deliver([]*types.Block{b01}) + req.True(stopper.ShouldStop(validators[0])) +} + +func TestStopper(t *testing.T) { + suite.Run(t, new(StopperTestSuite)) +} -- cgit v1.2.3