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