github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/state/rollback.go (about) 1 package state 2 3 import ( 4 "errors" 5 "fmt" 6 7 cmtstate "github.com/badrootd/celestia-core/proto/tendermint/state" 8 cmtversion "github.com/badrootd/celestia-core/proto/tendermint/version" 9 "github.com/badrootd/celestia-core/version" 10 ) 11 12 // Rollback overwrites the current CometBFT state (height n) with the most 13 // recent previous state (height n - 1). 14 // Note that this function does not affect application state. 15 func Rollback(bs BlockStore, ss Store, removeBlock bool) (int64, []byte, error) { 16 invalidState, err := ss.Load() 17 if err != nil { 18 return -1, nil, err 19 } 20 if invalidState.IsEmpty() { 21 return -1, nil, errors.New("no state found") 22 } 23 24 height := bs.Height() 25 26 // NOTE: persistence of state and blocks don't happen atomically. Therefore it is possible that 27 // when the user stopped the node the state wasn't updated but the blockstore was. Discard the 28 // pending block before continuing. 29 if height == invalidState.LastBlockHeight+1 { 30 if removeBlock { 31 if err := bs.DeleteLatestBlock(); err != nil { 32 return -1, nil, fmt.Errorf("failed to remove final block from blockstore: %w", err) 33 } 34 } 35 return invalidState.LastBlockHeight, invalidState.AppHash, nil 36 } 37 38 // If the state store isn't one below nor equal to the blockstore height than this violates the 39 // invariant 40 if height != invalidState.LastBlockHeight { 41 return -1, nil, fmt.Errorf("statestore height (%d) is not one below or equal to blockstore height (%d)", 42 invalidState.LastBlockHeight, height) 43 } 44 45 // state store height is equal to blockstore height. We're good to proceed with rolling back state 46 rollbackHeight := invalidState.LastBlockHeight - 1 47 rollbackBlock := bs.LoadBlockMeta(rollbackHeight) 48 if rollbackBlock == nil { 49 return -1, nil, fmt.Errorf("block at height %d not found", rollbackHeight) 50 } 51 // We also need to retrieve the latest block because the app hash and last 52 // results hash is only agreed upon in the following block. 53 latestBlock := bs.LoadBlockMeta(invalidState.LastBlockHeight) 54 if latestBlock == nil { 55 return -1, nil, fmt.Errorf("block at height %d not found", invalidState.LastBlockHeight) 56 } 57 58 previousLastValidatorSet, err := ss.LoadValidators(rollbackHeight) 59 if err != nil { 60 return -1, nil, err 61 } 62 63 previousParams, err := ss.LoadConsensusParams(rollbackHeight + 1) 64 if err != nil { 65 return -1, nil, err 66 } 67 68 valChangeHeight := invalidState.LastHeightValidatorsChanged 69 // this can only happen if the validator set changed since the last block 70 if valChangeHeight > rollbackHeight { 71 valChangeHeight = rollbackHeight + 1 72 } 73 74 paramsChangeHeight := invalidState.LastHeightConsensusParamsChanged 75 // this can only happen if params changed from the last block 76 if paramsChangeHeight > rollbackHeight { 77 paramsChangeHeight = rollbackHeight + 1 78 } 79 80 // build the new state from the old state and the prior block 81 rolledBackState := State{ 82 Version: cmtstate.Version{ 83 Consensus: cmtversion.Consensus{ 84 Block: version.BlockProtocol, 85 App: previousParams.Version.App, 86 }, 87 Software: version.TMCoreSemVer, 88 }, 89 // immutable fields 90 ChainID: invalidState.ChainID, 91 InitialHeight: invalidState.InitialHeight, 92 93 LastBlockHeight: rollbackBlock.Header.Height, 94 LastBlockID: rollbackBlock.BlockID, 95 LastBlockTime: rollbackBlock.Header.Time, 96 97 NextValidators: invalidState.Validators, 98 Validators: invalidState.LastValidators, 99 LastValidators: previousLastValidatorSet, 100 LastHeightValidatorsChanged: valChangeHeight, 101 102 ConsensusParams: previousParams, 103 LastHeightConsensusParamsChanged: paramsChangeHeight, 104 105 LastResultsHash: latestBlock.Header.LastResultsHash, 106 AppHash: latestBlock.Header.AppHash, 107 } 108 109 // persist the new state. This overrides the invalid one. NOTE: this will also 110 // persist the validator set and consensus params over the existing structures, 111 // but both should be the same 112 if err := ss.Save(rolledBackState); err != nil { 113 return -1, nil, fmt.Errorf("failed to save rolled back state: %w", err) 114 } 115 116 // If removeBlock is true then also remove the block associated with the previous state. 117 // This will mean both the last state and last block height is equal to n - 1 118 if removeBlock { 119 if err := bs.DeleteLatestBlock(); err != nil { 120 return -1, nil, fmt.Errorf("failed to remove final block from blockstore: %w", err) 121 } 122 } 123 124 return rolledBackState.LastBlockHeight, rolledBackState.AppHash, nil 125 }