github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/state/store.go (about)

     1  package state
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	dbm "github.com/cometbft/cometbft-db"
     8  	"github.com/gogo/protobuf/proto"
     9  
    10  	abci "github.com/badrootd/celestia-core/abci/types"
    11  	cmtmath "github.com/badrootd/celestia-core/libs/math"
    12  	cmtos "github.com/badrootd/celestia-core/libs/os"
    13  	cmtstate "github.com/badrootd/celestia-core/proto/tendermint/state"
    14  	cmtproto "github.com/badrootd/celestia-core/proto/tendermint/types"
    15  	"github.com/badrootd/celestia-core/types"
    16  )
    17  
    18  const (
    19  	// persist validators every valSetCheckpointInterval blocks to avoid
    20  	// LoadValidators taking too much time.
    21  	// https://github.com/cometbft/cometbft/pull/3438
    22  	// 100000 results in ~ 100ms to get 100 validators (see BenchmarkLoadValidators)
    23  	valSetCheckpointInterval = 100000
    24  )
    25  
    26  //------------------------------------------------------------------------
    27  
    28  func calcValidatorsKey(height int64) []byte {
    29  	return []byte(fmt.Sprintf("validatorsKey:%v", height))
    30  }
    31  
    32  func calcConsensusParamsKey(height int64) []byte {
    33  	return []byte(fmt.Sprintf("consensusParamsKey:%v", height))
    34  }
    35  
    36  func calcABCIResponsesKey(height int64) []byte {
    37  	return []byte(fmt.Sprintf("abciResponsesKey:%v", height))
    38  }
    39  
    40  //----------------------
    41  
    42  var (
    43  	lastABCIResponseKey = []byte("lastABCIResponseKey")
    44  )
    45  
    46  //go:generate ../scripts/mockery_generate.sh Store
    47  
    48  // Store defines the state store interface
    49  //
    50  // It is used to retrieve current state and save and load ABCI responses,
    51  // validators and consensus parameters
    52  type Store interface {
    53  	// LoadFromDBOrGenesisFile loads the most recent state.
    54  	// If the chain is new it will use the genesis file from the provided genesis file path as the current state.
    55  	LoadFromDBOrGenesisFile(string) (State, error)
    56  	// LoadFromDBOrGenesisDoc loads the most recent state.
    57  	// If the chain is new it will use the genesis doc as the current state.
    58  	LoadFromDBOrGenesisDoc(*types.GenesisDoc) (State, error)
    59  	// Load loads the current state of the blockchain
    60  	Load() (State, error)
    61  	// LoadValidators loads the validator set at a given height
    62  	LoadValidators(int64) (*types.ValidatorSet, error)
    63  	// LoadABCIResponses loads the abciResponse for a given height
    64  	LoadABCIResponses(int64) (*cmtstate.ABCIResponses, error)
    65  	// LoadLastABCIResponse loads the last abciResponse for a given height
    66  	LoadLastABCIResponse(int64) (*cmtstate.ABCIResponses, error)
    67  	// LoadConsensusParams loads the consensus params for a given height
    68  	LoadConsensusParams(int64) (types.ConsensusParams, error)
    69  	// Save overwrites the previous state with the updated one
    70  	Save(State) error
    71  	// SaveABCIResponses saves ABCIResponses for a given height
    72  	SaveABCIResponses(int64, *cmtstate.ABCIResponses) error
    73  	// Bootstrap is used for bootstrapping state when not starting from a initial height.
    74  	Bootstrap(State) error
    75  	// PruneStates takes the height from which to start prning and which height stop at
    76  	PruneStates(int64, int64) error
    77  	// Close closes the connection with the database
    78  	Close() error
    79  }
    80  
    81  // dbStore wraps a db (github.com/cometbft/cometbft-db)
    82  type dbStore struct {
    83  	db dbm.DB
    84  
    85  	StoreOptions
    86  }
    87  
    88  type StoreOptions struct {
    89  
    90  	// DiscardABCIResponses determines whether or not the store
    91  	// retains all ABCIResponses. If DiscardABCiResponses is enabled,
    92  	// the store will maintain only the response object from the latest
    93  	// height.
    94  	DiscardABCIResponses bool
    95  }
    96  
    97  var _ Store = (*dbStore)(nil)
    98  
    99  // NewStore creates the dbStore of the state pkg.
   100  func NewStore(db dbm.DB, options StoreOptions) Store {
   101  	return dbStore{db, options}
   102  }
   103  
   104  // LoadStateFromDBOrGenesisFile loads the most recent state from the database,
   105  // or creates a new one from the given genesisFilePath.
   106  func (store dbStore) LoadFromDBOrGenesisFile(genesisFilePath string) (State, error) {
   107  	state, err := store.Load()
   108  	if err != nil {
   109  		return State{}, err
   110  	}
   111  	if state.IsEmpty() {
   112  		var err error
   113  		state, err = MakeGenesisStateFromFile(genesisFilePath)
   114  		if err != nil {
   115  			return state, err
   116  		}
   117  	}
   118  
   119  	return state, nil
   120  }
   121  
   122  // LoadStateFromDBOrGenesisDoc loads the most recent state from the database,
   123  // or creates a new one from the given genesisDoc.
   124  func (store dbStore) LoadFromDBOrGenesisDoc(genesisDoc *types.GenesisDoc) (State, error) {
   125  	state, err := store.Load()
   126  	if err != nil {
   127  		return State{}, err
   128  	}
   129  
   130  	if state.IsEmpty() {
   131  		var err error
   132  		state, err = MakeGenesisState(genesisDoc)
   133  		if err != nil {
   134  			return state, err
   135  		}
   136  	}
   137  
   138  	return state, nil
   139  }
   140  
   141  // LoadState loads the State from the database.
   142  func (store dbStore) Load() (State, error) {
   143  	return store.loadState(stateKey)
   144  }
   145  
   146  func (store dbStore) loadState(key []byte) (state State, err error) {
   147  	buf, err := store.db.Get(key)
   148  	if err != nil {
   149  		return state, err
   150  	}
   151  	if len(buf) == 0 {
   152  		return state, nil
   153  	}
   154  
   155  	sp := new(cmtstate.State)
   156  
   157  	err = proto.Unmarshal(buf, sp)
   158  	if err != nil {
   159  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   160  		cmtos.Exit(fmt.Sprintf(`LoadState: Data has been corrupted or its spec has changed:
   161  		%v\n`, err))
   162  	}
   163  
   164  	sm, err := FromProto(sp)
   165  	if err != nil {
   166  		return state, err
   167  	}
   168  
   169  	return *sm, nil
   170  }
   171  
   172  // Save persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database.
   173  // This flushes the writes (e.g. calls SetSync).
   174  func (store dbStore) Save(state State) error {
   175  	return store.save(state, stateKey)
   176  }
   177  
   178  func (store dbStore) save(state State, key []byte) error {
   179  	nextHeight := state.LastBlockHeight + 1
   180  	// If first block, save validators for the block.
   181  	if nextHeight == 1 {
   182  		nextHeight = state.InitialHeight
   183  		// This extra logic due to validator set changes being delayed 1 block.
   184  		// It may get overwritten due to InitChain validator updates.
   185  		if err := store.saveValidatorsInfo(nextHeight, nextHeight, state.Validators); err != nil {
   186  			return err
   187  		}
   188  	}
   189  	// Save next validators.
   190  	if err := store.saveValidatorsInfo(nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators); err != nil {
   191  		return err
   192  	}
   193  
   194  	// Save next consensus params.
   195  	if err := store.saveConsensusParamsInfo(nextHeight,
   196  		state.LastHeightConsensusParamsChanged, state.ConsensusParams); err != nil {
   197  		return err
   198  	}
   199  	err := store.db.SetSync(key, state.Bytes())
   200  	if err != nil {
   201  		return err
   202  	}
   203  	return nil
   204  }
   205  
   206  // BootstrapState saves a new state, used e.g. by state sync when starting from non-zero height.
   207  func (store dbStore) Bootstrap(state State) error {
   208  	height := state.LastBlockHeight + 1
   209  	if height == 1 {
   210  		height = state.InitialHeight
   211  	}
   212  
   213  	if height > 1 && !state.LastValidators.IsNilOrEmpty() {
   214  		if err := store.saveValidatorsInfo(height-1, height-1, state.LastValidators); err != nil {
   215  			return err
   216  		}
   217  	}
   218  
   219  	if err := store.saveValidatorsInfo(height, height, state.Validators); err != nil {
   220  		return err
   221  	}
   222  
   223  	if err := store.saveValidatorsInfo(height+1, height+1, state.NextValidators); err != nil {
   224  		return err
   225  	}
   226  
   227  	if err := store.saveConsensusParamsInfo(height,
   228  		state.LastHeightConsensusParamsChanged, state.ConsensusParams); err != nil {
   229  		return err
   230  	}
   231  
   232  	return store.db.SetSync(stateKey, state.Bytes())
   233  }
   234  
   235  // PruneStates deletes states between the given heights (including from, excluding to). It is not
   236  // guaranteed to delete all states, since the last checkpointed state and states being pointed to by
   237  // e.g. `LastHeightChanged` must remain. The state at to must also exist.
   238  //
   239  // The from parameter is necessary since we can't do a key scan in a performant way due to the key
   240  // encoding not preserving ordering: https://github.com/cometbft/cometbft/issues/4567
   241  // This will cause some old states to be left behind when doing incremental partial prunes,
   242  // specifically older checkpoints and LastHeightChanged targets.
   243  func (store dbStore) PruneStates(from int64, to int64) error {
   244  	if from <= 0 || to <= 0 {
   245  		return fmt.Errorf("from height %v and to height %v must be greater than 0", from, to)
   246  	}
   247  	if from >= to {
   248  		return fmt.Errorf("from height %v must be lower than to height %v", from, to)
   249  	}
   250  	valInfo, err := loadValidatorsInfo(store.db, to)
   251  	if err != nil {
   252  		return fmt.Errorf("validators at height %v not found: %w", to, err)
   253  	}
   254  	paramsInfo, err := store.loadConsensusParamsInfo(to)
   255  	if err != nil {
   256  		return fmt.Errorf("consensus params at height %v not found: %w", to, err)
   257  	}
   258  
   259  	keepVals := make(map[int64]bool)
   260  	if valInfo.ValidatorSet == nil {
   261  		keepVals[valInfo.LastHeightChanged] = true
   262  		keepVals[lastStoredHeightFor(to, valInfo.LastHeightChanged)] = true // keep last checkpoint too
   263  	}
   264  	keepParams := make(map[int64]bool)
   265  	if paramsInfo.ConsensusParams.Equal(&cmtproto.ConsensusParams{}) {
   266  		keepParams[paramsInfo.LastHeightChanged] = true
   267  	}
   268  
   269  	batch := store.db.NewBatch()
   270  	defer batch.Close()
   271  	pruned := uint64(0)
   272  
   273  	// We have to delete in reverse order, to avoid deleting previous heights that have validator
   274  	// sets and consensus params that we may need to retrieve.
   275  	for h := to - 1; h >= from; h-- {
   276  		// For heights we keep, we must make sure they have the full validator set or consensus
   277  		// params, otherwise they will panic if they're retrieved directly (instead of
   278  		// indirectly via a LastHeightChanged pointer).
   279  		if keepVals[h] {
   280  			v, err := loadValidatorsInfo(store.db, h)
   281  			if err != nil || v.ValidatorSet == nil {
   282  				vip, err := store.LoadValidators(h)
   283  				if err != nil {
   284  					return err
   285  				}
   286  
   287  				pvi, err := vip.ToProto()
   288  				if err != nil {
   289  					return err
   290  				}
   291  
   292  				v.ValidatorSet = pvi
   293  				v.LastHeightChanged = h
   294  
   295  				bz, err := v.Marshal()
   296  				if err != nil {
   297  					return err
   298  				}
   299  				err = batch.Set(calcValidatorsKey(h), bz)
   300  				if err != nil {
   301  					return err
   302  				}
   303  			}
   304  		} else {
   305  			err = batch.Delete(calcValidatorsKey(h))
   306  			if err != nil {
   307  				return err
   308  			}
   309  		}
   310  
   311  		if keepParams[h] {
   312  			p, err := store.loadConsensusParamsInfo(h)
   313  			if err != nil {
   314  				return err
   315  			}
   316  
   317  			if p.ConsensusParams.Equal(&cmtproto.ConsensusParams{}) {
   318  				params, err := store.LoadConsensusParams(h)
   319  				if err != nil {
   320  					return err
   321  				}
   322  				p.ConsensusParams = params.ToProto()
   323  
   324  				p.LastHeightChanged = h
   325  				bz, err := p.Marshal()
   326  				if err != nil {
   327  					return err
   328  				}
   329  
   330  				err = batch.Set(calcConsensusParamsKey(h), bz)
   331  				if err != nil {
   332  					return err
   333  				}
   334  			}
   335  		} else {
   336  			err = batch.Delete(calcConsensusParamsKey(h))
   337  			if err != nil {
   338  				return err
   339  			}
   340  		}
   341  
   342  		err = batch.Delete(calcABCIResponsesKey(h))
   343  		if err != nil {
   344  			return err
   345  		}
   346  		pruned++
   347  
   348  		// avoid batches growing too large by flushing to database regularly
   349  		if pruned%1000 == 0 && pruned > 0 {
   350  			err := batch.Write()
   351  			if err != nil {
   352  				return err
   353  			}
   354  			batch.Close()
   355  			batch = store.db.NewBatch()
   356  			defer batch.Close()
   357  		}
   358  	}
   359  
   360  	err = batch.WriteSync()
   361  	if err != nil {
   362  		return err
   363  	}
   364  
   365  	return nil
   366  }
   367  
   368  //------------------------------------------------------------------------
   369  
   370  // ABCIResponsesResultsHash returns the root hash of a Merkle tree of
   371  // ResponseDeliverTx responses (see ABCIResults.Hash)
   372  //
   373  // See merkle.SimpleHashFromByteSlices
   374  func ABCIResponsesResultsHash(ar *cmtstate.ABCIResponses) []byte {
   375  	return types.NewResults(ar.DeliverTxs).Hash()
   376  }
   377  
   378  // LoadABCIResponses loads the ABCIResponses for the given height from the
   379  // database. If the node has DiscardABCIResponses set to true, ErrABCIResponsesNotPersisted
   380  // is persisted. If not found, ErrNoABCIResponsesForHeight is returned.
   381  func (store dbStore) LoadABCIResponses(height int64) (*cmtstate.ABCIResponses, error) {
   382  	if store.DiscardABCIResponses {
   383  		return nil, ErrABCIResponsesNotPersisted
   384  	}
   385  
   386  	buf, err := store.db.Get(calcABCIResponsesKey(height))
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  	if len(buf) == 0 {
   391  
   392  		return nil, ErrNoABCIResponsesForHeight{height}
   393  	}
   394  
   395  	abciResponses := new(cmtstate.ABCIResponses)
   396  	err = abciResponses.Unmarshal(buf)
   397  	if err != nil {
   398  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   399  		cmtos.Exit(fmt.Sprintf(`LoadABCIResponses: Data has been corrupted or its spec has
   400                  changed: %v\n`, err))
   401  	}
   402  	// TODO: ensure that buf is completely read.
   403  
   404  	return abciResponses, nil
   405  }
   406  
   407  // LoadLastABCIResponses loads the ABCIResponses from the most recent height.
   408  // The height parameter is used to ensure that the response corresponds to the latest height.
   409  // If not, an error is returned.
   410  //
   411  // This method is used for recovering in the case that we called the Commit ABCI
   412  // method on the application but crashed before persisting the results.
   413  func (store dbStore) LoadLastABCIResponse(height int64) (*cmtstate.ABCIResponses, error) {
   414  	bz, err := store.db.Get(lastABCIResponseKey)
   415  	if err != nil {
   416  		return nil, err
   417  	}
   418  
   419  	if len(bz) == 0 {
   420  		return nil, errors.New("no last ABCI response has been persisted")
   421  	}
   422  
   423  	abciResponse := new(cmtstate.ABCIResponsesInfo)
   424  	err = abciResponse.Unmarshal(bz)
   425  	if err != nil {
   426  		cmtos.Exit(fmt.Sprintf(`LoadLastABCIResponses: Data has been corrupted or its spec has
   427  			changed: %v\n`, err))
   428  	}
   429  
   430  	// Here we validate the result by comparing its height to the expected height.
   431  	if height != abciResponse.GetHeight() {
   432  		return nil, errors.New("expected height %d but last stored abci responses was at height %d")
   433  	}
   434  
   435  	return abciResponse.AbciResponses, nil
   436  }
   437  
   438  // SaveABCIResponses persists the ABCIResponses to the database.
   439  // This is useful in case we crash after app.Commit and before s.Save().
   440  // Responses are indexed by height so they can also be loaded later to produce
   441  // Merkle proofs.
   442  //
   443  // CONTRACT: height must be monotonically increasing every time this is called.
   444  func (store dbStore) SaveABCIResponses(height int64, abciResponses *cmtstate.ABCIResponses) error {
   445  	var dtxs []*abci.ResponseDeliverTx
   446  	// strip nil values,
   447  	for _, tx := range abciResponses.DeliverTxs {
   448  		if tx != nil {
   449  			dtxs = append(dtxs, tx)
   450  		}
   451  	}
   452  	abciResponses.DeliverTxs = dtxs
   453  
   454  	// If the flag is false then we save the ABCIResponse. This can be used for the /BlockResults
   455  	// query or to reindex an event using the command line.
   456  	if !store.DiscardABCIResponses {
   457  		bz, err := abciResponses.Marshal()
   458  		if err != nil {
   459  			return err
   460  		}
   461  		if err := store.db.Set(calcABCIResponsesKey(height), bz); err != nil {
   462  			return err
   463  		}
   464  	}
   465  
   466  	// We always save the last ABCI response for crash recovery.
   467  	// This overwrites the previous saved ABCI Response.
   468  	response := &cmtstate.ABCIResponsesInfo{
   469  		AbciResponses: abciResponses,
   470  		Height:        height,
   471  	}
   472  	bz, err := response.Marshal()
   473  	if err != nil {
   474  		return err
   475  	}
   476  
   477  	return store.db.SetSync(lastABCIResponseKey, bz)
   478  }
   479  
   480  //-----------------------------------------------------------------------------
   481  
   482  // LoadValidators loads the ValidatorSet for a given height.
   483  // Returns ErrNoValSetForHeight if the validator set can't be found for this height.
   484  func (store dbStore) LoadValidators(height int64) (*types.ValidatorSet, error) {
   485  	valInfo, err := loadValidatorsInfo(store.db, height)
   486  	if err != nil {
   487  		return nil, ErrNoValSetForHeight{height}
   488  	}
   489  	if valInfo.ValidatorSet == nil {
   490  		lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged)
   491  		valInfo2, err := loadValidatorsInfo(store.db, lastStoredHeight)
   492  		if err != nil || valInfo2.ValidatorSet == nil {
   493  			return nil,
   494  				fmt.Errorf("couldn't find validators at height %d (height %d was originally requested): %w",
   495  					lastStoredHeight,
   496  					height,
   497  					err,
   498  				)
   499  		}
   500  
   501  		vs, err := types.ValidatorSetFromProto(valInfo2.ValidatorSet)
   502  		if err != nil {
   503  			return nil, err
   504  		}
   505  
   506  		vs.IncrementProposerPriority(cmtmath.SafeConvertInt32(height - lastStoredHeight)) // mutate
   507  		vi2, err := vs.ToProto()
   508  		if err != nil {
   509  			return nil, err
   510  		}
   511  
   512  		valInfo2.ValidatorSet = vi2
   513  		valInfo = valInfo2
   514  	}
   515  
   516  	vip, err := types.ValidatorSetFromProto(valInfo.ValidatorSet)
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	return vip, nil
   522  }
   523  
   524  func lastStoredHeightFor(height, lastHeightChanged int64) int64 {
   525  	checkpointHeight := height - height%valSetCheckpointInterval
   526  	return cmtmath.MaxInt64(checkpointHeight, lastHeightChanged)
   527  }
   528  
   529  // CONTRACT: Returned ValidatorsInfo can be mutated.
   530  func loadValidatorsInfo(db dbm.DB, height int64) (*cmtstate.ValidatorsInfo, error) {
   531  	buf, err := db.Get(calcValidatorsKey(height))
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  
   536  	if len(buf) == 0 {
   537  		return nil, errors.New("value retrieved from db is empty")
   538  	}
   539  
   540  	v := new(cmtstate.ValidatorsInfo)
   541  	err = v.Unmarshal(buf)
   542  	if err != nil {
   543  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   544  		cmtos.Exit(fmt.Sprintf(`LoadValidators: Data has been corrupted or its spec has changed:
   545          %v\n`, err))
   546  	}
   547  	// TODO: ensure that buf is completely read.
   548  
   549  	return v, nil
   550  }
   551  
   552  // saveValidatorsInfo persists the validator set.
   553  //
   554  // `height` is the effective height for which the validator is responsible for
   555  // signing. It should be called from s.Save(), right before the state itself is
   556  // persisted.
   557  func (store dbStore) saveValidatorsInfo(height, lastHeightChanged int64, valSet *types.ValidatorSet) error {
   558  	if lastHeightChanged > height {
   559  		return errors.New("lastHeightChanged cannot be greater than ValidatorsInfo height")
   560  	}
   561  	valInfo := &cmtstate.ValidatorsInfo{
   562  		LastHeightChanged: lastHeightChanged,
   563  	}
   564  	// Only persist validator set if it was updated or checkpoint height (see
   565  	// valSetCheckpointInterval) is reached.
   566  	if height == lastHeightChanged || height%valSetCheckpointInterval == 0 {
   567  		pv, err := valSet.ToProto()
   568  		if err != nil {
   569  			return err
   570  		}
   571  		valInfo.ValidatorSet = pv
   572  	}
   573  
   574  	bz, err := valInfo.Marshal()
   575  	if err != nil {
   576  		return err
   577  	}
   578  
   579  	err = store.db.Set(calcValidatorsKey(height), bz)
   580  	if err != nil {
   581  		return err
   582  	}
   583  
   584  	return nil
   585  }
   586  
   587  //-----------------------------------------------------------------------------
   588  
   589  // ConsensusParamsInfo represents the latest consensus params, or the last height it changed
   590  
   591  // LoadConsensusParams loads the ConsensusParams for a given height.
   592  func (store dbStore) LoadConsensusParams(height int64) (types.ConsensusParams, error) {
   593  	var (
   594  		empty   = types.ConsensusParams{}
   595  		emptypb = cmtproto.ConsensusParams{}
   596  	)
   597  	paramsInfo, err := store.loadConsensusParamsInfo(height)
   598  	if err != nil {
   599  		return empty, fmt.Errorf("could not find consensus params for height #%d: %w", height, err)
   600  	}
   601  
   602  	if paramsInfo.ConsensusParams.Equal(&emptypb) {
   603  		paramsInfo2, err := store.loadConsensusParamsInfo(paramsInfo.LastHeightChanged)
   604  		if err != nil {
   605  			return empty, fmt.Errorf(
   606  				"couldn't find consensus params at height %d as last changed from height %d: %w",
   607  				paramsInfo.LastHeightChanged,
   608  				height,
   609  				err,
   610  			)
   611  		}
   612  
   613  		paramsInfo = paramsInfo2
   614  	}
   615  
   616  	return types.ConsensusParamsFromProto(paramsInfo.ConsensusParams), nil
   617  }
   618  
   619  func (store dbStore) loadConsensusParamsInfo(height int64) (*cmtstate.ConsensusParamsInfo, error) {
   620  	buf, err := store.db.Get(calcConsensusParamsKey(height))
   621  	if err != nil {
   622  		return nil, err
   623  	}
   624  	if len(buf) == 0 {
   625  		return nil, errors.New("value retrieved from db is empty")
   626  	}
   627  
   628  	paramsInfo := new(cmtstate.ConsensusParamsInfo)
   629  	if err = paramsInfo.Unmarshal(buf); err != nil {
   630  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   631  		cmtos.Exit(fmt.Sprintf(`LoadConsensusParams: Data has been corrupted or its spec has changed:
   632                  %v\n`, err))
   633  	}
   634  	// TODO: ensure that buf is completely read.
   635  
   636  	return paramsInfo, nil
   637  }
   638  
   639  // saveConsensusParamsInfo persists the consensus params for the next block to disk.
   640  // It should be called from s.Save(), right before the state itself is persisted.
   641  // If the consensus params did not change after processing the latest block,
   642  // only the last height for which they changed is persisted.
   643  func (store dbStore) saveConsensusParamsInfo(nextHeight, changeHeight int64, params types.ConsensusParams) error {
   644  	paramsInfo := &cmtstate.ConsensusParamsInfo{
   645  		LastHeightChanged: changeHeight,
   646  	}
   647  
   648  	if changeHeight == nextHeight {
   649  		paramsInfo.ConsensusParams = params.ToProto()
   650  	}
   651  	bz, err := paramsInfo.Marshal()
   652  	if err != nil {
   653  		return err
   654  	}
   655  
   656  	err = store.db.Set(calcConsensusParamsKey(nextHeight), bz)
   657  	if err != nil {
   658  		return err
   659  	}
   660  
   661  	return nil
   662  }
   663  
   664  func (store dbStore) Close() error {
   665  	return store.db.Close()
   666  }