OSDN Git Service

versoin1.1.9 (#594)
[bytom/vapor.git] / protocol / protocol.go
index 384c54b..90c531f 100644 (file)
@@ -3,45 +3,75 @@ package protocol
 import (
        "sync"
 
-       "github.com/golang/groupcache/lru"
        log "github.com/sirupsen/logrus"
 
-       "github.com/vapor/config"
-       "github.com/vapor/event"
-       "github.com/vapor/protocol/bc"
-       "github.com/vapor/protocol/bc/types"
-       "github.com/vapor/protocol/state"
+       "github.com/bytom/vapor/common"
+       "github.com/bytom/vapor/config"
+       "github.com/bytom/vapor/consensus"
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/event"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/protocol/state"
 )
 
-const maxProcessBlockChSize = 1024
+const (
+       maxProcessBlockChSize              = 1024
+       maxKnownTxs                        = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
+       maxPrevRoundVoteBlockHashCacheSize = 32768
+)
+
+// ErrNotInitSubProtocolChainStatus represent the node state of sub protocol has not been initialized
+var ErrNotInitSubProtocolChainStatus = errors.New("node state of sub protocol has not been initialized")
+
+// SubProtocol is interface for layer 2 consensus protocol
+type SubProtocol interface {
+       Name() string
+       StartHeight() uint64
+       BeforeProposalBlock(block *types.Block, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
+
+       // ChainStatus return the the current block height and block hash of sub protocol.
+       // it will return ErrNotInitSubProtocolChainStatus if not initialized.
+       ChainStatus() (uint64, *bc.Hash, error)
+       InitChainStatus(*bc.Hash) error
+       ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error
+       ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error
+       ApplyBlock(block *types.Block) error
+       DetachBlock(block *types.Block) error
+}
 
 // Chain provides functions for working with the Bytom block chain.
 type Chain struct {
-       index          *state.BlockIndex
        orphanManage   *OrphanManage
        txPool         *TxPool
        store          Store
        processBlockCh chan *processBlockMsg
+       subProtocols   []SubProtocol
+
+       signatureCache              *common.Cache
+       prevRoundVoteBlockHashCache *common.Cache
+       eventDispatcher             *event.Dispatcher
 
-       consensusNodeManager *consensusNodeManager
-       signatureCache       *lru.Cache
-       eventDispatcher      *event.Dispatcher
+       cond               sync.Cond
+       bestBlockHeader    *types.BlockHeader // the last block on current main chain
+       lastIrrBlockHeader *types.BlockHeader // the last irreversible block
 
-       cond                 sync.Cond
-       bestNode             *state.BlockNode
-       bestIrreversibleNode *state.BlockNode
+       knownTxs *common.OrderedSet
 }
 
 // NewChain returns a new Chain using store as the underlying storage.
-func NewChain(store Store, txPool *TxPool, eventDispatcher *event.Dispatcher) (*Chain, error) {
+func NewChain(store Store, txPool *TxPool, subProtocols []SubProtocol, eventDispatcher *event.Dispatcher) (*Chain, error) {
+       knownTxs, _ := common.NewOrderedSet(maxKnownTxs)
        c := &Chain{
-               orphanManage:         NewOrphanManage(),
-               txPool:               txPool,
-               store:                store,
-               signatureCache:       lru.New(maxSignatureCacheSize),
-               consensusNodeManager: newConsensusNodeManager(store, nil),
-               eventDispatcher:      eventDispatcher,
-               processBlockCh:       make(chan *processBlockMsg, maxProcessBlockChSize),
+               orphanManage:                NewOrphanManage(),
+               txPool:                      txPool,
+               store:                       store,
+               subProtocols:                subProtocols,
+               signatureCache:              common.NewCache(maxSignatureCacheSize),
+               prevRoundVoteBlockHashCache: common.NewCache(maxPrevRoundVoteBlockHashCacheSize),
+               eventDispatcher:             eventDispatcher,
+               processBlockCh:              make(chan *processBlockMsg, maxProcessBlockChSize),
+               knownTxs:                    knownTxs,
        }
        c.cond.L = new(sync.Mutex)
 
@@ -54,14 +84,22 @@ func NewChain(store Store, txPool *TxPool, eventDispatcher *event.Dispatcher) (*
        }
 
        var err error
-       if c.index, err = store.LoadBlockIndex(storeStatus.Height); err != nil {
+       c.bestBlockHeader, err = c.store.GetBlockHeader(storeStatus.Hash)
+       if err != nil {
+               return nil, err
+       }
+
+       c.lastIrrBlockHeader, err = c.store.GetBlockHeader(storeStatus.IrreversibleHash)
+       if err != nil {
                return nil, err
        }
 
-       c.bestNode = c.index.GetNode(storeStatus.Hash)
-       c.bestIrreversibleNode = c.index.GetNode(storeStatus.IrreversibleHash)
-       c.index.SetMainChain(c.bestNode)
-       c.consensusNodeManager.blockIndex = c.index
+       for _, p := range c.subProtocols {
+               if err := c.syncProtocolStatus(p); err != nil {
+                       return nil, errors.Wrap(err, p.Name(), "sync sub protocol status")
+               }
+       }
+
        go c.blockProcesser()
        return c, nil
 }
@@ -85,58 +123,208 @@ func (c *Chain) initChainStatus() error {
                return err
        }
 
-       voteResultMap := make(map[uint64]*state.VoteResult)
-       if err := c.ApplyBlock(voteResultMap, genesisBlock); err != nil {
-               return err
+       for _, subProtocol := range c.subProtocols {
+               if err := subProtocol.ApplyBlock(genesisBlock); err != nil {
+                       return err
+               }
+       }
+
+       consensusResults := []*state.ConsensusResult{{
+               Seq:            0,
+               NumOfVote:      make(map[string]uint64),
+               CoinbaseReward: make(map[string]uint64),
+               BlockHash:      genesisBlock.Hash(),
+               BlockHeight:    0,
+       }}
+
+       genesisBlockHeader := &genesisBlock.BlockHeader
+       return c.store.SaveChainStatus(genesisBlockHeader, genesisBlockHeader, []*types.BlockHeader{genesisBlockHeader}, utxoView, consensusResults)
+}
+
+// getPrevRoundVoteBlockHash return the previous round block hash by the given block header
+func (c *Chain) getPrevRoundVoteBlockHash(hash *bc.Hash) (*bc.Hash, error) {
+       if data, ok := c.prevRoundVoteBlockHashCache.Get(*hash); ok {
+               return data.(*bc.Hash), nil
        }
 
-       node, err := state.NewBlockNode(&genesisBlock.BlockHeader, nil)
+       header, err := c.store.GetBlockHeader(hash)
        if err != nil {
-               return err
+               return nil, errNotFoundBlockNode
+       }
+
+       if header.Height%consensus.ActiveNetParams.RoundVoteBlockNums == 0 {
+               c.prevRoundVoteBlockHashCache.Add(*hash, hash)
+               return hash, nil
+       }
+
+       if data, ok := c.prevRoundVoteBlockHashCache.Get(header.PreviousBlockHash); ok {
+               c.prevRoundVoteBlockHashCache.Add(*hash, data.(*bc.Hash))
+               return data.(*bc.Hash), nil
        }
 
-       return c.store.SaveChainStatus(node, node, utxoView, voteResultMap)
+       // loop find the prev round vote block hash
+       for header.Height%consensus.ActiveNetParams.RoundVoteBlockNums != 0 {
+               header, err = c.store.GetBlockHeader(&header.PreviousBlockHash)
+               if err != nil {
+                       return nil, err
+               }
+       }
+       preRoundVoteBlockHash := header.Hash()
+       c.prevRoundVoteBlockHashCache.Add(*hash, &preRoundVoteBlockHash)
+       return &preRoundVoteBlockHash, nil
 }
 
 // BestBlockHeight returns the current height of the blockchain.
 func (c *Chain) BestBlockHeight() uint64 {
        c.cond.L.Lock()
        defer c.cond.L.Unlock()
-       return c.bestNode.Height
+       return c.bestBlockHeader.Height
 }
 
-// BestBlockHash return the hash of the chain tail block
+// BestBlockHash return the hash of the main chain tail block
 func (c *Chain) BestBlockHash() *bc.Hash {
        c.cond.L.Lock()
        defer c.cond.L.Unlock()
-       return &c.bestNode.Hash
+       bestHash := c.bestBlockHeader.Hash()
+       return &bestHash
 }
 
-// BestBlockHeader returns the chain tail block
+// LastIrreversibleHeader returns the chain last irreversible block header
+func (c *Chain) LastIrreversibleHeader() *types.BlockHeader {
+       c.cond.L.Lock()
+       defer c.cond.L.Unlock()
+       return c.lastIrrBlockHeader
+}
+
+// BestBlockHeader returns the chain best block header
 func (c *Chain) BestBlockHeader() *types.BlockHeader {
-       node := c.index.BestNode()
-       return node.BlockHeader()
+       c.cond.L.Lock()
+       defer c.cond.L.Unlock()
+       return c.bestBlockHeader
 }
 
 // InMainChain checks wheather a block is in the main chain
 func (c *Chain) InMainChain(hash bc.Hash) bool {
-       return c.index.InMainchain(hash)
+       blockHeader, err := c.store.GetBlockHeader(&hash)
+       if err != nil {
+               return false
+       }
+
+       blockHash, err := c.store.GetMainChainHash(blockHeader.Height)
+       if err != nil {
+               log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height}).Debug("not contain block hash in main chain for specified height")
+               return false
+       }
+       return *blockHash == hash
+}
+
+// SubProtocols return list of layer 2 consensus protocol
+func (c *Chain) SubProtocols() []SubProtocol {
+       return c.subProtocols
+}
+
+// trace back to the tail of the chain from the given block header
+func (c *Chain) traceLongestChainTail(blockHeader *types.BlockHeader) (*types.BlockHeader, error) {
+       longestTail, workQueue := blockHeader, []*types.BlockHeader{blockHeader}
+
+       for ; len(workQueue) > 0; workQueue = workQueue[1:] {
+               currentHeader := workQueue[0]
+               currentHash := currentHeader.Hash()
+               hashes, err := c.store.GetBlockHashesByHeight(currentHeader.Height + 1)
+               if err != nil {
+                       return nil, err
+               }
+
+               for _, h := range hashes {
+                       if header, err := c.store.GetBlockHeader(h); err != nil {
+                               return nil, err
+                       } else if header.PreviousBlockHash == currentHash {
+                               if longestTail.Height < header.Height {
+                                       longestTail = header
+                               }
+                               workQueue = append(workQueue, header)
+                       }
+               }
+       }
+       return longestTail, nil
+}
+
+func (c *Chain) hasSeenTx(tx *types.Tx) bool {
+       return c.knownTxs.Has(tx.ID.String())
+}
+
+func (c *Chain) markTransactions(txs ...*types.Tx) {
+       for _, tx := range txs {
+               c.knownTxs.Add(tx.ID.String())
+       }
+}
+
+func (c *Chain) syncProtocolStatus(subProtocol SubProtocol) error {
+       if c.bestBlockHeader.Height < subProtocol.StartHeight() {
+               return nil
+       }
+
+       protocolHeight, protocolHash, err := subProtocol.ChainStatus()
+       if err == ErrNotInitSubProtocolChainStatus {
+               startHash, err := c.store.GetMainChainHash(subProtocol.StartHeight())
+               if err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "can't get block hash by height")
+               }
+
+               if err := subProtocol.InitChainStatus(startHash); err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "fail init chain status")
+               }
+
+               protocolHeight, protocolHash = subProtocol.StartHeight(), startHash
+       } else if err != nil {
+               return errors.Wrap(err, subProtocol.Name(), "can't get chain status")
+       }
+
+       if *protocolHash == c.bestBlockHeader.Hash() {
+               return nil
+       }
+
+       for !c.InMainChain(*protocolHash) {
+               block, err := c.GetBlockByHash(protocolHash)
+               if err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "can't get block by hash in chain")
+               }
+
+               if err := subProtocol.DetachBlock(block); err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "sub protocol detach block err")
+               }
+
+               protocolHeight, protocolHash = block.Height-1, &block.PreviousBlockHash
+       }
+
+       for height := protocolHeight + 1; height <= c.bestBlockHeader.Height; height++ {
+               block, err := c.GetBlockByHeight(height)
+               if err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "can't get block by height in chain")
+               }
+
+               if err := subProtocol.ApplyBlock(block); err != nil {
+                       return errors.Wrap(err, subProtocol.Name(), "sub protocol apply block err")
+               }
+
+               blockHash := block.Hash()
+               protocolHeight, protocolHash = block.Height, &blockHash
+       }
+
+       return nil
 }
 
 // This function must be called with mu lock in above level
