github.com/Finschia/finschia-sdk@v0.48.1/x/upgrade/keeper/keeper.go (about) 1 package keeper 2 3 import ( 4 "encoding/binary" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path" 9 "path/filepath" 10 "sort" 11 12 "github.com/Finschia/ostracon/libs/log" 13 ostos "github.com/Finschia/ostracon/libs/os" 14 15 "github.com/Finschia/finschia-sdk/codec" 16 "github.com/Finschia/finschia-sdk/store/prefix" 17 store "github.com/Finschia/finschia-sdk/store/types" 18 sdk "github.com/Finschia/finschia-sdk/types" 19 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 20 "github.com/Finschia/finschia-sdk/types/module" 21 xp "github.com/Finschia/finschia-sdk/x/upgrade/exported" 22 "github.com/Finschia/finschia-sdk/x/upgrade/types" 23 ) 24 25 // UpgradeInfoFileName file to store upgrade information 26 const UpgradeInfoFileName string = "upgrade-info.json" 27 28 // upgrade defines a comparable structure for sorting upgrades. 29 type upgrade struct { 30 Name string 31 BlockHeight int64 32 } 33 34 type Keeper struct { 35 homePath string // root directory of app config 36 skipUpgradeHeights map[int64]bool // map of heights to skip for an upgrade 37 storeKey sdk.StoreKey // key to access x/upgrade store 38 cdc codec.BinaryCodec // App-wide binary codec 39 upgradeHandlers map[string]types.UpgradeHandler // map of plan name to upgrade handler 40 versionSetter xp.ProtocolVersionSetter // implements setting the protocol version field on BaseApp 41 downgradeVerified bool // tells if we've already sanity checked that this binary version isn't being used against an old state. 42 } 43 44 // NewKeeper constructs an upgrade Keeper which requires the following arguments: 45 // skipUpgradeHeights - map of heights to skip an upgrade 46 // storeKey - a store key with which to access upgrade's store 47 // cdc - the app-wide binary codec 48 // homePath - root directory of the application's config 49 // vs - the interface implemented by baseapp which allows setting baseapp's protocol version field 50 func NewKeeper(skipUpgradeHeights map[int64]bool, storeKey sdk.StoreKey, cdc codec.BinaryCodec, homePath string, vs xp.ProtocolVersionSetter) Keeper { 51 return Keeper{ 52 homePath: homePath, 53 skipUpgradeHeights: skipUpgradeHeights, 54 storeKey: storeKey, 55 cdc: cdc, 56 upgradeHandlers: map[string]types.UpgradeHandler{}, 57 versionSetter: vs, 58 } 59 } 60 61 // SetUpgradeHandler sets an UpgradeHandler for the upgrade specified by name. This handler will be called when the upgrade 62 // with this name is applied. In order for an upgrade with the given name to proceed, a handler for this upgrade 63 // must be set even if it is a no-op function. 64 func (k Keeper) SetUpgradeHandler(name string, upgradeHandler types.UpgradeHandler) { 65 k.upgradeHandlers[name] = upgradeHandler 66 } 67 68 // setProtocolVersion sets the protocol version to state 69 func (k Keeper) setProtocolVersion(ctx sdk.Context, v uint64) { 70 store := ctx.KVStore(k.storeKey) 71 versionBytes := make([]byte, 8) 72 binary.BigEndian.PutUint64(versionBytes, v) 73 store.Set([]byte{types.ProtocolVersionByte}, versionBytes) 74 } 75 76 // getProtocolVersion gets the protocol version from state 77 func (k Keeper) getProtocolVersion(ctx sdk.Context) uint64 { 78 store := ctx.KVStore(k.storeKey) 79 ok := store.Has([]byte{types.ProtocolVersionByte}) 80 if ok { 81 pvBytes := store.Get([]byte{types.ProtocolVersionByte}) 82 protocolVersion := binary.BigEndian.Uint64(pvBytes) 83 84 return protocolVersion 85 } 86 // default value 87 return 0 88 } 89 90 // SetModuleVersionMap saves a given version map to state 91 func (k Keeper) SetModuleVersionMap(ctx sdk.Context, vm module.VersionMap) { 92 if len(vm) > 0 { 93 store := ctx.KVStore(k.storeKey) 94 versionStore := prefix.NewStore(store, []byte{types.VersionMapByte}) 95 // Even though the underlying store (cachekv) store is sorted, we still 96 // prefer a deterministic iteration order of the map, to avoid undesired 97 // surprises if we ever change stores. 98 sortedModNames := make([]string, 0, len(vm)) 99 100 for key := range vm { 101 sortedModNames = append(sortedModNames, key) 102 } 103 sort.Strings(sortedModNames) 104 105 for _, modName := range sortedModNames { 106 ver := vm[modName] 107 nameBytes := []byte(modName) 108 verBytes := make([]byte, 8) 109 binary.BigEndian.PutUint64(verBytes, ver) 110 versionStore.Set(nameBytes, verBytes) 111 } 112 } 113 } 114 115 // GetModuleVersionMap returns a map of key module name and value module consensus version 116 // as defined in ADR-041. 117 func (k Keeper) GetModuleVersionMap(ctx sdk.Context) module.VersionMap { 118 store := ctx.KVStore(k.storeKey) 119 it := sdk.KVStorePrefixIterator(store, []byte{types.VersionMapByte}) 120 121 vm := make(module.VersionMap) 122 defer it.Close() 123 for ; it.Valid(); it.Next() { 124 moduleBytes := it.Key() 125 // first byte is prefix key, so we remove it here 126 name := string(moduleBytes[1:]) 127 moduleVersion := binary.BigEndian.Uint64(it.Value()) 128 vm[name] = moduleVersion 129 } 130 131 return vm 132 } 133 134 // GetModuleVersions gets a slice of module consensus versions 135 func (k Keeper) GetModuleVersions(ctx sdk.Context) []*types.ModuleVersion { 136 store := ctx.KVStore(k.storeKey) 137 it := sdk.KVStorePrefixIterator(store, []byte{types.VersionMapByte}) 138 defer it.Close() 139 140 mv := make([]*types.ModuleVersion, 0) 141 for ; it.Valid(); it.Next() { 142 moduleBytes := it.Key() 143 name := string(moduleBytes[1:]) 144 moduleVersion := binary.BigEndian.Uint64(it.Value()) 145 mv = append(mv, &types.ModuleVersion{ 146 Name: name, 147 Version: moduleVersion, 148 }) 149 } 150 return mv 151 } 152 153 // gets the version for a given module, and returns true if it exists, false otherwise 154 func (k Keeper) getModuleVersion(ctx sdk.Context, name string) (uint64, bool) { 155 store := ctx.KVStore(k.storeKey) 156 it := sdk.KVStorePrefixIterator(store, []byte{types.VersionMapByte}) 157 defer it.Close() 158 159 for ; it.Valid(); it.Next() { 160 moduleName := string(it.Key()[1:]) 161 if moduleName == name { 162 version := binary.BigEndian.Uint64(it.Value()) 163 return version, true 164 } 165 } 166 return 0, false 167 } 168 169 // ScheduleUpgrade schedules an upgrade based on the specified plan. 170 // If there is another Plan already scheduled, it will overwrite it 171 // (implicitly cancelling the current plan) 172 // ScheduleUpgrade will also write the upgraded client to the upgraded client path 173 // if an upgraded client is specified in the plan 174 func (k Keeper) ScheduleUpgrade(ctx sdk.Context, plan types.Plan) error { 175 if err := plan.ValidateBasic(); err != nil { 176 return err 177 } 178 179 // NOTE: allow for the possibility of chains to schedule upgrades in begin block of the same block 180 // as a strategy for emergency hard fork recoveries 181 if plan.Height < ctx.BlockHeight() { 182 return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "upgrade cannot be scheduled in the past") 183 } 184 185 if k.GetDoneHeight(ctx, plan.Name) != 0 { 186 return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "upgrade with name %s has already been completed", plan.Name) 187 } 188 189 store := ctx.KVStore(k.storeKey) 190 191 // clear any old IBC state stored by previous plan 192 oldPlan, found := k.GetUpgradePlan(ctx) 193 if found { 194 k.ClearIBCState(ctx, oldPlan.Height) 195 } 196 197 bz := k.cdc.MustMarshal(&plan) 198 store.Set(types.PlanKey(), bz) 199 200 return nil 201 } 202 203 // SetUpgradedClient sets the expected upgraded client for the next version of this chain at the last height the current chain will commit. 204 func (k Keeper) SetUpgradedClient(ctx sdk.Context, planHeight int64, bz []byte) error { 205 store := ctx.KVStore(k.storeKey) 206 store.Set(types.UpgradedClientKey(planHeight), bz) 207 return nil 208 } 209 210 // GetUpgradedClient gets the expected upgraded client for the next version of this chain 211 func (k Keeper) GetUpgradedClient(ctx sdk.Context, height int64) ([]byte, bool) { 212 store := ctx.KVStore(k.storeKey) 213 bz := store.Get(types.UpgradedClientKey(height)) 214 if len(bz) == 0 { 215 return nil, false 216 } 217 218 return bz, true 219 } 220 221 // SetUpgradedConsensusState set the expected upgraded consensus state for the next version of this chain 222 // using the last height committed on this chain. 223 func (k Keeper) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error { 224 store := ctx.KVStore(k.storeKey) 225 store.Set(types.UpgradedConsStateKey(planHeight), bz) 226 return nil 227 } 228 229 // GetUpgradedConsensusState set the expected upgraded consensus state for the next version of this chain 230 func (k Keeper) GetUpgradedConsensusState(ctx sdk.Context, lastHeight int64) ([]byte, bool) { 231 store := ctx.KVStore(k.storeKey) 232 bz := store.Get(types.UpgradedConsStateKey(lastHeight)) 233 if len(bz) == 0 { 234 return nil, false 235 } 236 237 return bz, true 238 } 239 240 // GetLastCompletedUpgrade returns the last applied upgrade name and height. 241 func (k Keeper) GetLastCompletedUpgrade(ctx sdk.Context) (string, int64) { 242 iter := sdk.KVStoreReversePrefixIterator(ctx.KVStore(k.storeKey), []byte{types.DoneByte}) 243 defer iter.Close() 244 245 var ( 246 latest upgrade 247 found bool 248 ) 249 for ; iter.Valid(); iter.Next() { 250 upgradeHeight := int64(sdk.BigEndianToUint64(iter.Value())) 251 if !found || upgradeHeight >= latest.BlockHeight { 252 found = true 253 name := parseDoneKey(iter.Key()) 254 latest = upgrade{Name: name, BlockHeight: upgradeHeight} 255 } 256 } 257 258 return latest.Name, latest.BlockHeight 259 } 260 261 // parseDoneKey - split upgrade name from the done key 262 func parseDoneKey(key []byte) string { 263 if len(key) < 2 { 264 panic(fmt.Sprintf("expected key of length at least %d, got %d", 2, len(key))) 265 } 266 267 return string(key[1:]) 268 } 269 270 // GetDoneHeight returns the height at which the given upgrade was executed 271 func (k Keeper) GetDoneHeight(ctx sdk.Context, name string) int64 { 272 store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{types.DoneByte}) 273 bz := store.Get([]byte(name)) 274 if len(bz) == 0 { 275 return 0 276 } 277 278 return int64(binary.BigEndian.Uint64(bz)) 279 } 280 281 // ClearIBCState clears any planned IBC state 282 func (k Keeper) ClearIBCState(ctx sdk.Context, lastHeight int64) { 283 // delete IBC client and consensus state from store if this is IBC plan 284 store := ctx.KVStore(k.storeKey) 285 store.Delete(types.UpgradedClientKey(lastHeight)) 286 store.Delete(types.UpgradedConsStateKey(lastHeight)) 287 } 288 289 // ClearUpgradePlan clears any schedule upgrade and associated IBC states. 290 func (k Keeper) ClearUpgradePlan(ctx sdk.Context) { 291 // clear IBC states everytime upgrade plan is removed 292 oldPlan, found := k.GetUpgradePlan(ctx) 293 if found { 294 k.ClearIBCState(ctx, oldPlan.Height) 295 } 296 297 store := ctx.KVStore(k.storeKey) 298 store.Delete(types.PlanKey()) 299 } 300 301 // Logger returns a module-specific logger. 302 func (k Keeper) Logger(ctx sdk.Context) log.Logger { 303 return ctx.Logger().With("module", "x/"+types.ModuleName) 304 } 305 306 // GetUpgradePlan returns the currently scheduled Plan if any, setting havePlan to true if there is a scheduled 307 // upgrade or false if there is none 308 func (k Keeper) GetUpgradePlan(ctx sdk.Context) (plan types.Plan, havePlan bool) { 309 store := ctx.KVStore(k.storeKey) 310 bz := store.Get(types.PlanKey()) 311 if bz == nil { 312 return plan, false 313 } 314 315 k.cdc.MustUnmarshal(bz, &plan) 316 return plan, true 317 } 318 319 // setDone marks this upgrade name as being done so the name can't be reused accidentally 320 func (k Keeper) setDone(ctx sdk.Context, name string) { 321 store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{types.DoneByte}) 322 bz := make([]byte, 8) 323 binary.BigEndian.PutUint64(bz, uint64(ctx.BlockHeight())) 324 store.Set([]byte(name), bz) 325 } 326 327 // HasHandler returns true iff there is a handler registered for this name 328 func (k Keeper) HasHandler(name string) bool { 329 _, ok := k.upgradeHandlers[name] 330 return ok 331 } 332 333 // ApplyUpgrade will execute the handler associated with the Plan and mark the plan as done. 334 func (k Keeper) ApplyUpgrade(ctx sdk.Context, plan types.Plan) { 335 handler := k.upgradeHandlers[plan.Name] 336 if handler == nil { 337 panic("ApplyUpgrade should never be called without first checking HasHandler") 338 } 339 340 updatedVM, err := handler(ctx, plan, k.GetModuleVersionMap(ctx)) 341 if err != nil { 342 panic(err) 343 } 344 345 k.SetModuleVersionMap(ctx, updatedVM) 346 347 // incremement the protocol version and set it in state and baseapp 348 nextProtocolVersion := k.getProtocolVersion(ctx) + 1 349 k.setProtocolVersion(ctx, nextProtocolVersion) 350 if k.versionSetter != nil { 351 // set protocol version on BaseApp 352 k.versionSetter.SetProtocolVersion(nextProtocolVersion) 353 } 354 355 // Must clear IBC state after upgrade is applied as it is stored separately from the upgrade plan. 356 // This will prevent resubmission of upgrade msg after upgrade is already completed. 357 k.ClearIBCState(ctx, plan.Height) 358 k.ClearUpgradePlan(ctx) 359 k.setDone(ctx, plan.Name) 360 } 361 362 // IsSkipHeight checks if the given height is part of skipUpgradeHeights 363 func (k Keeper) IsSkipHeight(height int64) bool { 364 return k.skipUpgradeHeights[height] 365 } 366 367 // DumpUpgradeInfoToDisk writes upgrade information to UpgradeInfoFileName. The function 368 // doesn't save the `Plan.Info` data, hence it won't support auto download functionality 369 // by cosmvisor. 370 // NOTE: this function will be update in the next release. 371 func (k Keeper) DumpUpgradeInfoToDisk(height int64, name string) error { 372 return k.DumpUpgradeInfoWithInfoToDisk(height, name, "") 373 } 374 375 // Deprecated: DumpUpgradeInfoWithInfoToDisk writes upgrade information to UpgradeInfoFileName. 376 // `info` should be provided and contain Plan.Info data in order to support 377 // auto download functionality by cosmovisor and other tools using upgarde-info.json 378 // (GetUpgradeInfoPath()) file. 379 func (k Keeper) DumpUpgradeInfoWithInfoToDisk(height int64, name string, info string) error { 380 upgradeInfoFilePath, err := k.GetUpgradeInfoPath() 381 if err != nil { 382 return err 383 } 384 385 upgradeInfo := upgradeInfo{ 386 Name: name, 387 Height: height, 388 Info: info, 389 } 390 bz, err := json.Marshal(upgradeInfo) 391 if err != nil { 392 return err 393 } 394 395 return os.WriteFile(upgradeInfoFilePath, bz, 0o600) 396 } 397 398 // GetUpgradeInfoPath returns the upgrade info file path 399 func (k Keeper) GetUpgradeInfoPath() (string, error) { 400 upgradeInfoFileDir := path.Join(k.getHomeDir(), "data") 401 err := ostos.EnsureDir(upgradeInfoFileDir, os.ModePerm) 402 if err != nil { 403 return "", err 404 } 405 406 return filepath.Join(upgradeInfoFileDir, UpgradeInfoFileName), nil 407 } 408 409 // getHomeDir returns the height at which the given upgrade was executed 410 func (k Keeper) getHomeDir() string { 411 return k.homePath 412 } 413 414 // ReadUpgradeInfoFromDisk returns the name and height of the upgrade which is 415 // written to disk by the old binary when panicking. An error is returned if 416 // the upgrade path directory cannot be created or if the file exists and 417 // cannot be read or if the upgrade info fails to unmarshal. 418 func (k Keeper) ReadUpgradeInfoFromDisk() (store.UpgradeInfo, error) { 419 var upgradeInfo store.UpgradeInfo 420 421 upgradeInfoPath, err := k.GetUpgradeInfoPath() 422 if err != nil { 423 return upgradeInfo, err 424 } 425 426 data, err := os.ReadFile(upgradeInfoPath) 427 if err != nil { 428 // if file does not exist, assume there are no upgrades 429 if os.IsNotExist(err) { 430 return upgradeInfo, nil 431 } 432 433 return upgradeInfo, err 434 } 435 436 if err := json.Unmarshal(data, &upgradeInfo); err != nil { 437 return upgradeInfo, err 438 } 439 440 return upgradeInfo, nil 441 } 442 443 // upgradeInfo is stripped types.Plan structure used to dump upgrade plan data. 444 type upgradeInfo struct { 445 // Name has types.Plan.Name value 446 Name string `json:"name,omitempty"` 447 // Height has types.Plan.Height value 448 Height int64 `json:"height,omitempty"` 449 // Height has types.Plan.Height value 450 Info string `json:"info,omitempty"` 451 } 452 453 // SetDowngradeVerified updates downgradeVerified. 454 func (k *Keeper) SetDowngradeVerified(v bool) { 455 k.downgradeVerified = v 456 } 457 458 // DowngradeVerified returns downgradeVerified. 459 func (k Keeper) DowngradeVerified() bool { 460 return k.downgradeVerified 461 }