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