import (
"fmt"
- "sync"
- "github.com/golang/groupcache/lru"
"github.com/golang/groupcache/singleflight"
+ "github.com/vapor/common"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
)
-const maxCachedBlocks = 30
+const (
+ maxCachedBlockHeaders = 1000
+ maxCachedBlockTransactions = 1000
+)
+
+type fillBlockHeaderFn func(hash *bc.Hash, height uint64) (*types.BlockHeader, error)
+type fillBlockTransactionsFn func(hash *bc.Hash) ([]*types.Tx, error)
-func newBlockCache(fillFn func(hash *bc.Hash) (*types.Block, error)) blockCache {
+func newBlockCache(fillBlockHeader fillBlockHeaderFn, fillBlockTxs fillBlockTransactionsFn) blockCache {
return blockCache{
- lru: lru.New(maxCachedBlocks),
- fillFn: fillFn,
+ lruBlockHeaders: common.NewCache(maxCachedBlockHeaders),
+ lruBlockTxs: common.NewCache(maxCachedBlockTransactions),
+
+ fillBlockHeaderFn: fillBlockHeader,
+ fillBlockTransactionFn: fillBlockTxs,
}
}
type blockCache struct {
- mu sync.Mutex
- lru *lru.Cache
- fillFn func(hash *bc.Hash) (*types.Block, error)
- single singleflight.Group
+ lruBlockHeaders *common.Cache
+ lruBlockTxs *common.Cache
+
+ fillBlockHeaderFn func(hash *bc.Hash, height uint64) (*types.BlockHeader, error)
+ fillBlockTransactionFn func(hash *bc.Hash) ([]*types.Tx, error)
+
+ singleBlockHeader singleflight.Group
+ singleBlockTxs singleflight.Group
}
-func (c *blockCache) lookup(hash *bc.Hash) (*types.Block, error) {
- if b, ok := c.get(hash); ok {
- return b, nil
+func (c *blockCache) lookupBlockHeader(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
+ if bH, ok := c.getBlockHeader(hash); ok {
+ return bH, nil
}
- block, err := c.single.Do(hash.String(), func() (interface{}, error) {
- b, err := c.fillFn(hash)
+ blockHeader, err := c.singleBlockHeader.Do(hash.String(), func() (interface{}, error) {
+ bH, err := c.fillBlockHeaderFn(hash, height)
if err != nil {
return nil, err
}
- if b == nil {
- return nil, fmt.Errorf("There are no block with given hash %s", hash.String())
+ if bH == nil {
+ return nil, fmt.Errorf("There are no blockHeader with given hash %s", hash.String())
}
- c.add(b)
- return b, nil
+ c.addBlockHeader(bH)
+ return bH, nil
})
if err != nil {
return nil, err
}
- return block.(*types.Block), nil
+ return blockHeader.(*types.BlockHeader), nil
}
-func (c *blockCache) get(hash *bc.Hash) (*types.Block, bool) {
- c.mu.Lock()
- block, ok := c.lru.Get(*hash)
- c.mu.Unlock()
- if block == nil {
+func (c *blockCache) lookupBlockTxs(hash *bc.Hash) ([]*types.Tx, error) {
+ if bTxs, ok := c.getBlockTransactions(hash); ok {
+ return bTxs, nil
+ }
+
+ blockTransactions, err := c.singleBlockTxs.Do(hash.String(), func() (interface{}, error) {
+ bTxs, err := c.fillBlockTransactionFn(hash)
+ if err != nil {
+ return nil, err
+ }
+
+ if bTxs == nil {
+ return nil, fmt.Errorf("There are no block transactions with given hash %s", hash.String())
+ }
+
+ c.addBlockTxs(*hash, bTxs)
+ return bTxs, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return blockTransactions.([]*types.Tx), nil
+}
+
+func (c *blockCache) getBlockHeader(hash *bc.Hash) (*types.BlockHeader, bool) {
+ blockHeader, ok := c.lruBlockHeaders.Get(*hash)
+ if blockHeader == nil {
return nil, ok
}
- return block.(*types.Block), ok
+ return blockHeader.(*types.BlockHeader), ok
+}
+
+func (c *blockCache) getBlockTransactions(hash *bc.Hash) ([]*types.Tx, bool) {
+ txs, ok := c.lruBlockTxs.Get(*hash)
+ if txs == nil {
+ return nil, ok
+ }
+ return txs.([]*types.Tx), ok
+}
+
+func (c *blockCache) addBlockHeader(blockHeader *types.BlockHeader) {
+ c.lruBlockHeaders.Add(blockHeader.Hash(), blockHeader)
}
-func (c *blockCache) add(block *types.Block) {
- c.mu.Lock()
- c.lru.Add(block.Hash(), block)
- c.mu.Unlock()
+func (c *blockCache) addBlockTxs(hash bc.Hash, txs []*types.Tx) {
+ c.lruBlockTxs.Add(hash, txs)
}
}
}
blocks := make(map[bc.Hash]*types.Block)
- for i := 0; i < maxCachedBlocks+10; i++ {
+ for i := 0; i < maxCachedBlockHeaders+10; i++ {
block := newBlock(uint64(i))
blocks[block.Hash()] = block
}
- cache := newBlockCache(func(hash *bc.Hash) (*types.Block, error) {
- return blocks[*hash], nil
- })
+ fillBlockHeaderFn := func(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
+ return &blocks[*hash].BlockHeader, nil
+ }
+
+ fillBlockTxsFn := func(hash *bc.Hash) ([]*types.Tx, error) {
+ return blocks[*hash].Transactions, nil
+ }
+
+ cache := newBlockCache(fillBlockHeaderFn, fillBlockTxsFn)
- for i := 0; i < maxCachedBlocks+10; i++ {
+ for i := 0; i < maxCachedBlockHeaders+10; i++ {
block := newBlock(uint64(i))
hash := block.Hash()
- cache.lookup(&hash)
+ cache.lookupBlockHeader(&hash, block.Height)
}
for i := 0; i < 10; i++ {
block := newBlock(uint64(i))
hash := block.Hash()
- if b, _ := cache.get(&hash); b != nil {
+ if b, _ := cache.getBlockHeader(&hash); b != nil {
t.Fatalf("find old block")
}
}
- for i := 10; i < maxCachedBlocks+10; i++ {
+ for i := 10; i < maxCachedBlockHeaders+10; i++ {
block := newBlock(uint64(i))
hash := block.Hash()
- if b, _ := cache.get(&hash); b == nil {
+ if b, _ := cache.getBlockHeader(&hash); b == nil {
t.Fatalf("can't find new block")
}
}
const logModule = "leveldb"
var (
- blockStoreKey = []byte("blockStore")
- blockPrefix = []byte("B:")
- blockHeaderPrefix = []byte("BH:")
- txStatusPrefix = []byte("BTS:")
- voteResultPrefix = []byte("VR:")
+ blockStoreKey = []byte("blockStore")
+ blockHeaderPrefix = []byte("BH:")
+ blockTransactonsPrefix = []byte("BTXS:")
+ txStatusPrefix = []byte("BTS:")
+ voteResultPrefix = []byte("VR:")
)
func loadBlockStoreStateJSON(db dbm.DB) *protocol.BlockStoreState {
cache blockCache
}
-func calcBlockKey(hash *bc.Hash) []byte {
- return append(blockPrefix, hash.Bytes()...)
-}
-
func calcBlockHeaderKey(height uint64, hash *bc.Hash) []byte {
buf := [8]byte{}
binary.BigEndian.PutUint64(buf[:], height)
return append(key, hash.Bytes()...)
}
+func calcBlockTransactionsKey(hash *bc.Hash) []byte {
+ return append(blockTransactonsPrefix, hash.Bytes()...)
+}
+
func calcTxStatusKey(hash *bc.Hash) []byte {
return append(txStatusPrefix, hash.Bytes()...)
}
return append(voteResultPrefix, buf[:]...)
}
-// GetBlock return the block by given hash
-func GetBlock(db dbm.DB, hash *bc.Hash) (*types.Block, error) {
- bytez := db.Get(calcBlockKey(hash))
- if bytez == nil {
+// GetBlockHeader return the block header by given hash and height
+func GetBlockHeader(db dbm.DB, hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
+ block := &types.Block{}
+ binaryBlockHeader := db.Get(calcBlockHeaderKey(height, hash))
+ if binaryBlockHeader == nil {
return nil, nil
}
+ if err := block.UnmarshalText(binaryBlockHeader); err != nil {
+ return nil, err
+ }
+ return &block.BlockHeader, nil
+}
+
+// GetBlockTransactions return the block transactions by given hash
+func GetBlockTransactions(db dbm.DB, hash *bc.Hash) ([]*types.Tx, error) {
block := &types.Block{}
- err := block.UnmarshalText(bytez)
- return block, err
+ binaryBlockTxs := db.Get(calcBlockTransactionsKey(hash))
+ if binaryBlockTxs == nil {
+ return nil, errors.New("The transactions in the block is empty")
+ }
+
+ if err := block.UnmarshalText(binaryBlockTxs); err != nil {
+ return nil, err
+ }
+ return block.Transactions, nil
}
// NewStore creates and returns a new Store object.
func NewStore(db dbm.DB) *Store {
- cache := newBlockCache(func(hash *bc.Hash) (*types.Block, error) {
- return GetBlock(db, hash)
- })
+ fillBlockHeaderFn := func(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
+ return GetBlockHeader(db, hash, height)
+ }
+
+ fillBlockTxsFn := func(hash *bc.Hash) ([]*types.Tx, error) {
+ return GetBlockTransactions(db, hash)
+ }
+
+ cache := newBlockCache(fillBlockHeaderFn, fillBlockTxsFn)
return &Store{
db: db,
cache: cache,
}
// BlockExist check if the block is stored in disk
-func (s *Store) BlockExist(hash *bc.Hash) bool {
- block, err := s.cache.lookup(hash)
- return err == nil && block != nil
+func (s *Store) BlockExist(hash *bc.Hash, height uint64) bool {
+ blockHeader, err := s.cache.lookupBlockHeader(hash, height)
+ return err == nil && blockHeader != nil
}
// GetBlock return the block by given hash
-func (s *Store) GetBlock(hash *bc.Hash) (*types.Block, error) {
- return s.cache.lookup(hash)
+func (s *Store) GetBlock(hash *bc.Hash, height uint64) (*types.Block, error) {
+ blockHeader, err := s.GetBlockHeader(hash, height)
+ if err != nil {
+ return nil, err
+ }
+
+ txs, err := s.GetBlockTransactions(hash)
+ if err != nil {
+ return nil, err
+ }
+
+ return &types.Block{
+ BlockHeader: *blockHeader,
+ Transactions: txs,
+ }, nil
+}
+
+// GetBlockHeader return the BlockHeader by given hash
+func (s *Store) GetBlockHeader(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
+ blockHeader, err := s.cache.lookupBlockHeader(hash, height)
+ if err != nil {
+ return nil, err
+ }
+ return blockHeader, nil
+}
+
+// GetBlockTransactions return the Block transactions by given hash
+func (s *Store) GetBlockTransactions(hash *bc.Hash) ([]*types.Tx, error) {
+ txs, err := s.cache.lookupBlockTxs(hash)
+ if err != nil {
+ return nil, err
+ }
+ return txs, nil
}
// GetTransactionsUtxo will return all the utxo that related to the input txs
// SaveBlock persists a new block in the protocol.
func (s *Store) SaveBlock(block *types.Block, ts *bc.TransactionStatus) error {
startTime := time.Now()
- binaryBlock, err := block.MarshalText()
+
+ binaryBlockHeader, err := block.MarshalTextForBlockHeader()
if err != nil {
- return errors.Wrap(err, "Marshal block meta")
+ return errors.Wrap(err, "Marshal block header")
}
- binaryBlockHeader, err := block.BlockHeader.MarshalText()
+ binaryBlockTxs, err := block.MarshalTextForTransactions()
if err != nil {
- return errors.Wrap(err, "Marshal block header")
+ return errors.Wrap(err, "Marshal block transactions")
}
binaryTxStatus, err := proto.Marshal(ts)
blockHash := block.Hash()
batch := s.db.NewBatch()
- batch.Set(calcBlockKey(&blockHash), binaryBlock)
batch.Set(calcBlockHeaderKey(block.Height, &blockHash), binaryBlockHeader)
+ batch.Set(calcBlockTransactionsKey(&blockHash), binaryBlockTxs)
batch.Set(calcTxStatusKey(&blockHash), binaryTxStatus)
batch.Write()
return nil
}
+// SaveBlockHeader persists a new block header in the protocol.
+func (s *Store) SaveBlockHeader(blockHeader *types.BlockHeader) error {
+ startTime := time.Now()
+
+ binaryBlockHeader, err := blockHeader.MarshalText()
+ if err != nil {
+ return errors.Wrap(err, "Marshal block header")
+ }
+
+ blockHash := blockHeader.Hash()
+ batch := s.db.NewBatch()
+ batch.Set(calcBlockHeaderKey(blockHeader.Height, &blockHash), binaryBlockHeader)
+ batch.Write()
+
+ log.WithFields(log.Fields{
+ "module": logModule,
+ "height": blockHeader.Height,
+ "hash": blockHash.String(),
+ "duration": time.Since(startTime),
+ }).Info("blockHeader saved on disk")
+ return nil
+}
+
// SaveChainStatus save the core's newest status && delete old status
func (s *Store) SaveChainStatus(node, irreversibleNode *state.BlockNode, view *state.UtxoViewpoint, voteResults []*state.VoteResult) error {
batch := s.db.NewBatch()
}
blockHash := block.Hash()
- gotBlock, err := store.GetBlock(&blockHash)
+ gotBlock, err := store.GetBlock(&blockHash, block.Height)
if err != nil {
t.Fatal(err)
}
return err
}
- block, err := c.store.GetBlock(&blockNode.Hash)
+ blockHeader, err := c.store.GetBlockHeader(&blockNode.Hash, blockNode.Height)
if err != nil {
return err
}
- block.Set(nodeOrder, signature)
+ blockHeader.Set(nodeOrder, signature)
- txStatus, err := c.store.GetTransactionStatus(&blockNode.Hash)
- if err != nil {
- return err
- }
-
- if err := c.store.SaveBlock(block, txStatus); err != nil {
+ if err := c.store.SaveBlockHeader(blockHeader); err != nil {
return err
}
return nil
}
+// MarshalTextForBlockHeader fulfills the json.Marshaler interface.
+func (b *Block) MarshalTextForBlockHeader() ([]byte, error) {
+ buf := bufpool.Get()
+ defer bufpool.Put(buf)
+
+ ew := errors.NewWriter(buf)
+ if err := b.writeTo(ew, SerBlockHeader); err != nil {
+ return nil, err
+ }
+ if err := ew.Err(); err != nil {
+ return nil, err
+ }
+
+ enc := make([]byte, hex.EncodedLen(buf.Len()))
+ hex.Encode(enc, buf.Bytes())
+ return enc, nil
+}
+
+// MarshalTextForTransactions fulfills the json.Marshaler interface.
+func (b *Block) MarshalTextForTransactions() ([]byte, error) {
+ buf := bufpool.Get()
+ defer bufpool.Put(buf)
+
+ ew := errors.NewWriter(buf)
+ if err := b.writeTo(ew, SerBlockTransactions); err != nil {
+ return nil, err
+ }
+ if err := ew.Err(); err != nil {
+ return nil, err
+ }
+
+ enc := make([]byte, hex.EncodedLen(buf.Len()))
+ hex.Encode(enc, buf.Bytes())
+ return enc, nil
+}
+
func (b *Block) readFrom(r *blockchain.Reader) error {
- serflags, err := b.BlockHeader.readFrom(r)
+ serflag, err := b.BlockHeader.readFrom(r)
if err != nil {
return err
}
- if serflags == SerBlockHeader {
+ if serflag == SerBlockHeader {
return nil
}
}
func (b *Block) writeTo(w io.Writer, serflags uint8) error {
- if err := b.BlockHeader.writeTo(w, serflags); err != nil {
- return err
+ if serflags == SerBlockHeader || serflags == SerBlockFull {
+ if err := b.BlockHeader.writeTo(w, serflags); err != nil {
+ return err
+ }
}
if serflags == SerBlockHeader {
return nil
}
+ if serflags != SerBlockFull {
+ w.Write([]byte{serflags})
+ }
+
if _, err := blockchain.WriteVarint31(w, uint64(len(b.Transactions))); err != nil {
return err
}
return err
}
- _, err := bh.readFrom(blockchain.NewReader(decoded))
- return err
+ serflag, err := bh.readFrom(blockchain.NewReader(decoded))
+ if err != nil {
+ return err
+ }
+
+ if serflag == SerBlockTransactions {
+ return fmt.Errorf("unsupported serialization flags 0x%02x", serflag)
+ }
+ return nil
}
func (bh *BlockHeader) readFrom(r *blockchain.Reader) (serflag uint8, err error) {
serflag = serflags[0]
switch serflag {
case SerBlockHeader, SerBlockFull:
+ case SerBlockTransactions:
+ return
default:
return 0, fmt.Errorf("unsupported serialization flags 0x%x", serflags)
}
// GetBlockByHash return a block by given hash
func (c *Chain) GetBlockByHash(hash *bc.Hash) (*types.Block, error) {
- return c.store.GetBlock(hash)
+ node := c.index.GetNode(hash)
+ if node == nil {
+ return nil, errors.New("can't find block in given hash")
+ }
+ return c.store.GetBlock(hash, node.Height)
}
// GetBlockByHeight return a block header by given height
if node == nil {
return nil, errors.New("can't find block in given height")
}
- return c.store.GetBlock(&node.Hash)
+ return c.store.GetBlock(&node.Hash, height)
}
// GetHeaderByHash return a block header by given hash
}
for _, detachNode := range detachNodes {
- b, err := c.store.GetBlock(&detachNode.Hash)
+ b, err := c.store.GetBlock(&detachNode.Hash, detachNode.Height)
if err != nil {
return err
}
}
for _, attachNode := range attachNodes {
- b, err := c.store.GetBlock(&attachNode.Hash)
+ b, err := c.store.GetBlock(&attachNode.Hash, attachNode.Height)
if err != nil {
return err
}
// The start time of the last round of product block
lastRoundStartTime := startTimestamp + (blockTimestamp-startTimestamp)/roundBlockTime*roundBlockTime
// Order of blocker
- return (blockTimestamp - lastRoundStartTime)/(consensus.BlockNumEachNode*consensus.BlockTimeInterval)
+ return (blockTimestamp - lastRoundStartTime) / (consensus.BlockNumEachNode * consensus.BlockTimeInterval)
}
func (c *consensusNodeManager) getPrevRoundLastBlock(prevBlockHash *bc.Hash) (*state.BlockNode, error) {
// getVoteResult return the vote result
// seq represent the sequence of vote
// blockNode represent the chain in which the result of the vote is located
-// Voting results need to be adjusted according to the chain
+// Voting results need to be adjusted according to the chain
func (c *consensusNodeManager) getVoteResult(seq uint64, blockNode *state.BlockNode) (*state.VoteResult, error) {
voteResult, err := c.store.GetVoteResult(seq)
if err != nil {
var forChainRollback, mainChainRollBack bool
if forChainRollback = forkChainNode.Height >= mainChainNode.Height; forChainRollback {
attachNodes = append([]*state.BlockNode{forkChainNode}, attachNodes...)
- }
+ }
if mainChainRollBack = forkChainNode.Height <= mainChainNode.Height; mainChainRollBack {
detachNodes = append(detachNodes, mainChainNode)
}
}
for _, node := range detachNodes {
- block, err := c.store.GetBlock(&node.Hash)
+ block, err := c.store.GetBlock(&node.Hash, node.Height)
if err != nil {
return err
}
}
for _, node := range attachNodes {
- block, err := c.store.GetBlock(&node.Hash)
+ block, err := c.store.GetBlock(&node.Hash, node.Height)
if err != nil {
return err
}
// Store provides storage interface for blockchain data
type Store interface {
- BlockExist(*bc.Hash) bool
+ BlockExist(*bc.Hash, uint64) bool
- GetBlock(*bc.Hash) (*types.Block, error)
+ GetBlock(*bc.Hash, uint64) (*types.Block, error)
+ GetBlockHeader(*bc.Hash, uint64) (*types.BlockHeader, error)
GetStoreStatus() *BlockStoreState
GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error)
GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error
LoadBlockIndex(uint64) (*state.BlockIndex, error)
SaveBlock(*types.Block, *bc.TransactionStatus) error
+ SaveBlockHeader(*types.BlockHeader) error
SaveChainStatus(*state.BlockNode, *state.BlockNode, *state.UtxoViewpoint, []*state.VoteResult) error
}
type mockStore struct{}
-func (s *mockStore) BlockExist(hash *bc.Hash) bool { return false }
-func (s *mockStore) GetBlock(*bc.Hash) (*types.Block, error) { return nil, nil }
+func (s *mockStore) BlockExist(hash *bc.Hash, height uint64) bool { return false }
+func (s *mockStore) GetBlock(*bc.Hash, uint64) (*types.Block, error) { return nil, nil }
+func (s *mockStore) GetBlockHeader(*bc.Hash, uint64) (*types.BlockHeader, error) { return nil, nil }
func (s *mockStore) GetStoreStatus() *BlockStoreState { return nil }
func (s *mockStore) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil }
func (s *mockStore) GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error { return nil }
func (s *mockStore) GetVoteResult(uint64) (*state.VoteResult, error) { return nil, nil }
func (s *mockStore) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil }
func (s *mockStore) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
+func (s *mockStore) SaveBlockHeader(*types.BlockHeader) error { return nil }
func (s *mockStore) SaveChainStatus(*state.BlockNode, *state.BlockNode, *state.UtxoViewpoint, []*state.VoteResult) error {
return nil
}
type mockStore1 struct{}
-func (s *mockStore1) BlockExist(hash *bc.Hash) bool { return false }
-func (s *mockStore1) GetBlock(*bc.Hash) (*types.Block, error) { return nil, nil }
+func (s *mockStore1) BlockExist(hash *bc.Hash, height uint64) bool { return false }
+func (s *mockStore1) GetBlock(*bc.Hash, uint64) (*types.Block, error) { return nil, nil }
+func (s *mockStore1) GetBlockHeader(*bc.Hash, uint64) (*types.BlockHeader, error) { return nil, nil }
func (s *mockStore1) GetStoreStatus() *BlockStoreState { return nil }
func (s *mockStore1) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil }
func (s *mockStore1) GetTransactionsUtxo(utxoView *state.UtxoViewpoint, tx []*bc.Tx) error {
func (s *mockStore1) GetVoteResult(uint64) (*state.VoteResult, error) { return nil, nil }
func (s *mockStore1) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil }
func (s *mockStore1) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
+func (s *mockStore1) SaveBlockHeader(*types.BlockHeader) error { return nil }
func (s *mockStore1) SaveChainStatus(*state.BlockNode, *state.BlockNode, *state.UtxoViewpoint, []*state.VoteResult) error {
return nil
}