import (
"fmt"
+ "strconv"
+ "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"
+ "github.com/vapor/protocol/state"
)
const (
- maxCachedBlocks = 30
- maxCachedVoteResults = 144 // int(60 * 60 * 24 * 1000 / consensus.BlockTimeInterval / consensus.RoundVoteBlockNums)
+ maxCachedBlockHeaders = 1000
+ maxCachedBlockTransactions = 1000
++ maxCachedVoteResults = 144 // int(60 * 60 * 24 * 1000 / consensus.BlockTimeInterval / consensus.RoundVoteBlockNums)
)
- func newBlockCache(fillFn func(hash *bc.Hash) (*types.Block, error)) blockCache {
+ type fillBlockHeaderFn func(hash *bc.Hash, height uint64) (*types.BlockHeader, error)
+ type fillBlockTransactionsFn func(hash *bc.Hash) ([]*types.Tx, error)
+
+ 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,
}
}
if err != nil {
return nil, err
}
- return block.(*types.Block), nil
+ return blockTransactions.([]*types.Tx), 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) 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)
}
+
+func newVoteResultCache(fillFn func(seq uint64) (*state.VoteResult, error)) voteResultCache {
+ return voteResultCache{
+ lru: lru.New(maxCachedVoteResults),
+ fillFn: fillFn,
+ }
+}
+
+type voteResultCache struct {
+ mu sync.Mutex
+ lru *lru.Cache
+ fillFn func(seq uint64) (*state.VoteResult, error)
+ single singleflight.Group
+}
+
+func (vrc *voteResultCache) lookup(seq uint64) (*state.VoteResult, error) {
+ if voteResult, ok := vrc.get(seq); ok {
+ return voteResult, nil
+ }
+
+ seqStr := strconv.FormatUint(seq, 10)
+ voteResult, err := vrc.single.Do(seqStr, func() (interface{}, error) {
+ v, err := vrc.fillFn(seq)
+ if err != nil {
+ return nil, err
+ }
+
+ if v == nil {
+ return nil, fmt.Errorf("There are no vote result with given seq %s", seqStr)
+ }
+
+ vrc.add(v)
+ return v, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return voteResult.(*state.VoteResult), nil
+}
+
+func (vrc *voteResultCache) get(seq uint64) (*state.VoteResult, bool) {
+ vrc.mu.Lock()
+ voteResult, ok := vrc.lru.Get(seq)
+ vrc.mu.Unlock()
+ if voteResult == nil {
+ return nil, ok
+ }
+ return voteResult.(*state.VoteResult), ok
+}
+
+func (vrc *voteResultCache) add(voteResult *state.VoteResult) {
+ vrc.mu.Lock()
+ vrc.lru.Add(voteResult.Seq, voteResult)
+ vrc.mu.Unlock()
+}
// It satisfies the interface protocol.Store, and provides additional
// methods for querying current data.
type Store struct {
- db dbm.DB
- cache blockCache
+ db dbm.DB
+ bc blockCache
+ vrc voteResultCache
}
- 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(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 {
- return nil, nil // why return err 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
}
+// GetVoteResult return the vote result by given sequence
+func GetVoteResult(db dbm.DB, seq uint64) (*state.VoteResult, error) {
+ data := db.Get(calcVoteResultKey(seq))
+ if data == nil {
+ return nil, protocol.ErrNotFoundVoteResult
+ }
+
+ voteResult := new(state.VoteResult)
+ if err := json.Unmarshal(data, voteResult); err != nil {
+ return nil, errors.Wrap(err, "unmarshaling vote result")
+ }
+ return voteResult, nil
+}
+
// NewStore creates and returns a new Store object.
func NewStore(db dbm.DB) *Store {
- bc := 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)
++ bc := newBlockCache(fillBlockHeaderFn, fillBlockTxsFn)
+ vrc := newVoteResultCache(func(seq uint64) (*state.VoteResult, error) {
+ return GetVoteResult(db, seq)
+ })
return &Store{
- db: db,
- cache: cache,
+ db: db,
+ bc: bc,
+ vrc: vrc,
}
}
}
// BlockExist check if the block is stored in disk
- func (s *Store) BlockExist(hash *bc.Hash) bool {
- block, err := s.bc.lookup(hash)
- return err == nil && block != nil
+ func (s *Store) BlockExist(hash *bc.Hash, height uint64) bool {
- blockHeader, err := s.cache.lookupBlockHeader(hash, height)
++ blockHeader, err := s.bc.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.bc.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)
++ blockHeader, err := s.bc.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)
++ txs, err := s.bc.lookupBlockTxs(hash)
+ if err != nil {
+ return nil, err
+ }
+ return txs, nil
}
// GetTransactionsUtxo will return all the utxo that related to the input txs