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