aboutsummaryrefslogtreecommitdiffstats
path: root/core/acking.go
diff options
context:
space:
mode:
authorHaoping Ku <haoping.ku@dexon.org>2018-07-30 11:40:16 +0800
committerWei-Ning Huang <aitjcize@gmail.com>2018-07-30 11:40:16 +0800
commitaaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb (patch)
tree1682bfaa9abca34ef51e57ac527f5b28eb504106 /core/acking.go
parent279daea6e004ab6ad9d079ccc35b7c52d79630ad (diff)
downloaddexon-consensus-aaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb.tar
dexon-consensus-aaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb.tar.gz
dexon-consensus-aaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb.tar.bz2
dexon-consensus-aaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb.tar.lz
dexon-consensus-aaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb.tar.xz
dexon-consensus-aaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb.tar.zst
dexon-consensus-aaeaebaf5dec3a9a17d0a626cb19e76834f9e4cb.zip
Add acking module (#13)
* Refactor and add acking module Extract acking module for unit testing. This commit splits functions into small pieces for better understanding and easy unit testing.
Diffstat (limited to 'core/acking.go')
-rw-r--r--core/acking.go308
1 files changed, 308 insertions, 0 deletions
diff --git a/core/acking.go b/core/acking.go
new file mode 100644
index 0000000..b244831
--- /dev/null
+++ b/core/acking.go
@@ -0,0 +1,308 @@
+// 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"
+
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+)
+
+// Acking is for acking module.
+type Acking struct {
+ // lattice stores blocks by its validator ID and height.
+ lattice map[types.ValidatorID]*ackingValidatorStatus
+
+ // blocks stores the hash to block map.
+ blocks map[common.Hash]*types.Block
+
+ // receivedBlocks stores blocks which is received but its acks are not all
+ // in lattice.
+ receivedBlocks map[common.Hash]*types.Block
+
+ // ackedBlocks stores blocks in status types.BlockStatusAcked, which are
+ // strongly acked but not yet being output to total ordering module.
+ ackedBlocks map[common.Hash]*types.Block
+}
+
+type ackingValidatorStatus struct {
+ // blocks stores blocks proposed by specified validator in map which key is
+ // the height of the block.
+ blocks map[uint64]*types.Block
+
+ // nextAck stores the height of next height that should be acked, i.e. last
+ // acked height + 1. Initialized to 0, when genesis blocks are still not
+ // being acked. For example, a.lattice[vid1].NextAck[vid2] - 1 is the last
+ // acked height by vid1 acking vid2.
+ nextAck map[types.ValidatorID]uint64
+
+ // nextOutput is the next output height of block, default to 0.
+ nextOutput uint64
+
+ // restricted is the flag of a validator is in restricted mode or not.
+ restricted bool
+}
+
+// Errors for sanity check error.
+var (
+ ErrInvalidProposerID = fmt.Errorf("invalid_proposer_id")
+ ErrForkBlock = fmt.Errorf("fork_block")
+ ErrNotAckParent = fmt.Errorf("not_ack_parent")
+ ErrDoubleAck = fmt.Errorf("double_ack")
+ ErrInvalidBlockHeight = fmt.Errorf("invalid_block_height")
+)
+
+// NewAcking creates a new Acking struct.
+func NewAcking() *Acking {
+ return &Acking{
+ lattice: make(map[types.ValidatorID]*ackingValidatorStatus),
+ blocks: make(map[common.Hash]*types.Block),
+ receivedBlocks: make(map[common.Hash]*types.Block),
+ ackedBlocks: make(map[common.Hash]*types.Block),
+ }
+}
+
+func (a *Acking) sanityCheck(b *types.Block) error {
+ // Check if its proposer is in validator set.
+ if _, exist := a.lattice[b.ProposerID]; !exist {
+ return ErrInvalidProposerID
+ }
+
+ // Check if it forks.
+ if bInLattice, exist := a.lattice[b.ProposerID].blocks[b.Height]; exist {
+ if b.Hash != bInLattice.Hash {
+ return ErrForkBlock
+ }
+ }
+
+ // Check non-genesis blocks if it acks its parent.
+ if b.Height > 0 {
+ if _, exist := b.Acks[b.ParentHash]; !exist {
+ return ErrNotAckParent
+ }
+ bParent := a.blocks[b.ParentHash]
+ if bParent.Height != b.Height-1 {
+ return ErrInvalidBlockHeight
+ }
+ }
+
+ // Check if it acks older blocks.
+ for hash := range b.Acks {
+ if bAck, exist := a.blocks[hash]; exist {
+ if bAck.Height < a.lattice[b.ProposerID].nextAck[bAck.ProposerID] {
+ return ErrDoubleAck
+ }
+ }
+ }
+
+ // TODO(haoping): application layer check of block's content
+
+ return nil
+}
+
+// areAllAcksReceived checks if all ack blocks of a block are all in lattice.
+func (a *Acking) areAllAcksInLattice(b *types.Block) bool {
+ for h := range b.Acks {
+ bAck, exist := a.blocks[h]
+ if !exist {
+ return false
+ }
+ if bAckInLattice, exist := a.lattice[bAck.ProposerID].blocks[bAck.Height]; !exist {
+ if bAckInLattice.Hash != bAck.Hash {
+ panic("areAllAcksInLattice: Acking.lattice has corrupted")
+ }
+ return false
+ }
+ }
+ return true
+}
+
+// ProcessBlock processes block, it does sanity check, inserts block into
+// lattice, handles strong acking and deletes blocks which will not be used.
+func (a *Acking) ProcessBlock(block *types.Block) {
+ // If a block does not pass sanity check, discard this block.
+ if err := a.sanityCheck(block); err != nil {
+ return
+ }
+ a.blocks[block.Hash] = block
+ block.AckedValidators = make(map[types.ValidatorID]struct{})
+ a.receivedBlocks[block.Hash] = block
+
+ // Check blocks in receivedBlocks if its acks are all in lattice. If a block's
+ // acking blocks are all in lattice, execute sanity check and add the block
+ // into lattice.
+ blocksToAcked := map[common.Hash]*types.Block{}
+ for {
+ blocksToLattice := map[common.Hash]*types.Block{}
+ for _, b := range a.receivedBlocks {
+ if a.areAllAcksInLattice(b) {
+ blocksToLattice[b.Hash] = b
+ }
+ }
+ if len(blocksToLattice) == 0 {
+ break
+ }
+ for _, b := range blocksToLattice {
+ // Sanity check must been executed again here for the case that several
+ // valid blocks with different content being added into blocksToLattice
+ // in the same time. For example
+ // B C Block B and C both ack A and are valid. B, C received first
+ // \ / (added in receivedBlocks), and A comes, if sanity check is
+ // A not being executed here, B and C will both be added in lattice
+ if err := a.sanityCheck(b); err != nil {
+ delete(a.blocks, b.Hash)
+ delete(a.receivedBlocks, b.Hash)
+ continue
+ }
+ a.lattice[b.ProposerID].blocks[b.Height] = b
+ delete(a.receivedBlocks, b.Hash)
+ for h := range b.Acks {
+ bAck := a.blocks[h]
+ // Update nextAck only when bAck.Height + 1 is greater. A block might
+ // ack blocks proposed by same validator with different height.
+ if a.lattice[b.ProposerID].nextAck[bAck.ProposerID] < bAck.Height+1 {
+ a.lattice[b.ProposerID].nextAck[bAck.ProposerID] = bAck.Height + 1
+ }
+ // Update AckedValidators for each ack blocks and its parents.
+ for {
+ if _, exist := bAck.AckedValidators[b.ProposerID]; exist {
+ break
+ }
+ if bAck.Status > types.BlockStatusInit {
+ break
+ }
+ bAck.AckedValidators[b.ProposerID] = struct{}{}
+ // A block is strongly acked if it is acked by more than
+ // 2 * (maximum number of byzatine validators) unique validators.
+ if len(bAck.AckedValidators) > 2*((len(a.lattice)-1)/3) {
+ blocksToAcked[bAck.Hash] = bAck
+ }
+ if bAck.Height == 0 {
+ break
+ }
+ bAck = a.blocks[bAck.ParentHash]
+ }
+ }
+ }
+ }
+
+ for _, b := range blocksToAcked {
+ a.ackedBlocks[b.Hash] = b
+ b.Status = types.BlockStatusAcked
+ }
+
+ // TODO(haoping): delete blocks in received array when it is received a long
+ // time ago
+
+ // Delete old blocks in "lattice" and "blocks" for release memory space.
+ // First, find the height that blocks below it can be deleted. This height
+ // is defined by finding minimum of validator's nextOutput and last acking
+ // heights from other validators, i.e. a.lattice[v_other].nextAck[this_vid].
+ // This works because blocks of height below this minimum are not going to be
+ // acked anymore, the ackings of these blocks are illegal.
+ for vid := range a.lattice {
+ // Find the minimum height that heights lesser can be deleted.
+ min := a.lattice[vid].nextOutput
+ for vid2 := range a.lattice {
+ if a.lattice[vid2].nextAck[vid] < min {
+ min = a.lattice[vid2].nextAck[vid]
+ }
+ }
+ // "min" is the height of "next" last acked, min - 1 is the last height.
+ // Delete blocks from min - 2 which will never be acked.
+ if min < 3 {
+ continue
+ }
+ min -= 2
+ for {
+ b, exist := a.lattice[vid].blocks[min]
+ if !exist {
+ break
+ }
+ if b.Status >= types.BlockStatusOrdering {
+ delete(a.lattice[vid].blocks, b.Height)
+ delete(a.blocks, b.Hash)
+ }
+ if min == 0 {
+ break
+ }
+ min--
+ }
+ }
+}
+
+// ExtractBlocks returns all blocks that can be inserted into total ordering's
+// DAG. This function changes the status of blocks from types.BlockStatusAcked
+// to types.BlockStatusOrdering.
+func (a *Acking) ExtractBlocks() []*types.Block {
+ ret := []*types.Block{}
+ for {
+ updated := false
+ for vid := range a.lattice {
+ b, exist := a.lattice[vid].blocks[a.lattice[vid].nextOutput]
+ if !exist || b.Status < types.BlockStatusAcked {
+ continue
+ }
+ allAcksInOrderingStatus := true
+ // Check if all acks are in ordering or above status. If a block of an ack
+ // does not exist means that it deleted but its status is definitely Acked
+ // or ordering.
+ for ackHash := range b.Acks {
+ bAck, exist := a.blocks[ackHash]
+ if !exist {
+ continue
+ }
+ if bAck.Status < types.BlockStatusOrdering {
+ allAcksInOrderingStatus = false
+ break
+ }
+ }
+ if !allAcksInOrderingStatus {
+ continue
+ }
+ updated = true
+ b.Status = types.BlockStatusOrdering
+ delete(a.ackedBlocks, b.Hash)
+ ret = append(ret, b)
+ a.lattice[vid].nextOutput++
+ }
+ if !updated {
+ break
+ }
+ }
+ return ret
+}
+
+// AddValidator adds validator in the validator set.
+func (a *Acking) AddValidator(h types.ValidatorID) {
+ a.lattice[h] = &ackingValidatorStatus{
+ blocks: make(map[uint64]*types.Block),
+ nextAck: make(map[types.ValidatorID]uint64),
+ nextOutput: 0,
+ restricted: false,
+ }
+}
+
+// DeleteValidator deletes validator in validator set.
+func (a *Acking) DeleteValidator(h types.ValidatorID) {
+ for h := range a.lattice {
+ delete(a.lattice[h].nextAck, h)
+ }
+ delete(a.lattice, h)
+}