github.com/vipernet-xyz/tm@v0.34.24/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/vipernet-xyz/tm/libs/sync"
    11  	tmstore "github.com/vipernet-xyz/tm/proto/tendermint/store"
    12  	tmproto "github.com/vipernet-xyz/tm/proto/tendermint/types"
    13  	"github.com/vipernet-xyz/tm/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/vipernet-xyz/tm/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  //
   329  //	If all the nodes restart after committing a block,
   330  //	we need this to reload the precommits to catch-up nodes to the
   331  //	most recent height.  Otherwise they'd stall at H-1.
   332  func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
   333  	if block == nil {
   334  		panic("BlockStore can only save a non-nil block")
   335  	}
   336  
   337  	height := block.Height
   338  	hash := block.Hash()
   339  
   340  	if g, w := height, bs.Height()+1; bs.Base() > 0 && g != w {
   341  		panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g))
   342  	}
   343  	if !blockParts.IsComplete() {
   344  		panic("BlockStore can only save complete block part sets")
   345  	}
   346  
   347  	// Save block parts. This must be done before the block meta, since callers
   348  	// typically load the block meta first as an indication that the block exists
   349  	// and then go on to load block parts - we must make sure the block is
   350  	// complete as soon as the block meta is written.
   351  	for i := 0; i < int(blockParts.Total()); i++ {
   352  		part := blockParts.GetPart(i)
   353  		bs.saveBlockPart(height, i, part)
   354  	}
   355  
   356  	// Save block meta
   357  	blockMeta := types.NewBlockMeta(block, blockParts)
   358  	pbm := blockMeta.ToProto()
   359  	if pbm == nil {
   360  		panic("nil blockmeta")
   361  	}
   362  	metaBytes := mustEncode(pbm)
   363  	if err := bs.db.Set(calcBlockMetaKey(height), metaBytes); err != nil {
   364  		panic(err)
   365  	}
   366  	if err := bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil {
   367  		panic(err)
   368  	}
   369  
   370  	// Save block commit (duplicate and separate from the Block)
   371  	pbc := block.LastCommit.ToProto()
   372  	blockCommitBytes := mustEncode(pbc)
   373  	if err := bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes); err != nil {
   374  		panic(err)
   375  	}
   376  
   377  	// Save seen commit (seen +2/3 precommits for block)
   378  	// NOTE: we can delete this at a later height
   379  	pbsc := seenCommit.ToProto()
   380  	seenCommitBytes := mustEncode(pbsc)
   381  	if err := bs.db.Set(calcSeenCommitKey(height), seenCommitBytes); err != nil {
   382  		panic(err)
   383  	}
   384  
   385  	// Done!
   386  	bs.mtx.Lock()
   387  	bs.height = height
   388  	if bs.base == 0 {
   389  		bs.base = height
   390  	}
   391  	bs.mtx.Unlock()
   392  
   393  	// Save new BlockStoreState descriptor. This also flushes the database.
   394  	bs.saveState()
   395  }
   396  
   397  func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) {
   398  	pbp, err := part.ToProto()
   399  	if err != nil {
   400  		panic(fmt.Errorf("unable to make part into proto: %w", err))
   401  	}
   402  	partBytes := mustEncode(pbp)
   403  	if err := bs.db.Set(calcBlockPartKey(height, index), partBytes); err != nil {
   404  		panic(err)
   405  	}
   406  }
   407  
   408  func (bs *BlockStore) saveState() {
   409  	bs.mtx.RLock()
   410  	bss := tmstore.BlockStoreState{
   411  		Base:   bs.base,
   412  		Height: bs.height,
   413  	}
   414  	bs.mtx.RUnlock()
   415  	SaveBlockStoreState(&bss, bs.db)
   416  }
   417  
   418  // SaveSeenCommit saves a seen commit, used by e.g. the state sync reactor when bootstrapping node.
   419  func (bs *BlockStore) SaveSeenCommit(height int64, seenCommit *types.Commit) error {
   420  	pbc := seenCommit.ToProto()
   421  	seenCommitBytes, err := proto.Marshal(pbc)
   422  	if err != nil {
   423  		return fmt.Errorf("unable to marshal commit: %w", err)
   424  	}
   425  	return bs.db.Set(calcSeenCommitKey(height), seenCommitBytes)
   426  }
   427  
   428  func (bs *BlockStore) Close() error {
   429  	return bs.db.Close()
   430  }
   431  
   432  //-----------------------------------------------------------------------------
   433  
   434  func calcBlockMetaKey(height int64) []byte {
   435  	return []byte(fmt.Sprintf("H:%v", height))
   436  }
   437  
   438  func calcBlockPartKey(height int64, partIndex int) []byte {
   439  	return []byte(fmt.Sprintf("P:%v:%v", height, partIndex))
   440  }
   441  
   442  func calcBlockCommitKey(height int64) []byte {
   443  	return []byte(fmt.Sprintf("C:%v", height))
   444  }
   445  
   446  func calcSeenCommitKey(height int64) []byte {
   447  	return []byte(fmt.Sprintf("SC:%v", height))
   448  }
   449  
   450  func calcBlockHashKey(hash []byte) []byte {
   451  	return []byte(fmt.Sprintf("BH:%x", hash))
   452  }
   453  
   454  //-----------------------------------------------------------------------------
   455  
   456  var blockStoreKey = []byte("blockStore")
   457  
   458  // SaveBlockStoreState persists the blockStore state to the database.
   459  func SaveBlockStoreState(bsj *tmstore.BlockStoreState, db dbm.DB) {
   460  	bytes, err := proto.Marshal(bsj)
   461  	if err != nil {
   462  		panic(fmt.Sprintf("Could not marshal state bytes: %v", err))
   463  	}
   464  	if err := db.SetSync(blockStoreKey, bytes); err != nil {
   465  		panic(err)
   466  	}
   467  }
   468  
   469  // LoadBlockStoreState returns the BlockStoreState as loaded from disk.
   470  // If no BlockStoreState was previously persisted, it returns the zero value.
   471  func LoadBlockStoreState(db dbm.DB) tmstore.BlockStoreState {
   472  	bytes, err := db.Get(blockStoreKey)
   473  	if err != nil {
   474  		panic(err)
   475  	}
   476  
   477  	if len(bytes) == 0 {
   478  		return tmstore.BlockStoreState{
   479  			Base:   0,
   480  			Height: 0,
   481  		}
   482  	}
   483  
   484  	var bsj tmstore.BlockStoreState
   485  	if err := proto.Unmarshal(bytes, &bsj); err != nil {
   486  		panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes))
   487  	}
   488  
   489  	// Backwards compatibility with persisted data from before Base existed.
   490  	if bsj.Height > 0 && bsj.Base == 0 {
   491  		bsj.Base = 1
   492  	}
   493  	return bsj
   494  }
   495  
   496  // mustEncode proto encodes a proto.message and panics if fails
   497  func mustEncode(pb proto.Message) []byte {
   498  	bz, err := proto.Marshal(pb)
   499  	if err != nil {
   500  		panic(fmt.Errorf("unable to marshal: %w", err))
   501  	}
   502  	return bz
   503  }