aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorJimmy Hu <jimmy.hu@dexon.org>2018-10-23 09:43:24 +0800
committerGitHub <noreply@github.com>2018-10-23 09:43:24 +0800
commit56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3 (patch)
treed52155ea22e6bcfae6ac062893c32186005c362f /core
parent6fbd0cd47fd70cfad707e95bc9fb948e7b44d4cf (diff)
downloaddexon-consensus-56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3.tar
dexon-consensus-56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3.tar.gz
dexon-consensus-56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3.tar.bz2
dexon-consensus-56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3.tar.lz
dexon-consensus-56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3.tar.xz
dexon-consensus-56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3.tar.zst
dexon-consensus-56cbbbaf4c638264cbf3ceb5cf16161b6adf2bc3.zip
core: prepare empty block if null block is confirmed by BA. (#231)
Diffstat (limited to 'core')
-rw-r--r--core/consensus.go41
-rw-r--r--core/lattice-data.go34
-rw-r--r--core/lattice-data_test.go12
-rw-r--r--core/lattice.go28
-rw-r--r--core/test/blocks-generator.go18
-rw-r--r--core/test/blocks-generator_test.go21
-rw-r--r--core/types/block.go5
7 files changed, 136 insertions, 23 deletions
diff --git a/core/consensus.go b/core/consensus.go
index 76fa3b7..a0de742 100644
--- a/core/consensus.go
+++ b/core/consensus.go
@@ -93,19 +93,35 @@ func (recv *consensusBAReceiver) ProposeBlock() common.Hash {
func (recv *consensusBAReceiver) ConfirmBlock(
hash common.Hash, votes map[types.NodeID]*types.Vote) {
- block, exist := recv.consensus.baModules[recv.chainID].
- findCandidateBlock(hash)
- if !exist {
- recv.consensus.logger.Error("Unknown block confirmed", "hash", hash)
- return
+ var block *types.Block
+ if (hash == common.Hash{}) {
+ recv.consensus.logger.Info("Empty block is confirmed",
+ "position", recv.agreementModule.agreementID())
+ var err error
+ block, err = recv.consensus.proposeEmptyBlock(recv.chainID)
+ if err != nil {
+ recv.consensus.logger.Error("Propose empty block failed", "error", err)
+ return
+ }
+ } else {
+ var exist bool
+ block, exist = recv.consensus.baModules[recv.chainID].
+ findCandidateBlock(hash)
+ if !exist {
+ recv.consensus.logger.Error("Unknown block confirmed", "hash", hash)
+ return
+ }
}
recv.consensus.ccModule.registerBlock(block)
voteList := make([]types.Vote, 0, len(votes))
for _, vote := range votes {
+ if vote.BlockHash != hash {
+ continue
+ }
voteList = append(voteList, *vote)
}
result := &types.AgreementResult{
- BlockHash: hash,
+ BlockHash: block.Hash,
Position: block.Position,
Votes: voteList,
}
@@ -642,6 +658,19 @@ func (con *Consensus) proposeBlock(chainID uint32, round uint64) *types.Block {
return block
}
+func (con *Consensus) proposeEmptyBlock(
+ chainID uint32) (*types.Block, error) {
+ block := &types.Block{
+ Position: types.Position{
+ ChainID: chainID,
+ },
+ }
+ if err := con.lattice.PrepareEmptyBlock(block); err != nil {
+ return nil, err
+ }
+ return block, nil
+}
+
// ProcessVote is the entry point to submit ont vote to a Consensus instance.
func (con *Consensus) ProcessVote(vote *types.Vote) (err error) {
v := vote.Clone()
diff --git a/core/lattice-data.go b/core/lattice-data.go
index 50fa1d3..31604a6 100644
--- a/core/lattice-data.go
+++ b/core/lattice-data.go
@@ -412,6 +412,40 @@ func (data *latticeData) prepareBlock(b *types.Block) error {
return nil
}
+// prepareEmptyBlock helps to setup fields of block based on its ChainID.
+// including:
+// - Acks only acking its parent
+// - Timestamp with parent.Timestamp + minBlockProposeInterval
+// - ParentHash and Height from parent block. If there is no valid parent block
+// (ex. Newly added chain or bootstrap ), these fields would be setup as
+// genesis block.
+func (data *latticeData) prepareEmptyBlock(b *types.Block) {
+ // emptyBlock has no proposer.
+ b.ProposerID = types.NodeID{}
+ var acks common.Hashes
+ // Reset fields to make sure we got these information from parent block.
+ b.Position.Height = 0
+ b.Position.Round = 0
+ b.ParentHash = common.Hash{}
+ b.Timestamp = time.Time{}
+ // Decide valid timestamp range.
+ homeChain := data.chains[b.Position.ChainID]
+ if homeChain.tip != nil {
+ chainTip := homeChain.tip
+ b.ParentHash = chainTip.Hash
+ chainTipConfig := data.getConfig(chainTip.Position.Round)
+ if chainTip.Timestamp.After(chainTipConfig.roundEndTime) {
+ b.Position.Round = chainTip.Position.Round + 1
+ } else {
+ b.Position.Round = chainTip.Position.Round
+ }
+ b.Position.Height = chainTip.Position.Height + 1
+ b.Timestamp = chainTip.Timestamp.Add(chainTipConfig.minBlockTimeInterval)
+ acks = append(acks, chainTip.Hash)
+ }
+ b.Acks = common.NewSortedHashes(acks)
+}
+
// TODO(mission): make more abstraction for this method.
// nextHeight returns the next height for the chain.
func (data *latticeData) nextPosition(chainID uint32) types.Position {
diff --git a/core/lattice-data_test.go b/core/lattice-data_test.go
index 263474c..64767e1 100644
--- a/core/lattice-data_test.go
+++ b/core/lattice-data_test.go
@@ -577,6 +577,18 @@ func (s *LatticeDataTestSuite) TestNextPosition() {
s.Equal(data.nextPosition(0), types.Position{ChainID: 0, Height: 0})
}
+func (s *LatticeDataTestSuite) TestPrepareEmptyBlock() {
+ data, _ := s.genTestCase1()
+ b := &types.Block{
+ Position: types.Position{
+ ChainID: 0,
+ },
+ }
+ data.prepareEmptyBlock(b)
+ s.True(b.IsEmpty())
+ s.Equal(uint64(4), b.Position.Height)
+}
+
func (s *LatticeDataTestSuite) TestNumChainsChange() {
// This test case verify the behavior when NumChains
// changes. We only reply on methods of latticeData
diff --git a/core/lattice.go b/core/lattice.go
index 73fdcb0..984203d 100644
--- a/core/lattice.go
+++ b/core/lattice.go
@@ -92,15 +92,37 @@ func (s *Lattice) PrepareBlock(
return
}
+// PrepareEmptyBlock setup block's field based on current lattice status.
+func (s *Lattice) PrepareEmptyBlock(b *types.Block) (err error) {
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+ s.data.prepareEmptyBlock(b)
+ if b.Hash, err = hashBlock(b); err != nil {
+ return
+ }
+ return
+}
+
// SanityCheck check if a block is valid.
// If checkRelation is true, it also checks with current lattice status.
//
// If some acking blocks don't exists, Lattice would help to cache this block
// and retry when lattice updated in Lattice.ProcessBlock.
func (s *Lattice) SanityCheck(b *types.Block, checkRelation bool) (err error) {
- // Verify block's signature.
- if err = s.authModule.VerifyBlock(b); err != nil {
- return
+ if b.IsEmpty() {
+ // Only need to verify block's hash.
+ var hash common.Hash
+ if hash, err = hashBlock(b); err != nil {
+ return
+ }
+ if b.Hash != hash {
+ return ErrInvalidBlock
+ }
+ } else {
+ // Verify block's signature.
+ if err = s.authModule.VerifyBlock(b); err != nil {
+ return
+ }
}
// Make sure acks are sorted.
for i := range b.Acks {
diff --git a/core/test/blocks-generator.go b/core/test/blocks-generator.go
index 393e3a3..62c777e 100644
--- a/core/test/blocks-generator.go
+++ b/core/test/blocks-generator.go
@@ -190,26 +190,34 @@ func (ns *nodeSetStatus) proposeBlock(
blockHeight = status.tip.Position.Height + 1
acks = append(acks, parentHash)
}
+ // 10% of chance to produce empty block.
+ empty := ns.randGen.Float32() < 0.1 && blockHeight > 0
chainID := ns.proposerChain[proposerID]
newBlock := &types.Block{
- ProposerID: proposerID,
ParentHash: parentHash,
Position: types.Position{
Round: ns.round,
Height: blockHeight,
ChainID: chainID,
},
- Acks: common.NewSortedHashes(acks),
Timestamp: status.getNextBlockTime(ns.timePicker),
}
+ if empty {
+ newBlock.Acks = common.NewSortedHashes(common.Hashes{parentHash})
+ } else {
+ newBlock.ProposerID = proposerID
+ newBlock.Acks = common.NewSortedHashes(acks)
+ }
var err error
newBlock.Hash, err = ns.hashBlock(newBlock)
if err != nil {
return nil, err
}
- newBlock.Signature, err = status.prvKey.Sign(newBlock.Hash)
- if err != nil {
- return nil, err
+ if !empty {
+ newBlock.Signature, err = status.prvKey.Sign(newBlock.Hash)
+ if err != nil {
+ return nil, err
+ }
}
status.blocks = append(status.blocks, newBlock)
status.tip = newBlock
diff --git a/core/test/blocks-generator_test.go b/core/test/blocks-generator_test.go
index fafbd6c..075cc9f 100644
--- a/core/test/blocks-generator_test.go
+++ b/core/test/blocks-generator_test.go
@@ -51,7 +51,7 @@ func (s *BlocksGeneratorTestSuite) TestGenerate() {
// Load all blocks in that database for further checking.
iter, err := db.GetAll()
req.NoError(err)
- blocksByNode := make(map[types.NodeID][]*types.Block)
+ blocksByChain := make(map[uint32][]*types.Block)
blocksByHash := make(map[common.Hash]*types.Block)
for {
block, err := iter.Next()
@@ -62,11 +62,13 @@ func (s *BlocksGeneratorTestSuite) TestGenerate() {
// TODO(mission): Make sure each block is correctly signed once
// we have a way to access core.hashBlock.
req.NotEqual(block.Hash, common.Hash{})
- req.NotEmpty(block.Signature)
+ if !block.IsEmpty() {
+ req.NotEmpty(block.Signature)
+ }
req.Equal(block.Position.Round, uint64(1))
- blocksByNode[block.ProposerID] =
- append(blocksByNode[block.ProposerID], &block)
- sort.Sort(types.ByPosition(blocksByNode[block.ProposerID]))
+ blocksByChain[block.Position.ChainID] =
+ append(blocksByChain[block.Position.ChainID], &block)
+ sort.Sort(types.ByPosition(blocksByChain[block.Position.ChainID]))
blocksByHash[block.Hash] = &block
}
// Make sure these two rules are hold for these blocks:
@@ -77,8 +79,8 @@ func (s *BlocksGeneratorTestSuite) TestGenerate() {
// previous block.
// - The last block of each chain should pass endTime.
// - No Acks in genesis bloc
- for _, blocks := range blocksByNode {
- lastAckingHeights := map[types.NodeID]uint64{}
+ for _, blocks := range blocksByChain {
+ lastAckingHeights := map[uint32]uint64{}
req.NotEmpty(blocks)
// Check genesis block.
genesisBlock := blocks[0]
@@ -95,11 +97,12 @@ func (s *BlocksGeneratorTestSuite) TestGenerate() {
ackedBlock := blocksByHash[ack]
req.NotNil(ackedBlock)
prevAckingHeight, exists :=
- lastAckingHeights[ackedBlock.ProposerID]
+ lastAckingHeights[ackedBlock.Position.ChainID]
if exists {
s.True(prevAckingHeight < ackedBlock.Position.Height)
}
- lastAckingHeights[ackedBlock.ProposerID] = ackedBlock.Position.Height
+ lastAckingHeights[ackedBlock.Position.ChainID] =
+ ackedBlock.Position.Height
// Block Height should always incremental by 1.
//
// Because we iterate blocks slice from 1,
diff --git a/core/types/block.go b/core/types/block.go
index c8ee3c6..9de4673 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -257,6 +257,11 @@ func (b *Block) IsFinalized() bool {
return b.Finalization.Height != 0
}
+// IsEmpty checks if the block is an 'empty block'.
+func (b *Block) IsEmpty() bool {
+ return b.ProposerID.Hash == common.Hash{}
+}
+
// IsAcking checks if a block acking another by it's hash.
func (b *Block) IsAcking(hash common.Hash) bool {
idx := sort.Search(len(b.Acks), func(i int) bool {