diff options
author | Wei-Ning Huang <w@dexon.org> | 2018-07-16 00:12:17 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@cobinhood.com> | 2018-07-16 11:06:14 +0800 |
commit | aed24cf020bd11c3b20a7011b96c02e41894fa32 (patch) | |
tree | 720bc1542dd1edb7308c124a5265e21b3c01d08b | |
download | dexon-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-- | GNUmakefile | 30 | ||||
-rw-r--r-- | Gopkg.lock | 77 | ||||
-rw-r--r-- | Gopkg.toml | 46 | ||||
-rw-r--r-- | blockdb/interfaces.go | 59 | ||||
-rw-r--r-- | blockdb/memory.go | 85 | ||||
-rw-r--r-- | cmd/dexcon-simulation/main.go | 53 | ||||
-rw-r--r-- | common/types.go | 40 | ||||
-rw-r--r-- | common/utils.go | 14 | ||||
-rw-r--r-- | core/application.go | 27 | ||||
-rw-r--r-- | core/blockchain.go | 26 | ||||
-rw-r--r-- | core/blocklattice.go | 596 | ||||
-rw-r--r-- | core/blocklattice_test.go | 521 | ||||
-rw-r--r-- | core/network.go | 33 | ||||
-rw-r--r-- | core/types/block.go | 89 | ||||
-rw-r--r-- | core/types/validator.go | 37 | ||||
-rw-r--r-- | core/utils.go | 45 | ||||
-rw-r--r-- | core/validator.go | 22 | ||||
-rw-r--r-- | simulation/app.go | 50 | ||||
-rw-r--r-- | simulation/config/config.go | 89 | ||||
-rw-r--r-- | simulation/network-model.go | 68 | ||||
-rw-r--r-- | simulation/network-model_test.go | 31 | ||||
-rw-r--r-- | simulation/network.go | 86 | ||||
-rw-r--r-- | simulation/simulation.go | 58 | ||||
-rw-r--r-- | simulation/validator.go | 139 |
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) + } +} |