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 }