aboutsummaryrefslogtreecommitdiffstats
path: root/core/lattice-data.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/lattice-data.go')
-rw-r--r--core/lattice-data.go181
1 files changed, 127 insertions, 54 deletions
diff --git a/core/lattice-data.go b/core/lattice-data.go
index 75447c6..a674f3c 100644
--- a/core/lattice-data.go
+++ b/core/lattice-data.go
@@ -23,6 +23,7 @@ import (
"time"
"github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/blockdb"
"github.com/dexon-foundation/dexon-consensus-core/core/types"
)
@@ -44,58 +45,87 @@ var (
ErrInvalidBlockHeight = fmt.Errorf("invalid block height")
ErrAlreadyInLattice = fmt.Errorf("block already in lattice")
ErrIncorrectBlockTime = fmt.Errorf("block timestamp is incorrect")
+ ErrUnknownRoundID = fmt.Errorf("unknown round id")
)
// Errors for method usage
var (
- ErrRoundNotIncreasing = errors.New("round not increasing")
+ ErrRoundNotIncreasing = errors.New("round not increasing")
+ ErrPurgedBlockNotFound = errors.New("purged block not found")
)
+// latticeDataConfig is the configuration for latticeData for each round.
+type latticeDataConfig struct {
+ // Number of chains between runs
+ prevNumChains uint32
+ numChains uint32
+ // Block interval specifies reasonable time difference between
+ // parent/child blocks.
+ minBlockTimeInterval time.Duration
+ maxBlockTimeInterval time.Duration
+ // the range of clean cut at the beginning of this round.
+ cleanCutRange int
+}
+
+// newLatticeDataConfig constructs a latticeDataConfig instance.
+func newLatticeDataConfig(prev, cur *types.Config) *latticeDataConfig {
+ config := &latticeDataConfig{
+ numChains: cur.NumChains,
+ minBlockTimeInterval: cur.MinBlockInterval,
+ maxBlockTimeInterval: cur.MaxBlockInterval,
+ }
+ if prev != nil {
+ config.prevNumChains = prev.NumChains
+ if prev.K != cur.K ||
+ prev.PhiRatio != cur.PhiRatio ||
+ prev.NumChains != cur.NumChains {
+ // K plus 2 is a magic from GOD.
+ config.cleanCutRange = prev.K + 2
+ }
+ }
+ return config
+}
+
// latticeData is a module for storing lattice.
type latticeData struct {
+ // we need blockdb to read blocks purged from cache in memory.
+ db blockdb.Reader
// chains stores chains' blocks and other info.
chains []*chainStatus
-
// blockByHash stores blocks, indexed by block hash.
blockByHash map[common.Hash]*types.Block
-
- // Block interval specifies reasonable time difference between
- // parent/child blocks.
- minBlockTimeInterval time.Duration
- maxBlockTimeInterval time.Duration
-
// This stores configuration for each round.
- numChainsForRounds []uint32
- minRound uint64
+ configs []*latticeDataConfig
+ minRound uint64
}
// newLatticeData creates a new latticeData struct.
-func newLatticeData(
- round uint64,
- chainNum uint32,
- minBlockTimeInterval time.Duration,
- maxBlockTimeInterval time.Duration) (data *latticeData) {
+func newLatticeData(db blockdb.Reader,
+ round uint64, config *latticeDataConfig) (data *latticeData) {
data = &latticeData{
- chains: make([]*chainStatus, chainNum),
- blockByHash: make(map[common.Hash]*types.Block),
- minBlockTimeInterval: minBlockTimeInterval,
- maxBlockTimeInterval: maxBlockTimeInterval,
- numChainsForRounds: []uint32{chainNum},
- minRound: round,
+ db: db,
+ chains: make([]*chainStatus, config.numChains),
+ blockByHash: make(map[common.Hash]*types.Block),
+ configs: []*latticeDataConfig{config},
+ minRound: round,
}
for i := range data.chains {
data.chains[i] = &chainStatus{
ID: uint32(i),
blocks: []*types.Block{},
- nextAck: make([]uint64, chainNum),
+ nextAck: make([]uint64, config.numChains),
}
}
return
}
func (data *latticeData) sanityCheck(b *types.Block) error {
+ config := data.getConfig(b.Position.Round)
+ if config == nil {
+ return ErrUnknownRoundID
+ }
// Check if the chain id is valid.
- if b.Position.ChainID >= uint32(len(data.chains)) {
+ if b.Position.ChainID >= config.numChains {
return ErrInvalidChainID
}
@@ -117,20 +147,23 @@ func (data *latticeData) sanityCheck(b *types.Block) error {
// Check if it acks older blocks.
acksByChainID := make(map[uint32]struct{}, len(data.chains))
for _, hash := range b.Acks {
- if bAck, exist := data.blockByHash[hash]; exist {
- if bAck.Position.Height <
- data.chains[bAck.Position.ChainID].nextAck[b.Position.ChainID] {
- return ErrDoubleAck
- }
- // Check if ack two blocks on the same chain. This would need
- // to check after we replace map with slice for acks.
- if _, acked := acksByChainID[bAck.Position.ChainID]; acked {
- return ErrDuplicatedAckOnOneChain
+ bAck, err := data.findBlock(hash)
+ if err != nil {
+ if err == blockdb.ErrBlockDoesNotExist {
+ return ErrAckingBlockNotExists
}
- acksByChainID[bAck.Position.ChainID] = struct{}{}
- } else {
- return ErrAckingBlockNotExists
+ return err
+ }
+ if bAck.Position.Height <
+ data.chains[bAck.Position.ChainID].nextAck[b.Position.ChainID] {
+ return ErrDoubleAck
}
+ // Check if ack two blocks on the same chain. This would need
+ // to check after we replace map with slice for acks.
+ if _, acked := acksByChainID[bAck.Position.ChainID]; acked {
+ return ErrDuplicatedAckOnOneChain
+ }
+ acksByChainID[bAck.Position.ChainID] = struct{}{}
}
// Check non-genesis blocks if it acks its parent.
@@ -138,7 +171,12 @@ func (data *latticeData) sanityCheck(b *types.Block) error {
if !b.IsAcking(b.ParentHash) {
return ErrNotAckParent
}
- bParent := data.blockByHash[b.ParentHash]
+ bParent, err := data.findBlock(b.ParentHash)
+ if err != nil {
+ // This error should never happened, a parent block is always
+ // an acked block.
+ panic(err)
+ }
if bParent.Position.ChainID != b.Position.ChainID {
return ErrInvalidParentChain
}
@@ -153,8 +191,10 @@ func (data *latticeData) sanityCheck(b *types.Block) error {
return ErrInvalidTimestamp
}
// Check if its timestamp is in expected range.
- if b.Timestamp.Before(bParent.Timestamp.Add(data.minBlockTimeInterval)) ||
- b.Timestamp.After(bParent.Timestamp.Add(data.maxBlockTimeInterval)) {
+ if b.Timestamp.Before(
+ bParent.Timestamp.Add(config.minBlockTimeInterval)) ||
+ b.Timestamp.After(
+ bParent.Timestamp.Add(config.maxBlockTimeInterval)) {
return ErrIncorrectBlockTime
}
@@ -182,7 +222,9 @@ func (data *latticeData) addBlock(
data.blockByHash[block.Hash] = block
// Update nextAcks.
for _, ack := range block.Acks {
- bAck = data.blockByHash[ack]
+ if bAck, err = data.findBlock(ack); err != nil {
+ return
+ }
data.chains[bAck.Position.ChainID].nextAck[block.Position.ChainID] =
bAck.Position.Height + 1
}
@@ -198,9 +240,8 @@ func (data *latticeData) addBlock(
}
allAckingBlockDelivered := true
for _, ack := range tip.Acks {
- bAck, exists := data.blockByHash[ack]
- if !exists {
- continue
+ if bAck, err = data.findBlock(ack); err != nil {
+ return
}
if data.chains[bAck.Position.ChainID].nextOutput >
bAck.Position.Height {
@@ -231,9 +272,7 @@ func (data *latticeData) addBlock(
// This works because blocks of height below this minimum are not going to be
// acked anymore, the ackings of these blocks are illegal.
for _, status := range data.chains {
- for _, h := range status.purge() {
- delete(data.blockByHash, h)
- }
+ status.purge()
}
return
}
@@ -245,6 +284,7 @@ func (data *latticeData) addBlock(
// - Set 'ParentHash' and 'Height' from parent block, if we can't find a
// parent, these fields would be setup like a genesis block.
func (data *latticeData) prepareBlock(block *types.Block) {
+ config := data.getConfig(block.Position.Round)
// Reset fields to make sure we got these information from parent block.
block.Position.Height = 0
block.ParentHash = common.Hash{}
@@ -271,8 +311,8 @@ func (data *latticeData) prepareBlock(block *types.Block) {
block.ParentHash = curBlock.Hash
block.Position.Height = curBlock.Position.Height + 1
block.Witness.Height = curBlock.Witness.Height
- minTimestamp := curBlock.Timestamp.Add(data.minBlockTimeInterval)
- maxTimestamp := curBlock.Timestamp.Add(data.maxBlockTimeInterval)
+ minTimestamp := curBlock.Timestamp.Add(config.minBlockTimeInterval)
+ maxTimestamp := curBlock.Timestamp.Add(config.maxBlockTimeInterval)
if block.Timestamp.Before(minTimestamp) {
block.Timestamp = minTimestamp
} else if block.Timestamp.After(maxTimestamp) {
@@ -290,15 +330,51 @@ func (data *latticeData) nextPosition(chainID uint32) types.Position {
return data.chains[chainID].nextPosition()
}
+// findBlock seeks blocks in memory or db.
+func (data *latticeData) findBlock(h common.Hash) (b *types.Block, err error) {
+ if b = data.blockByHash[h]; b != nil {
+ return
+ }
+ var tmpB types.Block
+ if tmpB, err = data.db.Get(h); err != nil {
+ return
+ }
+ b = &tmpB
+ return
+}
+
+// purgeBlocks purges blocks from cache.
+func (data *latticeData) purgeBlocks(blocks []*types.Block) error {
+ for _, b := range blocks {
+ if _, exists := data.blockByHash[b.Hash]; !exists {
+ return ErrPurgedBlockNotFound
+ }
+ delete(data.blockByHash, b.Hash)
+ }
+ return nil
+}
+
+// getConfig get configuration for lattice-data by round ID.
+func (data *latticeData) getConfig(round uint64) (config *latticeDataConfig) {
+ if round < data.minRound {
+ return
+ }
+ diff := round - data.minRound
+ if diff >= uint64(len(data.configs)) {
+ return
+ }
+ return data.configs[diff]
+}
+
// appendConfig appends a configuration for upcoming round. When you append
// a config for round R, next time you can only append the config for round R+1.
func (data *latticeData) appendConfig(
- round uint64, config *types.Config) (err error) {
+ round uint64, config *latticeDataConfig) (err error) {
- if round != data.minRound+uint64(len(data.numChainsForRounds)) {
+ if round != data.minRound+uint64(len(data.configs)) {
return ErrRoundNotIncreasing
}
- data.numChainsForRounds = append(data.numChainsForRounds, config.NumChains)
+ data.configs = append(data.configs, config)
return nil
}
@@ -375,15 +451,12 @@ func (s *chainStatus) calcPurgeHeight() (safe uint64, ok bool) {
}
// purge blocks if they are safe to be deleted from working set.
-func (s *chainStatus) purge() (purged common.Hashes) {
+func (s *chainStatus) purge() {
safe, ok := s.calcPurgeHeight()
if !ok {
return
}
newMinIndex := safe - s.minHeight + 1
- for _, b := range s.blocks[:newMinIndex] {
- purged = append(purged, b.Hash)
- }
s.blocks = s.blocks[newMinIndex:]
s.minHeight = safe + 1
return