-func (c *Chain) setState(node *state.BlockNode, irreversibleNode *state.BlockNode, view *state.UtxoViewpoint, voteMap map[uint64]*state.VoteResult) error {
-       if err := c.store.SaveChainStatus(node, irreversibleNode, view, voteMap); err != nil {
+func (c *Chain) setState(blockHeader, irrBlockHeader *types.BlockHeader, mainBlockHeaders []*types.BlockHeader, view *state.UtxoViewpoint, consensusResults []*state.ConsensusResult) error {
+       if err := c.store.SaveChainStatus(blockHeader, irrBlockHeader, mainBlockHeaders, view, consensusResults); err != nil {
                return err
        }
 
-       c.cond.L.Lock()
-       defer c.cond.L.Unlock()
-
-       c.index.SetMainChain(node)
-       c.bestNode = node
-       c.bestIrreversibleNode = irreversibleNode
+       c.bestBlockHeader = blockHeader
+       c.lastIrrBlockHeader = irrBlockHeader
 
-       log.WithFields(log.Fields{"module": logModule, "height": c.bestNode.Height, "hash": c.bestNode.Hash.String()}).Debug("chain best status has been update")
+       blockHash := blockHeader.Hash()
+       log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height, "hash": blockHash.String()}).Debug("chain best status has been update")
        c.cond.Broadcast()
        return nil
 }
@@ -147,7 +335,7 @@ func (c *Chain) BlockWaiter(height uint64) <-chan struct{} {
        go func() {
                c.cond.L.Lock()
                defer c.cond.L.Unlock()
-               for c.bestNode.Height < height {
+               for c.bestBlockHeader.Height < height {
                        c.cond.Wait()
                }
                ch <- struct{}{}