github.com/Finschia/ostracon@v1.1.5/state/store.go (about) 1 package state 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/gogo/protobuf/proto" 8 abci "github.com/tendermint/tendermint/abci/types" 9 tmstate "github.com/tendermint/tendermint/proto/tendermint/state" 10 tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 11 dbm "github.com/tendermint/tm-db" 12 13 tmmath "github.com/Finschia/ostracon/libs/math" 14 tmos "github.com/Finschia/ostracon/libs/os" 15 ocstate "github.com/Finschia/ostracon/proto/ostracon/state" 16 "github.com/Finschia/ostracon/types" 17 ) 18 19 const ( 20 // persist validators every valSetCheckpointInterval blocks to avoid 21 // LoadValidators taking too much time. 22 // https://github.com/tendermint/tendermint/pull/3438 23 // 100000 results in ~ 100ms to get 100 validators (see BenchmarkLoadValidators) 24 valSetCheckpointInterval = 100000 25 ) 26 27 //------------------------------------------------------------------------ 28 29 func calcValidatorsKey(height int64) []byte { 30 return []byte(fmt.Sprintf("validatorsKey:%v", height)) 31 } 32 33 func calcProofHashKey(height int64) []byte { 34 return []byte(fmt.Sprintf("proofHashKey:%v", height)) 35 } 36 37 func calcConsensusParamsKey(height int64) []byte { 38 return []byte(fmt.Sprintf("consensusParamsKey:%v", height)) 39 } 40 41 func calcABCIResponsesKey(height int64) []byte { 42 return []byte(fmt.Sprintf("abciResponsesKey:%v", height)) 43 } 44 45 //---------------------- 46 47 var ( 48 lastABCIResponseKey = []byte("lastABCIResponseKey") 49 ) 50 51 //go:generate ../scripts/mockery_generate.sh Store 52 53 // Store defines the state store interface 54 // 55 // It is used to retrieve current state and save and load ABCI responses, 56 // validators and consensus parameters 57 type Store interface { 58 // LoadFromDBOrGenesisFile loads the most recent state. 59 // If the chain is new it will use the genesis file from the provided genesis file path as the current state. 60 LoadFromDBOrGenesisFile(string) (State, error) 61 // LoadFromDBOrGenesisDoc loads the most recent state. 62 // If the chain is new it will use the genesis doc as the current state. 63 LoadFromDBOrGenesisDoc(*types.GenesisDoc) (State, error) 64 // Load loads the current state of the blockchain 65 Load() (State, error) 66 // LoadValidators loads the validator set at a given height 67 LoadValidators(int64) (*types.ValidatorSet, error) 68 // LoadProofHash loads the proof hash at a given height 69 LoadProofHash(int64) ([]byte, error) 70 // LoadABCIResponses loads the abciResponse for a given height 71 LoadABCIResponses(int64) (*tmstate.ABCIResponses, error) 72 // LoadLastABCIResponse loads the last abciResponse for a given height 73 LoadLastABCIResponse(int64) (*tmstate.ABCIResponses, error) 74 // LoadConsensusParams loads the consensus params for a given height 75 LoadConsensusParams(int64) (tmproto.ConsensusParams, error) 76 // Save overwrites the previous state with the updated one 77 Save(State) error 78 // SaveABCIResponses saves ABCIResponses for a given height 79 SaveABCIResponses(int64, *tmstate.ABCIResponses) error 80 // Bootstrap is used for bootstrapping state when not starting from a initial height. 81 Bootstrap(State) error 82 // PruneStates takes the height from which to start prning and which height stop at 83 PruneStates(int64, int64) error 84 // Close closes the connection with the database 85 Close() error 86 } 87 88 // dbStore wraps a db (github.com/tendermint/tm-db) 89 type dbStore struct { 90 db dbm.DB 91 92 StoreOptions 93 } 94 95 type StoreOptions struct { 96 97 // DiscardABCIResponses determines whether or not the store 98 // retains all ABCIResponses. If DiscardABCiResponses is enabled, 99 // the store will maintain only the response object from the latest 100 // height. 101 DiscardABCIResponses bool 102 } 103 104 var _ Store = (*dbStore)(nil) 105 106 // NewStore creates the dbStore of the state pkg. 107 func NewStore(db dbm.DB, options StoreOptions) Store { 108 return dbStore{db, options} 109 } 110 111 // LoadFromDBOrGenesisFile loads the most recent state from the database, 112 // or creates a new one from the given genesisFilePath. 113 func (store dbStore) LoadFromDBOrGenesisFile(genesisFilePath string) (State, error) { 114 state, err := store.Load() 115 if err != nil { 116 return State{}, err 117 } 118 if state.IsEmpty() { 119 var err error 120 state, err = MakeGenesisStateFromFile(genesisFilePath) 121 if err != nil { 122 return state, err 123 } 124 } 125 126 return state, nil 127 } 128 129 // LoadFromDBOrGenesisDoc loads the most recent state from the database, 130 // or creates a new one from the given genesisDoc. 131 func (store dbStore) LoadFromDBOrGenesisDoc(genesisDoc *types.GenesisDoc) (State, error) { 132 state, err := store.Load() 133 if err != nil { 134 return State{}, err 135 } 136 137 if state.IsEmpty() { 138 var err error 139 state, err = MakeGenesisState(genesisDoc) 140 if err != nil { 141 return state, err 142 } 143 } 144 145 return state, nil 146 } 147 148 // Load loads the State from the database. 149 func (store dbStore) Load() (State, error) { 150 return store.loadState(stateKey) 151 } 152 153 func (store dbStore) loadState(key []byte) (state State, err error) { 154 buf, err := store.db.Get(key) 155 if err != nil { 156 return state, err 157 } 158 if len(buf) == 0 { 159 return state, nil 160 } 161 162 sp := new(ocstate.State) 163 164 err = proto.Unmarshal(buf, sp) 165 if err != nil { 166 // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED 167 tmos.Exit(fmt.Sprintf(`LoadState: Data has been corrupted or its spec has changed: 168 %v\n`, err)) 169 } 170 171 sm, err := FromProto(sp) 172 if err != nil { 173 return state, err 174 } 175 176 return *sm, nil 177 } 178 179 // Save persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database. 180 // This flushes the writes (e.g. calls SetSync). 181 func (store dbStore) Save(state State) error { 182 return store.save(state, stateKey) 183 } 184 185 func (store dbStore) save(state State, key []byte) error { 186 nextHeight := state.LastBlockHeight + 1 187 // If first block, save validators for the block. 188 if nextHeight == 1 { 189 nextHeight = state.InitialHeight 190 // This extra logic due to Ostracon validator set changes being delayed 1 block. 191 // It may get overwritten due to InitChain validator updates. 192 if err := store.saveValidatorsInfo(nextHeight, nextHeight, state.Validators); err != nil { 193 return err 194 } 195 } 196 // Save next validators. 197 if err := store.saveValidatorsInfo(nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators); err != nil { 198 return err 199 } 200 201 // Save current consensus params. 202 if err := store.saveConsensusParamsInfo(nextHeight, 203 state.LastHeightConsensusParamsChanged, state.ConsensusParams); err != nil { 204 return err 205 } 206 207 // Save current proof hash 208 if err := store.saveProofHash(nextHeight, state.LastProofHash); err != nil { 209 return err 210 } 211 212 err := store.db.SetSync(key, state.Bytes()) 213 if err != nil { 214 return err 215 } 216 return nil 217 } 218 219 // Bootstrap saves a new state, used e.g. by state sync when starting from non-zero height. 220 func (store dbStore) Bootstrap(state State) error { 221 height := state.LastBlockHeight + 1 222 if height == 1 { 223 height = state.InitialHeight 224 } 225 226 if err := store.saveValidatorsInfo(height, height, state.Validators); err != nil { 227 return err 228 } 229 230 if err := store.saveValidatorsInfo(height+1, height+1, state.NextValidators); err != nil { 231 return err 232 } 233 234 if err := store.saveConsensusParamsInfo(height, 235 state.LastHeightConsensusParamsChanged, state.ConsensusParams); err != nil { 236 return err 237 } 238 239 if err := store.saveProofHash(height, state.LastProofHash); err != nil { 240 return err 241 } 242 return store.db.SetSync(stateKey, state.Bytes()) 243 } 244 245 // PruneStates deletes states between the given heights (including from, excluding to). It is not 246 // guaranteed to delete all states, since the last checkpointed state and states being pointed to by 247 // e.g. `LastHeightChanged` must remain. The state at to must also exist. 248 // 249 // The from parameter is necessary since we can't do a key scan in a performant way due to the key 250 // encoding not preserving ordering: https://github.com/tendermint/tendermint/issues/4567 251 // This will cause some old states to be left behind when doing incremental partial prunes, 252 // specifically older checkpoints and LastHeightChanged targets. 253 func (store dbStore) PruneStates(from int64, to int64) error { 254 if from <= 0 || to <= 0 { 255 return fmt.Errorf("from height %v and to height %v must be greater than 0", from, to) 256 } 257 if from >= to { 258 return fmt.Errorf("from height %v must be lower than to height %v", from, to) 259 } 260 valInfo, err := loadValidatorsInfo(store.db, to) 261 if err != nil { 262 return fmt.Errorf("validators at height %v not found: %w", to, err) 263 } 264 paramsInfo, err := store.loadConsensusParamsInfo(to) 265 if err != nil { 266 return fmt.Errorf("consensus params at height %v not found: %w", to, err) 267 } 268 269 keepVals := make(map[int64]bool) 270 if valInfo.ValidatorSet == nil { 271 keepVals[valInfo.LastHeightChanged] = true 272 keepVals[lastStoredHeightFor(to, valInfo.LastHeightChanged)] = true // keep last checkpoint too 273 } 274 keepParams := make(map[int64]bool) 275 if paramsInfo.ConsensusParams.Equal(&tmproto.ConsensusParams{}) { 276 keepParams[paramsInfo.LastHeightChanged] = true 277 } 278 279 batch := store.db.NewBatch() 280 defer batch.Close() 281 pruned := uint64(0) 282 283 // We have to delete in reverse order, to avoid deleting previous heights that have validator 284 // sets and consensus params that we may need to retrieve. 285 for h := to - 1; h >= from; h-- { 286 // For heights we keep, we must make sure they have the full validator set or consensus 287 // params, otherwise they will panic if they're retrieved directly (instead of 288 // indirectly via a LastHeightChanged pointer). 289 if keepVals[h] { 290 v, err := loadValidatorsInfo(store.db, h) 291 if err != nil || v.ValidatorSet == nil { 292 vip, err := store.LoadValidators(h) 293 if err != nil { 294 return err 295 } 296 297 pvi, err := vip.ToProto() 298 if err != nil { 299 return err 300 } 301 302 v.ValidatorSet = pvi 303 v.LastHeightChanged = h 304 305 bz, err := v.Marshal() 306 if err != nil { 307 return err 308 } 309 err = batch.Set(calcValidatorsKey(h), bz) 310 if err != nil { 311 return err 312 } 313 } 314 } else { 315 err = batch.Delete(calcValidatorsKey(h)) 316 if err != nil { 317 return err 318 } 319 err = batch.Delete(calcProofHashKey(h)) 320 if err != nil { 321 return err 322 } 323 } 324 325 if keepParams[h] { 326 p, err := store.loadConsensusParamsInfo(h) 327 if err != nil { 328 return err 329 } 330 331 if p.ConsensusParams.Equal(&tmproto.ConsensusParams{}) { 332 p.ConsensusParams, err = store.LoadConsensusParams(h) 333 if err != nil { 334 return err 335 } 336 337 p.LastHeightChanged = h 338 bz, err := p.Marshal() 339 if err != nil { 340 return err 341 } 342 343 err = batch.Set(calcConsensusParamsKey(h), bz) 344 if err != nil { 345 return err 346 } 347 } 348 } else { 349 err = batch.Delete(calcConsensusParamsKey(h)) 350 if err != nil { 351 return err 352 } 353 } 354 355 err = batch.Delete(calcABCIResponsesKey(h)) 356 if err != nil { 357 return err 358 } 359 pruned++ 360 361 // avoid batches growing too large by flushing to database regularly 362 if pruned%1000 == 0 && pruned > 0 { 363 err := batch.Write() 364 if err != nil { 365 return err 366 } 367 batch.Close() 368 batch = store.db.NewBatch() 369 defer batch.Close() 370 } 371 } 372 373 err = batch.WriteSync() 374 if err != nil { 375 return err 376 } 377 378 return nil 379 } 380 381 //------------------------------------------------------------------------ 382 383 // ABCIResponsesResultsHash returns the root hash of a Merkle tree of 384 // ResponseDeliverTx responses (see ABCIResults.Hash) 385 // 386 // See merkle.SimpleHashFromByteSlices 387 func ABCIResponsesResultsHash(ar *tmstate.ABCIResponses) []byte { 388 return types.NewResults(ar.DeliverTxs).Hash() 389 } 390 391 // LoadABCIResponses loads the ABCIResponses for the given height from the 392 // database. If the node has DiscardABCIResponses set to true, ErrABCIResponsesNotPersisted 393 // is persisted. If not found, ErrNoABCIResponsesForHeight is returned. 394 func (store dbStore) LoadABCIResponses(height int64) (*tmstate.ABCIResponses, error) { 395 if store.DiscardABCIResponses { 396 return nil, ErrABCIResponsesNotPersisted 397 } 398 399 buf, err := store.db.Get(calcABCIResponsesKey(height)) 400 if err != nil { 401 return nil, err 402 } 403 if len(buf) == 0 { 404 405 return nil, ErrNoABCIResponsesForHeight{height} 406 } 407 408 abciResponses := new(tmstate.ABCIResponses) 409 err = abciResponses.Unmarshal(buf) 410 if err != nil { 411 // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED 412 tmos.Exit(fmt.Sprintf(`LoadABCIResponses: Data has been corrupted or its spec has 413 changed: %v\n`, err)) 414 } 415 // TODO: ensure that buf is completely read. 416 417 return abciResponses, nil 418 } 419 420 // LoadLastABCIResponses loads the ABCIResponses from the most recent height. 421 // The height parameter is used to ensure that the response corresponds to the latest height. 422 // If not, an error is returned. 423 // 424 // This method is used for recovering in the case that we called the Commit ABCI 425 // method on the application but crashed before persisting the results. 426 func (store dbStore) LoadLastABCIResponse(height int64) (*tmstate.ABCIResponses, error) { 427 bz, err := store.db.Get(lastABCIResponseKey) 428 if err != nil { 429 return nil, err 430 } 431 432 if len(bz) == 0 { 433 return nil, errors.New("no last ABCI response has been persisted") 434 } 435 436 abciResponse := new(tmstate.ABCIResponsesInfo) 437 err = abciResponse.Unmarshal(bz) 438 if err != nil { 439 tmos.Exit(fmt.Sprintf(`LoadLastABCIResponses: Data has been corrupted or its spec has 440 changed: %v\n`, err)) 441 } 442 443 // Here we validate the result by comparing its height to the expected height. 444 if height != abciResponse.GetHeight() { 445 return nil, errors.New("expected height %d but last stored abci responses was at height %d") 446 } 447 448 return abciResponse.AbciResponses, nil 449 } 450 451 // SaveABCIResponses persists the ABCIResponses to the database. 452 // This is useful in case we crash after app.Commit and before s.Save(). 453 // Responses are indexed by height so they can also be loaded later to produce 454 // Merkle proofs. 455 // 456 // CONTRACT: height must be monotonically increasing every time this is called. 457 func (store dbStore) SaveABCIResponses(height int64, abciResponses *tmstate.ABCIResponses) error { 458 var dtxs []*abci.ResponseDeliverTx 459 // strip nil values, 460 for _, tx := range abciResponses.DeliverTxs { 461 if tx != nil { 462 dtxs = append(dtxs, tx) 463 } 464 } 465 abciResponses.DeliverTxs = dtxs 466 467 // If the flag is false then we save the ABCIResponse. This can be used for the /BlockResults 468 // query or to reindex an event using the command line. 469 if !store.DiscardABCIResponses { 470 bz, err := abciResponses.Marshal() 471 if err != nil { 472 return err 473 } 474 if err := store.db.Set(calcABCIResponsesKey(height), bz); err != nil { 475 return err 476 } 477 } 478 479 // We always save the last ABCI response for crash recovery. 480 // This overwrites the previous saved ABCI Response. 481 response := &tmstate.ABCIResponsesInfo{ 482 AbciResponses: abciResponses, 483 Height: height, 484 } 485 bz, err := response.Marshal() 486 if err != nil { 487 return err 488 } 489 490 return store.db.SetSync(lastABCIResponseKey, bz) 491 } 492 493 //----------------------------------------------------------------------------- 494 495 // LoadValidators loads the ValidatorSet for a given height. 496 // Returns ErrNoValSetForHeight if the validator set can't be found for this height. 497 func (store dbStore) LoadValidators(height int64) (*types.ValidatorSet, error) { 498 if height == 0 { 499 return nil, ErrNoValSetForHeight{height} 500 } 501 valInfo, err := loadValidatorsInfo(store.db, height) 502 if err != nil || valInfo == nil { 503 return nil, ErrNoValSetForHeight{height} 504 } 505 if valInfo.ValidatorSet == nil { 506 lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged) 507 valInfo2, err := loadValidatorsInfo(store.db, lastStoredHeight) 508 if err != nil || valInfo2 == nil || valInfo2.ValidatorSet == nil { 509 return nil, 510 fmt.Errorf("couldn't find validators at height %d (height %d was originally requested): %w", 511 lastStoredHeight, 512 height, 513 err, 514 ) 515 } 516 517 vs, err := types.ValidatorSetFromProto(valInfo2.ValidatorSet) 518 if err != nil { 519 return nil, err 520 } 521 522 vs.IncrementProposerPriority(tmmath.SafeConvertInt32(height - lastStoredHeight)) // mutate 523 vi2, err := vs.ToProto() 524 if err != nil { 525 return nil, err 526 } 527 528 valInfo2.ValidatorSet = vi2 529 valInfo = valInfo2 530 } 531 532 vip, err := types.ValidatorSetFromProto(valInfo.ValidatorSet) 533 if err != nil { 534 return nil, err 535 } 536 537 return vip, nil 538 } 539 540 func (store dbStore) LoadProofHash(height int64) ([]byte, error) { 541 if height == 0 { 542 return nil, ErrNoValSetForHeight{height} 543 } 544 proofHash, err := loadProofHash(store.db, height) 545 if err != nil { 546 return nil, ErrNoProofHashForHeight{height} 547 } 548 549 return proofHash, nil 550 } 551 552 func lastStoredHeightFor(height, lastHeightChanged int64) int64 { 553 checkpointHeight := height - height%valSetCheckpointInterval 554 return tmmath.MaxInt64(checkpointHeight, lastHeightChanged) 555 } 556 557 // CONTRACT: Returned ValidatorsInfo can be mutated. 558 func loadValidatorsInfo(db dbm.DB, height int64) (*tmstate.ValidatorsInfo, error) { 559 buf, err := db.Get(calcValidatorsKey(height)) 560 if err != nil { 561 return nil, err 562 } 563 564 if len(buf) == 0 { 565 return nil, errors.New("value retrieved from db is empty") 566 } 567 568 v := new(tmstate.ValidatorsInfo) 569 err = v.Unmarshal(buf) 570 if err != nil { 571 // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED 572 tmos.Exit(fmt.Sprintf(`LoadValidators: Data has been corrupted or its spec has changed: 573 %v\n`, err)) 574 } 575 // TODO: ensure that buf is completely read. 576 577 return v, nil 578 } 579 580 // saveValidatorsInfo persists the validator set. 581 // 582 // `height` is the effective height for which the validator is responsible for 583 // signing. It should be called from s.Save(), right before the state itself is 584 // persisted. 585 func (store dbStore) saveValidatorsInfo(height, lastHeightChanged int64, valSet *types.ValidatorSet) error { 586 if lastHeightChanged > height { 587 return errors.New("lastHeightChanged cannot be greater than ValidatorsInfo height") 588 } 589 valInfo := &tmstate.ValidatorsInfo{ 590 LastHeightChanged: lastHeightChanged, 591 } 592 // Only persist validator set if it was updated or checkpoint height (see 593 // valSetCheckpointInterval) is reached. 594 if height == lastHeightChanged || height%valSetCheckpointInterval == 0 { 595 pv, err := valSet.ToProto() 596 if err != nil { 597 return err 598 } 599 valInfo.ValidatorSet = pv 600 } 601 602 bz, err := valInfo.Marshal() 603 if err != nil { 604 return err 605 } 606 607 err = store.db.Set(calcValidatorsKey(height), bz) 608 if err != nil { 609 return err 610 } 611 612 return nil 613 } 614 615 func loadProofHash(db dbm.DB, height int64) ([]byte, error) { 616 buf, err := db.Get(calcProofHashKey(height)) 617 if err != nil { 618 // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED 619 tmos.Exit(fmt.Sprintf(`LoadValidators: ProofHash has been corrupted or its spec has changed: 620 %v\n`, err)) 621 } 622 if len(buf) == 0 { 623 return nil, ErrNoProofHashForHeight{height} 624 } 625 626 return buf, nil 627 } 628 629 func (store dbStore) saveProofHash(height int64, proofHash []byte) error { 630 if err := store.db.Set(calcProofHashKey(height), proofHash); err != nil { 631 return err 632 } 633 return nil 634 } 635 636 //----------------------------------------------------------------------------- 637 638 // ConsensusParamsInfo represents the latest consensus params, or the last height it changed 639 640 // LoadConsensusParams loads the ConsensusParams for a given height. 641 func (store dbStore) LoadConsensusParams(height int64) (tmproto.ConsensusParams, error) { 642 empty := tmproto.ConsensusParams{} 643 644 paramsInfo, err := store.loadConsensusParamsInfo(height) 645 if err != nil { 646 return empty, fmt.Errorf("could not find consensus params for height #%d: %w", height, err) 647 } 648 649 if paramsInfo.ConsensusParams.Equal(&empty) { 650 paramsInfo2, err := store.loadConsensusParamsInfo(paramsInfo.LastHeightChanged) 651 if err != nil { 652 return empty, fmt.Errorf( 653 "couldn't find consensus params at height %d as last changed from height %d: %w", 654 paramsInfo.LastHeightChanged, 655 height, 656 err, 657 ) 658 } 659 660 paramsInfo = paramsInfo2 661 } 662 663 return paramsInfo.ConsensusParams, nil 664 } 665 666 func (store dbStore) loadConsensusParamsInfo(height int64) (*tmstate.ConsensusParamsInfo, error) { 667 buf, err := store.db.Get(calcConsensusParamsKey(height)) 668 if err != nil { 669 return nil, err 670 } 671 if len(buf) == 0 { 672 return nil, errors.New("value retrieved from db is empty") 673 } 674 675 paramsInfo := new(tmstate.ConsensusParamsInfo) 676 if err = paramsInfo.Unmarshal(buf); err != nil { 677 // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED 678 tmos.Exit(fmt.Sprintf(`LoadConsensusParams: Data has been corrupted or its spec has changed: 679 %v\n`, err)) 680 } 681 // TODO: ensure that buf is completely read. 682 683 return paramsInfo, nil 684 } 685 686 // saveConsensusParamsInfo persists the consensus params for the next block to disk. 687 // It should be called from s.Save(), right before the state itself is persisted. 688 // If the consensus params did not change after processing the latest block, 689 // only the last height for which they changed is persisted. 690 func (store dbStore) saveConsensusParamsInfo(nextHeight, changeHeight int64, params tmproto.ConsensusParams) error { 691 paramsInfo := &tmstate.ConsensusParamsInfo{ 692 LastHeightChanged: changeHeight, 693 } 694 695 if changeHeight == nextHeight { 696 paramsInfo.ConsensusParams = params 697 } 698 bz, err := paramsInfo.Marshal() 699 if err != nil { 700 return err 701 } 702 703 err = store.db.Set(calcConsensusParamsKey(nextHeight), bz) 704 if err != nil { 705 return err 706 } 707 708 return nil 709 } 710 711 func (store dbStore) Close() error { 712 return store.db.Close() 713 }