aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/test/app.go12
-rw-r--r--core/test/governance.go21
-rw-r--r--core/test/interface.go29
-rw-r--r--core/test/scheduler-event.go76
-rw-r--r--core/test/scheduler.go214
-rw-r--r--core/test/scheduler_test.go175
-rw-r--r--core/test/stopper.go86
-rw-r--r--core/test/stopper_test.go103
-rw-r--r--integration_test/latency.go44
-rw-r--r--integration_test/non-byzantine_test.go90
-rw-r--r--integration_test/validator.go131
11 files changed, 976 insertions, 5 deletions
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
+// <http://www.gnu.org/licenses/>.
+
+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
+// <http://www.gnu.org/licenses/>.
+
+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
+// <http://www.gnu.org/licenses/>.
+
+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
+// <http://www.gnu.org/licenses/>.
+
+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
+// <http://www.gnu.org/licenses/>.
+
+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))
+}
diff --git a/integration_test/latency.go b/integration_test/latency.go
new file mode 100644
index 0000000..383d069
--- /dev/null
+++ b/integration_test/latency.go
@@ -0,0 +1,44 @@
+// 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 integration
+
+import (
+ "math/rand"
+ "time"
+)
+
+// LatencyModel defines an interface to randomly decide latency
+// for one operation.
+type LatencyModel interface {
+ Delay() time.Duration
+}
+
+// normalLatencyModel would return latencies in normal distribution.
+type normalLatencyModel struct {
+ Sigma float64
+ Mean float64
+}
+
+// Delay implements LatencyModel interface.
+func (m *normalLatencyModel) Delay() time.Duration {
+ delay := rand.NormFloat64()*m.Sigma + m.Mean
+ if delay < 0 {
+ delay = m.Sigma / 2
+ }
+ return time.Duration(delay) * time.Millisecond
+}
diff --git a/integration_test/non-byzantine_test.go b/integration_test/non-byzantine_test.go
new file mode 100644
index 0000000..111dcd0
--- /dev/null
+++ b/integration_test/non-byzantine_test.go
@@ -0,0 +1,90 @@
+// 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 integration
+
+import (
+ "testing"
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus-core/blockdb"
+ "github.com/dexon-foundation/dexon-consensus-core/core/test"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+ "github.com/stretchr/testify/suite"
+)
+
+type NonByzantineTestSuite struct {
+ suite.Suite
+}
+
+func (s *NonByzantineTestSuite) TestNonByzantine() {
+ var (
+ networkLatency = &normalLatencyModel{
+ Sigma: 20,
+ Mean: 250,
+ }
+ proposingLatency = &normalLatencyModel{
+ Sigma: 30,
+ Mean: 500,
+ }
+ apps = make(map[types.ValidatorID]*test.App)
+ dbs = make(map[types.ValidatorID]blockdb.BlockDatabase)
+ req = s.Require()
+ )
+
+ gov, err := test.NewGovernance(25, 700)
+ req.Nil(err)
+ now := time.Now().UTC()
+ for vID := range gov.GetValidatorSet() {
+ apps[vID] = test.NewApp()
+
+ db, err := blockdb.NewMemBackedBlockDB()
+ req.Nil(err)
+ dbs[vID] = db
+ }
+ stopper := test.NewStopByConfirmedBlocks(50, apps, dbs)
+ sch := test.NewScheduler(stopper)
+ for vID := range gov.GetValidatorSet() {
+ key, err := gov.GetPrivateKey(vID)
+ req.Nil(err)
+ v := newValidator(
+ apps[vID],
+ gov,
+ dbs[vID],
+ key,
+ vID,
+ networkLatency,
+ proposingLatency)
+ sch.RegisterEventHandler(vID, v)
+ req.Nil(sch.Seed(newProposeBlockEvent(vID, now)))
+ }
+ sch.Run(10)
+ // Check results by comparing test.App instances.
+ for vFrom := range gov.GetValidatorSet() {
+ req.Nil(apps[vFrom].Verify())
+ for vTo := range gov.GetValidatorSet() {
+ if vFrom == vTo {
+ continue
+ }
+ req.Nil(apps[vFrom].Compare(apps[vTo]))
+ }
+ }
+}
+
+func TestNonByzantine(t *testing.T) {
+ suite.Run(t, new(NonByzantineTestSuite))
+}
diff --git a/integration_test/validator.go b/integration_test/validator.go
new file mode 100644
index 0000000..00ffff2
--- /dev/null
+++ b/integration_test/validator.go
@@ -0,0 +1,131 @@
+// 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 integration
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus-core/blockdb"
+ "github.com/dexon-foundation/dexon-consensus-core/core"
+ "github.com/dexon-foundation/dexon-consensus-core/core/test"
+ "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"
+)
+
+type consensusEventType int
+
+const (
+ evtProposeBlock consensusEventType = iota
+ evtReceiveBlock
+)
+
+type consensusEventPayload struct {
+ Type consensusEventType
+ PiggyBack interface{}
+}
+
+func newProposeBlockEvent(vID types.ValidatorID, when time.Time) *test.Event {
+ return test.NewEvent(vID, when, &consensusEventPayload{
+ Type: evtProposeBlock,
+ })
+}
+
+func newReceiveBlockEvent(
+ vID types.ValidatorID, when time.Time, block *types.Block) *test.Event {
+
+ return test.NewEvent(vID, when, &consensusEventPayload{
+ Type: evtReceiveBlock,
+ PiggyBack: block,
+ })
+}
+
+type validator struct {
+ ID types.ValidatorID
+ cons *core.Consensus
+ gov core.Governance
+ networkLatency LatencyModel
+ proposingLatency LatencyModel
+}
+
+func newValidator(
+ app core.Application,
+ gov core.Governance,
+ db blockdb.BlockDatabase,
+ privateKey crypto.PrivateKey,
+ vID types.ValidatorID,
+ networkLatency LatencyModel,
+ proposingLatency LatencyModel) *validator {
+
+ return &validator{
+ ID: vID,
+ gov: gov,
+ networkLatency: networkLatency,
+ proposingLatency: proposingLatency,
+ cons: core.NewConsensus(
+ app, gov, db, privateKey, eth.SigToPub),
+ }
+}
+
+func (v *validator) Handle(e *test.Event) (events []*test.Event) {
+ payload := e.Payload.(*consensusEventPayload)
+ switch payload.Type {
+ case evtProposeBlock:
+ events, e.ExecError = v.handleProposeBlock(e.Time, payload.PiggyBack)
+ case evtReceiveBlock:
+ events, e.ExecError = v.handleReceiveBlock(payload.PiggyBack)
+ default:
+ panic(fmt.Errorf("unknown consensus event type: %v", payload.Type))
+ }
+ return
+}
+
+func (v *validator) handleProposeBlock(when time.Time, piggyback interface{}) (
+ events []*test.Event, err error) {
+
+ b := &types.Block{ProposerID: v.ID}
+ if err = v.cons.PrepareBlock(b, when); err != nil {
+ return
+ }
+ if err = v.cons.ProcessBlock(b); err != nil {
+ return
+ }
+ // Create 'block received' event for each other validators.
+ for vID := range v.gov.GetValidatorSet() {
+ if vID == v.ID {
+ continue
+ }
+ events = append(events, newReceiveBlockEvent(
+ vID, when.Add(v.networkLatency.Delay()), b.Clone()))
+ }
+ // Create next 'block proposing' event for this validators.
+ events = append(events, newProposeBlockEvent(
+ v.ID, when.Add(v.proposingLatency.Delay())))
+ return
+}
+
+func (v *validator) handleReceiveBlock(piggyback interface{}) (
+ events []*test.Event, err error) {
+
+ err = v.cons.ProcessBlock(piggyback.(*types.Block))
+ if err != nil {
+ panic(err)
+ }
+ return
+}