OSDN Git Service

get BlockNode from database (#188)
[bytom/vapor.git] / database / store.go
index c037b32..ea947a1 100644 (file)
@@ -3,11 +3,11 @@ package database
 import (
        "encoding/binary"
        "encoding/json"
+       "fmt"
        "time"
 
        "github.com/golang/protobuf/proto"
        log "github.com/sirupsen/logrus"
-       "github.com/tendermint/tmlibs/common"
 
        dbm "github.com/vapor/database/leveldb"
        "github.com/vapor/database/storage"
@@ -18,14 +18,31 @@ import (
        "github.com/vapor/protocol/state"
 )
 
-const logModule = "leveldb"
+const (
+       // log module
+       logModule = "leveldb"
+       // the byte of colon(:)
+       colon = byte(0x3a)
+)
+
+const (
+       blockStore byte = iota
+       blockHashes
+       blockHeader
+       blockTransactons
+       mainChainIndex
+       txStatus
+       voteResult
+)
 
 var (
-       blockStoreKey     = []byte("blockStore")
-       blockPrefix       = []byte("B:")
-       blockHeaderPrefix = []byte("BH:")
-       txStatusPrefix    = []byte("BTS:")
-       voteResultPrefix  = []byte("VR:")
+       blockStoreKey          = []byte{blockStore}
+       blockHashesPrefix      = []byte{blockHashes, colon}
+       blockHeaderPrefix      = []byte{blockHeader, colon}
+       blockTransactonsPrefix = []byte{blockTransactons, colon}
+       mainChainIndexPrefix   = []byte{mainChainIndex, colon}
+       txStatusPrefix         = []byte{txStatus, colon}
+       voteResultPrefix       = []byte{voteResult, colon}
 )
 
 func loadBlockStoreStateJSON(db dbm.DB) *protocol.BlockStoreState {
@@ -33,9 +50,10 @@ func loadBlockStoreStateJSON(db dbm.DB) *protocol.BlockStoreState {
        if bytes == nil {
                return nil
        }
+
        bsj := &protocol.BlockStoreState{}
        if err := json.Unmarshal(bytes, bsj); err != nil {
-               common.PanicCrisis(common.Fmt("Could not unmarshal bytes: %X", bytes))
+               log.WithField("err", err).Panic("fail on unmarshal BlockStoreStateJSON")
        }
        return bsj
 }
@@ -45,18 +63,27 @@ func loadBlockStoreStateJSON(db dbm.DB) *protocol.BlockStoreState {
 // methods for querying current data.
 type Store struct {
        db    dbm.DB
-       cache blockCache
+       cache cache
 }
 
-func calcBlockKey(hash *bc.Hash) []byte {
-       return append(blockPrefix, hash.Bytes()...)
+func calcMainChainIndexPrefix(height uint64) []byte {
+       buf := [8]byte{}
+       binary.BigEndian.PutUint64(buf[:], height)
+       return append(mainChainIndexPrefix, buf[:]...)
 }
 
-func calcBlockHeaderKey(height uint64, hash *bc.Hash) []byte {
+func calcBlockHashesPrefix(height uint64) []byte {
        buf := [8]byte{}
        binary.BigEndian.PutUint64(buf[:], height)
-       key := append(blockHeaderPrefix, buf[:]...)
-       return append(key, hash.Bytes()...)
+       return append(blockHashesPrefix, buf[:]...)
+}
+
+func calcBlockHeaderKey(hash *bc.Hash) []byte {
+       return append(blockHeaderPrefix, hash.Bytes()...)
+}
+
+func calcBlockTransactionsKey(hash *bc.Hash) []byte {
+       return append(blockTransactonsPrefix, hash.Bytes()...)
 }
 
 func calcTxStatusKey(hash *bc.Hash) []byte {
@@ -69,43 +96,151 @@ func calcVoteResultKey(seq uint64) []byte {
        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
+// GetBlockHeader return the block header by given hash
+func GetBlockHeader(db dbm.DB, hash *bc.Hash) (*types.BlockHeader, error) {
+       binaryBlockHeader := db.Get(calcBlockHeaderKey(hash))
+       if binaryBlockHeader == nil {
+               return nil, fmt.Errorf("There are no blockHeader with given hash %s", hash.String())
+       }
+
+       blockHeader := &types.BlockHeader{}
+       if err := blockHeader.UnmarshalText(binaryBlockHeader); err != nil {
+               return nil, err
+       }
+       return blockHeader, nil
+}
+
+// GetBlockTransactions return the block transactions by given hash
+func GetBlockTransactions(db dbm.DB, hash *bc.Hash) ([]*types.Tx, error) {
+       binaryBlockTxs := db.Get(calcBlockTransactionsKey(hash))
+       if binaryBlockTxs == nil {
+               return nil, fmt.Errorf("There are no block transactions with given hash %s", hash.String())
        }
 
        block := &types.Block{}
-       err := block.UnmarshalText(bytez)
-       return block, err
+       if err := block.UnmarshalText(binaryBlockTxs); err != nil {
+               return nil, err
+       }
+       return block.Transactions, nil
+}
+
+// GetBlockHashesByHeight return block hashes by given height
+func GetBlockHashesByHeight(db dbm.DB, height uint64) ([]*bc.Hash, error) {
+       binaryHashes := db.Get(calcBlockHashesPrefix(height))
+       if binaryHashes == nil {
+               return []*bc.Hash{}, nil
+       }
+
+       hashes := []*bc.Hash{}
+       if err := json.Unmarshal(binaryHashes, &hashes); err != nil {
+               return nil, err
+       }
+       return hashes, nil
+}
+
+// GetMainChainHash return BlockHash by given height
+func GetMainChainHash(db dbm.DB, height uint64) (*bc.Hash, error) {
+       binaryHash := db.Get(calcMainChainIndexPrefix(height))
+       if binaryHash == nil {
+               return nil, fmt.Errorf("There are no BlockHash with given height %d", height)
+       }
+
+       hash := &bc.Hash{}
+       if err := hash.UnmarshalText(binaryHash); err != nil {
+               return nil, err
+       }
+       return hash, 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 {
-       cache := newBlockCache(func(hash *bc.Hash) (*types.Block, error) {
-               return GetBlock(db, hash)
-       })
+       fillBlockHeaderFn := func(hash *bc.Hash) (*types.BlockHeader, error) {
+               return GetBlockHeader(db, hash)
+       }
+       fillBlockTxsFn := func(hash *bc.Hash) ([]*types.Tx, error) {
+               return GetBlockTransactions(db, hash)
+       }
+
+       fillBlockHashesFn := func(height uint64) ([]*bc.Hash, error) {
+               return GetBlockHashesByHeight(db, height)
+       }
+
+       fillMainChainHashFn := func(height uint64) (*bc.Hash, error) {
+               return GetMainChainHash(db, height)
+       }
+
+       fillVoteResultFn := func(seq uint64) (*state.VoteResult, error) {
+               return GetVoteResult(db, seq)
+       }
+
+       cache := newCache(fillBlockHeaderFn, fillBlockTxsFn, fillBlockHashesFn, fillMainChainHashFn, fillVoteResultFn)
        return &Store{
                db:    db,
                cache: cache,
        }
 }
 
-// GetUtxo will search the utxo in db
-func (s *Store) GetUtxo(hash *bc.Hash) (*storage.UtxoEntry, error) {
-       return getUtxo(s.db, hash)
-}
-
 // 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
+       _, err := s.cache.lookupBlockHeader(hash)
+       return err == nil
 }
 
 // GetBlock return the block by given hash
 func (s *Store) GetBlock(hash *bc.Hash) (*types.Block, error) {
-       return s.cache.lookup(hash)
+       blockHeader, err := s.GetBlockHeader(hash)
+       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) (*types.BlockHeader, error) {
+       return s.cache.lookupBlockHeader(hash)
+}
+
+// GetBlockTransactions return the Block transactions by given hash
+func (s *Store) GetBlockTransactions(hash *bc.Hash) ([]*types.Tx, error) {
+       return s.cache.lookupBlockTxs(hash)
+}
+
+// GetBlockHashesByHeight return the block hash by the specified height
+func (s *Store) GetBlockHashesByHeight(height uint64) ([]*bc.Hash, error) {
+       return s.cache.lookupBlockHashesByHeight(height)
+}
+
+// GetMainChainHash return the block hash by the specified height
+func (s *Store) GetMainChainHash(height uint64) (*bc.Hash, error) {
+       return s.cache.lookupMainChainHash(height)
+}
+
+// GetStoreStatus return the BlockStoreStateJSON
+func (s *Store) GetStoreStatus() *protocol.BlockStoreState {
+       return loadBlockStoreStateJSON(s.db)
 }
 
 // GetTransactionsUtxo will return all the utxo that related to the input txs
@@ -127,93 +262,55 @@ func (s *Store) GetTransactionStatus(hash *bc.Hash) (*bc.TransactionStatus, erro
        return ts, nil
 }
 
-// GetStoreStatus return the BlockStoreStateJSON
-func (s *Store) GetStoreStatus() *protocol.BlockStoreState {
-       return loadBlockStoreStateJSON(s.db)
+// GetUtxo will search the utxo in db
+func (s *Store) GetUtxo(hash *bc.Hash) (*storage.UtxoEntry, error) {
+       return getUtxo(s.db, hash)
 }
 
 // GetVoteResult retrive the voting result in specified vote sequence
 func (s *Store) GetVoteResult(seq uint64) (*state.VoteResult, error) {
-       data := s.db.Get(calcVoteResultKey(seq))
-       if data == nil {
-               return nil, errors.New("can't find the vote result by given sequence")
-       }
-
-       vr := &state.VoteResult{}
-       if err := json.Unmarshal(data, vr); err != nil {
-               return nil, errors.Wrap(err, "unmarshaling vote result")
-       }
-       return vr, nil
-}
-
-func (s *Store) LoadBlockIndex(stateBestHeight uint64) (*state.BlockIndex, error) {
-       startTime := time.Now()
-       blockIndex := state.NewBlockIndex()
-       bhIter := s.db.IteratorPrefix(blockHeaderPrefix)
-       defer bhIter.Release()
-
-       var lastNode *state.BlockNode
-       for bhIter.Next() {
-               bh := &types.BlockHeader{}
-               if err := bh.UnmarshalText(bhIter.Value()); err != nil {
-                       return nil, err
-               }
-
-               // If a block with a height greater than the best height of state is added to the index,
-               // It may cause a bug that the new block cant not be process properly.
-               if bh.Height > stateBestHeight {
-                       break
-               }
-
-               var parent *state.BlockNode
-               if lastNode == nil || lastNode.Hash == bh.PreviousBlockHash {
-                       parent = lastNode
-               } else {
-                       parent = blockIndex.GetNode(&bh.PreviousBlockHash)
-               }
-
-               node, err := state.NewBlockNode(bh, parent)
-               if err != nil {
-                       return nil, err
-               }
-
-               blockIndex.AddNode(node)
-               lastNode = node
-       }
-
-       log.WithFields(log.Fields{
-               "module":   logModule,
-               "height":   stateBestHeight,
-               "duration": time.Since(startTime),
-       }).Debug("initialize load history block index from database")
-       return blockIndex, nil
+       return s.cache.lookupVoteResult(seq)
 }
 
 // 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)
        if err != nil {
-               return errors.Wrap(err, "marshal block transaction status")
+               return errors.Wrap(err, "Marshal block transaction status")
        }
 
+       blockHashes := []*bc.Hash{}
+       hashes, err := s.GetBlockHashesByHeight(block.Height)
+       if err != nil {
+               return err
+       }
+       blockHashes = append(blockHashes, hashes...)
        blockHash := block.Hash()
+       blockHashes = append(blockHashes, &blockHash)
+       binaryBlockHashes, err := json.Marshal(blockHashes)
+       if err != nil {
+               return errors.Wrap(err, "Marshal block hashes")
+       }
+
        batch := s.db.NewBatch()
-       batch.Set(calcBlockKey(&blockHash), binaryBlock)
-       batch.Set(calcBlockHeaderKey(block.Height, &blockHash), binaryBlockHeader)
+       batch.Set(calcBlockHashesPrefix(block.Height), binaryBlockHashes)
+       batch.Set(calcBlockHeaderKey(&blockHash), binaryBlockHeader)
+       batch.Set(calcBlockTransactionsKey(&blockHash), binaryBlockTxs)
        batch.Set(calcTxStatusKey(&blockHash), binaryTxStatus)
        batch.Write()
 
+       s.cache.removeBlockHashes(block.Height)
        log.WithFields(log.Fields{
                "module":   logModule,
                "height":   block.Height,
@@ -223,30 +320,60 @@ func (s *Store) SaveBlock(block *types.Block, ts *bc.TransactionStatus) error {
        return nil
 }
 
+// SaveBlockHeader persists a new block header in the protocol.
+func (s *Store) SaveBlockHeader(blockHeader *types.BlockHeader) error {
+       binaryBlockHeader, err := blockHeader.MarshalText()
+       if err != nil {
+               return errors.Wrap(err, "Marshal block header")
+       }
+
+       blockHash := blockHeader.Hash()
+       s.db.Set(calcBlockHeaderKey(&blockHash), binaryBlockHeader)
+       s.cache.removeBlockHeader(blockHeader)
+       return nil
+}
+
 // SaveChainStatus save the core's newest status && delete old status
-func (s *Store) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint) error {
+func (s *Store) SaveChainStatus(blockHeader, irrBlockHeader *types.BlockHeader, mainBlockHeaders []*types.BlockHeader, view *state.UtxoViewpoint, voteResults []*state.VoteResult) error {
        batch := s.db.NewBatch()
        if err := saveUtxoView(batch, view); err != nil {
                return err
        }
 
-       bytes, err := json.Marshal(protocol.BlockStoreState{Height: node.Height, Hash: &node.Hash})
-       if err != nil {
-               return err
-       }
+       for _, vote := range voteResults {
+               bytes, err := json.Marshal(vote)
+               if err != nil {
+                       return err
+               }
 
-       batch.Set(blockStoreKey, bytes)
-       batch.Write()
-       return nil
-}
+               batch.Set(calcVoteResultKey(vote.Seq), bytes)
+               s.cache.removeVoteResult(vote)
+       }
 
-// SaveVoteResult update the voting results generated by each irreversible block
-func (s *Store) SaveVoteResult(vr *state.VoteResult) error {
-       bytes, err := json.Marshal(vr)
+       blockHash := blockHeader.Hash()
+       irrBlockHash := irrBlockHeader.Hash()
+       bytes, err := json.Marshal(protocol.BlockStoreState{
+               Height:             blockHeader.Height,
+               Hash:               &blockHash,
+               IrreversibleHeight: irrBlockHeader.Height,
+               IrreversibleHash:   &irrBlockHash,
+       })
        if err != nil {
                return err
        }
+       batch.Set(blockStoreKey, bytes)
+
+       // save main chain blockHeaders
+       for _, bh := range mainBlockHeaders {
+               blockHash := bh.Hash()
+               binaryBlockHash, err := blockHash.MarshalText()
+               if err != nil {
+                       return errors.Wrap(err, "Marshal block hash")
+               }
 
-       s.db.Set(calcVoteResultKey(vr.Seq), bytes)
+               batch.Set(calcMainChainIndexPrefix(bh.Height), binaryBlockHash)
+               s.cache.removeMainChainHash(bh.Height)
+       }
+       batch.Write()
        return nil
 }