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