github.com/vipernet-xyz/tm@v0.34.24/state/store.go (about)

     1  package state
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/gogo/protobuf/proto"
     8  	dbm "github.com/tendermint/tm-db"
     9  
    10  	abci "github.com/vipernet-xyz/tm/abci/types"
    11  	tmmath "github.com/vipernet-xyz/tm/libs/math"
    12  	tmos "github.com/vipernet-xyz/tm/libs/os"
    13  	tmstate "github.com/vipernet-xyz/tm/proto/tendermint/state"
    14  	tmproto "github.com/vipernet-xyz/tm/proto/tendermint/types"
    15  	"github.com/vipernet-xyz/tm/types"
    16  )
    17  
    18  const (
    19  	// persist validators every valSetCheckpointInterval blocks to avoid
    20  	// LoadValidators taking too much time.
    21  	// https://github.com/vipernet-xyz/tm/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) (*tmstate.ABCIResponses, error)
    65  	// LoadLastABCIResponse loads the last abciResponse for a given height
    66  	LoadLastABCIResponse(int64) (*tmstate.ABCIResponses, error)
    67  	// LoadConsensusParams loads the consensus params for a given height
    68  	LoadConsensusParams(int64) (tmproto.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, *tmstate.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/tendermint/tm-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(tmstate.State)
   156  
   157  	err = proto.Unmarshal(buf, sp)
   158  	if err != nil {
   159  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   160  		tmos.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 Tendermint 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/vipernet-xyz/tm/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(&tmproto.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(&tmproto.ConsensusParams{}) {
   318  				p.ConsensusParams, err = store.LoadConsensusParams(h)
   319  				if err != nil {
   320  					return err
   321  				}
   322  
   323  				p.LastHeightChanged = h
   324  				bz, err := p.Marshal()
   325  				if err != nil {
   326  					return err
   327  				}
   328  
   329  				err = batch.Set(calcConsensusParamsKey(h), bz)
   330  				if err != nil {
   331  					return err
   332  				}
   333  			}
   334  		} else {
   335  			err = batch.Delete(calcConsensusParamsKey(h))
   336  			if err != nil {
   337  				return err
   338  			}
   339  		}
   340  
   341  		err = batch.Delete(calcABCIResponsesKey(h))
   342  		if err != nil {
   343  			return err
   344  		}
   345  		pruned++
   346  
   347  		// avoid batches growing too large by flushing to database regularly
   348  		if pruned%1000 == 0 && pruned > 0 {
   349  			err := batch.Write()
   350  			if err != nil {
   351  				return err
   352  			}
   353  			batch.Close()
   354  			batch = store.db.NewBatch()
   355  			defer batch.Close()
   356  		}
   357  	}
   358  
   359  	err = batch.WriteSync()
   360  	if err != nil {
   361  		return err
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  //------------------------------------------------------------------------
   368  
   369  // ABCIResponsesResultsHash returns the root hash of a Merkle tree of
   370  // ResponseDeliverTx responses (see ABCIResults.Hash)
   371  //
   372  // See merkle.SimpleHashFromByteSlices
   373  func ABCIResponsesResultsHash(ar *tmstate.ABCIResponses) []byte {
   374  	return types.NewResults(ar.DeliverTxs).Hash()
   375  }
   376  
   377  // LoadABCIResponses loads the ABCIResponses for the given height from the
   378  // database. If the node has DiscardABCIResponses set to true, ErrABCIResponsesNotPersisted
   379  // is persisted. If not found, ErrNoABCIResponsesForHeight is returned.
   380  func (store dbStore) LoadABCIResponses(height int64) (*tmstate.ABCIResponses, error) {
   381  	if store.DiscardABCIResponses {
   382  		return nil, ErrABCIResponsesNotPersisted
   383  	}
   384  
   385  	buf, err := store.db.Get(calcABCIResponsesKey(height))
   386  	if err != nil {
   387  		return nil, err
   388  	}
   389  	if len(buf) == 0 {
   390  
   391  		return nil, ErrNoABCIResponsesForHeight{height}
   392  	}
   393  
   394  	abciResponses := new(tmstate.ABCIResponses)
   395  	err = abciResponses.Unmarshal(buf)
   396  	if err != nil {
   397  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   398  		tmos.Exit(fmt.Sprintf(`LoadABCIResponses: Data has been corrupted or its spec has
   399                  changed: %v\n`, err))
   400  	}
   401  	// TODO: ensure that buf is completely read.
   402  
   403  	return abciResponses, nil
   404  }
   405  
   406  // LoadLastABCIResponses loads the ABCIResponses from the most recent height.
   407  // The height parameter is used to ensure that the response corresponds to the latest height.
   408  // If not, an error is returned.
   409  //
   410  // This method is used for recovering in the case that we called the Commit ABCI
   411  // method on the application but crashed before persisting the results.
   412  func (store dbStore) LoadLastABCIResponse(height int64) (*tmstate.ABCIResponses, error) {
   413  	bz, err := store.db.Get(lastABCIResponseKey)
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  
   418  	if len(bz) == 0 {
   419  		return nil, errors.New("no last ABCI response has been persisted")
   420  	}
   421  
   422  	abciResponse := new(tmstate.ABCIResponsesInfo)
   423  	err = abciResponse.Unmarshal(bz)
   424  	if err != nil {
   425  		tmos.Exit(fmt.Sprintf(`LoadLastABCIResponses: Data has been corrupted or its spec has
   426  			changed: %v\n`, err))
   427  	}
   428  
   429  	// Here we validate the result by comparing its height to the expected height.
   430  	if height != abciResponse.GetHeight() {
   431  		return nil, errors.New("expected height %d but last stored abci responses was at height %d")
   432  	}
   433  
   434  	return abciResponse.AbciResponses, nil
   435  }
   436  
   437  // SaveABCIResponses persists the ABCIResponses to the database.
   438  // This is useful in case we crash after app.Commit and before s.Save().
   439  // Responses are indexed by height so they can also be loaded later to produce
   440  // Merkle proofs.
   441  //
   442  // CONTRACT: height must be monotonically increasing every time this is called.
   443  func (store dbStore) SaveABCIResponses(height int64, abciResponses *tmstate.ABCIResponses) error {
   444  	var dtxs []*abci.ResponseDeliverTx
   445  	// strip nil values,
   446  	for _, tx := range abciResponses.DeliverTxs {
   447  		if tx != nil {
   448  			dtxs = append(dtxs, tx)
   449  		}
   450  	}
   451  	abciResponses.DeliverTxs = dtxs
   452  
   453  	// If the flag is false then we save the ABCIResponse. This can be used for the /BlockResults
   454  	// query or to reindex an event using the command line.
   455  	if !store.DiscardABCIResponses {
   456  		bz, err := abciResponses.Marshal()
   457  		if err != nil {
   458  			return err
   459  		}
   460  		if err := store.db.Set(calcABCIResponsesKey(height), bz); err != nil {
   461  			return err
   462  		}
   463  	}
   464  
   465  	// We always save the last ABCI response for crash recovery.
   466  	// This overwrites the previous saved ABCI Response.
   467  	response := &tmstate.ABCIResponsesInfo{
   468  		AbciResponses: abciResponses,
   469  		Height:        height,
   470  	}
   471  	bz, err := response.Marshal()
   472  	if err != nil {
   473  		return err
   474  	}
   475  
   476  	return store.db.SetSync(lastABCIResponseKey, bz)
   477  }
   478  
   479  //-----------------------------------------------------------------------------
   480  
   481  // LoadValidators loads the ValidatorSet for a given height.
   482  // Returns ErrNoValSetForHeight if the validator set can't be found for this height.
   483  func (store dbStore) LoadValidators(height int64) (*types.ValidatorSet, error) {
   484  	valInfo, err := loadValidatorsInfo(store.db, height)
   485  	if err != nil {
   486  		return nil, ErrNoValSetForHeight{height}
   487  	}
   488  	if valInfo.ValidatorSet == nil {
   489  		lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged)
   490  		valInfo2, err := loadValidatorsInfo(store.db, lastStoredHeight)
   491  		if err != nil || valInfo2.ValidatorSet == nil {
   492  			return nil,
   493  				fmt.Errorf("couldn't find validators at height %d (height %d was originally requested): %w",
   494  					lastStoredHeight,
   495  					height,
   496  					err,
   497  				)
   498  		}
   499  
   500  		vs, err := types.ValidatorSetFromProto(valInfo2.ValidatorSet)
   501  		if err != nil {
   502  			return nil, err
   503  		}
   504  
   505  		vs.IncrementProposerPriority(tmmath.SafeConvertInt32(height - lastStoredHeight)) // mutate
   506  		vi2, err := vs.ToProto()
   507  		if err != nil {
   508  			return nil, err
   509  		}
   510  
   511  		valInfo2.ValidatorSet = vi2
   512  		valInfo = valInfo2
   513  	}
   514  
   515  	vip, err := types.ValidatorSetFromProto(valInfo.ValidatorSet)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  
   520  	return vip, nil
   521  }
   522  
   523  func lastStoredHeightFor(height, lastHeightChanged int64) int64 {
   524  	checkpointHeight := height - height%valSetCheckpointInterval
   525  	return tmmath.MaxInt64(checkpointHeight, lastHeightChanged)
   526  }
   527  
   528  // CONTRACT: Returned ValidatorsInfo can be mutated.
   529  func loadValidatorsInfo(db dbm.DB, height int64) (*tmstate.ValidatorsInfo, error) {
   530  	buf, err := db.Get(calcValidatorsKey(height))
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  
   535  	if len(buf) == 0 {
   536  		return nil, errors.New("value retrieved from db is empty")
   537  	}
   538  
   539  	v := new(tmstate.ValidatorsInfo)
   540  	err = v.Unmarshal(buf)
   541  	if err != nil {
   542  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   543  		tmos.Exit(fmt.Sprintf(`LoadValidators: Data has been corrupted or its spec has changed:
   544          %v\n`, err))
   545  	}
   546  	// TODO: ensure that buf is completely read.
   547  
   548  	return v, nil
   549  }
   550  
   551  // saveValidatorsInfo persists the validator set.
   552  //
   553  // `height` is the effective height for which the validator is responsible for
   554  // signing. It should be called from s.Save(), right before the state itself is
   555  // persisted.
   556  func (store dbStore) saveValidatorsInfo(height, lastHeightChanged int64, valSet *types.ValidatorSet) error {
   557  	if lastHeightChanged > height {
   558  		return errors.New("lastHeightChanged cannot be greater than ValidatorsInfo height")
   559  	}
   560  	valInfo := &tmstate.ValidatorsInfo{
   561  		LastHeightChanged: lastHeightChanged,
   562  	}
   563  	// Only persist validator set if it was updated or checkpoint height (see
   564  	// valSetCheckpointInterval) is reached.
   565  	if height == lastHeightChanged || height%valSetCheckpointInterval == 0 {
   566  		pv, err := valSet.ToProto()
   567  		if err != nil {
   568  			return err
   569  		}
   570  		valInfo.ValidatorSet = pv
   571  	}
   572  
   573  	bz, err := valInfo.Marshal()
   574  	if err != nil {
   575  		return err
   576  	}
   577  
   578  	err = store.db.Set(calcValidatorsKey(height), bz)
   579  	if err != nil {
   580  		return err
   581  	}
   582  
   583  	return nil
   584  }
   585  
   586  //-----------------------------------------------------------------------------
   587  
   588  // ConsensusParamsInfo represents the latest consensus params, or the last height it changed
   589  
   590  // LoadConsensusParams loads the ConsensusParams for a given height.
   591  func (store dbStore) LoadConsensusParams(height int64) (tmproto.ConsensusParams, error) {
   592  	empty := tmproto.ConsensusParams{}
   593  
   594  	paramsInfo, err := store.loadConsensusParamsInfo(height)
   595  	if err != nil {
   596  		return empty, fmt.Errorf("could not find consensus params for height #%d: %w", height, err)
   597  	}
   598  
   599  	if paramsInfo.ConsensusParams.Equal(&empty) {
   600  		paramsInfo2, err := store.loadConsensusParamsInfo(paramsInfo.LastHeightChanged)
   601  		if err != nil {
   602  			return empty, fmt.Errorf(
   603  				"couldn't find consensus params at height %d as last changed from height %d: %w",
   604  				paramsInfo.LastHeightChanged,
   605  				height,
   606  				err,
   607  			)
   608  		}
   609  
   610  		paramsInfo = paramsInfo2
   611  	}
   612  
   613  	return paramsInfo.ConsensusParams, nil
   614  }
   615  
   616  func (store dbStore) loadConsensusParamsInfo(height int64) (*tmstate.ConsensusParamsInfo, error) {
   617  	buf, err := store.db.Get(calcConsensusParamsKey(height))
   618  	if err != nil {
   619  		return nil, err
   620  	}
   621  	if len(buf) == 0 {
   622  		return nil, errors.New("value retrieved from db is empty")
   623  	}
   624  
   625  	paramsInfo := new(tmstate.ConsensusParamsInfo)
   626  	if err = paramsInfo.Unmarshal(buf); err != nil {
   627  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   628  		tmos.Exit(fmt.Sprintf(`LoadConsensusParams: Data has been corrupted or its spec has changed:
   629                  %v\n`, err))
   630  	}
   631  	// TODO: ensure that buf is completely read.
   632  
   633  	return paramsInfo, nil
   634  }
   635  
   636  // saveConsensusParamsInfo persists the consensus params for the next block to disk.
   637  // It should be called from s.Save(), right before the state itself is persisted.
   638  // If the consensus params did not change after processing the latest block,
   639  // only the last height for which they changed is persisted.
   640  func (store dbStore) saveConsensusParamsInfo(nextHeight, changeHeight int64, params tmproto.ConsensusParams) error {
   641  	paramsInfo := &tmstate.ConsensusParamsInfo{
   642  		LastHeightChanged: changeHeight,
   643  	}
   644  
   645  	if changeHeight == nextHeight {
   646  		paramsInfo.ConsensusParams = params
   647  	}
   648  	bz, err := paramsInfo.Marshal()
   649  	if err != nil {
   650  		return err
   651  	}
   652  
   653  	err = store.db.Set(calcConsensusParamsKey(nextHeight), bz)
   654  	if err != nil {
   655  		return err
   656  	}
   657  
   658  	return nil
   659  }
   660  
   661  func (store dbStore) Close() error {
   662  	return store.db.Close()
   663  }