"sync"
"time"
- log "github.com/sirupsen/logrus"
-
+ "github.com/bytom/consensus"
+ "github.com/bytom/database"
+ "github.com/bytom/database/storage"
"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"
)
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 {
- Height() uint64
- GetBlock(uint64) (*legacy.Block, error)
- LatestSnapshot(context.Context) (*state.Snapshot, uint64, error)
+// OrphanManage is use to handle all the orphan block
+type OrphanManage struct {
+ //TODO: add orphan cached block limit
+ orphan map[bc.Hash]*types.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]*types.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 *types.Block) {
+ blockHash := block.Hash()
+ o.mtx.Lock()
+ defer o.mtx.Unlock()
+
+ if _, ok := o.orphan[blockHash]; ok {
+ return
+ }
+
+ o.orphan[blockHash] = block
+ o.preOrphans[block.PreviousBlockHash] = append(o.preOrphans[block.PreviousBlockHash], &blockHash)
+}
+
+// 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
+ }
+ delete(o.orphan, *hash)
+
+ preOrphans, ok := o.preOrphans[block.PreviousBlockHash]
+ if !ok || len(preOrphans) == 1 {
+ delete(o.preOrphans, block.PreviousBlockHash)
+ return
+ }
+
+ for i, preOrphan := range preOrphans {
+ if preOrphan == hash {
+ o.preOrphans[block.PreviousBlockHash] = append(preOrphans[:i], preOrphans[i+1:]...)
+ return
+ }
+ }
+}
- SaveBlock(*legacy.Block) error
- FinalizeBlock(context.Context, uint64) error
- SaveSnapshot(context.Context, uint64, *state.Snapshot) error
+// Get return the orphan block by hash
+func (o *OrphanManage) Get(hash *bc.Hash) (*types.Block, bool) {
+ o.mtx.RLock()
+ block, ok := o.orphan[*hash]
+ o.mtx.RUnlock()
+ return block, ok
}
// Chain provides a complete, minimal blockchain database. It
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
- block *legacy.Block
- snapshot *state.Snapshot
+ cond sync.Cond
+ block *types.Block
+ hash *bc.Hash
+ mainChain map[uint64]*bc.Hash
}
- store Store
-
- lastQueuedSnapshot time.Time
- pendingSnapshots chan pendingSnapshot
-
- txPool *TxPool
-}
-
-type pendingSnapshot struct {
- height uint64
- snapshot *state.Snapshot
+ store database.Store
}
// 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) {
+func NewChain(initialBlockHash bc.Hash, store database.Store, txPool *TxPool) (*Chain, error) {
c := &Chain{
InitialBlockHash: initialBlockHash,
+ orphanManage: NewOrphanManage(),
store: store,
- pendingSnapshots: make(chan pendingSnapshot, 1),
txPool: txPool,
}
c.state.cond.L = new(sync.Mutex)
+ storeStatus := store.GetStoreStatus()
- log.WithField("current height", store.Height()).Info("Resume from the database")
- c.state.height = store.Height()
-
- 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)
+ if storeStatus.Hash == nil {
+ c.state.mainChain = make(map[uint64]*bc.Hash)
+ return c, nil
}
- // Note that c.height.n may still be zero here.
- if heights != nil {
- go func() {
- for h := range heights {
- c.setHeight(h)
- }
- }()
+ 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
}
-
- go func() {
- for {
- select {
- case <-ctx.Done():
- return
- case ps := <-c.pendingSnapshots:
- err := store.SaveSnapshot(ctx, ps.height, ps.snapshot)
- if err != nil {
- log.WithField("error", err).Error("Error occurs when saving snapshot")
- }
- }
- }
- }()
-
return c, nil
}
-func (c *Chain) GetStore() *Store {
- return &(c.store)
-}
-
// 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
+ if c.state.block == nil {
+ return 0
+ }
+ return c.state.block.Height
}
-// TimestampMS returns the latest known block timestamp.
-func (c *Chain) TimestampMS() uint64 {
+// 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()
- if c.state.block == nil {
- return 0
+ return c.state.hash
+}
+
+func (c *Chain) inMainchain(block *types.Block) bool {
+ hash, ok := c.state.mainChain[block.Height]
+ if !ok {
+ return false
+ }
+ return *hash == block.Hash()
+}
+
+// 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 c.state.block.TimestampMS
+
+ return *h == hash
}
-// 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) {
+// Timestamp returns the latest known block timestamp.
+func (c *Chain) Timestamp() uint64 {
c.state.cond.L.Lock()
defer c.state.cond.L.Unlock()
- return c.state.block, c.state.snapshot
+ if c.state.block == nil {
+ return 0
+ }
+ return c.state.block.Timestamp
}
-func (c *Chain) setState(b *legacy.Block, s *state.Snapshot) {
+// BestBlock returns the chain tail block
+func (c *Chain) BestBlock() *types.Block {
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()
+ return c.state.block
+}
+
+// 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
+ }
+ 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)
+}
+
+// This function must be called with mu lock in above level
+func (c *Chain) setState(block *types.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 {
+ return err
}
+
+ c.state.cond.Broadcast()
+ return nil
}
// BlockSoonWaiter returns a channel that
go func() {
c.state.cond.L.Lock()
defer c.state.cond.L.Unlock()
- for c.state.height < height {
+ for c.state.block.Height < height {
c.state.cond.Wait()
}
ch <- struct{}{}