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