github.com/DFWallet/tendermint-cosmos@v0.0.2/store/store.go (about)

     1  package store
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  
     7  	"github.com/gogo/protobuf/proto"
     8  	dbm "github.com/tendermint/tm-db"
     9  
    10  	tmsync "github.com/DFWallet/tendermint-cosmos/libs/sync"
    11  	tmstore "github.com/DFWallet/tendermint-cosmos/proto/tendermint/store"
    12  	tmproto "github.com/DFWallet/tendermint-cosmos/proto/tendermint/types"
    13  	"github.com/DFWallet/tendermint-cosmos/types"
    14  )
    15  
    16  /*
    17  BlockStore is a simple low level store for blocks.
    18  
    19  There are three types of information stored:
    20   - BlockMeta:   Meta information about each block
    21   - Block part:  Parts of each block, aggregated w/ PartSet
    22   - Commit:      The commit part of each block, for gossiping precommit votes
    23  
    24  Currently the precommit signatures are duplicated in the Block parts as
    25  well as the Commit.  In the future this may change, perhaps by moving
    26  the Commit data outside the Block. (TODO)
    27  
    28  The store can be assumed to contain all contiguous blocks between base and height (inclusive).
    29  
    30  // NOTE: BlockStore methods will panic if they encounter errors
    31  // deserializing loaded data, indicating probable corruption on disk.
    32  */
    33  type BlockStore struct {
    34  	db dbm.DB
    35  
    36  	// mtx guards access to the struct fields listed below it. We rely on the database to enforce
    37  	// fine-grained concurrency control for its data, and thus this mutex does not apply to
    38  	// database contents. The only reason for keeping these fields in the struct is that the data
    39  	// can't efficiently be queried from the database since the key encoding we use is not
    40  	// lexicographically ordered (see https://github.com/DFWallet/tendermint-cosmos/issues/4567).
    41  	mtx    tmsync.RWMutex
    42  	base   int64
    43  	height int64
    44  }
    45  
    46  // NewBlockStore returns a new BlockStore with the given DB,
    47  // initialized to the last height that was committed to the DB.
    48  func NewBlockStore(db dbm.DB) *BlockStore {
    49  	bs := LoadBlockStoreState(db)
    50  	return &BlockStore{
    51  		base:   bs.Base,
    52  		height: bs.Height,
    53  		db:     db,
    54  	}
    55  }
    56  
    57  // Base returns the first known contiguous block height, or 0 for empty block stores.
    58  func (bs *BlockStore) Base() int64 {
    59  	bs.mtx.RLock()
    60  	defer bs.mtx.RUnlock()
    61  	return bs.base
    62  }
    63  
    64  // Height returns the last known contiguous block height, or 0 for empty block stores.
    65  func (bs *BlockStore) Height() int64 {
    66  	bs.mtx.RLock()
    67  	defer bs.mtx.RUnlock()
    68  	return bs.height
    69  }
    70  
    71  // Size returns the number of blocks in the block store.
    72  func (bs *BlockStore) Size() int64 {
    73  	bs.mtx.RLock()
    74  	defer bs.mtx.RUnlock()
    75  	if bs.height == 0 {
    76  		return 0
    77  	}
    78  	return bs.height - bs.base + 1
    79  }
    80  
    81  // LoadBase atomically loads the base block meta, or returns nil if no base is found.
    82  func (bs *BlockStore) LoadBaseMeta() *types.BlockMeta {
    83  	bs.mtx.RLock()
    84  	defer bs.mtx.RUnlock()
    85  	if bs.base == 0 {
    86  		return nil
    87  	}
    88  	return bs.LoadBlockMeta(bs.base)
    89  }
    90  
    91  // LoadBlock returns the block with the given height.
    92  // If no block is found for that height, it returns nil.
    93  func (bs *BlockStore) LoadBlock(height int64) *types.Block {
    94  	var blockMeta = bs.LoadBlockMeta(height)
    95  	if blockMeta == nil {
    96  		return nil
    97  	}
    98  
    99  	pbb := new(tmproto.Block)
   100  	buf := []byte{}
   101  	for i := 0; i < int(blockMeta.BlockID.PartSetHeader.Total); i++ {
   102  		part := bs.LoadBlockPart(height, i)
   103  		// If the part is missing (e.g. since it has been deleted after we
   104  		// loaded the block meta) we consider the whole block to be missing.
   105  		if part == nil {
   106  			return nil
   107  		}
   108  		buf = append(buf, part.Bytes...)
   109  	}
   110  	err := proto.Unmarshal(buf, pbb)
   111  	if err != nil {
   112  		// NOTE: The existence of meta should imply the existence of the
   113  		// block. So, make sure meta is only saved after blocks are saved.
   114  		panic(fmt.Sprintf("Error reading block: %v", err))
   115  	}
   116  
   117  	block, err := types.BlockFromProto(pbb)
   118  	if err != nil {
   119  		panic(fmt.Errorf("error from proto block: %w", err))
   120  	}
   121  
   122  	return block
   123  }
   124  
   125  // LoadBlockByHash returns the block with the given hash.
   126  // If no block is found for that hash, it returns nil.
   127  // Panics if it fails to parse height associated with the given hash.
   128  func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block {
   129  	bz, err := bs.db.Get(calcBlockHashKey(hash))
   130  	if err != nil {
   131  		panic(err)
   132  	}
   133  	if len(bz) == 0 {
   134  		return nil
   135  	}
   136  
   137  	s := string(bz)
   138  	height, err := strconv.ParseInt(s, 10, 64)
   139  
   140  	if err != nil {
   141  		panic(fmt.Sprintf("failed to extract height from %s: %v", s, err))
   142  	}
   143  	return bs.LoadBlock(height)
   144  }
   145  
   146  // LoadBlockPart returns the Part at the given index
   147  // from the block at the given height.
   148  // If no part is found for the given height and index, it returns nil.
   149  func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part {
   150  	var pbpart = new(tmproto.Part)
   151  
   152  	bz, err := bs.db.Get(calcBlockPartKey(height, index))
   153  	if err != nil {
   154  		panic(err)
   155  	}
   156  	if len(bz) == 0 {
   157  		return nil
   158  	}
   159  
   160  	err = proto.Unmarshal(bz, pbpart)
   161  	if err != nil {
   162  		panic(fmt.Errorf("unmarshal to tmproto.Part failed: %w", err))
   163  	}
   164  	part, err := types.PartFromProto(pbpart)
   165  	if err != nil {
   166  		panic(fmt.Sprintf("Error reading block part: %v", err))
   167  	}
   168  
   169  	return part
   170  }
   171  
   172  // LoadBlockMeta returns the BlockMeta for the given height.
   173  // If no block is found for the given height, it returns nil.
   174  func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
   175  	var pbbm = new(tmproto.BlockMeta)
   176  	bz, err := bs.db.Get(calcBlockMetaKey(height))
   177  
   178  	if err != nil {
   179  		panic(err)
   180  	}
   181  
   182  	if len(bz) == 0 {
   183  		return nil
   184  	}
   185  
   186  	err = proto.Unmarshal(bz, pbbm)
   187  	if err != nil {
   188  		panic(fmt.Errorf("unmarshal to tmproto.BlockMeta: %w", err))
   189  	}
   190  
   191  	blockMeta, err := types.BlockMetaFromProto(pbbm)
   192  	if err != nil {
   193  		panic(fmt.Errorf("error from proto blockMeta: %w", err))
   194  	}
   195  
   196  	return blockMeta
   197  }
   198  
   199  // LoadBlockCommit returns the Commit for the given height.
   200  // This commit consists of the +2/3 and other Precommit-votes for block at `height`,
   201  // and it comes from the block.LastCommit for `height+1`.
   202  // If no commit is found for the given height, it returns nil.
   203  func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit {
   204  	var pbc = new(tmproto.Commit)
   205  	bz, err := bs.db.Get(calcBlockCommitKey(height))
   206  	if err != nil {
   207  		panic(err)
   208  	}
   209  	if len(bz) == 0 {
   210  		return nil
   211  	}
   212  	err = proto.Unmarshal(bz, pbc)
   213  	if err != nil {
   214  		panic(fmt.Errorf("error reading block commit: %w", err))
   215  	}
   216  	commit, err := types.CommitFromProto(pbc)
   217  	if err != nil {
   218  		panic(fmt.Sprintf("Error reading block commit: %v", err))
   219  	}
   220  	return commit
   221  }
   222  
   223  // LoadSeenCommit returns the locally seen Commit for the given height.
   224  // This is useful when we've seen a commit, but there has not yet been
   225  // a new block at `height + 1` that includes this commit in its block.LastCommit.
   226  func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit {
   227  	var pbc = new(tmproto.Commit)
   228  	bz, err := bs.db.Get(calcSeenCommitKey(height))
   229  	if err != nil {
   230  		panic(err)
   231  	}
   232  	if len(bz) == 0 {
   233  		return nil
   234  	}
   235  	err = proto.Unmarshal(bz, pbc)
   236  	if err != nil {
   237  		panic(fmt.Sprintf("error reading block seen commit: %v", err))
   238  	}
   239  
   240  	commit, err := types.CommitFromProto(pbc)
   241  	if err != nil {
   242  		panic(fmt.Errorf("error from proto commit: %w", err))
   243  	}
   244  	return commit
   245  }
   246  
   247  // PruneBlocks removes block up to (but not including) a height. It returns number of blocks pruned.
   248  func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) {
   249  	if height <= 0 {
   250  		return 0, fmt.Errorf("height must be greater than 0")
   251  	}
   252  	bs.mtx.RLock()
   253  	if height > bs.height {
   254  		bs.mtx.RUnlock()
   255  		return 0, fmt.Errorf("cannot prune beyond the latest height %v", bs.height)
   256  	}
   257  	base := bs.base
   258  	bs.mtx.RUnlock()
   259  	if height < base {
   260  		return 0, fmt.Errorf("cannot prune to height %v, it is lower than base height %v",
   261  			height, base)
   262  	}
   263  
   264  	pruned := uint64(0)
   265  	batch := bs.db.NewBatch()
   266  	defer batch.Close()
   267  	flush := func(batch dbm.Batch, base int64) error {
   268  		// We can't trust batches to be atomic, so update base first to make sure noone
   269  		// tries to access missing blocks.
   270  		bs.mtx.Lock()
   271  		bs.base = base
   272  		bs.mtx.Unlock()
   273  		bs.saveState()
   274  
   275  		err := batch.WriteSync()
   276  		if err != nil {
   277  			return fmt.Errorf("failed to prune up to height %v: %w", base, err)
   278  		}
   279  		batch.Close()
   280  		return nil
   281  	}
   282  
   283  	for h := base; h < height; h++ {
   284  		meta := bs.LoadBlockMeta(h)
   285  		if meta == nil { // assume already deleted
   286  			continue
   287  		}
   288  		if err := batch.Delete(calcBlockMetaKey(h)); err != nil {
   289  			return 0, err
   290  		}
   291  		if err := batch.Delete(calcBlockHashKey(meta.BlockID.Hash)); err != nil {
   292  			return 0, err
   293  		}
   294  		if err := batch.Delete(calcBlockCommitKey(h)); err != nil {
   295  			return 0, err
   296  		}
   297  		if err := batch.Delete(calcSeenCommitKey(h)); err != nil {
   298  			return 0, err
   299  		}
   300  		for p := 0; p < int(meta.BlockID.PartSetHeader.Total); p++ {
   301  			if err := batch.Delete(calcBlockPartKey(h, p)); err != nil {
   302  				return 0, err
   303  			}
   304  		}
   305  		pruned++
   306  
   307  		// flush every 1000 blocks to avoid batches becoming too large
   308  		if pruned%1000 == 0 && pruned > 0 {
   309  			err := flush(batch, h)
   310  			if err != nil {
   311  				return 0, err
   312  			}
   313  			batch = bs.db.NewBatch()
   314  			defer batch.Close()
   315  		}
   316  	}
   317  
   318  	err := flush(batch, height)
   319  	if err != nil {
   320  		return 0, err
   321  	}
   322  	return pruned, nil
   323  }
   324  
   325  // SaveBlock persists the given block, blockParts, and seenCommit to the underlying db.
   326  // blockParts: Must be parts of the block
   327  // seenCommit: The +2/3 precommits that were seen which committed at height.
   328  //             If all the nodes restart after committing a block,
   329  //             we need this to reload the precommits to catch-up nodes to the
   330  //             most recent height.  Otherwise they'd stall at H-1.
   331  func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
   332  	if block == nil {
   333  		panic("BlockStore can only save a non-nil block")
   334  	}
   335  
   336  	height := block.Height
   337  	hash := block.Hash()
   338  
   339  	if g, w := height, bs.Height()+1; bs.Base() > 0 && g != w {
   340  		panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g))
   341  	}
   342  	if !blockParts.IsComplete() {
   343  		panic("BlockStore can only save complete block part sets")
   344  	}
   345  
   346  	// Save block parts. This must be done before the block meta, since callers
   347  	// typically load the block meta first as an indication that the block exists
   348  	// and then go on to load block parts - we must make sure the block is
   349  	// complete as soon as the block meta is written.
   350  	for i := 0; i < int(blockParts.Total()); i++ {
   351  		part := blockParts.GetPart(i)
   352  		bs.saveBlockPart(height, i, part)
   353  	}
   354  
   355  	// Save block meta
   356  	blockMeta := types.NewBlockMeta(block, blockParts)
   357  	pbm := blockMeta.ToProto()
   358  	if pbm == nil {
   359  		panic("nil blockmeta")
   360  	}
   361  	metaBytes := mustEncode(pbm)
   362  	if err := bs.db.Set(calcBlockMetaKey(height), metaBytes); err != nil {
   363  		panic(err)
   364  	}
   365  	if err := bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil {
   366  		panic(err)
   367  	}
   368  
   369  	// Save block commit (duplicate and separate from the Block)
   370  	pbc := block.LastCommit.ToProto()
   371  	blockCommitBytes := mustEncode(pbc)
   372  	if err := bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes); err != nil {
   373  		panic(err)
   374  	}
   375  
   376  	// Save seen commit (seen +2/3 precommits for block)
   377  	// NOTE: we can delete this at a later height
   378  	pbsc := seenCommit.ToProto()
   379  	seenCommitBytes := mustEncode(pbsc)
   380  	if err := bs.db.Set(calcSeenCommitKey(height), seenCommitBytes); err != nil {
   381  		panic(err)
   382  	}
   383  
   384  	// Done!
   385  	bs.mtx.Lock()
   386  	bs.height = height
   387  	if bs.base == 0 {
   388  		bs.base = height
   389  	}
   390  	bs.mtx.Unlock()
   391  
   392  	// Save new BlockStoreState descriptor. This also flushes the database.
   393  	bs.saveState()
   394  }
   395  
   396  func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) {
   397  	pbp, err := part.ToProto()
   398  	if err != nil {
   399  		panic(fmt.Errorf("unable to make part into proto: %w", err))
   400  	}
   401  	partBytes := mustEncode(pbp)
   402  	if err := bs.db.Set(calcBlockPartKey(height, index), partBytes); err != nil {
   403  		panic(err)
   404  	}
   405  }
   406  
   407  func (bs *BlockStore) saveState() {
   408  	bs.mtx.RLock()
   409  	bss := tmstore.BlockStoreState{
   410  		Base:   bs.base,
   411  		Height: bs.height,
   412  	}
   413  	bs.mtx.RUnlock()
   414  	SaveBlockStoreState(&bss, bs.db)
   415  }
   416  
   417  // SaveSeenCommit saves a seen commit, used by e.g. the state sync reactor when bootstrapping node.
   418  func (bs *BlockStore) SaveSeenCommit(height int64, seenCommit *types.Commit) error {
   419  	pbc := seenCommit.ToProto()
   420  	seenCommitBytes, err := proto.Marshal(pbc)
   421  	if err != nil {
   422  		return fmt.Errorf("unable to marshal commit: %w", err)
   423  	}
   424  	return bs.db.Set(calcSeenCommitKey(height), seenCommitBytes)
   425  }
   426  
   427  //-----------------------------------------------------------------------------
   428  
   429  func calcBlockMetaKey(height int64) []byte {
   430  	return []byte(fmt.Sprintf("H:%v", height))
   431  }
   432  
   433  func calcBlockPartKey(height int64, partIndex int) []byte {
   434  	return []byte(fmt.Sprintf("P:%v:%v", height, partIndex))
   435  }
   436  
   437  func calcBlockCommitKey(height int64) []byte {
   438  	return []byte(fmt.Sprintf("C:%v", height))
   439  }
   440  
   441  func calcSeenCommitKey(height int64) []byte {
   442  	return []byte(fmt.Sprintf("SC:%v", height))
   443  }
   444  
   445  func calcBlockHashKey(hash []byte) []byte {
   446  	return []byte(fmt.Sprintf("BH:%x", hash))
   447  }
   448  
   449  //-----------------------------------------------------------------------------
   450  
   451  var blockStoreKey = []byte("blockStore")
   452  
   453  // SaveBlockStoreState persists the blockStore state to the database.
   454  func SaveBlockStoreState(bsj *tmstore.BlockStoreState, db dbm.DB) {
   455  	bytes, err := proto.Marshal(bsj)
   456  	if err != nil {
   457  		panic(fmt.Sprintf("Could not marshal state bytes: %v", err))
   458  	}
   459  	if err := db.SetSync(blockStoreKey, bytes); err != nil {
   460  		panic(err)
   461  	}
   462  }
   463  
   464  // LoadBlockStoreState returns the BlockStoreState as loaded from disk.
   465  // If no BlockStoreState was previously persisted, it returns the zero value.
   466  func LoadBlockStoreState(db dbm.DB) tmstore.BlockStoreState {
   467  	bytes, err := db.Get(blockStoreKey)
   468  	if err != nil {
   469  		panic(err)
   470  	}
   471  
   472  	if len(bytes) == 0 {
   473  		return tmstore.BlockStoreState{
   474  			Base:   0,
   475  			Height: 0,
   476  		}
   477  	}
   478  
   479  	var bsj tmstore.BlockStoreState
   480  	if err := proto.Unmarshal(bytes, &bsj); err != nil {
   481  		panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes))
   482  	}
   483  
   484  	// Backwards compatibility with persisted data from before Base existed.
   485  	if bsj.Height > 0 && bsj.Base == 0 {
   486  		bsj.Base = 1
   487  	}
   488  	return bsj
   489  }
   490  
   491  // mustEncode proto encodes a proto.message and panics if fails
   492  func mustEncode(pb proto.Message) []byte {
   493  	bz, err := proto.Marshal(pb)
   494  	if err != nil {
   495  		panic(fmt.Errorf("unable to marshal: %w", err))
   496  	}
   497  	return bz
   498  }