package protocol
import (
- "context"
"sync"
- "time"
- "github.com/bytom/blockchain/txdb"
+ 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
+const maxProcessBlockChSize = 1024
-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")
-)
+// 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")
-// 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
-
- GetBlock(*bc.Hash) (*legacy.Block, error)
- GetMainchain() (map[uint64]*bc.Hash, txdb.MainchainStatusJSON, error)
- GetRawBlock(*bc.Hash) ([]byte, error)
- GetSnapshot() (*state.Snapshot, txdb.SnapshotStatusJSON, error)
- GetStoreStatus() txdb.BlockStoreStateJSON
-
- SaveBlock(*legacy.Block) error
- SaveMainchain(map[uint64]*bc.Hash, uint64, *bc.Hash) error
- SaveSnapshot(*state.Snapshot, uint64, *bc.Hash) error
- SaveStoreStatus(uint64, *bc.Hash)
-}
+// 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
-type OrphanManage struct {
- orphan map[bc.Hash]*legacy.Block
- preOrphans map[bc.Hash][]*bc.Hash
- mtx sync.RWMutex
+ cond sync.Cond
+ bestNode *state.BlockNode
}
-func (o *OrphanManage) BlockExist(hash *bc.Hash) bool {
- o.mtx.RLock()
- _, ok := o.orphan[*hash]
- o.mtx.RUnlock()
- return ok
-}
+// 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),
+ }
+ c.cond.L = new(sync.Mutex)
-func (o *OrphanManage) Add(block *legacy.Block) {
- blockHash := block.Hash()
- o.mtx.Lock()
- defer o.mtx.Unlock()
+ storeStatus := store.GetStoreStatus()
+ if storeStatus == nil {
+ if err := c.initChainStatus(); err != nil {
+ return nil, err
+ }
+ storeStatus = store.GetStoreStatus()
+ }
- if _, ok := o.orphan[blockHash]; ok {
- return
+ var err error
+ if c.index, err = store.LoadBlockIndex(); err != nil {
+ return nil, err
}
- o.orphan[blockHash] = block
- o.preOrphans[block.PreviousBlockHash] = append(o.preOrphans[block.PreviousBlockHash], &blockHash)
+ c.bestNode = c.index.GetNode(storeStatus.Hash)
+ c.index.SetMainChain(c.bestNode)
+ go c.blockProcesser()
+ return c, nil
}
-func (o *OrphanManage) Delete(hash *bc.Hash) {
- o.mtx.Lock()
- defer o.mtx.Unlock()
- block, ok := o.orphan[*hash]
- if !ok {
- return
+func (c *Chain) initChainStatus() error {
+ genesisBlock := config.GenesisBlock()
+ txStatus := bc.NewTransactionStatus()
+ for i := range genesisBlock.Transactions {
+ txStatus.SetStatus(i, false)
}
- delete(o.orphan, *hash)
- preOrphans, ok := o.preOrphans[block.PreviousBlockHash]
- if len(preOrphans) == 1 {
- delete(o.preOrphans, block.PreviousBlockHash)
- return
+ if err := c.store.SaveBlock(genesisBlock, txStatus); err != nil {
+ return err
}
- for i, preOrphan := range preOrphans {
- if preOrphan == hash {
- o.preOrphans[block.PreviousBlockHash] = append(preOrphans[:i], preOrphans[i+1:]...)
- return
- }
+ utxoView := state.NewUtxoViewpoint()
+ bcBlock := types.MapBlock(genesisBlock)
+ if err := utxoView.ApplyBlock(bcBlock, txStatus); err != nil {
+ return err
}
-}
-func (o *OrphanManage) Get(hash *bc.Hash) (*legacy.Block, bool) {
- o.mtx.RLock()
- block, ok := o.orphan[*hash]
- o.mtx.RUnlock()
- return block, ok
-}
-
-// 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 // protects height, block, snapshot
- height uint64
- hash *bc.Hash
- block *legacy.Block
- snapshot *state.Snapshot
- mainChain map[uint64]*bc.Hash
+ node, err := state.NewBlockNode(&genesisBlock.BlockHeader, nil)
+ if err != nil {
+ return err
}
- store Store
-
- lastQueuedSnapshot time.Time
+ return c.store.SaveChainStatus(node, utxoView)
}
-type pendingSnapshot struct {
- height uint64
- snapshot *state.Snapshot
+// 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
}
-// NewChain returns a new Chain using store as the underlying storage.
-func NewChain(ctx context.Context, initialBlockHash bc.Hash, store Store, txPool *TxPool) (*Chain, error) {
- c := &Chain{
- InitialBlockHash: initialBlockHash,
- orphanManage: &OrphanManage{
- orphan: make(map[bc.Hash]*legacy.Block),
- preOrphans: make(map[bc.Hash][]*bc.Hash),
- },
- store: store,
- txPool: txPool,
- }
- c.state.cond.L = new(sync.Mutex)
-
- storeStatus := store.GetStoreStatus()
- c.state.height = storeStatus.Height
-
- if c.state.height < 1 {
- c.state.snapshot = state.Empty()
- c.state.mainChain = make(map[uint64]*bc.Hash)
- } else {
- //TODO: snapshot, mainChain version check
- c.state.hash = storeStatus.Hash
- c.state.block, _ = store.GetBlock(storeStatus.Hash)
- c.state.snapshot, _, _ = store.GetSnapshot()
- c.state.mainChain, _, _ = store.GetMainchain()
- }
- return c, nil
+// BestBlockHash return the hash of the chain tail block
+func (c *Chain) BestBlockHash() *bc.Hash {
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
+ return &c.bestNode.Hash
}
-func (c *Chain) GetStore() *Store {
- return &(c.store)
+// BestBlockHeader returns the chain tail block
+func (c *Chain) BestBlockHeader() *types.BlockHeader {
+ node := c.index.BestNode()
+ return node.BlockHeader()
}
-// Height returns the current height of the blockchain.
-func (c *Chain) Height() uint64 {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- return c.state.height
+// InMainChain checks wheather a block is in the main chain
+func (c *Chain) InMainChain(hash bc.Hash) bool {
+ return c.index.InMainchain(hash)
}
-func (c *Chain) InMainchain(block *legacy.Block) bool {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- hash, ok := c.state.mainChain[block.Height]
- if !ok {
- return false
+// 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 *hash == block.Hash()
+ return node.CalcNextSeed(), nil
}
-// TimestampMS returns the latest known block timestamp.
-func (c *Chain) TimestampMS() uint64 {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- if c.state.block == nil {
- return 0
+// 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.state.block.TimestampMS
-}
-
-// State returns the most recent state available. It will not be current
-// unless the current process is the leader. Callers should examine the
-// returned block header's height if they need to verify the current state.
-func (c *Chain) State() (*legacy.Block, *state.Snapshot) {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- return c.state.block, c.state.snapshot
+ return node.CalcNextBits(), nil
}
-func (c *Chain) setState(b *legacy.Block, s *state.Snapshot) {
- c.state.cond.L.Lock()
- defer c.state.cond.L.Unlock()
- c.state.block = b
- c.state.snapshot = s
- if b != nil && b.Height > c.state.height {
- c.state.height = b.Height
- c.state.cond.Broadcast()
+// This function must be called with mu lock in above level
+func (c *Chain) setState(node *state.BlockNode, view *state.UtxoViewpoint) error {
+ if err := c.store.SaveChainStatus(node, view); err != nil {
+ return err
}
-}
-// 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)
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
- go func() {
- const slop = 3
- if height > c.Height()+slop {
- ch <- ErrTheDistantFuture
- return
- }
+ c.index.SetMainChain(node)
+ c.bestNode = node
- select {
- case <-c.BlockWaiter(height):
- ch <- nil
- case <-ctx.Done():
- ch <- ctx.Err()
- }
- }()
-
- 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.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
+}