func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
	if block == nil {
		panic("BlockStore can only save a non-nil block")
	}
	batch := bs.db.NewBatch()
	height := block.Height
	hash := block.Hash()
	if g, w := height, bs.Height()+1; bs.Base() > 0 && g != w {
		panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g))
	}
	if !blockParts.IsComplete() {
		panic("BlockStore can only save complete block part sets")
	}
	for i := 0; i < int(blockParts.Total()); i++ {
		part := blockParts.GetPart(i)
		bs.saveBlockPart(height, i, part, batch)
	}
	blockMeta := types.NewBlockMeta(block, blockParts)
	pbm := blockMeta.ToProto()
	if pbm == nil {
		panic("nil blockmeta")
	}
	metaBytes := mustEncode(pbm)
	if err := batch.Set(blockMetaKey(height), metaBytes); err != nil {
		panic(err)
	}
	if err := batch.Set(blockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil {
		panic(err)
	}
	pbc := block.LastCommit.ToProto()
	blockCommitBytes := mustEncode(pbc)
	if err := batch.Set(blockCommitKey(height-1), blockCommitBytes); err != nil {
		panic(err)
	}
	pbsc := seenCommit.ToProto()
	seenCommitBytes := mustEncode(pbsc)
	if err := batch.Set(seenCommitKey(), seenCommitBytes); err != nil {
		panic(err)
	}
	if err := batch.WriteSync(); err != nil {
		panic(err)
	}
	if err := batch.Close(); err != nil {
		panic(err)
	}
}
SaveBlock persists the given block, blockParts, and seenCommit to the underlying db.
blockParts: Must be parts of the block
seenCommit: The +2/3 precommits that were seen which committed at height.
If all the nodes restart after committing a block,
we need this to reload the precommits to catch-up nodes to the
most recent height.  Otherwise they'd stall at H-1.
package store
import (
	"bytes"
	"fmt"
	"strconv"
	"github.com/gogo/protobuf/proto"
	"github.com/google/orderedcode"
	dbm "github.com/tendermint/tm-db"
	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
	"github.com/tendermint/tendermint/types"
)
type BlockStore struct {
	db dbm.DB
}
func NewBlockStore(db dbm.DB) *BlockStore {
	return &BlockStore{db}
}
func (bs *BlockStore) Base() int64 {
	iter, err := bs.db.Iterator(
		blockMetaKey(1),
		blockMetaKey(1<<63-1),
	)
	if err != nil {
		panic(err)
	}
	defer iter.Close()
	if iter.Valid() {
		height, err := decodeBlockMetaKey(iter.Key())
		if err == nil {
			return height
		}
	}
	if err := iter.Error(); err != nil {
		panic(err)
	}
	return 0
}
func (bs *BlockStore) Height() int64 {
	iter, err := bs.db.ReverseIterator(
		blockMetaKey(1),
		blockMetaKey(1<<63-1),
	)
	if err != nil {
		panic(err)
	}
	defer iter.Close()
	if iter.Valid() {
		height, err := decodeBlockMetaKey(iter.Key())
		if err == nil {
			return height
		}
	}
	if err := iter.Error(); err != nil {
		panic(err)
	}
	return 0
}
func (bs *BlockStore) Size() int64 {
	height := bs.Height()
	if height == 0 {
		return 0
	}
	return height + 1 - bs.Base()
}
func (bs *BlockStore) LoadBaseMeta() *types.BlockMeta {
	iter, err := bs.db.Iterator(
		blockMetaKey(1),
		blockMetaKey(1<<63-1),
	)
	if err != nil {
		return nil
	}
	defer iter.Close()
	if iter.Valid() {
		var pbbm = new(tmproto.BlockMeta)
		err = proto.Unmarshal(iter.Value(), pbbm)
		if err != nil {
			panic(fmt.Errorf("unmarshal to tmproto.BlockMeta: %w", err))
		}
		blockMeta, err := types.BlockMetaFromProto(pbbm)
		if err != nil {
			panic(fmt.Errorf("error from proto blockMeta: %w", err))
		}
		return blockMeta
	}
	return nil
}
func (bs *BlockStore) LoadBlock(height int64) *types.Block {
	var blockMeta = bs.LoadBlockMeta(height)
	if blockMeta == nil {
		return nil
	}
	pbb := new(tmproto.Block)
	buf := []byte{}
	for i := 0; i < int(blockMeta.BlockID.PartSetHeader.Total); i++ {
		part := bs.LoadBlockPart(height, i)
		if part == nil {
			return nil
		}
		buf = append(buf, part.Bytes...)
	}
	err := proto.Unmarshal(buf, pbb)
	if err != nil {
		panic(fmt.Sprintf("Error reading block: %v", err))
	}
	block, err := types.BlockFromProto(pbb)
	if err != nil {
		panic(fmt.Errorf("error from proto block: %w", err))
	}
	return block
}
func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block {
	bz, err := bs.db.Get(blockHashKey(hash))
	if err != nil {
		panic(err)
	}
	if len(bz) == 0 {
		return nil
	}
	s := string(bz)
	height, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		panic(fmt.Sprintf("failed to extract height from %s: %v", s, err))
	}
	return bs.LoadBlock(height)
}
func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part {
	var pbpart = new(tmproto.Part)
	bz, err := bs.db.Get(blockPartKey(height, index))
	if err != nil {
		panic(err)
	}
	if len(bz) == 0 {
		return nil
	}
	err = proto.Unmarshal(bz, pbpart)
	if err != nil {
		panic(fmt.Errorf("unmarshal to tmproto.Part failed: %w", err))
	}
	part, err := types.PartFromProto(pbpart)
	if err != nil {
		panic(fmt.Sprintf("Error reading block part: %v", err))
	}
	return part
}
func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
	var pbbm = new(tmproto.BlockMeta)
	bz, err := bs.db.Get(blockMetaKey(height))
	if err != nil {
		panic(err)
	}
	if len(bz) == 0 {
		return nil
	}
	err = proto.Unmarshal(bz, pbbm)
	if err != nil {
		panic(fmt.Errorf("unmarshal to tmproto.BlockMeta: %w", err))
	}
	blockMeta, err := types.BlockMetaFromProto(pbbm)
	if err != nil {
		panic(fmt.Errorf("error from proto blockMeta: %w", err))
	}
	return blockMeta
}
func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit {
	var pbc = new(tmproto.Commit)
	bz, err := bs.db.Get(blockCommitKey(height))
	if err != nil {
		panic(err)
	}
	if len(bz) == 0 {
		return nil
	}
	err = proto.Unmarshal(bz, pbc)
	if err != nil {
		panic(fmt.Errorf("error reading block commit: %w", err))
	}
	commit, err := types.CommitFromProto(pbc)
	if err != nil {
		panic(fmt.Sprintf("Error reading block commit: %v", err))
	}
	return commit
}
func (bs *BlockStore) LoadSeenCommit() *types.Commit {
	var pbc = new(tmproto.Commit)
	bz, err := bs.db.Get(seenCommitKey())
	if err != nil {
		panic(err)
	}
	if len(bz) == 0 {
		return nil
	}
	err = proto.Unmarshal(bz, pbc)
	if err != nil {
		panic(fmt.Sprintf("error reading block seen commit: %v", err))
	}
	commit, err := types.CommitFromProto(pbc)
	if err != nil {
		panic(fmt.Errorf("error from proto commit: %w", err))
	}
	return commit
}
func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) {
	if height <= 0 {
		return 0, fmt.Errorf("height must be greater than 0")
	}
	if height > bs.Height() {
		return 0, fmt.Errorf("height must be equal to or less than the latest height %d", bs.Height())
	}
	removeBlockHash := func(key, value []byte, batch dbm.Batch) error {
		var pbbm = new(tmproto.BlockMeta)
		err := proto.Unmarshal(value, pbbm)
		if err != nil {
			return fmt.Errorf("unmarshal to tmproto.BlockMeta: %w", err)
		}
		blockMeta, err := types.BlockMetaFromProto(pbbm)
		if err != nil {
			return fmt.Errorf("error from proto blockMeta: %w", err)
		}
		if err := batch.Delete(blockHashKey(blockMeta.BlockID.Hash)); err != nil {
			return fmt.Errorf("failed to delete hash key: %X: %w", blockHashKey(blockMeta.BlockID.Hash), err)
		}
		return nil
	}
	pruned, err := bs.pruneRange(blockMetaKey(0), blockMetaKey(height), removeBlockHash)
	if err != nil {
		return pruned, err
	}
	if _, err := bs.pruneRange(blockPartKey(0, 0), blockPartKey(height, 0), nil); err != nil {
		return pruned, err
	}
	if _, err := bs.pruneRange(blockCommitKey(0), blockCommitKey(height), nil); err != nil {
		return pruned, err
	}
	return pruned, nil
}
func (bs *BlockStore) pruneRange(
	start []byte,
	end []byte,
	preDeletionHook func(key, value []byte, batch dbm.Batch) error,
) (uint64, error) {
	var (
		err         error
		pruned      uint64
		totalPruned uint64
	)
	batch := bs.db.NewBatch()
	defer batch.Close()
	pruned, start, err = bs.batchDelete(batch, start, end, preDeletionHook)
	if err != nil {
		return totalPruned, err
	}
	for !bytes.Equal(start, end) {
		if err := batch.Write(); err != nil {
			return totalPruned, err
		}
		totalPruned += pruned
		if err := batch.Close(); err != nil {
			return totalPruned, err
		}
		batch = bs.db.NewBatch()
		pruned, start, err = bs.batchDelete(batch, start, end, preDeletionHook)
		if err != nil {
			return totalPruned, err
		}
	}
	if err := batch.WriteSync(); err != nil {
		return totalPruned, err
	}
	totalPruned += pruned
	return totalPruned, nil
}
func (bs *BlockStore) batchDelete(
	batch dbm.Batch,
	start, end []byte,
	preDeletionHook func(key, value []byte, batch dbm.Batch) error,
) (uint64, []byte, error) {
	var pruned uint64
	iter, err := bs.db.Iterator(start, end)
	if err != nil {
		return pruned, start, err
	}
	defer iter.Close()
	for ; iter.Valid(); iter.Next() {
		key := iter.Key()
		if preDeletionHook != nil {
			if err := preDeletionHook(key, iter.Value(), batch); err != nil {
				return 0, start, fmt.Errorf("pruning error at key %X: %w", iter.Key(), err)
			}
		}
		if err := batch.Delete(key); err != nil {
			return 0, start, fmt.Errorf("pruning error at key %X: %w", iter.Key(), err)
		}
		pruned++
		if pruned == 1000 {
			return pruned, iter.Key(), iter.Error()
		}
	}
	return pruned, end, iter.Error()
}