github.com/devwanda/aphelion-staking@v0.33.9/store/store.go (about)

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