package protocol
import (
- "context"
"sync"
- "time"
- "github.com/bytom/blockchain/txdb"
- "github.com/bytom/blockchain/txdb/storage"
- "github.com/bytom/consensus"
+ log "github.com/sirupsen/logrus"
+
+ "github.com/bytom/config"
"github.com/bytom/errors"
"github.com/bytom/protocol/bc"
- "github.com/bytom/protocol/bc/legacy"
+ "github.com/bytom/protocol/bc/types"
"github.com/bytom/protocol/state"
)
-// maxCachedValidatedTxs is the max number of validated txs to cache.
-const maxCachedValidatedTxs = 1000
-
-var (
- // ErrTheDistantFuture is returned when waiting for a blockheight
- // too far in excess of the tip of the blockchain.
- ErrTheDistantFuture = errors.New("block height too far in future")
-)
+const maxProcessBlockChSize = 1024
-// Store provides storage for blockchain data: blocks and state tree
-// snapshots.
-//
-// Note, this is different from a state snapshot. A state snapshot
-// provides access to the state at a given point in time -- outputs
-// and issuance memory. The Chain type uses Store to load state
-// from storage and persist validated data.
-type Store interface {
- BlockExist(*bc.Hash) bool
+// ErrTheDistantFuture is returned when waiting for a blockheight too far in
+// excess of the tip of the blockchain.
+var ErrTheDistantFuture = errors.New("block height too far in future")
- GetBlock(*bc.Hash) (*legacy.Block, error)
- GetMainchain(*bc.Hash) (map[uint64]*bc.Hash, error)
- GetStoreStatus() txdb.BlockStoreStateJSON
- GetSeed(*bc.Hash) (*bc.Hash, error)
- GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error)
- GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error
- GetUtxo(*bc.Hash) (*storage.UtxoEntry, error)
-
- SaveBlock(*legacy.Block, *bc.TransactionStatus, *bc.Hash) error
- SaveChainStatus(*legacy.Block, *state.UtxoViewpoint, map[uint64]*bc.Hash) error
-}
-
-// OrphanManage is use to handle all the orphan block
-type OrphanManage struct {
- //TODO: add orphan cached block limit
- orphan map[bc.Hash]*legacy.Block
- preOrphans map[bc.Hash][]*bc.Hash
- mtx sync.RWMutex
-}
-
-// NewOrphanManage return a new orphan block
-func NewOrphanManage() *OrphanManage {
- return &OrphanManage{
- orphan: make(map[bc.Hash]*legacy.Block),
- preOrphans: make(map[bc.Hash][]*bc.Hash),
- }
-}
-
-// BlockExist check is the block in OrphanManage
-func (o *OrphanManage) BlockExist(hash *bc.Hash) bool {
- o.mtx.RLock()
- _, ok := o.orphan[*hash]
- o.mtx.RUnlock()
- return ok
-}
-
-// Add will add the block to OrphanManage
-func (o *OrphanManage) Add(block *legacy.Block) {
- blockHash := block.Hash()
- o.mtx.Lock()
- defer o.mtx.Unlock()
-
- if _, ok := o.orphan[blockHash]; ok {
- return
- }
+// 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
- o.orphan[blockHash] = block
- o.preOrphans[block.PreviousBlockHash] = append(o.preOrphans[block.PreviousBlockHash], &blockHash)
+ cond sync.Cond
+ bestNode *state.BlockNode
}
-// Delete will delelte the block from OrphanManage
-func (o *OrphanManage) Delete(hash *bc.Hash) {
- o.mtx.Lock()
- defer o.mtx.Unlock()
- block, ok := o.orphan[*hash]
- if !ok {
- return
+// NewChain returns a new Chain using store as the underlying storage.
+func NewChain(store Store, txPool *TxPool) (*Chain, error) {
+ c := &Chain{
+ orphanManage: NewOrphanManage(),
+ txPool: txPool,
+ store: store,
+ processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize),
}
- delete(o.orphan, *hash)
+ c.cond.L = new(sync.Mutex)
- preOrphans, ok := o.preOrphans[block.PreviousBlockHash]
- if !ok || len(preOrphans) == 1 {
- delete(o.preOrphans, block.PreviousBlockHash)
- return
+ storeStatus := store.GetStoreStatus()
+ if storeStatus == nil {
+ if err := c.initChainStatus(); err != nil {
+ return nil, err
+ }
+ storeStatus = store.GetStoreStatus()
}
- for i, preOrphan := range preOrphans {
- if preOrphan == hash {
- o.preOrphans[block.PreviousBlockHash] = append(preOrphans[:i], preOrphans[i+1:]...)
- return
- }
+ var err error
+ if c.index, err = store.LoadBlockIndex(); err != nil {
+ return nil, err
}
-}
-// Get return the orphan block by hash
-func (o *OrphanManage) Get(hash *bc.Hash) (*legacy.Block, bool) {
- o.mtx.RLock()
- block, ok := o.orphan[*hash]
- o.mtx.RUnlock()
- return block, ok
+ c.bestNode = c.index.GetNode(storeStatus.Hash)
+ c.index.SetMainChain(c.bestNode)
+ go c.blockProcesser()
+ return c, nil
}
-// Chain provides a complete, minimal blockchain database. It
-// delegates the underlying storage to other objects, and uses
-// validation logic from package validation to decide what
-// objects can be safely stored.
-type Chain struct {
- InitialBlockHash bc.Hash
- MaxIssuanceWindow time.Duration // only used by generators
-
- orphanManage *OrphanManage
- txPool *TxPool
-
- state struct {
- cond sync.Cond
- block *legacy.Block
- hash *bc.Hash
- mainChain map[uint64]*bc.Hash
+func (c *Chain) initChainStatus() error {
+ genesisBlock := config.GenesisBlock()
+ txStatus := bc.NewTransactionStatus()
+ for i := range genesisBlock.Transactions {
+ txStatus.SetStatus(i, false)
}
- store Store
-}
-// NewChain returns a new Chain using store as the underlying storage.
-func NewChain(initialBlockHash bc.Hash, store Store, txPool *TxPool) (*Chain, error) {
- c := &Chain{
- InitialBlockHash: initialBlockHash,
- orphanManage: NewOrphanManage(),
- store: store,
- txPool: txPool,
+ if err := c.store.SaveBlock(genesisBlock, txStatus); err != nil {
+ return err
}
- c.state.cond.L = new(sync.Mutex)
- storeStatus := store.GetStoreStatus()
- if storeStatus.Hash == nil {
- c.state.mainChain = make(map[uint64]*bc.Hash)
- return c, nil
+ utxoView := state.NewUtxoViewpoint()
+ bcBlock := types.MapBlock(genesisBlock)
+ if err := utxoView.ApplyBlock(bcBlock, txStatus); err != nil {
+ return err
}
- c.state.hash = storeStatus.Hash
- var err error
- if c.state.block, err = store.GetBlock(storeStatus.Hash); err != nil {
- return nil, err
- }
- if c.state.mainChain, err = store.GetMainchain(storeStatus.Hash); err != nil {
- return nil, err
+ node, err := state.NewBlockNode(&genesisBlock.BlockHeader, nil)
+ if err != nil {
+ return err
}
- return c, nil
+ return c.store.SaveChainStatus(node, utxoView)
}
-// Height returns the current height of the blockchain.
-func (c *Chain) Height() uint64 {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- if c.state.block == nil {
- return 0
- }
- return c.state.block.Height
+// 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
}
// BestBlockHash return the hash of the chain tail block
func (c *Chain) BestBlockHash() *bc.Hash {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- return c.state.hash
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
+ return &c.bestNode.Hash
}
-func (c *Chain) inMainchain(block *legacy.Block) bool {
- hash, ok := c.state.mainChain[block.Height]
- if !ok {
- return false
- }
- return *hash == block.Hash()
+// BestBlockHeader returns the chain tail block
+func (c *Chain) BestBlockHeader() *types.BlockHeader {
+ node := c.index.BestNode()
+ return node.BlockHeader()
}
// InMainChain checks wheather a block is in the main chain
-func (c *Chain) InMainChain(height uint64, hash bc.Hash) bool {
- c.state.cond.L.Lock()
- h, ok := c.state.mainChain[height]
- c.state.cond.L.Unlock()
- if !ok {
- return false
- }
-
- return *h == hash
+func (c *Chain) InMainChain(hash bc.Hash) bool {
+ return c.index.InMainchain(hash)
}
-// Timestamp returns the latest known block timestamp.
-func (c *Chain) Timestamp() uint64 {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- if c.state.block == nil {
- return 0
+// CalcNextSeed return the seed for the given block
+func (c *Chain) CalcNextSeed(preBlock *bc.Hash) (*bc.Hash, error) {
+ node := c.index.GetNode(preBlock)
+ if node == nil {
+ return nil, errors.New("can't find preblock in the blockindex")
}
- return c.state.block.Timestamp
-}
-
-// BestBlock returns the chain tail block
-func (c *Chain) BestBlock() *legacy.Block {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- return c.state.block
+ return node.CalcNextSeed(), nil
}
-// GetUtxo try to find the utxo status in db
-func (c *Chain) GetUtxo(hash *bc.Hash) (*storage.UtxoEntry, error) {
- return c.store.GetUtxo(hash)
-}
-
-// GetSeed return the seed for the given block
-func (c *Chain) GetSeed(height uint64, preBlock *bc.Hash) (*bc.Hash, error) {
- if height == 0 {
- return consensus.InitialSeed, nil
- } else if height%consensus.SeedPerRetarget == 0 {
- return preBlock, nil
+// CalcNextBits return the seed for the given block
+func (c *Chain) CalcNextBits(preBlock *bc.Hash) (uint64, error) {
+ node := c.index.GetNode(preBlock)
+ if node == nil {
+ return 0, errors.New("can't find preblock in the blockindex")
}
- return c.store.GetSeed(preBlock)
-}
-
-// GetTransactionStatus return the transaction status of give block
-func (c *Chain) GetTransactionStatus(hash *bc.Hash) (*bc.TransactionStatus, error) {
- return c.store.GetTransactionStatus(hash)
-}
-
-// GetTransactionsUtxo return all the utxos that related to the txs' inputs
-func (c *Chain) GetTransactionsUtxo(view *state.UtxoViewpoint, txs []*bc.Tx) error {
- return c.store.GetTransactionsUtxo(view, txs)
+ return node.CalcNextBits(), nil
}
// This function must be called with mu lock in above level
-func (c *Chain) setState(block *legacy.Block, view *state.UtxoViewpoint, m map[uint64]*bc.Hash) error {
- blockHash := block.Hash()
- c.state.block = block
- c.state.hash = &blockHash
- for k, v := range m {
- c.state.mainChain[k] = v
- }
-
- if err := c.store.SaveChainStatus(block, view, c.state.mainChain); err != nil {
+func (c *Chain) setState(node *state.BlockNode, view *state.UtxoViewpoint) error {
+ if err := c.store.SaveChainStatus(node, view); err != nil {
return err
}
- c.state.cond.Broadcast()
- return nil
-}
-
-// BlockSoonWaiter returns a channel that
-// waits for the block at the given height,
-// but it is an error to wait for a block far in the future.
-// WaitForBlockSoon will timeout if the context times out.
-// To wait unconditionally, the caller should use WaitForBlock.
-func (c *Chain) BlockSoonWaiter(ctx context.Context, height uint64) <-chan error {
- ch := make(chan error, 1)
-
- go func() {
- const slop = 3
- if height > c.Height()+slop {
- ch <- ErrTheDistantFuture
- return
- }
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
- select {
- case <-c.BlockWaiter(height):
- ch <- nil
- case <-ctx.Done():
- ch <- ctx.Err()
- }
- }()
+ c.index.SetMainChain(node)
+ c.bestNode = node
- return ch
+ log.WithFields(log.Fields{"height": c.bestNode.Height, "hash": c.bestNode.Hash}).Debug("chain best status has been update")
+ c.cond.Broadcast()
+ return nil
}
-// BlockWaiter returns a channel that
-// waits for the block at the given height.
+// BlockWaiter returns a channel that waits for the block at the given height.
func (c *Chain) BlockWaiter(height uint64) <-chan struct{} {
ch := make(chan struct{}, 1)
go func() {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- for c.state.block.Height < height {
- c.state.cond.Wait()
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
+ for c.bestNode.Height < height {
+ c.cond.Wait()
}
ch <- struct{}{}
}()
return ch
}
+
+// GetTxPool return chain txpool.
+func (c *Chain) GetTxPool() *TxPool {
+ return c.txPool
+}