github.com/evdatsion/aphelion-dpos-bft@v0.32.1/state/store.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  
     6  	abci "github.com/evdatsion/aphelion-dpos-bft/abci/types"
     7  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
     8  	dbm "github.com/evdatsion/aphelion-dpos-bft/libs/db"
     9  	"github.com/evdatsion/aphelion-dpos-bft/types"
    10  )
    11  
    12  const (
    13  	// persist validators every valSetCheckpointInterval blocks to avoid
    14  	// LoadValidators taking too much time.
    15  	// https://github.com/evdatsion/aphelion-dpos-bft/pull/3438
    16  	// 100000 results in ~ 100ms to get 100 validators (see BenchmarkLoadValidators)
    17  	valSetCheckpointInterval = 100000
    18  )
    19  
    20  //------------------------------------------------------------------------
    21  
    22  func calcValidatorsKey(height int64) []byte {
    23  	return []byte(fmt.Sprintf("validatorsKey:%v", height))
    24  }
    25  
    26  func calcConsensusParamsKey(height int64) []byte {
    27  	return []byte(fmt.Sprintf("consensusParamsKey:%v", height))
    28  }
    29  
    30  func calcABCIResponsesKey(height int64) []byte {
    31  	return []byte(fmt.Sprintf("abciResponsesKey:%v", height))
    32  }
    33  
    34  // LoadStateFromDBOrGenesisFile loads the most recent state from the database,
    35  // or creates a new one from the given genesisFilePath and persists the result
    36  // to the database.
    37  func LoadStateFromDBOrGenesisFile(stateDB dbm.DB, genesisFilePath string) (State, error) {
    38  	state := LoadState(stateDB)
    39  	if state.IsEmpty() {
    40  		var err error
    41  		state, err = MakeGenesisStateFromFile(genesisFilePath)
    42  		if err != nil {
    43  			return state, err
    44  		}
    45  		SaveState(stateDB, state)
    46  	}
    47  
    48  	return state, nil
    49  }
    50  
    51  // LoadStateFromDBOrGenesisDoc loads the most recent state from the database,
    52  // or creates a new one from the given genesisDoc and persists the result
    53  // to the database.
    54  func LoadStateFromDBOrGenesisDoc(stateDB dbm.DB, genesisDoc *types.GenesisDoc) (State, error) {
    55  	state := LoadState(stateDB)
    56  	if state.IsEmpty() {
    57  		var err error
    58  		state, err = MakeGenesisState(genesisDoc)
    59  		if err != nil {
    60  			return state, err
    61  		}
    62  		SaveState(stateDB, state)
    63  	}
    64  
    65  	return state, nil
    66  }
    67  
    68  // LoadState loads the State from the database.
    69  func LoadState(db dbm.DB) State {
    70  	return loadState(db, stateKey)
    71  }
    72  
    73  func loadState(db dbm.DB, key []byte) (state State) {
    74  	buf := db.Get(key)
    75  	if len(buf) == 0 {
    76  		return state
    77  	}
    78  
    79  	err := cdc.UnmarshalBinaryBare(buf, &state)
    80  	if err != nil {
    81  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
    82  		cmn.Exit(fmt.Sprintf(`LoadState: Data has been corrupted or its spec has changed:
    83                  %v\n`, err))
    84  	}
    85  	// TODO: ensure that buf is completely read.
    86  
    87  	return state
    88  }
    89  
    90  // SaveState persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database.
    91  // This flushes the writes (e.g. calls SetSync).
    92  func SaveState(db dbm.DB, state State) {
    93  	saveState(db, state, stateKey)
    94  }
    95  
    96  func saveState(db dbm.DB, state State, key []byte) {
    97  	nextHeight := state.LastBlockHeight + 1
    98  	// If first block, save validators for block 1.
    99  	if nextHeight == 1 {
   100  		// This extra logic due to Tendermint validator set changes being delayed 1 block.
   101  		// It may get overwritten due to InitChain validator updates.
   102  		lastHeightVoteChanged := int64(1)
   103  		saveValidatorsInfo(db, nextHeight, lastHeightVoteChanged, state.Validators)
   104  	}
   105  	// Save next validators.
   106  	saveValidatorsInfo(db, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
   107  	// Save next consensus params.
   108  	saveConsensusParamsInfo(db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
   109  	db.SetSync(key, state.Bytes())
   110  }
   111  
   112  //------------------------------------------------------------------------
   113  
   114  // ABCIResponses retains the responses
   115  // of the various ABCI calls during block processing.
   116  // It is persisted to disk for each height before calling Commit.
   117  type ABCIResponses struct {
   118  	DeliverTx  []*abci.ResponseDeliverTx `json:"deliver_tx"`
   119  	EndBlock   *abci.ResponseEndBlock    `json:"end_block"`
   120  	BeginBlock *abci.ResponseBeginBlock  `json:"begin_block"`
   121  }
   122  
   123  // NewABCIResponses returns a new ABCIResponses
   124  func NewABCIResponses(block *types.Block) *ABCIResponses {
   125  	resDeliverTxs := make([]*abci.ResponseDeliverTx, block.NumTxs)
   126  	if block.NumTxs == 0 {
   127  		// This makes Amino encoding/decoding consistent.
   128  		resDeliverTxs = nil
   129  	}
   130  	return &ABCIResponses{
   131  		DeliverTx: resDeliverTxs,
   132  	}
   133  }
   134  
   135  // Bytes serializes the ABCIResponse using go-amino.
   136  func (arz *ABCIResponses) Bytes() []byte {
   137  	return cdc.MustMarshalBinaryBare(arz)
   138  }
   139  
   140  func (arz *ABCIResponses) ResultsHash() []byte {
   141  	results := types.NewResults(arz.DeliverTx)
   142  	return results.Hash()
   143  }
   144  
   145  // LoadABCIResponses loads the ABCIResponses for the given height from the database.
   146  // This is useful for recovering from crashes where we called app.Commit and before we called
   147  // s.Save(). It can also be used to produce Merkle proofs of the result of txs.
   148  func LoadABCIResponses(db dbm.DB, height int64) (*ABCIResponses, error) {
   149  	buf := db.Get(calcABCIResponsesKey(height))
   150  	if len(buf) == 0 {
   151  		return nil, ErrNoABCIResponsesForHeight{height}
   152  	}
   153  
   154  	abciResponses := new(ABCIResponses)
   155  	err := cdc.UnmarshalBinaryBare(buf, abciResponses)
   156  	if err != nil {
   157  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   158  		cmn.Exit(fmt.Sprintf(`LoadABCIResponses: Data has been corrupted or its spec has
   159                  changed: %v\n`, err))
   160  	}
   161  	// TODO: ensure that buf is completely read.
   162  
   163  	return abciResponses, nil
   164  }
   165  
   166  // SaveABCIResponses persists the ABCIResponses to the database.
   167  // This is useful in case we crash after app.Commit and before s.Save().
   168  // Responses are indexed by height so they can also be loaded later to produce Merkle proofs.
   169  func saveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) {
   170  	db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes())
   171  }
   172  
   173  //-----------------------------------------------------------------------------
   174  
   175  // ValidatorsInfo represents the latest validator set, or the last height it changed
   176  type ValidatorsInfo struct {
   177  	ValidatorSet      *types.ValidatorSet
   178  	LastHeightChanged int64
   179  }
   180  
   181  // Bytes serializes the ValidatorsInfo using go-amino.
   182  func (valInfo *ValidatorsInfo) Bytes() []byte {
   183  	return cdc.MustMarshalBinaryBare(valInfo)
   184  }
   185  
   186  // LoadValidators loads the ValidatorSet for a given height.
   187  // Returns ErrNoValSetForHeight if the validator set can't be found for this height.
   188  func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) {
   189  	valInfo := loadValidatorsInfo(db, height)
   190  	if valInfo == nil {
   191  		return nil, ErrNoValSetForHeight{height}
   192  	}
   193  	if valInfo.ValidatorSet == nil {
   194  		lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged)
   195  		valInfo2 := loadValidatorsInfo(db, lastStoredHeight)
   196  		if valInfo2 == nil || valInfo2.ValidatorSet == nil {
   197  			// TODO (melekes): remove the below if condition in the 0.33 major
   198  			// release and just panic. Old chains might panic otherwise if they
   199  			// haven't saved validators at intermediate (%valSetCheckpointInterval)
   200  			// height yet.
   201  			// https://github.com/evdatsion/aphelion-dpos-bft/issues/3543
   202  			valInfo2 = loadValidatorsInfo(db, valInfo.LastHeightChanged)
   203  			lastStoredHeight = valInfo.LastHeightChanged
   204  			if valInfo2 == nil || valInfo2.ValidatorSet == nil {
   205  				panic(
   206  					fmt.Sprintf("Couldn't find validators at height %d (height %d was originally requested)",
   207  						lastStoredHeight,
   208  						height,
   209  					),
   210  				)
   211  			}
   212  		}
   213  		valInfo2.ValidatorSet.IncrementProposerPriority(int(height - lastStoredHeight)) // mutate
   214  		valInfo = valInfo2
   215  	}
   216  
   217  	return valInfo.ValidatorSet, nil
   218  }
   219  
   220  func lastStoredHeightFor(height, lastHeightChanged int64) int64 {
   221  	checkpointHeight := height - height%valSetCheckpointInterval
   222  	return cmn.MaxInt64(checkpointHeight, lastHeightChanged)
   223  }
   224  
   225  // CONTRACT: Returned ValidatorsInfo can be mutated.
   226  func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo {
   227  	buf := db.Get(calcValidatorsKey(height))
   228  	if len(buf) == 0 {
   229  		return nil
   230  	}
   231  
   232  	v := new(ValidatorsInfo)
   233  	err := cdc.UnmarshalBinaryBare(buf, v)
   234  	if err != nil {
   235  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   236  		cmn.Exit(fmt.Sprintf(`LoadValidators: Data has been corrupted or its spec has changed:
   237                  %v\n`, err))
   238  	}
   239  	// TODO: ensure that buf is completely read.
   240  
   241  	return v
   242  }
   243  
   244  // saveValidatorsInfo persists the validator set.
   245  //
   246  // `height` is the effective height for which the validator is responsible for
   247  // signing. It should be called from s.Save(), right before the state itself is
   248  // persisted.
   249  func saveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *types.ValidatorSet) {
   250  	if lastHeightChanged > height {
   251  		panic("LastHeightChanged cannot be greater than ValidatorsInfo height")
   252  	}
   253  	valInfo := &ValidatorsInfo{
   254  		LastHeightChanged: lastHeightChanged,
   255  	}
   256  	// Only persist validator set if it was updated or checkpoint height (see
   257  	// valSetCheckpointInterval) is reached.
   258  	if height == lastHeightChanged || height%valSetCheckpointInterval == 0 {
   259  		valInfo.ValidatorSet = valSet
   260  	}
   261  	db.Set(calcValidatorsKey(height), valInfo.Bytes())
   262  }
   263  
   264  //-----------------------------------------------------------------------------
   265  
   266  // ConsensusParamsInfo represents the latest consensus params, or the last height it changed
   267  type ConsensusParamsInfo struct {
   268  	ConsensusParams   types.ConsensusParams
   269  	LastHeightChanged int64
   270  }
   271  
   272  // Bytes serializes the ConsensusParamsInfo using go-amino.
   273  func (params ConsensusParamsInfo) Bytes() []byte {
   274  	return cdc.MustMarshalBinaryBare(params)
   275  }
   276  
   277  // LoadConsensusParams loads the ConsensusParams for a given height.
   278  func LoadConsensusParams(db dbm.DB, height int64) (types.ConsensusParams, error) {
   279  	empty := types.ConsensusParams{}
   280  
   281  	paramsInfo := loadConsensusParamsInfo(db, height)
   282  	if paramsInfo == nil {
   283  		return empty, ErrNoConsensusParamsForHeight{height}
   284  	}
   285  
   286  	if paramsInfo.ConsensusParams.Equals(&empty) {
   287  		paramsInfo2 := loadConsensusParamsInfo(db, paramsInfo.LastHeightChanged)
   288  		if paramsInfo2 == nil {
   289  			panic(
   290  				fmt.Sprintf(
   291  					"Couldn't find consensus params at height %d as last changed from height %d",
   292  					paramsInfo.LastHeightChanged,
   293  					height,
   294  				),
   295  			)
   296  		}
   297  		paramsInfo = paramsInfo2
   298  	}
   299  
   300  	return paramsInfo.ConsensusParams, nil
   301  }
   302  
   303  func loadConsensusParamsInfo(db dbm.DB, height int64) *ConsensusParamsInfo {
   304  	buf := db.Get(calcConsensusParamsKey(height))
   305  	if len(buf) == 0 {
   306  		return nil
   307  	}
   308  
   309  	paramsInfo := new(ConsensusParamsInfo)
   310  	err := cdc.UnmarshalBinaryBare(buf, paramsInfo)
   311  	if err != nil {
   312  		// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
   313  		cmn.Exit(fmt.Sprintf(`LoadConsensusParams: Data has been corrupted or its spec has changed:
   314                  %v\n`, err))
   315  	}
   316  	// TODO: ensure that buf is completely read.
   317  
   318  	return paramsInfo
   319  }
   320  
   321  // saveConsensusParamsInfo persists the consensus params for the next block to disk.
   322  // It should be called from s.Save(), right before the state itself is persisted.
   323  // If the consensus params did not change after processing the latest block,
   324  // only the last height for which they changed is persisted.
   325  func saveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params types.ConsensusParams) {
   326  	paramsInfo := &ConsensusParamsInfo{
   327  		LastHeightChanged: changeHeight,
   328  	}
   329  	if changeHeight == nextHeight {
   330  		paramsInfo.ConsensusParams = params
   331  	}
   332  	db.Set(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes())
   333  }