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 /core | |
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
Diffstat (limited to 'core')
-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 |
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 { +} |