github.com/vipernet-xyz/tendermint-core@v0.32.0/store/store.go (about)

     1  package store
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/pkg/errors"
    10  
    11  	db "github.com/tendermint/tm-db"
    12  	dbm "github.com/tendermint/tm-db"
    13  
    14  	"github.com/tendermint/tendermint/types"
    15  )
    16  
    17  /*
    18  BlockStore is a simple low level store for blocks.
    19  
    20  There are three types of information stored:
    21   - BlockMeta:   Meta information about each block
    22   - Block part:  Parts of each block, aggregated w/ PartSet
    23   - Commit:      The commit part of each block, for gossiping precommit votes
    24  
    25  Currently the precommit signatures are duplicated in the Block parts as
    26  well as the Commit.  In the future this may change, perhaps by moving
    27  the Commit data outside the Block. (TODO)
    28  
    29  The store can be assumed to contain all contiguous blocks between base and height (inclusive).
    30  
    31  // NOTE: BlockStore methods will panic if they encounter errors
    32  // deserializing loaded data, indicating probable corruption on disk.
    33  */
    34  type BlockStore struct {
    35  	db dbm.DB
    36  
    37  	mtx    sync.RWMutex
    38  	base   int64
    39  	height int64
    40  }
    41  
    42  // NewBlockStore returns a new BlockStore with the given DB,
    43  // initialized to the last height that was committed to the DB.
    44  func NewBlockStore(db dbm.DB) *BlockStore {
    45  	bsjson := LoadBlockStoreStateJSON(db)
    46  	return &BlockStore{
    47  		base:   bsjson.Base,
    48  		height: bsjson.Height,
    49  		db:     db,
    50  	}
    51  }
    52  
    53  // Base returns the first known contiguous block height, or 0 for empty block stores.
    54  func (bs *BlockStore) Base() int64 {
    55  	bs.mtx.RLock()
    56  	defer bs.mtx.RUnlock()
    57  	return bs.base
    58  }
    59  
    60  // Height returns the last known contiguous block height, or 0 for empty block stores.
    61  func (bs *BlockStore) Height() int64 {
    62  	bs.mtx.RLock()
    63  	defer bs.mtx.RUnlock()
    64  	return bs.height
    65  }
    66  
    67  // Size returns the number of blocks in the block store.
    68  func (bs *BlockStore) Size() int64 {
    69  	bs.mtx.RLock()
    70  	defer bs.mtx.RUnlock()
    71  	if bs.height == 0 {
    72  		return 0
    73  	}
    74  	return bs.height - bs.base + 1
    75  }
    76  
    77  // LoadBlock returns the block with the given height.
    78  // If no block is found for that height, it returns nil.
    79  func (bs *BlockStore) LoadBlock(height int64) *types.Block {
    80  	var blockMeta = bs.LoadBlockMeta(height)
    81  	if blockMeta == nil {
    82  		return nil
    83  	}
    84  
    85  	var block = new(types.Block)
    86  	buf := []byte{}
    87  	for i := 0; i < blockMeta.BlockID.PartsHeader.Total; i++ {
    88  		part := bs.LoadBlockPart(height, i)
    89  		buf = append(buf, part.Bytes...)
    90  	}
    91  	err := cdc.UnmarshalBinaryLengthPrefixed(buf, block)
    92  	if err != nil {
    93  		// NOTE: The existence of meta should imply the existence of the
    94  		// block. So, make sure meta is only saved after blocks are saved.
    95  		panic(errors.Wrap(err, "Error reading block"))
    96  	}
    97  	return block
    98  }
    99  
   100  // LoadBlockByHash returns the block with the given hash.
   101  // If no block is found for that hash, it returns nil.
   102  // Panics if it fails to parse height associated with the given hash.
   103  func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block {
   104  	bz, err := bs.db.Get(calcBlockHashKey(hash))
   105  	if err != nil {
   106  		panic(err)
   107  	}
   108  	if len(bz) == 0 {
   109  		return nil
   110  	}
   111  
   112  	s := string(bz)
   113  	height, err := strconv.ParseInt(s, 10, 64)
   114  
   115  	if err != nil {
   116  		panic(errors.Wrapf(err, "failed to extract height from %s", s))
   117  	}
   118  	return bs.LoadBlock(height)
   119  }
   120  
   121  // LoadBlockPart returns the Part at the given index
   122  // from the block at the given height.
   123  // If no part is found for the given height and index, it returns nil.
   124  func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part {
   125  	var part = new(types.Part)
   126  	bz, err := bs.db.Get(calcBlockPartKey(height, index))
   127  	if err != nil {
   128  		panic(err)
   129  	}
   130  	if len(bz) == 0 {
   131  		return nil
   132  	}
   133  	err = cdc.UnmarshalBinaryBare(bz, part)
   134  	if err != nil {
   135  		panic(errors.Wrap(err, "Error reading block part"))
   136  	}
   137  	return part
   138  }
   139  
   140  // LoadBlockMeta returns the BlockMeta for the given height.
   141  // If no block is found for the given height, it returns nil.
   142  func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
   143  	var blockMeta = new(types.BlockMeta)
   144  	bz, err := bs.db.Get(calcBlockMetaKey(height))
   145  	if err != nil {
   146  		panic(err)
   147  	}
   148  	if len(bz) == 0 {
   149  		return nil
   150  	}
   151  	err = cdc.UnmarshalBinaryBare(bz, blockMeta)
   152  	if err != nil {
   153  		panic(errors.Wrap(err, "Error reading block meta"))
   154  	}
   155  	return blockMeta
   156  }
   157  
   158  // LoadBlockCommit returns the Commit for the given height.
   159  // This commit consists of the +2/3 and other Precommit-votes for block at `height`,
   160  // and it comes from the block.LastCommit for `height+1`.
   161  // If no commit is found for the given height, it returns nil.
   162  func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit {
   163  	var commit = new(types.Commit)
   164  	bz, err := bs.db.Get(calcBlockCommitKey(height))
   165  	if err != nil {
   166  		panic(err)
   167  	}
   168  	if len(bz) == 0 {
   169  		return nil
   170  	}
   171  	err = cdc.UnmarshalBinaryBare(bz, commit)
   172  	if err != nil {
   173  		panic(errors.Wrap(err, "Error reading block commit"))
   174  	}
   175  	return commit
   176  }
   177  
   178  // LoadSeenCommit returns the locally seen Commit for the given height.
   179  // This is useful when we've seen a commit, but there has not yet been
   180  // a new block at `height + 1` that includes this commit in its block.LastCommit.
   181  func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit {
   182  	var commit = new(types.Commit)
   183  	bz, err := bs.db.Get(calcSeenCommitKey(height))
   184  	if err != nil {
   185  		panic(err)
   186  	}
   187  	if len(bz) == 0 {
   188  		return nil
   189  	}
   190  	err = cdc.UnmarshalBinaryBare(bz, commit)
   191  	if err != nil {
   192  		panic(errors.Wrap(err, "Error reading block seen commit"))
   193  	}
   194  	return commit
   195  }
   196  
   197  // PruneBlocks removes block up to (but not including) a height. It returns number of blocks pruned.
   198  func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) {
   199  	if height <= 0 {
   200  		return 0, fmt.Errorf("height must be greater than 0")
   201  	}
   202  	bs.mtx.RLock()
   203  	if height > bs.height {
   204  		bs.mtx.RUnlock()
   205  		return 0, fmt.Errorf("cannot prune beyond the latest height %v", bs.height)
   206  	}
   207  	base := bs.base
   208  	bs.mtx.RUnlock()
   209  	if height < base {
   210  		return 0, fmt.Errorf("cannot prune to height %v, it is lower than base height %v",
   211  			height, base)
   212  	}
   213  
   214  	pruned := uint64(0)
   215  	batch := bs.db.NewBatch()
   216  	defer batch.Close()
   217  	flush := func(batch db.Batch, base int64) error {
   218  		// We can't trust batches to be atomic, so update base first to make sure noone
   219  		// tries to access missing blocks.
   220  		bs.mtx.Lock()
   221  		bs.base = base
   222  		bs.mtx.Unlock()
   223  		bs.saveState()
   224  
   225  		err := batch.WriteSync()
   226  		if err != nil {
   227  			return fmt.Errorf("failed to prune up to height %v: %w", base, err)
   228  		}
   229  		batch.Close()
   230  		return nil
   231  	}
   232  
   233  	for h := base; h < height; h++ {
   234  		meta := bs.LoadBlockMeta(h)
   235  		if meta == nil { // assume already deleted
   236  			continue
   237  		}
   238  		batch.Delete(calcBlockMetaKey(h))
   239  		batch.Delete(calcBlockHashKey(meta.BlockID.Hash))
   240  		batch.Delete(calcBlockCommitKey(h))
   241  		batch.Delete(calcSeenCommitKey(h))
   242  		for p := 0; p < meta.BlockID.PartsHeader.Total; p++ {
   243  			batch.Delete(calcBlockPartKey(h, p))
   244  		}
   245  		pruned++
   246  
   247  		// flush every 1000 blocks to avoid batches becoming too large
   248  		if pruned%1000 == 0 && pruned > 0 {
   249  			err := flush(batch, h)
   250  			if err != nil {
   251  				return 0, err
   252  			}
   253  			batch = bs.db.NewBatch()
   254  			defer batch.Close()
   255  		}
   256  	}
   257  
   258  	err := flush(batch, height)
   259  	if err != nil {
   260  		return 0, err
   261  	}
   262  	return pruned, nil
   263  }
   264  
   265  // SaveBlock persists the given block, blockParts, and seenCommit to the underlying db.
   266  // blockParts: Must be parts of the block
   267  // seenCommit: The +2/3 precommits that were seen which committed at height.
   268  //             If all the nodes restart after committing a block,
   269  //             we need this to reload the precommits to catch-up nodes to the
   270  //             most recent height.  Otherwise they'd stall at H-1.
   271  func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
   272  	defer types.TimeTrack(time.Now(), nil)
   273  
   274  	if block == nil {
   275  		panic("BlockStore can only save a non-nil block")
   276  	}
   277  
   278  	height := block.Height
   279  	hash := block.Hash()
   280  
   281  	if g, w := height, bs.Height()+1; bs.Base() > 0 && g != w {
   282  		panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g))
   283  	}
   284  	if !blockParts.IsComplete() {
   285  		panic(fmt.Sprintf("BlockStore can only save complete block part sets"))
   286  	}
   287  
   288  	// Save block meta
   289  	blockMeta := types.NewBlockMeta(block, blockParts)
   290  	metaBytes := cdc.MustMarshalBinaryBare(blockMeta)
   291  	bs.db.Set(calcBlockMetaKey(height), metaBytes)
   292  	bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height)))
   293  
   294  	// Save block parts
   295  	for i := 0; i < blockParts.Total(); i++ {
   296  		part := blockParts.GetPart(i)
   297  		bs.saveBlockPart(height, i, part)
   298  	}
   299  
   300  	// Save block commit (duplicate and separate from the Block)
   301  	blockCommitBytes := cdc.MustMarshalBinaryBare(block.LastCommit)
   302  	bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes)
   303  
   304  	// Save seen commit (seen +2/3 precommits for block)
   305  	// NOTE: we can delete this at a later height
   306  	seenCommitBytes := cdc.MustMarshalBinaryBare(seenCommit)
   307  	bs.db.Set(calcSeenCommitKey(height), seenCommitBytes)
   308  
   309  	// Done!
   310  	bs.mtx.Lock()
   311  	bs.height = height
   312  	if bs.base == 0 {
   313  		bs.base = height
   314  	}
   315  	bs.mtx.Unlock()
   316  
   317  	// Save new BlockStoreStateJSON descriptor
   318  	bs.saveState()
   319  
   320  	// Flush
   321  	bs.db.SetSync(nil, nil)
   322  }
   323  
   324  func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) {
   325  	partBytes := cdc.MustMarshalBinaryBare(part)
   326  	bs.db.Set(calcBlockPartKey(height, index), partBytes)
   327  }
   328  
   329  func (bs *BlockStore) saveState() {
   330  	bs.mtx.RLock()
   331  	bsJSON := BlockStoreStateJSON{
   332  		Base:   bs.base,
   333  		Height: bs.height,
   334  	}
   335  	bs.mtx.RUnlock()
   336  	bsJSON.Save(bs.db)
   337  }
   338  
   339  //-----------------------------------------------------------------------------
   340  
   341  func calcBlockMetaKey(height int64) []byte {
   342  	return []byte(fmt.Sprintf("H:%v", height))
   343  }
   344  
   345  func calcBlockPartKey(height int64, partIndex int) []byte {
   346  	return []byte(fmt.Sprintf("P:%v:%v", height, partIndex))
   347  }
   348  
   349  func calcBlockCommitKey(height int64) []byte {
   350  	return []byte(fmt.Sprintf("C:%v", height))
   351  }
   352  
   353  func calcSeenCommitKey(height int64) []byte {
   354  	return []byte(fmt.Sprintf("SC:%v", height))
   355  }
   356  
   357  func calcBlockHashKey(hash []byte) []byte {
   358  	return []byte(fmt.Sprintf("BH:%x", hash))
   359  }
   360  
   361  //-----------------------------------------------------------------------------
   362  
   363  var blockStoreKey = []byte("blockStore")
   364  
   365  // BlockStoreStateJSON is the block store state JSON structure.
   366  type BlockStoreStateJSON struct {
   367  	Base   int64 `json:"base"`
   368  	Height int64 `json:"height"`
   369  }
   370  
   371  // Save persists the blockStore state to the database as JSON.
   372  func (bsj BlockStoreStateJSON) Save(db dbm.DB) {
   373  	bytes, err := cdc.MarshalJSON(bsj)
   374  	if err != nil {
   375  		panic(fmt.Sprintf("Could not marshal state bytes: %v", err))
   376  	}
   377  	db.SetSync(blockStoreKey, bytes)
   378  }
   379  
   380  // LoadBlockStoreStateJSON returns the BlockStoreStateJSON as loaded from disk.
   381  // If no BlockStoreStateJSON was previously persisted, it returns the zero value.
   382  func LoadBlockStoreStateJSON(db dbm.DB) BlockStoreStateJSON {
   383  	bytes, err := db.Get(blockStoreKey)
   384  	if err != nil {
   385  		panic(err)
   386  	}
   387  	if len(bytes) == 0 {
   388  		return BlockStoreStateJSON{
   389  			Base:   0,
   390  			Height: 0,
   391  		}
   392  	}
   393  	bsj := BlockStoreStateJSON{}
   394  	err = cdc.UnmarshalJSON(bytes, &bsj)
   395  	if err != nil {
   396  		panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes))
   397  	}
   398  	// Backwards compatibility with persisted data from before Base existed.
   399  	if bsj.Height > 0 && bsj.Base == 0 {
   400  		bsj.Base = 1
   401  	}
   402  	return bsj
   403  }