OSDN Git Service

fix vote result (#94)
[bytom/vapor.git] / protocol / consensus_node_manager.go
index be5e6bb..33cc83c 100644 (file)
@@ -3,10 +3,12 @@ package protocol
 import (
        "encoding/hex"
        "sort"
-       "sync"
        "time"
 
        "github.com/vapor/errors"
+       "github.com/vapor/math/checked"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
        "github.com/vapor/protocol/state"
 )
 
@@ -16,14 +18,14 @@ const (
 
        // BlockTimeInterval indicate product one block per 500 milliseconds
        BlockTimeInterval = 500
-       BlockNumEachNode  = 3
+       // BlockNumEachNode indicate product three blocks per node in succession
+       BlockNumEachNode = 3
 )
 
 var (
-       errHasNoChanceProductBlock     = errors.New("the node has no chance to product a block in this round of voting")
-       errNotFoundConsensusNode       = errors.New("can not found consensus node")
-       errVoteResultIsNotfinalized    = errors.New("vote result is not finalized")
-       errPublicKeyIsNotConsensusNode = errors.New("public key is not consensus node")
+       errHasNoChanceProductBlock = errors.New("the node has no chance to product a block in this round of voting")
+       errNotFoundConsensusNode   = errors.New("can not found consensus node")
+       errNotFoundBlockNode       = errors.New("can not find block node")
 )
 
 type consensusNode struct {
@@ -39,79 +41,76 @@ func (c consensusNodeSlice) Less(i, j int) bool { return c[i].voteNum > c[j].vot
 func (c consensusNodeSlice) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
 
 type consensusNodeManager struct {
-       consensusNodeMap     map[string]*consensusNode
-       effectiveStartHeight uint64
-       store                Store
-       blockIndex           *state.BlockIndex
-       sync.RWMutex
+       store      Store
+       blockIndex *state.BlockIndex
 }
 
 func newConsensusNodeManager(store Store, blockIndex *state.BlockIndex) *consensusNodeManager {
        return &consensusNodeManager{
-               consensusNodeMap:     make(map[string]*consensusNode),
-               effectiveStartHeight: 1,
-               store:                store,
-               blockIndex:           blockIndex,
+               store:      store,
+               blockIndex: blockIndex,
        }
 }
 
-func (c *consensusNodeManager) getConsensusNode(height uint64, pubkey string) (*consensusNode, error) {
-       defer c.RUnlock()
-       c.RLock()
-       if height >= c.effectiveStartHeight+roundVoteBlockNums {
-               return nil, errors.New("the vote has not been completed for the specified block height ")
-       }
-
-       var err error
-       consensusNodeMap := c.consensusNodeMap
-       // query history vote result
-       if height < c.effectiveStartHeight {
-               consensusNodeMap, err = c.getConsensusNodesByVoteResult(height)
-               if err != nil {
-                       return nil, err
-               }
+func (c *consensusNodeManager) getConsensusNode(blockHash *bc.Hash, pubkey string) (*consensusNode, error) {
+       consensusNodeMap, err := c.getConsensusNodesByVoteResult(blockHash)
+       if err != nil {
+               return nil, err
        }
 
        node, exist := consensusNodeMap[pubkey]
        if !exist {
-               return node, errNotFoundConsensusNode
+               return nil, errNotFoundConsensusNode
        }
        return node, nil
 }
 
-func (c *consensusNodeManager) isBlocker(height uint64, blockTimestamp uint64, pubkey string) (bool, error) {
-       prevVoteRoundLastBlock := c.blockIndex.NodeByHeight(height - 1)
-       startTimestamp := prevVoteRoundLastBlock.Timestamp + BlockTimeInterval
+func (c *consensusNodeManager) isBlocker(blockHash *bc.Hash, pubkey string) (bool, error) {
+       blockNode := c.blockIndex.GetNode(blockHash)
+       if blockNode == nil {
+               return false, errNotFoundBlockNode
+       }
 
-       consensusNodeMap, err := c.getConsensusNodesByVoteResult(height)
-       if err != nil {
+       consensusNode, err := c.getConsensusNode(blockHash, pubkey)
+       if err != nil && err != errNotFoundConsensusNode {
                return false, err
        }
 
-       blockerNode, exist := consensusNodeMap[pubkey]
-       if !exist {
+       if consensusNode == nil {
                return false, nil
        }
 
-       begin := getLastBlockTimeInTimeRange(startTimestamp, blockTimestamp, blockerNode.order)
+       prevVoteRoundLastBlock, err := c.getPrevRoundVoteLastBlock(blockNode)
+       if err != nil {
+               return false, err
+       }
+
+       startTimestamp := prevVoteRoundLastBlock.Timestamp + BlockTimeInterval
+
+       begin := getLastBlockTimeInTimeRange(startTimestamp, blockNode.Timestamp, consensusNode.order)
        end := begin + BlockNumEachNode*BlockTimeInterval
-       return blockTimestamp >= begin && blockTimestamp < end, nil
+       return blockNode.Timestamp >= begin && blockNode.Timestamp < end, nil
 }
 
-func (c *consensusNodeManager) nextLeaderTimeRange(pubkey []byte, bestBlockTimestamp, bestBlockHeight uint64) (uint64, uint64, error) {
-       defer c.RUnlock()
-       c.RLock()
+func (c *consensusNodeManager) nextLeaderTimeRange(pubkey []byte, bestBlockHash *bc.Hash) (uint64, uint64, error) {
+       bestBlockNode := c.blockIndex.GetNode(bestBlockHash)
+       if bestBlockNode == nil {
+               return 0, 0, errNotFoundBlockNode
+       }
 
-       startHeight := c.effectiveStartHeight
-       prevRoundLastBlock := c.blockIndex.NodeByHeight(startHeight - 1)
-       startTime := prevRoundLastBlock.Timestamp + BlockTimeInterval
-       endTime := bestBlockTimestamp + (roundVoteBlockNums-bestBlockHeight%roundVoteBlockNums)*BlockTimeInterval
+       consensusNode, err := c.getConsensusNode(bestBlockHash, hex.EncodeToString(pubkey))
+       if err != nil {
+               return 0, 0, err
+       }
 
-       consensusNode, exist := c.consensusNodeMap[hex.EncodeToString(pubkey)]
-       if !exist {
-               return 0, 0, errPublicKeyIsNotConsensusNode
+       prevRoundLastBlock, err := c.getPrevRoundVoteLastBlock(bestBlockNode)
+       if err != nil {
+               return 0, 0, nil
        }
 
+       startTime := prevRoundLastBlock.Timestamp + BlockTimeInterval
+       endTime := startTime + roundVoteBlockNums*BlockTimeInterval
+
        nextLeaderTime, err := nextLeaderTimeHelper(startTime, endTime, uint64(time.Now().UnixNano()/1e6), consensusNode.order)
        if err != nil {
                return 0, 0, err
@@ -134,25 +133,6 @@ func nextLeaderTimeHelper(startTime, endTime, now, nodeOrder uint64) (uint64, er
        return nextLeaderTimestamp, nil
 }
 
-// updateConsensusNodes used to update consensus node after each round of voting
-func (c *consensusNodeManager) updateConsensusNodes(bestBlockHeight uint64) error {
-       defer c.Unlock()
-       c.Lock()
-
-       consensusNodeMap, err := c.getConsensusNodesByVoteResult(bestBlockHeight)
-       if err != nil && err != errVoteResultIsNotfinalized {
-               return err
-       }
-
-       if err == errVoteResultIsNotfinalized {
-               return nil
-       }
-
-       c.consensusNodeMap = consensusNodeMap
-       c.effectiveStartHeight = bestBlockHeight / roundVoteBlockNums * roundVoteBlockNums
-       return nil
-}
-
 func getLastBlockTimeInTimeRange(startTimestamp, endTimestamp, order uint64) uint64 {
        // One round of product block time for all consensus nodes
        roundBlockTime := uint64(BlockNumEachNode * numOfConsensusNode * BlockTimeInterval)
@@ -162,24 +142,39 @@ func getLastBlockTimeInTimeRange(startTimestamp, endTimestamp, order uint64) uin
        return lastRoundStartTime + order*(BlockNumEachNode*BlockTimeInterval)
 }
 
-func (c *consensusNodeManager) getConsensusNodesByVoteResult(blockHeight uint64) (map[string]*consensusNode, error) {
-       defer c.RUnlock()
-       c.RLock()
-       if blockHeight >= c.effectiveStartHeight+roundVoteBlockNums {
-               return nil, errors.New("the given block height is greater than current vote start height")
+func (c *consensusNodeManager) getPrevRoundVoteLastBlock(blockNode *state.BlockNode) (*state.BlockNode, error) {
+       prevVoteRoundLastBlockHeight := blockNode.Height/roundVoteBlockNums*roundVoteBlockNums - 1
+       lastBlockNode := c.blockIndex.NodeByHeightInSameChain(&blockNode.Hash, prevVoteRoundLastBlockHeight)
+       if blockNode == nil {
+               return nil, errNotFoundBlockNode
+       }
+       return lastBlockNode, nil
+}
+
+func (c *consensusNodeManager) getConsensusNodesByVoteResult(blockHash *bc.Hash) (map[string]*consensusNode, error) {
+       blockNode := c.blockIndex.GetNode(blockHash)
+       if blockNode == nil {
+               return nil, errNotFoundBlockNode
        }
 
-       if blockHeight >= c.effectiveStartHeight {
-               return c.consensusNodeMap, nil
+       seq := blockNode.Height / roundVoteBlockNums
+       voteResult, err := c.store.GetVoteResult(seq)
+       if err != nil {
+               // fail to find vote result, try to construct
+               voteResult = &state.VoteResult{
+                       Seq:       seq,
+                       NumOfVote: make(map[string]uint64),
+                       Finalized: false,
+               }
        }
 
-       voteResult, err := c.store.GetVoteResult(blockHeight / roundVoteBlockNums)
+       lastBlockNode, err := c.getPrevRoundVoteLastBlock(blockNode)
        if err != nil {
                return nil, err
        }
 
-       if !voteResult.Finalized {
-               return nil, errVoteResultIsNotfinalized
+       if err := c.reorganizeVoteResult(voteResult, lastBlockNode); err != nil {
+               return nil, err
        }
 
        var nodes []*consensusNode
@@ -202,3 +197,153 @@ func (c *consensusNodeManager) getConsensusNodesByVoteResult(blockHeight uint64)
        }
        return result, nil
 }
+
+func (c *consensusNodeManager) reorganizeVoteResult(voteResult *state.VoteResult, forkChainNode *state.BlockNode) error {
+       var mainChainNode *state.BlockNode
+       emptyHash := bc.Hash{}
+       if voteResult.LastBlockHash != emptyHash {
+               mainChainNode = c.blockIndex.GetNode(&voteResult.LastBlockHash)
+               if mainChainNode == nil {
+                       return errNotFoundBlockNode
+               }
+       }
+
+       var attachBlocks []*types.Block
+       var detachBlocks []*types.Block
+
+       for forkChainNode.Hash != mainChainNode.Hash && forkChainNode.Height >= (voteResult.Seq-1)*roundVoteBlockNums {
+               attachBlock, err := c.store.GetBlock(&forkChainNode.Hash)
+               if err != nil {
+                       return err
+               }
+
+               attachBlocks = append([]*types.Block{attachBlock}, attachBlocks...)
+               forkChainNode = forkChainNode.Parent
+
+               if mainChainNode != nil && forkChainNode.Height == mainChainNode.Height {
+                       detachBlock, err := c.store.GetBlock(&mainChainNode.Hash)
+                       if err != nil {
+                               return err
+                       }
+
+                       detachBlocks = append(detachBlocks, detachBlock)
+                       mainChainNode = mainChainNode.Parent
+               }
+       }
+
+       for _, block := range detachBlocks {
+               if err := c.detachBlock(map[uint64]*state.VoteResult{voteResult.Seq: voteResult}, block); err != nil {
+                       return err
+               }
+       }
+
+       for _, block := range attachBlocks {
+               if err := c.applyBlock(map[uint64]*state.VoteResult{voteResult.Seq: voteResult}, block); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (c *consensusNodeManager) applyBlock(voteResultMap map[uint64]*state.VoteResult, block *types.Block) (err error) {
+       voteSeq := block.Height / roundVoteBlockNums
+       voteResult := voteResultMap[voteSeq]
+
+       if voteResult == nil {
+               voteResult, err = c.store.GetVoteResult(voteSeq)
+               if err != nil && err != ErrNotFoundVoteResult {
+                       return err
+               }
+       }
+
+       if voteResult == nil {
+               voteResult = &state.VoteResult{
+                       Seq:           voteSeq,
+                       NumOfVote:     make(map[string]uint64),
+                       LastBlockHash: block.Hash(),
+               }
+       }
+
+       voteResultMap[voteSeq] = voteResult
+
+       if voteResult.LastBlockHash != block.PreviousBlockHash {
+               return errors.New("bbft append block error, the block parent hash is not equals last block hash of vote result")
+       }
+
+       for _, tx := range block.Transactions {
+               for _, input := range tx.Inputs {
+                       unVoteInput, ok := input.TypedInput.(*types.UnvoteInput)
+                       if !ok {
+                               continue
+                       }
+
+                       pubkey := hex.EncodeToString(unVoteInput.Vote)
+                       voteResult.NumOfVote[pubkey], ok = checked.SubUint64(voteResult.NumOfVote[pubkey], unVoteInput.Amount)
+                       if !ok {
+                               return errVotingOperationOverFlow
+                       }
+               }
+               for _, output := range tx.Outputs {
+                       voteOutput, ok := output.TypedOutput.(*types.VoteTxOutput)
+                       if !ok {
+                               continue
+                       }
+
+                       pubkey := hex.EncodeToString(voteOutput.Vote)
+                       voteResult.NumOfVote[pubkey], ok = checked.AddUint64(voteResult.NumOfVote[pubkey], voteOutput.Amount)
+                       if !ok {
+                               return errVotingOperationOverFlow
+                       }
+               }
+       }
+
+       voteResult.Finalized = (block.Height+1)%roundVoteBlockNums == 0
+       return nil
+}
+
+func (c *consensusNodeManager) detachBlock(voteResultMap map[uint64]*state.VoteResult, block *types.Block) error {
+       voteSeq := block.Height / roundVoteBlockNums
+       voteResult := voteResultMap[voteSeq]
+
+       if voteResult == nil {
+               voteResult, err := c.store.GetVoteResult(voteSeq)
+               if err != nil {
+                       return err
+               }
+               voteResultMap[voteSeq] = voteResult
+       }
+
+       if voteResult.LastBlockHash != block.Hash() {
+               return errors.New("bbft detach block error, the block hash is not equals last block hash of vote result")
+       }
+
+       for _, tx := range block.Transactions {
+               for _, input := range tx.Inputs {
+                       unVoteInput, ok := input.TypedInput.(*types.UnvoteInput)
+                       if !ok {
+                               continue
+                       }
+
+                       pubkey := hex.EncodeToString(unVoteInput.Vote)
+                       voteResult.NumOfVote[pubkey], ok = checked.AddUint64(voteResult.NumOfVote[pubkey], unVoteInput.Amount)
+                       if !ok {
+                               return errVotingOperationOverFlow
+                       }
+               }
+               for _, output := range tx.Outputs {
+                       voteOutput, ok := output.TypedOutput.(*types.VoteTxOutput)
+                       if !ok {
+                               continue
+                       }
+
+                       pubkey := hex.EncodeToString(voteOutput.Vote)
+                       voteResult.NumOfVote[pubkey], ok = checked.SubUint64(voteResult.NumOfVote[pubkey], voteOutput.Amount)
+                       if !ok {
+                               return errVotingOperationOverFlow
+                       }
+               }
+       }
+
+       voteResult.Finalized = false
+       return nil
+}