github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/store/store.go (about)

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