package protocol
import (
- "context"
"sync"
- "time"
+ log "github.com/sirupsen/logrus"
+
+ "github.com/bytom/config"
"github.com/bytom/errors"
- "github.com/bytom/log"
"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")
-)
+// 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
+
+ cond sync.Cond
+ bestNode *state.BlockNode
+}
-// 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 {
- Height() uint64
- GetBlock(uint64) (*legacy.Block, error)
- LatestSnapshot(context.Context) (*state.Snapshot, uint64, error)
-
- SaveBlock(*legacy.Block) error
- FinalizeBlock(context.Context, uint64) error
- SaveSnapshot(context.Context, uint64, *state.Snapshot) error
+// NewChain returns a new Chain using store as the underlying storage.
+func NewChain(store Store, txPool *TxPool) (*Chain, error) {
+ return NewChainWithOrphanManage(store, txPool, NewOrphanManage())
}
-// 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
-
- state struct {
- cond sync.Cond // protects height, block, snapshot
- height uint64
- block *legacy.Block
- snapshot *state.Snapshot
+func NewChainWithOrphanManage(store Store, txPool *TxPool, manage *OrphanManage) (*Chain, error) {
+ c := &Chain{
+ orphanManage: manage,
+ txPool: txPool,
+ store: store,
+ processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize),
}
- store Store
+ c.cond.L = new(sync.Mutex)
- lastQueuedSnapshot time.Time
- pendingSnapshots chan pendingSnapshot
+ storeStatus := store.GetStoreStatus()
+ if storeStatus == nil {
+ if err := c.initChainStatus(); err != nil {
+ return nil, err
+ }
+ storeStatus = store.GetStoreStatus()
+ }
- txPool *TxPool
-}
+ var err error
+ if c.index, err = store.LoadBlockIndex(storeStatus.Height); err != nil {
+ return nil, err
+ }
-type pendingSnapshot struct {
- height uint64
- snapshot *state.Snapshot
+ c.bestNode = c.index.GetNode(storeStatus.Hash)
+ c.index.SetMainChain(c.bestNode)
+ go c.blockProcesser()
+ return c, nil
}
-// NewChain returns a new Chain using store as the underlying storage.
-func NewChain(ctx context.Context, initialBlockHash bc.Hash, store Store, txPool *TxPool, heights <-chan uint64) (*Chain, error) {
- c := &Chain{
- InitialBlockHash: initialBlockHash,
- store: store,
- pendingSnapshots: make(chan pendingSnapshot, 1),
- txPool: txPool,
+func (c *Chain) initChainStatus() error {
+ genesisBlock := config.GenesisBlock()
+ txStatus := bc.NewTransactionStatus()
+ for i := range genesisBlock.Transactions {
+ if err := txStatus.SetStatus(i, false); err != nil {
+ return err
+ }
}
- c.state.cond.L = new(sync.Mutex)
- log.Printf(ctx, "bytom's Height:%v.", store.Height())
- c.state.height = store.Height()
+ if err := c.store.SaveBlock(genesisBlock, txStatus); err != nil {
+ return err
+ }
- if c.state.height < 1 {
- c.state.snapshot = state.Empty()
- } else {
- c.state.block, _ = store.GetBlock(c.state.height)
- c.state.snapshot, _, _ = store.LatestSnapshot(ctx)
+ utxoView := state.NewUtxoViewpoint()
+ bcBlock := types.MapBlock(genesisBlock)
+ if err := utxoView.ApplyBlock(bcBlock, txStatus); err != nil {
+ return err
}
- // Note that c.height.n may still be zero here.
- if heights != nil {
- go func() {
- for h := range heights {
- c.setHeight(h)
- }
- }()
+ node, err := state.NewBlockNode(&genesisBlock.BlockHeader, nil)
+ if err != nil {
+ return err
}
+ return c.store.SaveChainStatus(node, utxoView)
+}
- go func() {
- for {
- select {
- case <-ctx.Done():
- return
- case ps := <-c.pendingSnapshots:
- err := store.SaveSnapshot(ctx, ps.height, ps.snapshot)
- if err != nil {
- log.Error(ctx, err, "at", "saving 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
+}
- 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)
}
-// 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
+// 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.TimestampMS
+ return node.CalcNextSeed(), nil
}
-// 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
+// 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 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()
- }
+func (c *Chain) GetBlockIndex() *state.BlockIndex {
+ return c.index
}
-// 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)
+// 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
+ }
- 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{"module": logModule, "height": c.bestNode.Height, "hash": c.bestNode.Hash.String()}).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
+}