- package state
-
- import (
- "fmt"
-
- "github.com/gogo/protobuf/proto"
- dbm "github.com/tendermint/tm-db"
-
- abci "github.com/tendermint/tendermint/abci/types"
- "github.com/tendermint/tendermint/crypto/merkle"
- tmmath "github.com/tendermint/tendermint/libs/math"
- tmos "github.com/tendermint/tendermint/libs/os"
- tmstate "github.com/tendermint/tendermint/proto/state"
- tmproto "github.com/tendermint/tendermint/proto/types"
- "github.com/tendermint/tendermint/types"
- )
-
- const (
- // persist validators every valSetCheckpointInterval blocks to avoid
- // LoadValidators taking too much time.
- // https://github.com/tendermint/tendermint/pull/3438
- // 100000 results in ~ 100ms to get 100 validators (see BenchmarkLoadValidators)
- valSetCheckpointInterval = 100000
- )
-
- //------------------------------------------------------------------------
-
- func calcValidatorsKey(height int64) []byte {
- return []byte(fmt.Sprintf("validatorsKey:%v", height))
- }
-
- func calcConsensusParamsKey(height int64) []byte {
- return []byte(fmt.Sprintf("consensusParamsKey:%v", height))
- }
-
- func calcABCIResponsesKey(height int64) []byte {
- return []byte(fmt.Sprintf("abciResponsesKey:%v", height))
- }
-
- // LoadStateFromDBOrGenesisFile loads the most recent state from the database,
- // or creates a new one from the given genesisFilePath and persists the result
- // to the database.
- func LoadStateFromDBOrGenesisFile(stateDB dbm.DB, genesisFilePath string) (State, error) {
- state := LoadState(stateDB)
- if state.IsEmpty() {
- var err error
- state, err = MakeGenesisStateFromFile(genesisFilePath)
- if err != nil {
- return state, err
- }
- SaveState(stateDB, state)
- }
-
- return state, nil
- }
-
- // LoadStateFromDBOrGenesisDoc loads the most recent state from the database,
- // or creates a new one from the given genesisDoc and persists the result
- // to the database.
- func LoadStateFromDBOrGenesisDoc(stateDB dbm.DB, genesisDoc *types.GenesisDoc) (State, error) {
- state := LoadState(stateDB)
-
- if state.IsEmpty() {
- var err error
- state, err = MakeGenesisState(genesisDoc)
- if err != nil {
- return state, err
- }
- SaveState(stateDB, state)
- }
-
- return state, nil
- }
-
- // LoadState loads the State from the database.
- func LoadState(db dbm.DB) State {
- return loadState(db, stateKey)
- }
-
- func loadState(db dbm.DB, key []byte) (state State) {
- buf, err := db.Get(key)
- if err != nil {
- panic(err)
- }
- if len(buf) == 0 {
- return state
- }
-
- sp := new(tmstate.State)
-
- err = proto.Unmarshal(buf, sp)
- if err != nil {
- // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
- tmos.Exit(fmt.Sprintf(`LoadState: Data has been corrupted or its spec has changed:
- %v\n`, err))
- }
-
- sm, err := StateFromProto(sp)
- if err != nil {
- panic(err)
- }
-
- return *sm
- }
-
- // SaveState persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database.
- // This flushes the writes (e.g. calls SetSync).
- func SaveState(db dbm.DB, state State) {
- saveState(db, state, stateKey)
- }
-
- func saveState(db dbm.DB, state State, key []byte) {
- nextHeight := state.LastBlockHeight + 1
- // If first block, save validators for block 1.
- if nextHeight == 1 {
- // This extra logic due to Tendermint validator set changes being delayed 1 block.
- // It may get overwritten due to InitChain validator updates.
- lastHeightVoteChanged := int64(1)
- saveValidatorsInfo(db, nextHeight, lastHeightVoteChanged, state.Validators)
- }
- // Save next validators.
- saveValidatorsInfo(db, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
-
- // Save next consensus params.
- saveConsensusParamsInfo(db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
- err := db.SetSync(key, state.Bytes())
- if err != nil {
- panic(err)
- }
- }
-
- // BootstrapState saves a new state, used e.g. by state sync when starting from non-zero height.
- func BootstrapState(db dbm.DB, state State) error {
- height := state.LastBlockHeight
- saveValidatorsInfo(db, height, height, state.LastValidators)
- saveValidatorsInfo(db, height+1, height+1, state.Validators)
- saveValidatorsInfo(db, height+2, height+2, state.NextValidators)
- saveConsensusParamsInfo(db, height+1, height+1, state.ConsensusParams)
- return db.SetSync(stateKey, state.Bytes())
- }
-
- // PruneStates deletes states between the given heights (including from, excluding to). It is not
- // guaranteed to delete all states, since the last checkpointed state and states being pointed to by
- // e.g. `LastHeightChanged` must remain. The state at to must also exist.
- //
- // The from parameter is necessary since we can't do a key scan in a performant way due to the key
- // encoding not preserving ordering: https://github.com/tendermint/tendermint/issues/4567
- // This will cause some old states to be left behind when doing incremental partial prunes,
- // specifically older checkpoints and LastHeightChanged targets.
- func PruneStates(db dbm.DB, from int64, to int64) error {
- if from <= 0 || to <= 0 {
- return fmt.Errorf("from height %v and to height %v must be greater than 0", from, to)
- }
- if from >= to {
- return fmt.Errorf("from height %v must be lower than to height %v", from, to)
- }
- valInfo := loadValidatorsInfo(db, to)
- if valInfo == nil {
- return fmt.Errorf("validators at height %v not found", to)
- }
- paramsInfo := loadConsensusParamsInfo(db, to)
- if paramsInfo == nil {
- return fmt.Errorf("consensus params at height %v not found", to)
- }
-
- keepVals := make(map[int64]bool)
- if valInfo.ValidatorSet == nil {
- keepVals[valInfo.LastHeightChanged] = true
- keepVals[lastStoredHeightFor(to, valInfo.LastHeightChanged)] = true // keep last checkpoint too
- }
- keepParams := make(map[int64]bool)
- if paramsInfo.ConsensusParams.Equal(&tmproto.ConsensusParams{}) {
- keepParams[paramsInfo.LastHeightChanged] = true
- }
-
- batch := db.NewBatch()
- defer batch.Close()
- pruned := uint64(0)
- var err error
-
- // We have to delete in reverse order, to avoid deleting previous heights that have validator
- // sets and consensus params that we may need to retrieve.
- for h := to - 1; h >= from; h-- {
- // For heights we keep, we must make sure they have the full validator set or consensus
- // params, otherwise they will panic if they're retrieved directly (instead of
- // indirectly via a LastHeightChanged pointer).
- if keepVals[h] {
- v := loadValidatorsInfo(db, h)
- if v.ValidatorSet == nil {
-
- vip, err := LoadValidators(db, h)
- if err != nil {
- return err
- }
-
- pvi, err := vip.ToProto()
- if err != nil {
- return err
- }
-
- v.ValidatorSet = pvi
- v.LastHeightChanged = h
-
- bz, err := v.Marshal()
- if err != nil {
- return err
- }
- batch.Set(calcValidatorsKey(h), bz)
- }
- } else {
- batch.Delete(calcValidatorsKey(h))
- }
-
- if keepParams[h] {
- p := loadConsensusParamsInfo(db, h)
- if p.ConsensusParams.Equal(&tmproto.ConsensusParams{}) {
- p.ConsensusParams, err = LoadConsensusParams(db, h)
- if err != nil {
- return err
- }
- p.LastHeightChanged = h
- bz, err := p.Marshal()
- if err != nil {
- return err
- }
- batch.Set(calcConsensusParamsKey(h), bz)
- }
- } else {
- batch.Delete(calcConsensusParamsKey(h))
- }
-
- batch.Delete(calcABCIResponsesKey(h))
- pruned++
-
- // avoid batches growing too large by flushing to database regularly
- if pruned%1000 == 0 && pruned > 0 {
- err := batch.Write()
- if err != nil {
- return err
- }
- batch.Close()
- batch = db.NewBatch()
- defer batch.Close()
- }
- }
-
- err = batch.WriteSync()
- if err != nil {
- return err
- }
-
- return nil
- }
-
- //------------------------------------------------------------------------
-
- // ABCIResponsesResultsHash returns the root hash of a Merkle tree with 3 leafs:
- // 1) proto encoded ResponseBeginBlock.Events
- // 2) root hash of a Merkle tree of ResponseDeliverTx responses (see ABCIResults.Hash)
- // 3) proto encoded ResponseEndBlock.Events
- //
- // See merkle.SimpleHashFromByteSlices
- func ABCIResponsesResultsHash(ar *tmstate.ABCIResponses) []byte {
- // proto-encode BeginBlock events.
- bbeBytes, err := proto.Marshal(&abci.ResponseBeginBlock{
- Events: ar.BeginBlock.Events,
- })
- if err != nil {
- panic(err)
- }
-
- // Build a Merkle tree of proto-encoded DeliverTx results and get a hash.
- results := types.NewResults(ar.DeliverTxs)
-
- // proto-encode EndBlock events.
- ebeBytes, err := proto.Marshal(&abci.ResponseEndBlock{
- Events: ar.EndBlock.Events,
- })
- if err != nil {
- panic(err)
- }
-
- // Build a Merkle tree out of the above 3 binary slices.
- return merkle.HashFromByteSlices([][]byte{bbeBytes, results.Hash(), ebeBytes})
- }
-
- // LoadABCIResponses loads the ABCIResponses for the given height from the
- // database. If not found, ErrNoABCIResponsesForHeight is returned.
- //
- // This is useful for recovering from crashes where we called app.Commit and
- // before we called s.Save(). It can also be used to produce Merkle proofs of
- // the result of txs.
- func LoadABCIResponses(db dbm.DB, height int64) (*tmstate.ABCIResponses, error) {
- buf, err := db.Get(calcABCIResponsesKey(height))
- if err != nil {
- return nil, err
- }
- if len(buf) == 0 {
-
- return nil, ErrNoABCIResponsesForHeight{height}
- }
-
- abciResponses := new(tmstate.ABCIResponses)
- err = abciResponses.Unmarshal(buf)
- if err != nil {
- // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
- tmos.Exit(fmt.Sprintf(`LoadABCIResponses: Data has been corrupted or its spec has
- changed: %v\n`, err))
- }
- // TODO: ensure that buf is completely read.
-
- return abciResponses, nil
- }
-
- // SaveABCIResponses persists the ABCIResponses to the database.
- // This is useful in case we crash after app.Commit and before s.Save().
- // Responses are indexed by height so they can also be loaded later to produce
- // Merkle proofs.
- //
- // Exposed for testing.
- func SaveABCIResponses(db dbm.DB, height int64, abciResponses *tmstate.ABCIResponses) {
- var dtxs []*abci.ResponseDeliverTx
- //strip nil values,
- for _, tx := range abciResponses.DeliverTxs {
- if tx != nil {
- dtxs = append(dtxs, tx)
- }
- }
- abciResponses.DeliverTxs = dtxs
-
- bz, err := abciResponses.Marshal()
- if err != nil {
- panic(err)
- }
- db.SetSync(calcABCIResponsesKey(height), bz)
- }
-
- //-----------------------------------------------------------------------------
-
- // LoadValidators loads the ValidatorSet for a given height.
- // Returns ErrNoValSetForHeight if the validator set can't be found for this height.
- func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) {
- valInfo := loadValidatorsInfo(db, height)
- if valInfo == nil {
- return nil, ErrNoValSetForHeight{height}
- }
- if valInfo.ValidatorSet == nil {
- lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged)
- valInfo2 := loadValidatorsInfo(db, lastStoredHeight)
- if valInfo2 == nil || valInfo2.ValidatorSet == nil {
- panic(
- fmt.Sprintf("Couldn't find validators at height %d (height %d was originally requested)",
- lastStoredHeight,
- height,
- ),
- )
- }
-
- vs, err := types.ValidatorSetFromProto(valInfo2.ValidatorSet)
- if err != nil {
- return nil, err
- }
-
- vs.IncrementProposerPriority(tmmath.SafeConvertInt32(height - lastStoredHeight)) // mutate
- vi2, err := vs.ToProto()
- if err != nil {
- return nil, err
- }
-
- valInfo2.ValidatorSet = vi2
- valInfo = valInfo2
- }
-
- vip, err := types.ValidatorSetFromProto(valInfo.ValidatorSet)
- if err != nil {
- return nil, err
- }
-
- return vip, nil
- }
-
- func lastStoredHeightFor(height, lastHeightChanged int64) int64 {
- checkpointHeight := height - height%valSetCheckpointInterval
- return tmmath.MaxInt64(checkpointHeight, lastHeightChanged)
- }
-
- // CONTRACT: Returned ValidatorsInfo can be mutated.
- func loadValidatorsInfo(db dbm.DB, height int64) *tmstate.ValidatorsInfo {
- buf, err := db.Get(calcValidatorsKey(height))
- if err != nil {
- panic(err)
- }
-
- if len(buf) == 0 {
- return nil
- }
-
- v := new(tmstate.ValidatorsInfo)
- err = v.Unmarshal(buf)
- if err != nil {
- // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
- tmos.Exit(fmt.Sprintf(`LoadValidators: Data has been corrupted or its spec has changed:
- %v\n`, err))
- }
- // TODO: ensure that buf is completely read.
-
- return v
- }
-
- // saveValidatorsInfo persists the validator set.
- //
- // `height` is the effective height for which the validator is responsible for
- // signing. It should be called from s.Save(), right before the state itself is
- // persisted.
- func saveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *types.ValidatorSet) {
- if lastHeightChanged > height {
- panic("LastHeightChanged cannot be greater than ValidatorsInfo height")
- }
- valInfo := &tmstate.ValidatorsInfo{
- LastHeightChanged: lastHeightChanged,
- }
- // Only persist validator set if it was updated or checkpoint height (see
- // valSetCheckpointInterval) is reached.
- if height == lastHeightChanged || height%valSetCheckpointInterval == 0 {
- pv, err := valSet.ToProto()
- if err != nil {
- panic(err)
- }
- valInfo.ValidatorSet = pv
- }
-
- bz, err := valInfo.Marshal()
- if err != nil {
- panic(err)
- }
-
- db.Set(calcValidatorsKey(height), bz)
- }
-
- //-----------------------------------------------------------------------------
-
- // ConsensusParamsInfo represents the latest consensus params, or the last height it changed
-
- // LoadConsensusParams loads the ConsensusParams for a given height.
- func LoadConsensusParams(db dbm.DB, height int64) (tmproto.ConsensusParams, error) {
- empty := tmproto.ConsensusParams{}
-
- paramsInfo := loadConsensusParamsInfo(db, height)
- if paramsInfo == nil {
- return empty, ErrNoConsensusParamsForHeight{height}
- }
-
- if paramsInfo.ConsensusParams.Equal(&empty) {
- paramsInfo2 := loadConsensusParamsInfo(db, paramsInfo.LastHeightChanged)
- if paramsInfo2 == nil {
- panic(
- fmt.Sprintf(
- "Couldn't find consensus params at height %d as last changed from height %d",
- paramsInfo.LastHeightChanged,
- height,
- ),
- )
- }
-
- paramsInfo = paramsInfo2
- }
-
- return paramsInfo.ConsensusParams, nil
- }
-
- func loadConsensusParamsInfo(db dbm.DB, height int64) *tmstate.ConsensusParamsInfo {
- buf, err := db.Get(calcConsensusParamsKey(height))
- if err != nil {
- panic(err)
- }
- if len(buf) == 0 {
- return nil
- }
-
- paramsInfo := new(tmstate.ConsensusParamsInfo)
- if err = paramsInfo.Unmarshal(buf); err != nil {
- // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
- tmos.Exit(fmt.Sprintf(`LoadConsensusParams: Data has been corrupted or its spec has changed:
- %v\n`, err))
- }
- // TODO: ensure that buf is completely read.
-
- return paramsInfo
- }
-
- // saveConsensusParamsInfo persists the consensus params for the next block to disk.
- // It should be called from s.Save(), right before the state itself is persisted.
- // If the consensus params did not change after processing the latest block,
- // only the last height for which they changed is persisted.
- func saveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params tmproto.ConsensusParams) {
- paramsInfo := &tmstate.ConsensusParamsInfo{
- LastHeightChanged: changeHeight,
- }
-
- if changeHeight == nextHeight {
- paramsInfo.ConsensusParams = params
- }
- bz, err := paramsInfo.Marshal()
- if err != nil {
- panic(err)
- }
-
- db.Set(calcConsensusParamsKey(nextHeight), bz)
- }
|