aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWei-Ning Huang <w@dexon.org>2018-07-16 00:12:17 +0800
committerWei-Ning Huang <w@cobinhood.com>2018-07-16 11:06:14 +0800
commitaed24cf020bd11c3b20a7011b96c02e41894fa32 (patch)
tree720bc1542dd1edb7308c124a5265e21b3c01d08b
downloaddexon-consensus-aed24cf020bd11c3b20a7011b96c02e41894fa32.tar
dexon-consensus-aed24cf020bd11c3b20a7011b96c02e41894fa32.tar.gz
dexon-consensus-aed24cf020bd11c3b20a7011b96c02e41894fa32.tar.bz2
dexon-consensus-aed24cf020bd11c3b20a7011b96c02e41894fa32.tar.lz
dexon-consensus-aed24cf020bd11c3b20a7011b96c02e41894fa32.tar.xz
dexon-consensus-aed24cf020bd11c3b20a7011b96c02e41894fa32.tar.zst
dexon-consensus-aed24cf020bd11c3b20a7011b96c02e41894fa32.zip
Initial implementation of DEXON consensus algorithm
-rw-r--r--GNUmakefile30
-rw-r--r--Gopkg.lock77
-rw-r--r--Gopkg.toml46
-rw-r--r--blockdb/interfaces.go59
-rw-r--r--blockdb/memory.go85
-rw-r--r--cmd/dexcon-simulation/main.go53
-rw-r--r--common/types.go40
-rw-r--r--common/utils.go14
-rw-r--r--core/application.go27
-rw-r--r--core/blockchain.go26
-rw-r--r--core/blocklattice.go596
-rw-r--r--core/blocklattice_test.go521
-rw-r--r--core/network.go33
-rw-r--r--core/types/block.go89
-rw-r--r--core/types/validator.go37
-rw-r--r--core/utils.go45
-rw-r--r--core/validator.go22
-rw-r--r--simulation/app.go50
-rw-r--r--simulation/config/config.go89
-rw-r--r--simulation/network-model.go68
-rw-r--r--simulation/network-model_test.go31
-rw-r--r--simulation/network.go86
-rw-r--r--simulation/simulation.go58
-rw-r--r--simulation/validator.go139
24 files changed, 2321 insertions, 0 deletions
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..09d680a
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,30 @@
+# Makefile for Cobinhood Backend
+
+DEXON_CONSENSUS_CORE=github.com/dexon-foundation/dexon-consensus-core
+
+.PHONY: clean default
+
+default: all
+
+all: dexcon-simulation
+
+pre-submit: lint
+
+dexcon-simulation:
+ go install $(DEXON_CONSENSUS_CORE)/cmd/dexcon-simulation
+
+pre-submit: lint test vet
+
+lint:
+ @$(GOPATH)/bin/golint -set_exit_status `go list ./... | grep -v 'vendor'`
+
+vet:
+ @go vet `go list ./... | grep -v 'vendor'`
+
+test:
+ @for pkg in `go list ./... | grep -v 'vendor'`; do \
+ if ! go test $$pkg; then \
+ echo 'Some test failed, abort'; \
+ exit 1; \
+ fi; \
+ done
diff --git a/Gopkg.lock b/Gopkg.lock
new file mode 100644
index 0000000..57b1fdd
--- /dev/null
+++ b/Gopkg.lock
@@ -0,0 +1,77 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "github.com/davecgh/go-spew"
+ packages = ["spew"]
+ revision = "346938d642f2ec3594ed81d874461961cd0faa76"
+ version = "v1.1.0"
+
+[[projects]]
+ name = "github.com/getlantern/deepcopy"
+ packages = ["."]
+ revision = "b923171e8640f94369e21e324b1b0465cb82ec9f"
+ version = "v1"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/golang/snappy"
+ packages = ["."]
+ revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
+
+[[projects]]
+ name = "github.com/naoina/go-stringutil"
+ packages = ["."]
+ revision = "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b"
+ version = "v0.1.0"
+
+[[projects]]
+ name = "github.com/naoina/toml"
+ packages = [
+ ".",
+ "ast"
+ ]
+ revision = "e6f5723bf2a66af014955e0888881314cf294129"
+ version = "v0.1.1"
+
+[[projects]]
+ name = "github.com/pmezard/go-difflib"
+ packages = ["difflib"]
+ revision = "792786c7400a136282c1664665ae0a8db921c6c2"
+ version = "v1.0.0"
+
+[[projects]]
+ name = "github.com/stretchr/testify"
+ packages = [
+ "assert",
+ "require",
+ "suite"
+ ]
+ revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
+ version = "v1.2.2"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/syndtr/goleveldb"
+ packages = [
+ "leveldb",
+ "leveldb/cache",
+ "leveldb/comparer",
+ "leveldb/errors",
+ "leveldb/filter",
+ "leveldb/iterator",
+ "leveldb/journal",
+ "leveldb/memdb",
+ "leveldb/opt",
+ "leveldb/storage",
+ "leveldb/table",
+ "leveldb/util"
+ ]
+ revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "15c32472a1fe08e302ec948065c4bd908fd8e81b4fda7cbb210f9431e63b3714"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
new file mode 100644
index 0000000..67d7bbe
--- /dev/null
+++ b/Gopkg.toml
@@ -0,0 +1,46 @@
+# Gopkg.toml example
+#
+# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+#
+# [prune]
+# non-go = false
+# go-tests = true
+# unused-packages = true
+
+
+[[constraint]]
+ name = "github.com/getlantern/deepcopy"
+ version = "1.0.0"
+
+[[constraint]]
+ name = "github.com/naoina/toml"
+ version = "0.1.1"
+
+[[constraint]]
+ name = "github.com/stretchr/testify"
+ version = "1.2.2"
+
+[[constraint]]
+ branch = "master"
+ name = "github.com/syndtr/goleveldb"
+
+[prune]
+ go-tests = true
+ unused-packages = true
diff --git a/blockdb/interfaces.go b/blockdb/interfaces.go
new file mode 100644
index 0000000..4d35e2e
--- /dev/null
+++ b/blockdb/interfaces.go
@@ -0,0 +1,59 @@
+// 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 blockdb
+
+import (
+ "errors"
+
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+)
+
+var (
+ // ErrBlockExists is the error when block eixsts.
+ ErrBlockExists = errors.New("block exists")
+ // ErrBlockDoesNotExist is the error when block does not eixsts.
+ ErrBlockDoesNotExist = errors.New("block does not exist")
+ // ErrValidatorDoesNotExist is the error when validator does not eixsts.
+ ErrValidatorDoesNotExist = errors.New("validator does not exist")
+)
+
+// BlockDatabase is the interface for a BlockDatabse.
+type BlockDatabase interface {
+ Reader
+ Writer
+ Deleter
+}
+
+// Reader defines the interface for reading blocks into DB.
+type Reader interface {
+ Has(hash common.Hash) bool
+ Get(hash common.Hash) (types.Block, error)
+ GetByValidatorAndHeight(vID types.ValidatorID, height uint64) (types.Block, error)
+}
+
+// Writer defines the interface for writing blocks into DB.
+type Writer interface {
+ Update(block types.Block) error
+ Put(block types.Block) error
+}
+
+// Deleter defines the interface for deleting blocks in the DB.
+type Deleter interface {
+ Delete(hash common.Hash)
+}
diff --git a/blockdb/memory.go b/blockdb/memory.go
new file mode 100644
index 0000000..fd62b71
--- /dev/null
+++ b/blockdb/memory.go
@@ -0,0 +1,85 @@
+// 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 blockdb
+
+import (
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+)
+
+// MemBackendBlockDB is a memory bakcend BlockDB implementation.
+type MemBackendBlockDB struct {
+ blocksByHash map[common.Hash]*types.Block
+ blocksByValidator map[types.ValidatorID]map[uint64]*types.Block
+}
+
+// NewMemBackedBlockDB initialize a memory-backed block database.
+func NewMemBackedBlockDB() *MemBackendBlockDB {
+ return &MemBackendBlockDB{
+ blocksByHash: make(map[common.Hash]*types.Block),
+ blocksByValidator: make(map[types.ValidatorID]map[uint64]*types.Block),
+ }
+}
+
+// Has returns wheter or not the DB has a block identified with the hash.
+func (m *MemBackendBlockDB) Has(hash common.Hash) bool {
+ _, ok := m.blocksByHash[hash]
+ return ok
+}
+
+// Get returns a block given a hash.
+func (m *MemBackendBlockDB) Get(hash common.Hash) (types.Block, error) {
+ b, ok := m.blocksByHash[hash]
+ if !ok {
+ return types.Block{}, ErrBlockDoesNotExist
+ }
+ return *b, nil
+}
+
+// GetByValidatorAndHeight returns a block given validator ID and hash.
+func (m *MemBackendBlockDB) GetByValidatorAndHeight(
+ vID types.ValidatorID, height uint64) (types.Block, error) {
+ validatorBlocks, ok := m.blocksByValidator[vID]
+ if !ok {
+ return types.Block{}, ErrValidatorDoesNotExist
+ }
+ block, ok2 := validatorBlocks[height]
+ if !ok2 {
+ return types.Block{}, ErrBlockDoesNotExist
+ }
+ return *block, nil
+}
+
+// Put inserts a new block into the database.
+func (m *MemBackendBlockDB) Put(block types.Block) error {
+ if m.Has(block.Hash) {
+ return ErrBlockExists
+ }
+ return m.Update(block)
+}
+
+// Update updates a block in the database.
+func (m *MemBackendBlockDB) Update(block types.Block) error {
+ m.blocksByHash[block.Hash] = &block
+ return nil
+}
+
+// Delete deletes a block in the database.
+func (m *MemBackendBlockDB) Delete(hash common.Hash) {
+ delete(m.blocksByHash, hash)
+}
diff --git a/cmd/dexcon-simulation/main.go b/cmd/dexcon-simulation/main.go
new file mode 100644
index 0000000..5547700
--- /dev/null
+++ b/cmd/dexcon-simulation/main.go
@@ -0,0 +1,53 @@
+// 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 main
+
+import (
+ "flag"
+ "fmt"
+ "math/rand"
+ "os"
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus-core/simulation"
+ "github.com/dexon-foundation/dexon-consensus-core/simulation/config"
+)
+
+var initialize = flag.Bool("init", false, "initialize config file")
+var configFile = flag.String("config", "", "path to simulation config file")
+
+func main() {
+ flag.Parse()
+
+ rand.Seed(time.Now().UnixNano())
+
+ if *configFile == "" {
+ fmt.Fprintln(os.Stderr, "error: no configuration file specified")
+ os.Exit(1)
+ }
+
+ if *initialize {
+ if err := config.GenerateDefault(*configFile); err != nil {
+ fmt.Fprintf(os.Stderr, "error: %s", err)
+ os.Exit(1)
+ }
+ //os.Exit(0)
+ }
+
+ simulation.Run(*configFile)
+}
diff --git a/common/types.go b/common/types.go
new file mode 100644
index 0000000..a52aabf
--- /dev/null
+++ b/common/types.go
@@ -0,0 +1,40 @@
+// 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 common
+
+import (
+ "bytes"
+ "encoding/hex"
+)
+
+const (
+ // HashLength is the length of a hash in DEXON.
+ HashLength = 32
+)
+
+// Hash is the basic hash type in DEXON.
+type Hash [HashLength]byte
+
+func (h Hash) String() string {
+ return hex.EncodeToString([]byte(h[:]))
+}
+
+// Equal compares if two hash are the same.
+func (h Hash) Equal(hp Hash) bool {
+ return bytes.Compare([]byte(h[:]), []byte(hp[:])) == 0
+}
diff --git a/common/utils.go b/common/utils.go
new file mode 100644
index 0000000..7e89c05
--- /dev/null
+++ b/common/utils.go
@@ -0,0 +1,14 @@
+package common
+
+import (
+ "math/rand"
+)
+
+// NewRandomHash returns a random Hash-like value.
+func NewRandomHash() Hash {
+ x := Hash{}
+ for i := 0; i < HashLength; i++ {
+ x[i] = byte(rand.Int() % 256)
+ }
+ return x
+}
diff --git a/core/application.go b/core/application.go
new file mode 100644
index 0000000..7eca66e
--- /dev/null
+++ b/core/application.go
@@ -0,0 +1,27 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import "github.com/dexon-foundation/dexon-consensus-core/core/types"
+
+// Application describes the application interface that interacts with DEXON
+// consensus core.
+type Application interface {
+ ValidateBlock(b *types.Block) bool
+ Deliver(blocks []*types.Block, early bool)
+}
diff --git a/core/blockchain.go b/core/blockchain.go
new file mode 100644
index 0000000..6da74af
--- /dev/null
+++ b/core/blockchain.go
@@ -0,0 +1,26 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import "github.com/dexon-foundation/dexon-consensus-core/core/types"
+
+// BlockChain is the basic datastucture used for storing blocks in each
+// validator.
+type BlockChain struct {
+ validatorID types.ValidatorID
+}
diff --git a/core/blocklattice.go b/core/blocklattice.go
new file mode 100644
index 0000000..2f95733
--- /dev/null
+++ b/core/blocklattice.go
@@ -0,0 +1,596 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "fmt"
+ "math"
+ "sort"
+ "sync"
+ "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"
+)
+
+const (
+ epsilon = 1 * time.Microsecond
+ tdelay = 500 * time.Millisecond
+)
+
+const (
+ infinity uint64 = math.MaxUint64
+)
+
+// BlockLattice represent the local view of a single validator.
+//
+// blockDB stores blocks that are final. blocks stores blocks that are in ToTo
+// State.
+type BlockLattice struct {
+ owner types.ValidatorID
+ validatorSet map[types.ValidatorID]struct{}
+ blocks map[common.Hash]*types.Block
+
+ fmax int
+ phi int
+ lastSeenTimestamps map[types.ValidatorID]time.Time
+
+ blockDB blockdb.BlockDatabase
+ network Network
+ app Application
+ mutex sync.Mutex
+
+ // Reliable Broadcast.
+ waitingSet map[common.Hash]*types.Block
+ stronglyAckedSet map[common.Hash]*types.Block
+ ackCandidateSet map[types.ValidatorID]*types.Block
+ restricted map[types.ValidatorID]struct{}
+
+ // Total Ordering.
+ pendingSet map[common.Hash]*types.Block
+ candidateSet map[common.Hash]*types.Block
+ ABS map[common.Hash]map[types.ValidatorID]uint64
+ AHV map[common.Hash]map[types.ValidatorID]uint64
+}
+
+// NewBlockLattice returns a new empty BlockLattice instance.
+func NewBlockLattice(
+ db blockdb.BlockDatabase,
+ network Network,
+ app Application) *BlockLattice {
+ return &BlockLattice{
+ validatorSet: make(map[types.ValidatorID]struct{}),
+ blocks: make(map[common.Hash]*types.Block),
+ lastSeenTimestamps: make(map[types.ValidatorID]time.Time),
+ blockDB: db,
+ network: network,
+ app: app,
+ waitingSet: make(map[common.Hash]*types.Block),
+ stronglyAckedSet: make(map[common.Hash]*types.Block),
+ ackCandidateSet: make(map[types.ValidatorID]*types.Block),
+ restricted: make(map[types.ValidatorID]struct{}),
+ pendingSet: make(map[common.Hash]*types.Block),
+ candidateSet: make(map[common.Hash]*types.Block),
+ ABS: make(map[common.Hash]map[types.ValidatorID]uint64),
+ AHV: make(map[common.Hash]map[types.ValidatorID]uint64),
+ }
+}
+
+// AddValidator adds a validator into the lattice.
+func (l *BlockLattice) AddValidator(
+ id types.ValidatorID, genesis *types.Block) {
+
+ l.validatorSet[id] = struct{}{}
+ l.fmax = len(l.validatorSet) / 3
+ l.phi = 2*l.fmax + 1
+
+ genesis.State = types.BlockStatusFinal
+ l.blockDB.Put(*genesis)
+}
+
+// SetOwner sets the blocklattice's owner, which is the localview of whom.
+func (l *BlockLattice) SetOwner(id types.ValidatorID) {
+ if _, exists := l.validatorSet[id]; !exists {
+ panic("SetOnwer: owner is not a valid validator")
+ }
+ l.owner = id
+}
+
+// getBlock returns a block no matter where it is located at (either local
+// blocks cache or blockDB).
+func (l *BlockLattice) getBlock(hash common.Hash) *types.Block {
+ if b, exists := l.blocks[hash]; exists {
+ return b
+ }
+ if b, err := l.blockDB.Get(hash); err == nil {
+ return &b
+ }
+ return nil
+}
+
+// processAcks updates the ack count of the blocks that is acked by *b*.
+func (l *BlockLattice) processAcks(b *types.Block) {
+ if b.IndirectAcks == nil {
+ b.IndirectAcks = make(map[common.Hash]struct{})
+ }
+
+ for ackBlockHash := range b.Acks {
+ ackedBlock, ok := l.blocks[ackBlockHash]
+ if !ok {
+ // Acks a finalized block, don't need to increase it's count.
+ if l.blockDB.Has(ackBlockHash) {
+ continue
+ }
+ panic(fmt.Sprintf("failed to get block: %v", ackBlockHash))
+ }
+
+ // Populate IndirectAcks.
+ for a := range ackedBlock.Acks {
+ if _, exists := b.Acks[a]; !exists {
+ b.IndirectAcks[a] = struct{}{}
+ }
+ }
+ for a := range ackedBlock.IndirectAcks {
+ if _, exists := b.Acks[a]; !exists {
+ b.IndirectAcks[a] = struct{}{}
+ }
+ }
+
+ // Populate AckedBy.
+ if ackedBlock.AckedBy == nil {
+ ackedBlock.AckedBy = make(map[common.Hash]bool)
+ }
+ ackedBlock.AckedBy[b.Hash] = true
+
+ bp := ackedBlock
+ for bp != nil && bp.State < types.BlockStatusAcked {
+ if bp.AckedBy == nil {
+ bp.AckedBy = make(map[common.Hash]bool)
+ }
+ if _, exists := bp.AckedBy[b.Hash]; !exists {
+ bp.AckedBy[b.Hash] = false
+ }
+
+ // Calculate acked by nodes.
+ ackedByNodes := make(map[types.ValidatorID]struct{})
+ for hash := range bp.AckedBy {
+ bp := l.getBlock(hash)
+ ackedByNodes[bp.ProposerID] = struct{}{}
+ }
+
+ if len(ackedByNodes) > 2*l.fmax {
+ bp.State = types.BlockStatusAcked
+ l.stronglyAckedSet[bp.Hash] = bp
+ }
+ bp = l.getBlock(bp.ParentHash)
+ }
+ }
+}
+
+// updateTimestamps updates the last seen timestamp of the lattice local view.
+func (l *BlockLattice) updateTimestamps(b *types.Block) {
+ q := b.ProposerID
+ l.lastSeenTimestamps[q] = b.Timestamps[q].Add(epsilon)
+ for vid := range l.validatorSet {
+ if b.Timestamps[vid].After(l.lastSeenTimestamps[vid]) {
+ l.lastSeenTimestamps[vid] = b.Timestamps[vid]
+ }
+ }
+}
+
+func (l *BlockLattice) recievedAndNotInWaitingSet(hash common.Hash) bool {
+ if _, exists := l.blocks[hash]; !exists {
+ if !l.blockDB.Has(hash) {
+ return false
+ }
+ }
+ return true
+}
+
+func (l *BlockLattice) isValidAckCandidate(b *types.Block) bool {
+ // Block proposer is not restricted.
+ if _, isRestricted := l.restricted[b.ProposerID]; isRestricted {
+ return false
+ }
+
+ hasHistoryBeenRecieved := func(hash common.Hash) bool {
+ bx := l.getBlock(hash)
+ if bx == nil {
+ return false
+ }
+
+ for {
+ bx = l.getBlock(bx.ParentHash)
+ if bx == nil {
+ return false
+ }
+ if bx.State == types.BlockStatusFinal {
+ return true
+ }
+ }
+ }
+
+ // Previous block is recieved.
+ if !hasHistoryBeenRecieved(b.ParentHash) {
+ return false
+ }
+
+ // All acked blocks are recieved.
+ for ackedBlockHash := range b.Acks {
+ if !hasHistoryBeenRecieved(ackedBlockHash) {
+ return false
+ }
+ }
+
+ return true
+}
+
+// ProcessBlock implements the recieving part of DEXON reliable broadcast.
+func (l *BlockLattice) ProcessBlock(b *types.Block, runTotal ...bool) {
+ l.mutex.Lock()
+ defer l.mutex.Unlock()
+
+ if l.getBlock(b.Hash) != nil {
+ return
+ }
+
+ // TODO(w): drop if it does not pass sanity check.
+
+ // Store into local blocks cache.
+ l.blocks[b.Hash] = b
+
+ if l.isValidAckCandidate(b) {
+ l.ackCandidateSet[b.ProposerID] = b
+ l.processAcks(b)
+ } else {
+ l.waitingSet[b.Hash] = b
+ }
+
+ // Scan the rest of waiting set for valid candidate.
+ for bpHash, bp := range l.waitingSet {
+ if l.isValidAckCandidate(bp) {
+ l.ackCandidateSet[bp.ProposerID] = bp
+ l.processAcks(bp)
+ delete(l.waitingSet, bpHash)
+ }
+ }
+
+IterateStronglyAckedSet:
+ for bpHash, bp := range l.stronglyAckedSet {
+ for ackBlockHash := range bp.Acks {
+ bx := l.getBlock(ackBlockHash)
+ if bx == nil || bx.State < types.BlockStatusAcked {
+ break IterateStronglyAckedSet
+ }
+ }
+ bp.State = types.BlockStatusToTo
+ l.pendingSet[bp.Hash] = bp
+ delete(l.stronglyAckedSet, bpHash)
+
+ if len(runTotal) > 0 && runTotal[0] {
+ l.totalOrdering(bp)
+ }
+ }
+}
+
+// ProposeBlock implements the send part of DEXON reliable broadcast.
+func (l *BlockLattice) ProposeBlock(b *types.Block) {
+ l.mutex.Lock()
+ defer l.mutex.Unlock()
+
+ b.Acks = make(map[common.Hash]struct{})
+ for _, bp := range l.ackCandidateSet {
+ b.Acks[bp.Hash] = struct{}{}
+ l.updateTimestamps(b)
+ }
+ l.lastSeenTimestamps[l.owner] = time.Now().UTC()
+
+ b.Timestamps = make(map[types.ValidatorID]time.Time)
+ for vID, ts := range l.lastSeenTimestamps {
+ b.Timestamps[vID] = ts
+ }
+
+ //l.ProcessBlock(b)
+ l.network.BroadcastBlock(b)
+
+ l.ackCandidateSet = make(map[types.ValidatorID]*types.Block)
+}
+
+// DetectNack implements the NACK detection.
+func (l *BlockLattice) DetectNack() {
+
+}
+
+func (l *BlockLattice) setAHV(
+ block common.Hash, vID types.ValidatorID, v uint64) {
+
+ if l.AHV[block] == nil {
+ l.AHV[block] = make(map[types.ValidatorID]uint64)
+ }
+ l.AHV[block][vID] = v
+}
+
+func (l *BlockLattice) pushABS(block *types.Block, vID types.ValidatorID) {
+ if l.ABS[block.Hash] == nil {
+ l.ABS[block.Hash] = make(map[types.ValidatorID]uint64)
+ }
+ v, exists := l.ABS[block.Hash][vID]
+ if !exists || block.Height < v {
+ l.ABS[block.Hash][vID] = block.Height
+ }
+}
+
+func (l *BlockLattice) abs() map[types.ValidatorID]struct{} {
+ abs := make(map[types.ValidatorID]struct{})
+ for blockHash := range l.candidateSet {
+ for x := range l.ABS[blockHash] {
+ abs[x] = struct{}{}
+ }
+ }
+ return abs
+}
+
+func (l *BlockLattice) calculateABSofBlock(b *types.Block) {
+ // Calculate ABS of a block.
+ l.ABS[b.Hash] = make(map[types.ValidatorID]uint64)
+
+ var calculateABSRecursive func(target *types.Block)
+
+ calculateABSRecursive = func(target *types.Block) {
+ for hash := range target.AckedBy {
+ ackedByBlock := l.getBlock(hash)
+ if ackedByBlock.State != types.BlockStatusToTo {
+ continue
+ }
+ v, exists := l.ABS[b.Hash][ackedByBlock.ProposerID]
+ if !exists || ackedByBlock.Height < v {
+ l.ABS[b.Hash][ackedByBlock.ProposerID] = ackedByBlock.Height
+ }
+ calculateABSRecursive(ackedByBlock)
+ }
+ }
+
+ // ABS always include the block's proposer
+ l.ABS[b.Hash][b.ProposerID] = b.Height
+
+ calculateABSRecursive(b)
+}
+
+func (l *BlockLattice) calculateAHVofBlock(
+ b *types.Block, globalMins map[types.ValidatorID]uint64) {
+
+ // Calculate ABS of a block.
+ l.AHV[b.Hash] = make(map[types.ValidatorID]uint64)
+
+ for v := range l.validatorSet {
+ gv, gExists := globalMins[v]
+ lv, lExists := l.ABS[b.Hash][v]
+
+ if !gExists {
+ // Do nothing.
+ } else if !lExists || lv > gv {
+ l.AHV[b.Hash][v] = infinity
+ } else {
+ l.AHV[b.Hash][v] = gv
+ }
+ }
+}
+
+func (l *BlockLattice) updateABSAHV() {
+ globalMins := make(map[types.ValidatorID]uint64)
+
+ for _, block := range l.pendingSet {
+ v, exists := globalMins[block.ProposerID]
+ if !exists || block.Height < v {
+ globalMins[block.ProposerID] = block.Height
+ }
+ }
+
+ for _, block := range l.candidateSet {
+ l.calculateABSofBlock(block)
+ l.calculateAHVofBlock(block, globalMins)
+ }
+}
+
+// totalOrdering implements the DEXON total ordering algorithm.
+func (l *BlockLattice) totalOrdering(b *types.Block) {
+ acksOnlyFinal := true
+ for ackedBlockHash := range b.Acks {
+ bp := l.getBlock(ackedBlockHash)
+ if bp.State != types.BlockStatusFinal {
+ acksOnlyFinal = false
+ break
+ }
+ }
+
+ abs := l.abs()
+ if acksOnlyFinal {
+ l.candidateSet[b.Hash] = b
+ /*
+ for r := range abs {
+ l.setAHV(b.Hash, r, infinity)
+ }
+ */
+ }
+
+ /*
+ q := b.ProposerID
+ if _, exists := abs[q]; !exists {
+ for _, bp := range l.candidateSet {
+ if bp.Hash.Equal(b.Hash) {
+ continue
+ }
+
+ _, directlyAckedBy := b.Acks[bp.Hash]
+ _, indirectlyAckedBy := b.IndirectAcks[bp.Hash]
+
+ if directlyAckedBy || indirectlyAckedBy {
+ l.setAHV(bp.Hash, q, b.Height)
+ l.pushABS(bp, q)
+ } else {
+ l.setAHV(bp.Hash, q, infinity)
+ }
+ }
+ }
+ */
+
+ // Update ABS and AHV.
+ l.updateABSAHV()
+ abs = l.abs()
+
+ // Calculate preceding set.
+ precedingSet := make(map[common.Hash]*types.Block)
+
+ // Grade(b', b) = 0 for all b' in candidate set.
+ for targetHash, targetBlock := range l.candidateSet {
+ winAll := true
+ for otherHash := range l.candidateSet {
+ if targetHash.Equal(otherHash) {
+ continue
+ }
+
+ lose := 0
+ for vID, targetAHV := range l.AHV[targetHash] {
+ if otherAHV, exists := l.AHV[otherHash][vID]; exists {
+ if otherAHV < targetAHV {
+ lose++
+ }
+ } else if otherAHV != infinity {
+ lose++
+ }
+ }
+
+ if lose >= l.phi {
+ winAll = false
+ break
+ } else if lose < l.phi-len(l.validatorSet)+len(abs) {
+ // Do nothing.
+ } else {
+ winAll = false
+ break
+ }
+ }
+
+ if winAll {
+ precedingSet[targetHash] = targetBlock
+ }
+ }
+
+ // Internal stability.
+ winned := false
+ for hash := range l.candidateSet {
+ if _, exists := precedingSet[hash]; exists {
+ continue
+ }
+
+ // Grade(b, b') = 1
+ for precedingHash := range precedingSet {
+ win := 0
+ for vID, precedingAHV := range l.AHV[precedingHash] {
+ if candidateAHV, exists := l.AHV[hash][vID]; exists {
+ if precedingAHV < candidateAHV {
+ win++
+ }
+ } else if precedingAHV != infinity {
+ win++
+ }
+ }
+ if win > l.phi {
+ winned = true
+ break
+ }
+ }
+ if !winned {
+ return
+ }
+ }
+
+ earlyDelivery := false
+
+ // Does not satisfy External stability a.
+ if len(abs) < len(l.validatorSet) {
+ earlyDelivery = true
+
+ // External stability b.
+ extBSatisfied := false
+ for precedingHash := range precedingSet {
+ count := 0
+ for _, ahv := range l.AHV[precedingHash] {
+ if ahv != infinity {
+ count++
+ }
+ }
+ if count > l.phi {
+ extBSatisfied = true
+ break
+ }
+ }
+ if !extBSatisfied {
+ return
+ }
+ for precedingHash := range precedingSet {
+ if len(l.ABS[precedingHash]) < len(l.validatorSet)-l.phi {
+ extBSatisfied = false
+ }
+ }
+ if !extBSatisfied {
+ return
+ }
+ }
+
+ var output []*types.Block
+ for hash, x := range precedingSet {
+ output = append(output, x)
+ x.State = types.BlockStatusFinal
+
+ // Remove from pending set and candidate set.
+ delete(l.pendingSet, hash)
+ delete(l.candidateSet, hash)
+
+ // Delete ABS and AHV
+ delete(l.ABS, hash)
+ delete(l.AHV, hash)
+
+ // Store output blocks into blockDB.
+ l.blockDB.Put(*x)
+ delete(l.blocks, hash)
+ }
+ sort.Sort(types.ByHash(output))
+
+ if len(output) > 0 {
+ l.app.Deliver(output, earlyDelivery)
+ }
+
+ // Rescan pending blocks to add into candidate set.
+ for hash, block := range l.pendingSet {
+ if _, exists := l.candidateSet[hash]; exists {
+ continue
+ }
+ acksOnlyFinal := true
+ for ackedBlockHash := range block.Acks {
+ if !l.blockDB.Has(ackedBlockHash) {
+ acksOnlyFinal = false
+ break
+ }
+ }
+ if acksOnlyFinal {
+ l.candidateSet[hash] = block
+ }
+ }
+}
diff --git a/core/blocklattice_test.go b/core/blocklattice_test.go
new file mode 100644
index 0000000..e462b8a
--- /dev/null
+++ b/core/blocklattice_test.go
@@ -0,0 +1,521 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+
+ "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"
+)
+
+var lattice *BlockLattice
+var validators []types.ValidatorID
+var genesises []*types.Block
+
+var b01, b11, b21, b31,
+ b02, b12, b22, b32,
+ b03, b13, b23, b33 *types.Block
+
+// TestNetwork.
+type TestNetwork struct {
+}
+
+func (n *TestNetwork) Join(endpoint Endpoint) chan interface{} {
+ return nil
+}
+
+func (n *TestNetwork) BroadcastBlock(block *types.Block) {
+}
+
+// TestApp.
+type TestApp struct {
+ Outputs []*types.Block
+ Early bool
+}
+
+func (a *TestApp) ValidateBlock(b *types.Block) bool {
+ return true
+}
+
+func (a *TestApp) Deliver(blocks []*types.Block, early bool) {
+ a.Outputs = blocks
+ a.Early = early
+}
+
+func (a *TestApp) Clear() {
+ a.Outputs = nil
+ a.Early = false
+}
+
+type BlockLatticeTest struct {
+ suite.Suite
+
+ app *TestApp
+}
+
+func (s *BlockLatticeTest) SetupTest() {
+ Debugf("--------------------------------------------" +
+ "-------------------------\n")
+
+ s.app = &TestApp{}
+
+ lattice = NewBlockLattice(
+ blockdb.NewMemBackedBlockDB(),
+ &TestNetwork{},
+ s.app)
+
+ for i := 0; i < 4; i++ {
+ validators = append(validators, types.ValidatorID(common.NewRandomHash()))
+ Debugf("V%d: %s\n", i, validators[i])
+ }
+ Debugf("\n")
+ for i := 0; i < 4; i++ {
+ hash := common.NewRandomHash()
+ genesises = append(genesises, &types.Block{
+ ProposerID: validators[i],
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: map[common.Hash]struct{}{},
+ })
+
+ Debugf("G%d: %s\n", i, hash)
+ lattice.AddValidator(validators[i], genesises[i])
+ }
+
+ // Make lattice validator[0]'s local view.
+ lattice.SetOwner(validators[0])
+
+ b01 = &types.Block{
+ ProposerID: validators[0],
+ ParentHash: genesises[0].Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ genesises[1].Hash: struct{}{},
+ genesises[2].Hash: struct{}{},
+ genesises[3].Hash: struct{}{},
+ },
+ }
+ b11 = &types.Block{
+ ProposerID: validators[1],
+ ParentHash: genesises[1].Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b01.Hash: struct{}{},
+ genesises[2].Hash: struct{}{},
+ genesises[3].Hash: struct{}{},
+ },
+ }
+ b21 = &types.Block{
+ ProposerID: validators[2],
+ ParentHash: genesises[2].Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b01.Hash: struct{}{},
+ genesises[1].Hash: struct{}{},
+ genesises[3].Hash: struct{}{},
+ },
+ }
+ b31 = &types.Block{
+ ProposerID: validators[3],
+ ParentHash: genesises[3].Hash,
+ Hash: common.NewRandomHash(),
+ Height: 1,
+ Acks: map[common.Hash]struct{}{
+ b01.Hash: struct{}{},
+ b11.Hash: struct{}{},
+ genesises[2].Hash: struct{}{},
+ },
+ }
+
+ b02 = &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b01.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b11.Hash: struct{}{},
+ b21.Hash: struct{}{},
+ b31.Hash: struct{}{},
+ },
+ }
+ b12 = &types.Block{
+ ProposerID: validators[1],
+ ParentHash: b11.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b21.Hash: struct{}{},
+ b31.Hash: struct{}{},
+ },
+ }
+ b22 = &types.Block{
+ ProposerID: validators[2],
+ ParentHash: b21.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b02.Hash: struct{}{},
+ b12.Hash: struct{}{},
+ b31.Hash: struct{}{},
+ },
+ }
+ b32 = &types.Block{
+ ProposerID: validators[3],
+ ParentHash: b31.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 2,
+ Acks: map[common.Hash]struct{}{
+ b02.Hash: struct{}{},
+ b12.Hash: struct{}{},
+ b21.Hash: struct{}{},
+ },
+ }
+
+ b03 = &types.Block{
+ ProposerID: validators[0],
+ ParentHash: b02.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b12.Hash: struct{}{},
+ b22.Hash: struct{}{},
+ b32.Hash: struct{}{},
+ },
+ }
+ b13 = &types.Block{
+ ProposerID: validators[1],
+ ParentHash: b12.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b02.Hash: struct{}{},
+ b22.Hash: struct{}{},
+ b32.Hash: struct{}{},
+ },
+ }
+ b23 = &types.Block{
+ ProposerID: validators[2],
+ ParentHash: b22.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b02.Hash: struct{}{},
+ b12.Hash: struct{}{},
+ b32.Hash: struct{}{},
+ },
+ }
+ b33 = &types.Block{
+ ProposerID: validators[3],
+ ParentHash: b32.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 3,
+ Acks: map[common.Hash]struct{}{
+ b02.Hash: struct{}{},
+ b12.Hash: struct{}{},
+ b22.Hash: struct{}{},
+ },
+ }
+ Debugf("\n")
+ Debugf("B01: %s\n", b01.Hash)
+ Debugf("B11: %s\n", b11.Hash)
+ Debugf("B21: %s\n", b21.Hash)
+ Debugf("B31: %s\n", b31.Hash)
+ Debugf("\n")
+ Debugf("B02: %s\n", b02.Hash)
+ Debugf("B12: %s\n", b12.Hash)
+ Debugf("B22: %s\n", b22.Hash)
+ Debugf("B32: %s\n", b32.Hash)
+ Debugf("\n")
+ Debugf("B03: %s\n", b03.Hash)
+ Debugf("B13: %s\n", b13.Hash)
+ Debugf("B23: %s\n", b23.Hash)
+ Debugf("B33: %s\n", b33.Hash)
+ Debugf("\n")
+}
+
+func (s *BlockLatticeTest) TestAckAndStateTransition() {
+ // Recieve Order:
+ // B01 -> B12 -> B11 -> B21 -> B31 -> B02 -> B32 -> B22 -> B13 -> B33
+ // -> B03 -> B23
+
+ // B01
+ lattice.ProcessBlock(b01)
+
+ // Set status check.
+ s.Require().Equal(0, len(lattice.waitingSet))
+ s.Require().Equal(1, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(0, len(lattice.pendingSet))
+
+ s.Require().Equal(0, len(b01.IndirectAcks))
+ s.Require().Equal(0, len(b01.AckedBy))
+
+ // B12
+ lattice.ProcessBlock(b12)
+
+ // Set status check.
+ s.Require().Equal(1, len(lattice.waitingSet))
+ s.Require().Equal(1, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(0, len(lattice.pendingSet))
+
+ s.Require().NotNil(lattice.waitingSet[b12.Hash])
+
+ // B11
+ lattice.ProcessBlock(b11)
+
+ // b01 is acked.
+ s.Require().Equal(1, len(b01.AckedBy))
+ s.Require().NotNil(b01.AckedBy[b11.Hash])
+ // b11 indirect acks.
+ s.Require().Equal(1, len(b11.IndirectAcks))
+ s.Require().Equal(0, len(b11.AckedBy))
+ s.Require().NotNil(b11.IndirectAcks[genesises[0].Hash])
+
+ // Set status check.
+ s.Require().Equal(1, len(lattice.waitingSet))
+ s.Require().Equal(2, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(0, len(lattice.pendingSet))
+
+ // B21
+ lattice.ProcessBlock(b21)
+
+ // Set status check.
+ s.Require().Equal(1, len(lattice.waitingSet))
+ s.Require().Equal(3, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(0, len(lattice.pendingSet))
+
+ // b01 is acked.
+ s.Require().Equal(2, len(b01.AckedBy))
+ s.Require().NotNil(b01.AckedBy[b21.Hash])
+ // b21 indirect acks.
+ s.Require().Equal(1, len(b21.IndirectAcks))
+ s.Require().Equal(0, len(b21.AckedBy))
+
+ // B31
+ lattice.ProcessBlock(b31)
+
+ // Set status check.
+ s.Require().Equal(0, len(lattice.waitingSet))
+ s.Require().Equal(4, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(1, len(lattice.pendingSet))
+
+ s.Require().NotNil(lattice.pendingSet[b01.Hash])
+ s.Require().Equal(types.BlockStatusToTo, b01.State)
+
+ // b01 is acked.
+ s.Require().Equal(3, len(b01.AckedBy))
+ s.Require().NotNil(b01.AckedBy[b31.Hash])
+
+ // b11 is acked.
+ s.Require().Equal(1, len(b11.AckedBy))
+ s.Require().NotNil(b11.AckedBy[b31.Hash])
+
+ // b31 indirect acks.
+ s.Require().Equal(2, len(b31.IndirectAcks))
+ s.Require().Equal(1, len(b31.AckedBy))
+ s.Require().NotNil(b31.AckedBy[b12.Hash])
+
+ // b21 & b31 is acked by b12 (which is previously in waiting set).
+ s.Require().Equal(1, len(b21.AckedBy))
+ s.Require().NotNil(b21.AckedBy[b12.Hash])
+ s.Require().Equal(1, len(b31.AckedBy))
+ s.Require().NotNil(b31.AckedBy[b12.Hash])
+
+ // B02
+ lattice.ProcessBlock(b02)
+
+ // Set status check.
+ s.Require().Equal(0, len(lattice.waitingSet))
+ s.Require().Equal(4, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(1, len(lattice.pendingSet))
+
+ // b11 is acked.
+ s.Require().Equal(2, len(b11.AckedBy))
+ s.Require().NotNil(b11.AckedBy[b02.Hash])
+ // b21 is acked.
+ s.Require().Equal(2, len(b21.AckedBy))
+ s.Require().NotNil(b21.AckedBy[b02.Hash])
+ s.Require().NotNil(b21.AckedBy[b12.Hash])
+ // b31 is acked.
+ s.Require().Equal(2, len(b31.AckedBy))
+ s.Require().NotNil(b31.AckedBy[b02.Hash])
+ s.Require().NotNil(b31.AckedBy[b12.Hash])
+
+ // B32
+ lattice.ProcessBlock(b32)
+
+ // Set status check.
+ s.Require().Equal(0, len(lattice.waitingSet))
+ s.Require().Equal(4, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(2, len(lattice.pendingSet))
+
+ s.Require().NotNil(lattice.pendingSet[b01.Hash])
+ s.Require().NotNil(lattice.pendingSet[b21.Hash])
+
+ // b02 is acked.
+ s.Require().Equal(1, len(b02.AckedBy))
+ s.Require().NotNil(b02.AckedBy[b32.Hash])
+ // b12 is acked.
+ s.Require().Equal(1, len(b12.AckedBy))
+ s.Require().NotNil(b12.AckedBy[b32.Hash])
+
+ // B22
+ lattice.ProcessBlock(b22)
+
+ // Set status check.
+ s.Require().Equal(0, len(lattice.waitingSet))
+ s.Require().Equal(4, len(lattice.ackCandidateSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(4, len(lattice.pendingSet))
+
+ s.Require().NotNil(lattice.pendingSet[b01.Hash])
+ s.Require().NotNil(lattice.pendingSet[b11.Hash])
+ s.Require().NotNil(lattice.pendingSet[b21.Hash])
+ s.Require().NotNil(lattice.pendingSet[b31.Hash])
+ s.Require().Equal(types.BlockStatusToTo, b01.State)
+ s.Require().Equal(types.BlockStatusToTo, b11.State)
+ s.Require().Equal(types.BlockStatusToTo, b21.State)
+ s.Require().Equal(types.BlockStatusToTo, b31.State)
+
+ // b02 is acked.
+ s.Require().Equal(2, len(b02.AckedBy))
+ s.Require().NotNil(b02.AckedBy[b22.Hash])
+ // b12 is acked.
+ s.Require().Equal(2, len(b12.AckedBy))
+ s.Require().NotNil(b12.AckedBy[b22.Hash])
+ // b31 is acked.
+ s.Require().Equal(3, len(b31.AckedBy))
+ s.Require().NotNil(b31.AckedBy[b22.Hash])
+
+ // b22 indirect acks.
+ s.Require().NotNil(b22.IndirectAcks[b11.Hash])
+ s.Require().NotNil(b22.IndirectAcks[b01.Hash])
+
+ // B13, B33, B03, B23
+ lattice.ProcessBlock(b13)
+ lattice.ProcessBlock(b33)
+ lattice.ProcessBlock(b03)
+ lattice.ProcessBlock(b23)
+
+ s.Require().Equal(8, len(lattice.pendingSet))
+}
+
+func (s *BlockLatticeTest) TesttotalOrdering() {
+ // Recieve Order:
+ // B01 -> B12 -> B11 -> B21 -> B31 -> B02 -> B32 -> B22 -> B13 -> B33
+ // -> B03 -> B23
+
+ lattice.ProcessBlock(b01)
+ lattice.ProcessBlock(b12)
+ lattice.ProcessBlock(b11)
+ lattice.ProcessBlock(b21)
+
+ // B01 in pendingSet after b31 is recieved.
+ lattice.ProcessBlock(b31)
+ s.Require().NotNil(lattice.pendingSet[b01.Hash])
+
+ // Run total ordering for b01.
+ lattice.totalOrdering(b01)
+ s.Require().Equal(0, len(s.app.Outputs))
+ s.Require().Equal(1, len(lattice.candidateSet))
+ s.Require().Equal(1, len(lattice.pendingSet))
+ s.Require().NotNil(lattice.candidateSet[b01.Hash])
+
+ // ABS & AHV
+ s.Require().Equal(1, len(lattice.AHV[b01.Hash]))
+
+ lattice.ProcessBlock(b02)
+ lattice.ProcessBlock(b32)
+
+ // B21 in pendingSet after b32 is recieved.
+ s.Require().Equal(2, len(lattice.pendingSet))
+ s.Require().NotNil(lattice.pendingSet[b01.Hash])
+ s.Require().NotNil(lattice.pendingSet[b21.Hash])
+
+ // Run total ordering for b21.
+ lattice.totalOrdering(b21)
+ s.Require().Equal(2, len(lattice.pendingSet))
+ s.Require().Equal(0, len(s.app.Outputs))
+
+ // ABS & AHV
+ s.Require().Equal(uint64(1), lattice.ABS[b01.Hash][b21.ProposerID])
+ s.Require().Equal(uint64(1), lattice.AHV[b01.Hash][b21.ProposerID])
+
+ lattice.ProcessBlock(b22)
+ s.Require().Equal(4, len(lattice.pendingSet))
+
+ // Run total ordering for b31.
+ lattice.totalOrdering(b31)
+ s.Require().Equal(1, len(s.app.Outputs))
+ s.Require().Equal(b01.Hash, s.app.Outputs[0].Hash)
+ s.Require().Equal(false, s.app.Early)
+ lattice.updateABSAHV()
+ s.Require().Equal(3, len(lattice.abs()))
+ s.Require().Equal(2, len(lattice.candidateSet))
+ s.app.Clear()
+
+ lattice.totalOrdering(b22)
+ s.Require().Equal(0, len(s.app.Outputs))
+ s.Require().Equal(2, len(lattice.candidateSet))
+ s.Require().Equal(3, len(lattice.abs()))
+
+ lattice.ProcessBlock(b13, true)
+ s.Require().Equal(2, len(s.app.Outputs))
+ s.Require().Equal(b21.Hash, s.app.Outputs[0].Hash)
+ s.Require().Equal(b11.Hash, s.app.Outputs[1].Hash)
+ s.Require().Equal(false, s.app.Early)
+ s.app.Clear()
+
+ lattice.ProcessBlock(b33, true)
+ s.Require().Equal(0, len(s.app.Outputs))
+
+ lattice.ProcessBlock(b03, true)
+ s.Require().Equal(1, len(s.app.Outputs))
+ s.Require().Equal(b31.Hash, s.app.Outputs[0].Hash)
+ s.Require().Equal(false, s.app.Early)
+ s.app.Clear()
+
+ lattice.ProcessBlock(b23, true)
+ s.Require().Equal(2, len(s.app.Outputs))
+ s.Require().Equal(b02.Hash, s.app.Outputs[0].Hash)
+ s.Require().Equal(b12.Hash, s.app.Outputs[1].Hash)
+
+ s.Require().Equal(0, len(lattice.waitingSet))
+ s.Require().Equal(0, len(lattice.stronglyAckedSet))
+ s.Require().Equal(2, len(lattice.pendingSet))
+ lattice.updateABSAHV()
+ s.Require().Equal(2, len(lattice.abs()))
+}
+
+func TestBlockLattice(t *testing.T) {
+ suite.Run(t, new(BlockLatticeTest))
+}
diff --git a/core/network.go b/core/network.go
new file mode 100644
index 0000000..1bd714d
--- /dev/null
+++ b/core/network.go
@@ -0,0 +1,33 @@
+// copyright 2018 the dexon-consensus-core authors
+// this file is part of the dexon-consensus-core library.
+//
+// the dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the gnu lesser general public license as
+// published by the free software foundation, either version 3 of the license,
+// or (at your option) any later version.
+//
+// the dexon-consensus-core library is distributed in the hope that it will be
+// useful, but without any warranty; without even the implied warranty of
+// merchantability or fitness for a particular purpose. see the gnu lesser
+// general public license for more details.
+//
+// you should have received a copy of the gnu lesser general public license
+// along with the dexon-consensus-core library. if not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+)
+
+// Endpoint is the interface for a client network endoint.
+type Endpoint interface {
+ GetID() types.ValidatorID
+}
+
+// Network is the interface for network related functions.
+type Network interface {
+ Join(endpoint Endpoint) chan interface{}
+ BroadcastBlock(block *types.Block)
+}
diff --git a/core/types/block.go b/core/types/block.go
new file mode 100644
index 0000000..265d32f
--- /dev/null
+++ b/core/types/block.go
@@ -0,0 +1,89 @@
+// 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 Pubic License as
+// pubished 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 Pubic License for more details.
+//
+// You should have received a copy of the GNU Lesser General Pubic License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package types
+
+import (
+ "bytes"
+ "fmt"
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+)
+
+// State represent the block process state.
+type State int
+
+// Block Status.
+const (
+ BlockStatusInit State = iota
+ BlockStatusAcked
+ BlockStatusToTo
+ BlockStatusFinal
+)
+
+// Block represents a single event broadcasted on the network.
+type Block struct {
+ ProposerID ValidatorID
+ ParentHash common.Hash
+ Hash common.Hash
+ Height uint64
+ Timestamps map[ValidatorID]time.Time
+ Acks map[common.Hash]struct{}
+
+ IndirectAcks map[common.Hash]struct{}
+ AckedBy map[common.Hash]bool // bool: direct
+ State State
+}
+
+func (b *Block) String() string {
+ return fmt.Sprintf("Block(%v)", b.Hash.String()[:6])
+}
+
+// Clone returns a deep copy of a block.
+func (b *Block) Clone() *Block {
+ bcopy := &Block{
+ ProposerID: b.ProposerID,
+ ParentHash: b.ParentHash,
+ Hash: b.Hash,
+ Height: b.Height,
+ Timestamps: make(map[ValidatorID]time.Time),
+ Acks: make(map[common.Hash]struct{}),
+ }
+ for k, v := range b.Timestamps {
+ bcopy.Timestamps[k] = v
+ }
+ for k, v := range b.Acks {
+ bcopy.Acks[k] = v
+ }
+ return bcopy
+}
+
+// ByHash is the helper type for sorting slice of blocks by hash.
+type ByHash []*Block
+
+func (b ByHash) Len() int {
+ return len(b)
+}
+
+func (b ByHash) Less(i int, j int) bool {
+ return bytes.Compare([]byte(b[i].Hash[:]), []byte(b[j].Hash[:])) == -1
+}
+
+func (b ByHash) Swap(i int, j int) {
+ b[i], b[j] = b[j], b[i]
+}
diff --git a/core/types/validator.go b/core/types/validator.go
new file mode 100644
index 0000000..5d424e8
--- /dev/null
+++ b/core/types/validator.go
@@ -0,0 +1,37 @@
+// 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 types
+
+import (
+ "bytes"
+ "encoding/hex"
+
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+)
+
+// ValidatorID is the ID type for validators.
+type ValidatorID common.Hash
+
+func (v ValidatorID) String() string {
+ return hex.EncodeToString([]byte(v[:]))[:6]
+}
+
+// Equal check if two validator IDs are the same.
+func (v ValidatorID) Equal(v2 ValidatorID) bool {
+ return bytes.Compare([]byte(v[:]), []byte(v2[:])) == 0
+}
diff --git a/core/utils.go b/core/utils.go
new file mode 100644
index 0000000..2f87f97
--- /dev/null
+++ b/core/utils.go
@@ -0,0 +1,45 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "fmt"
+ "os"
+)
+
+var debug = false
+
+func init() {
+ if os.Getenv("DEBUG") != "" {
+ debug = true
+ }
+}
+
+// Debugf is like fmt.Printf, but only output when we are in debug mode.
+func Debugf(format string, args ...interface{}) {
+ if debug {
+ fmt.Printf(format, args...)
+ }
+}
+
+// Debugln is like fmt.Println, but only output when we are in debug mode.
+func Debugln(args ...interface{}) {
+ if debug {
+ fmt.Println(args)
+ }
+}
diff --git a/core/validator.go b/core/validator.go
new file mode 100644
index 0000000..93dbbce
--- /dev/null
+++ b/core/validator.go
@@ -0,0 +1,22 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+// Validator represents a validator in DEXON consensus algorithm.
+type Validator struct {
+}
diff --git a/simulation/app.go b/simulation/app.go
new file mode 100644
index 0000000..02d1c1e
--- /dev/null
+++ b/simulation/app.go
@@ -0,0 +1,50 @@
+// 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 simulation
+
+import (
+ "fmt"
+
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+)
+
+// SimApp is an DEXON app for simulation.
+type SimApp struct {
+ ValidatorID types.ValidatorID
+ Outputs []*types.Block
+ Early bool
+}
+
+// NewSimApp returns point to a new instance of SimApp.
+func NewSimApp(id types.ValidatorID) *SimApp {
+ return &SimApp{
+ ValidatorID: id,
+ }
+}
+
+// ValidateBlock validates a given block.
+func (a *SimApp) ValidateBlock(b *types.Block) bool {
+ return true
+}
+
+// Deliver is called when blocks are delivered by the total ordering algorithm.
+func (a *SimApp) Deliver(blocks []*types.Block, early bool) {
+ a.Outputs = blocks
+ a.Early = early
+ fmt.Println("OUTPUT", a.ValidatorID, a.Early, a.Outputs)
+}
diff --git a/simulation/config/config.go b/simulation/config/config.go
new file mode 100644
index 0000000..228d69b
--- /dev/null
+++ b/simulation/config/config.go
@@ -0,0 +1,89 @@
+// 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 config
+
+import (
+ "os"
+
+ "github.com/naoina/toml"
+)
+
+// Validator config for the simulation.
+type Validator struct {
+ Num int
+ ProposeIntervalMean float64
+ ProposeIntervalSigma float64
+}
+
+// Networking config.
+type Networking struct {
+ Mean float64
+ Sigma float64
+ LossRateValue float64
+}
+
+// Config represents the configuration for simulation.
+type Config struct {
+ Title string
+ Validator Validator
+ Networking Networking
+}
+
+// GenerateDefault generates a default configuration file.
+func GenerateDefault(path string) error {
+ f, err := os.Create(path)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ config := Config{
+ Title: "DEXON Consensus Simulation Config",
+ Validator: Validator{
+ Num: 4,
+ ProposeIntervalMean: 500,
+ ProposeIntervalSigma: 30,
+ },
+ Networking: Networking{
+ Mean: 100,
+ Sigma: 30,
+ LossRateValue: 0,
+ },
+ }
+
+ if err := toml.NewEncoder(f).Encode(&config); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Read reads the config from a file.
+func Read(path string) (*Config, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ var config Config
+
+ if toml.NewDecoder(f).Decode(&config); err != nil {
+ return nil, err
+ }
+ return &config, nil
+}
diff --git a/simulation/network-model.go b/simulation/network-model.go
new file mode 100644
index 0000000..041bd12
--- /dev/null
+++ b/simulation/network-model.go
@@ -0,0 +1,68 @@
+package simulation
+
+import (
+ "math/rand"
+ "time"
+)
+
+// Model is the interface for define a given network environment.
+type Model interface {
+ // LossRate returns the message lost ratio between [0, 1)
+ LossRate() float64
+
+ // Delay returns the send delay of the message. This function is called each
+ // time before the message is sent, so one can return different number each
+ // time.
+ Delay() time.Duration
+}
+
+// LosslessNetwork is a lossless network model.
+type LosslessNetwork struct {
+}
+
+// LossRate returns lossrate for the model.
+func (l *LosslessNetwork) LossRate() float64 {
+ return 0.0
+}
+
+// Delay returns the send delay of a given message.
+func (l *LosslessNetwork) Delay() time.Duration {
+ return time.Duration(0)
+}
+
+// FixedLostNoDelayModel is a network with no delay and a fixed lost
+// ratio.
+type FixedLostNoDelayModel struct {
+ LossRateValue float64
+}
+
+// LossRate returns lossrate for the model.
+func (f *FixedLostNoDelayModel) LossRate() float64 {
+ return f.LossRateValue
+}
+
+// Delay returns the send delay of a given message.
+func (f *FixedLostNoDelayModel) Delay() time.Duration {
+ return time.Duration(0)
+}
+
+// NormalNetwork is a model where it's delay is a normal distribution.
+type NormalNetwork struct {
+ Sigma float64
+ Mean float64
+ LossRateValue float64
+}
+
+// LossRate returns lossrate for the model.
+func (n *NormalNetwork) LossRate() float64 {
+ return n.LossRateValue
+}
+
+// Delay returns the send delay of a given message.
+func (n *NormalNetwork) Delay() time.Duration {
+ delay := rand.NormFloat64()*n.Sigma + n.Mean
+ if delay < 0 {
+ delay = n.Sigma / 2
+ }
+ return time.Duration(delay) * time.Millisecond
+}
diff --git a/simulation/network-model_test.go b/simulation/network-model_test.go
new file mode 100644
index 0000000..aefa3c6
--- /dev/null
+++ b/simulation/network-model_test.go
@@ -0,0 +1,31 @@
+package simulation
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/suite"
+)
+
+type NetworkModelsTestSuite struct {
+ suite.Suite
+}
+
+func (n *NetworkModelsTestSuite) SetupTest() {
+}
+
+func (n *NetworkModelsTestSuite) TearDownTest() {
+}
+
+// TestNormalNetwork make sure the Delay() or NormalNetwork does not
+// exceeds 200ms.
+func (n *NetworkModelsTestSuite) TestNormalNetwork() {
+ m := NormalNetwork{}
+ for i := 0; i < 1000; i++ {
+ n.Require().True(m.Delay() < 200*time.Millisecond)
+ }
+}
+
+func TestNetworkModels(t *testing.T) {
+ suite.Run(t, new(NetworkModelsTestSuite))
+}
diff --git a/simulation/network.go b/simulation/network.go
new file mode 100644
index 0000000..51eb868
--- /dev/null
+++ b/simulation/network.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 simulation
+
+import (
+ "math/rand"
+ "sync"
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus-core/core"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+)
+
+const msgBufferSize = 128
+
+// Network implements the consensus.Network interface.
+type Network struct {
+ model Model
+
+ endpointMutex sync.RWMutex
+ endpoints map[types.ValidatorID]chan interface{}
+}
+
+// NewNetwork returns pointer to a new Network instance.
+func NewNetwork(model Model) *Network {
+ return &Network{
+ model: model,
+ endpoints: make(map[types.ValidatorID]chan interface{}),
+ }
+}
+
+// Join allow a client to join the network. It reutnrs a interface{} channel for
+// the client to recieve information.
+func (n *Network) Join(endpoint core.Endpoint) chan interface{} {
+ n.endpointMutex.Lock()
+ defer n.endpointMutex.Unlock()
+
+ if x, exists := n.endpoints[endpoint.GetID()]; exists {
+ return x
+ }
+
+ recivingChannel := make(chan interface{}, msgBufferSize)
+ n.endpoints[endpoint.GetID()] = recivingChannel
+ return recivingChannel
+}
+
+// Send sends a msg to another client.
+func (n *Network) Send(destID types.ValidatorID, msg interface{}) {
+ n.endpointMutex.RLock()
+ defer n.endpointMutex.RUnlock()
+
+ clientChannel, exists := n.endpoints[destID]
+ if !exists {
+ return
+ }
+
+ go func() {
+ if rand.Float64() > n.model.LossRate() {
+ time.Sleep(n.model.Delay())
+
+ clientChannel <- msg
+ }
+ }()
+}
+
+// BroadcastBlock broadcast blocks into the network.
+func (n *Network) BroadcastBlock(block *types.Block) {
+ for endpoint := range n.endpoints {
+ n.Send(endpoint, block.Clone())
+ }
+}
diff --git a/simulation/simulation.go b/simulation/simulation.go
new file mode 100644
index 0000000..8708293
--- /dev/null
+++ b/simulation/simulation.go
@@ -0,0 +1,58 @@
+// 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 simulation
+
+import (
+ "fmt"
+
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+ "github.com/dexon-foundation/dexon-consensus-core/simulation/config"
+)
+
+// Run starts the simulation.
+func Run(configPath string) {
+ config, err := config.Read(configPath)
+ if err != nil {
+ panic(err)
+ }
+
+ networkModel := &NormalNetwork{
+ Sigma: config.Networking.Sigma,
+ Mean: config.Networking.Mean,
+ LossRateValue: config.Networking.LossRateValue,
+ }
+ network := NewNetwork(networkModel)
+
+ var vs []*Validator
+ for i := 0; i < config.Validator.Num; i++ {
+ id := types.ValidatorID(common.NewRandomHash())
+ vs = append(vs, NewValidator(id, config.Validator, network, nil))
+ }
+
+ for i := 0; i < config.Validator.Num; i++ {
+ vs[i].Bootstrap(vs)
+ }
+
+ for i := 0; i < config.Validator.Num; i++ {
+ fmt.Printf("Validator %d: %s\n", i, vs[i].ID)
+ go vs[i].Run()
+ }
+
+ select {}
+}
diff --git a/simulation/validator.go b/simulation/validator.go
new file mode 100644
index 0000000..84e5e52
--- /dev/null
+++ b/simulation/validator.go
@@ -0,0 +1,139 @@
+// 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 simulation
+
+import (
+ "time"
+
+ "github.com/syndtr/goleveldb/leveldb"
+
+ "github.com/dexon-foundation/dexon-consensus-core/blockdb"
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+ "github.com/dexon-foundation/dexon-consensus-core/simulation/config"
+)
+
+// Validator represents a validator in DexCon.
+type Validator struct {
+ network core.Network
+ app *SimApp
+
+ config config.Validator
+ db *leveldb.DB
+ msgChannel chan interface{}
+
+ ID types.ValidatorID
+ lattice *core.BlockLattice
+ compactionChain *core.BlockChain
+
+ genesis *types.Block
+ current *types.Block
+}
+
+// NewValidator returns a new empty validator.
+func NewValidator(
+ id types.ValidatorID,
+ config config.Validator,
+ network core.Network,
+ db *leveldb.DB) *Validator {
+
+ hash := common.NewRandomHash()
+ genesis := &types.Block{
+ ProposerID: id,
+ ParentHash: hash,
+ Hash: hash,
+ Height: 0,
+ Acks: map[common.Hash]struct{}{},
+ }
+
+ app := NewSimApp(id)
+ lattice := core.NewBlockLattice(blockdb.NewMemBackedBlockDB(), network, app)
+
+ return &Validator{
+ ID: id,
+ config: config,
+ network: network,
+ app: app,
+ db: db,
+ lattice: lattice,
+ genesis: genesis,
+ current: genesis,
+ }
+}
+
+// GetID returns the ID of validator.
+func (v *Validator) GetID() types.ValidatorID {
+ return v.ID
+}
+
+// Bootstrap bootstraps a validator.
+func (v *Validator) Bootstrap(vs []*Validator) {
+ for _, x := range vs {
+ v.lattice.AddValidator(x.ID, x.genesis)
+ }
+ v.lattice.SetOwner(v.ID)
+}
+
+// Run starts the validator.
+func (v *Validator) Run() {
+ v.msgChannel = v.network.Join(v)
+
+ go v.MsgServer()
+ go v.BlockProposer()
+
+ // Blocks forever.
+ select {}
+}
+
+// MsgServer listen to the network channel for message and handle it.
+func (v *Validator) MsgServer() {
+ for {
+ msg := <-v.msgChannel
+
+ switch val := msg.(type) {
+ case *types.Block:
+ //if val.ProposerID.Equal(v.ID) {
+ // continue
+ //}
+ v.lattice.ProcessBlock(val, true)
+ }
+ }
+}
+
+// BlockProposer propose blocks to be send to the DEXON network.
+func (v *Validator) BlockProposer() {
+ model := &NormalNetwork{
+ Sigma: v.config.ProposeIntervalSigma,
+ Mean: v.config.ProposeIntervalMean,
+ }
+
+ for {
+ time.Sleep(model.Delay())
+
+ block := &types.Block{
+ ProposerID: v.ID,
+ ParentHash: v.current.Hash,
+ Hash: common.NewRandomHash(),
+ Height: 0,
+ Acks: map[common.Hash]struct{}{},
+ }
+ v.current = block
+ v.lattice.ProposeBlock(block)
+ }
+}