6 log "github.com/sirupsen/logrus"
7 "github.com/tendermint/tmlibs/common"
9 dbm "github.com/vapor/database/db"
10 "github.com/vapor/database/orm"
11 "github.com/vapor/database/storage"
12 "github.com/vapor/errors"
13 "github.com/vapor/protocol"
14 "github.com/vapor/protocol/bc"
15 "github.com/vapor/protocol/bc/types"
16 "github.com/vapor/protocol/state"
19 const logModuleSQL = "SQLdb"
21 func loadBlockSQLStoreStateJSON(db dbm.SQLDB) *protocol.BlockStoreState {
22 bsj := orm.BlockStoreState{
23 StoreKey: string(blockStoreKey),
27 if err := SQLDB.Where(&bsj).First(&bsj).Error; err != nil {
32 if err := hash.UnmarshalText([]byte(bsj.Hash)); err != nil {
33 common.PanicCrisis(common.Fmt("Could not unmarshalText bytes: %s", bsj.Hash))
36 return &protocol.BlockStoreState{Height: bsj.Height, Hash: hash}
39 // A SQLStore encapsulates storage for blockchain validation.
40 // It satisfies the interface protocol.Store, and provides additional
41 // methods for querying current data.
42 type SQLStore struct {
47 // GetBlockFromSQLDB return the block by given hash
48 func GetBlockFromSQLDB(db dbm.SQLDB, hash *bc.Hash) *types.Block {
49 blockHeader := &orm.BlockHeader{BlockHash: hash.String()}
50 if err := db.Db().Where(blockHeader).Find(blockHeader).Error; err != nil {
54 txs := []*orm.Transaction{}
55 if err := db.Db().Where(&orm.Transaction{BlockHeaderID: blockHeader.ID}).Order("tx_index asc").Find(&txs).Error; err != nil {
59 block, err := toBlock(blockHeader, txs)
67 func toBlock(header *orm.BlockHeader, txs []*orm.Transaction) (*types.Block, error) {
69 blockHeader, err := header.ToTypesBlockHeader()
74 var transactions []*types.Tx
76 for _, tx := range txs {
77 transaction, err := tx.UnmarshalText()
81 transactions = append(transactions, transaction)
84 block := &types.Block{
85 BlockHeader: *blockHeader,
86 Transactions: transactions,
92 // NewSQLStore creates and returns a new Store object.
93 func NewSQLStore(db dbm.SQLDB) *SQLStore {
94 cache := newBlockCache(func(hash *bc.Hash) *types.Block {
95 return GetBlockFromSQLDB(db, hash)
103 // GetUtxo will search the utxo in db
104 func (s *SQLStore) GetUtxo(hash *bc.Hash) (*storage.UtxoEntry, error) {
105 return getUtxoFromSQLDB(s.db, hash)
108 // BlockExist check if the block is stored in disk
109 func (s *SQLStore) BlockExist(hash *bc.Hash) bool {
110 block, err := s.cache.lookup(hash)
111 return err == nil && block != nil
114 // GetBlock return the block by given hash
115 func (s *SQLStore) GetBlock(hash *bc.Hash) (*types.Block, error) {
116 return s.cache.lookup(hash)
119 // GetTransactionsUtxo will return all the utxo that related to the input txs
120 func (s *SQLStore) GetTransactionsUtxo(view *state.UtxoViewpoint, txs []*bc.Tx) error {
121 return getTransactionsUtxoFromSQLDB(s.db, view, txs)
124 // GetTransactionStatus will return the utxo that related to the block hash
125 func (s *SQLStore) GetTransactionStatus(hash *bc.Hash) (*bc.TransactionStatus, error) {
126 ts := &bc.TransactionStatus{}
127 query := s.db.Db().Model(&orm.Transaction{}).Joins("join block_headers on block_headers.id = transactions.block_header_id").Where("block_headers.block_hash = ?", hash.String())
128 rows, err := query.Select("transactions.status_fail, block_headers.version").Order("transactions.tx_index asc").Rows()
138 if err := rows.Scan(&statusFail, &version); err != nil {
143 ts.VerifyStatus = append(ts.VerifyStatus, &bc.TxVerifyResult{StatusFail: statusFail})
149 // GetStoreStatus return the BlockStoreStateJSON
150 func (s *SQLStore) GetStoreStatus() *protocol.BlockStoreState {
151 return loadBlockSQLStoreStateJSON(s.db)
154 func (s *SQLStore) LoadBlockIndex(stateBestHeight uint64) (*state.BlockIndex, error) {
155 startTime := time.Now()
156 blockIndex := state.NewBlockIndex()
158 var lastNode *state.BlockNode
159 rows, err := s.db.Db().Model(&orm.BlockHeader{}).Order("height").Rows()
166 header := orm.BlockHeader{}
167 if err := rows.Scan(&header.ID, &header.BlockHash, &header.Height, &header.Version, &header.PreviousBlockHash, &header.Timestamp, &header.TransactionsMerkleRoot, &header.TransactionStatusHash); err != nil {
170 if header.Height > stateBestHeight {
174 typesBlockHeader, err := header.ToTypesBlockHeader()
179 previousBlockHash := typesBlockHeader.PreviousBlockHash
181 var parent *state.BlockNode
182 if lastNode == nil || lastNode.Hash == previousBlockHash {
185 parent = blockIndex.GetNode(&previousBlockHash)
188 node, err := state.NewBlockNode(typesBlockHeader, parent)
193 blockIndex.AddNode(node)
197 log.WithFields(log.Fields{
199 "height": stateBestHeight,
200 "duration": time.Since(startTime),
201 }).Debug("initialize load history block index from database")
202 return blockIndex, nil
205 // SaveBlock persists a new block in the protocol.
206 func (s *SQLStore) SaveBlock(block *types.Block, ts *bc.TransactionStatus) error {
207 startTime := time.Now()
209 blockHash := block.Hash()
213 // Save block header details
214 blockHeader := &orm.BlockHeader{
215 Height: block.Height,
216 BlockHash: blockHash.String(),
217 Version: block.Version,
218 PreviousBlockHash: block.PreviousBlockHash.String(),
219 Timestamp: block.Timestamp,
220 TransactionsMerkleRoot: block.TransactionsMerkleRoot.String(),
221 TransactionStatusHash: block.TransactionStatusHash.String(),
224 if err := tx.Create(blockHeader).Error; err != nil {
230 for index, transaction := range block.Transactions {
231 rawTx, err := transaction.MarshalText()
235 ormTransaction := &orm.Transaction{
236 BlockHeaderID: blockHeader.ID,
237 TxIndex: uint64(index),
238 RawData: string(rawTx),
239 StatusFail: ts.VerifyStatus[index].StatusFail,
241 if err := tx.Create(ormTransaction).Error; err != nil {
247 if err := tx.Commit().Error; err != nil {
249 return errors.Wrap(err, "commit transaction")
252 log.WithFields(log.Fields{
254 "height": block.Height,
255 "hash": blockHash.String(),
256 "duration": time.Since(startTime),
257 }).Info("block saved on disk")
261 // SaveChainStatus save the core's newest status && delete old status
262 func (s *SQLStore) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint) error {
266 if err := saveUtxoViewToSQLDB(tx, view); err != nil {
271 state := &orm.BlockStoreState{
272 StoreKey: string(blockStoreKey),
274 Hash: node.Hash.String(),
277 db := tx.Model(&orm.BlockStoreState{}).Update(state)
279 if err := db.Error; err != nil {
284 if db.RowsAffected == 0 {
285 if err := tx.Save(state).Error; err != nil {
291 if err := tx.Commit().Error; err != nil {
293 return errors.Wrap(err, "commit transaction")
299 func (s *SQLStore) IsWithdrawSpent(hash *bc.Hash) bool {
300 data := &orm.ClaimTxState{
301 TxHash: hash.String(),
304 if err := s.db.Db().Where(data).First(data).Count(&count).Error; err != nil {
311 func (s *SQLStore) SetWithdrawSpent(hash *bc.Hash) error {
312 data := &orm.ClaimTxState{
313 TxHash: hash.String(),
315 if err := s.db.Db().Create(data).Error; err != nil {