aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-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
9 files changed, 1396 insertions, 0 deletions
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 {
+}