cosmos-sdk/x/upgrade/keeper/keeper.go

467 lines
15 KiB
Go

package keeper
import (
"encoding/binary"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
store "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/module"
xp "github.com/cosmos/cosmos-sdk/x/upgrade/exported"
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
)
// UpgradeInfoFileName file to store upgrade information
const UpgradeInfoFileName string = "upgrade-info.json"
// upgrade defines a comparable structure for sorting upgrades.
type upgrade struct {
Name string
BlockHeight int64
}
type Keeper struct {
homePath string // root directory of app config
skipUpgradeHeights map[int64]bool // map of heights to skip for an upgrade
storeKey sdk.StoreKey // key to access x/upgrade store
cdc codec.BinaryCodec // App-wide binary codec
upgradeHandlers map[string]types.UpgradeHandler // map of plan name to upgrade handler
versionSetter xp.ProtocolVersionSetter // implements setting the protocol version field on BaseApp
downgradeVerified bool // tells if we've already sanity checked that this binary version isn't being used against an old state.
}
// NewKeeper constructs an upgrade Keeper which requires the following arguments:
// skipUpgradeHeights - map of heights to skip an upgrade
// storeKey - a store key with which to access upgrade's store
// cdc - the app-wide binary codec
// homePath - root directory of the application's config
// vs - the interface implemented by baseapp which allows setting baseapp's protocol version field
func NewKeeper(skipUpgradeHeights map[int64]bool, storeKey sdk.StoreKey, cdc codec.BinaryCodec, homePath string, vs xp.ProtocolVersionSetter) Keeper {
return Keeper{
homePath: homePath,
skipUpgradeHeights: skipUpgradeHeights,
storeKey: storeKey,
cdc: cdc,
upgradeHandlers: map[string]types.UpgradeHandler{},
versionSetter: vs,
}
}
// SetUpgradeHandler sets an UpgradeHandler for the upgrade specified by name. This handler will be called when the upgrade
// with this name is applied. In order for an upgrade with the given name to proceed, a handler for this upgrade
// must be set even if it is a no-op function.
func (k Keeper) SetUpgradeHandler(name string, upgradeHandler types.UpgradeHandler) {
k.upgradeHandlers[name] = upgradeHandler
}
// setProtocolVersion sets the protocol version to state
func (k Keeper) setProtocolVersion(ctx sdk.Context, v uint64) {
store := ctx.KVStore(k.storeKey)
versionBytes := make([]byte, 8)
binary.BigEndian.PutUint64(versionBytes, v)
store.Set([]byte{types.ProtocolVersionByte}, versionBytes)
}
// getProtocolVersion gets the protocol version from state
func (k Keeper) getProtocolVersion(ctx sdk.Context) uint64 {
store := ctx.KVStore(k.storeKey)
ok := store.Has([]byte{types.ProtocolVersionByte})
if ok {
pvBytes := store.Get([]byte{types.ProtocolVersionByte})
protocolVersion := binary.BigEndian.Uint64(pvBytes)
return protocolVersion
}
// default value
return 0
}
// SetModuleVersionMap saves a given version map to state
func (k Keeper) SetModuleVersionMap(ctx sdk.Context, vm module.VersionMap) {
if len(vm) > 0 {
store := ctx.KVStore(k.storeKey)
versionStore := prefix.NewStore(store, []byte{types.VersionMapByte})
// Even though the underlying store (cachekv) store is sorted, we still
// prefer a deterministic iteration order of the map, to avoid undesired
// surprises if we ever change stores.
sortedModNames := make([]string, 0, len(vm))
for key := range vm {
sortedModNames = append(sortedModNames, key)
}
sort.Strings(sortedModNames)
for _, modName := range sortedModNames {
ver := vm[modName]
nameBytes := []byte(modName)
verBytes := make([]byte, 8)
binary.BigEndian.PutUint64(verBytes, ver)
versionStore.Set(nameBytes, verBytes)
}
}
}
// GetModuleVersionMap returns a map of key module name and value module consensus version
// as defined in ADR-041.
func (k Keeper) GetModuleVersionMap(ctx sdk.Context) module.VersionMap {
store := ctx.KVStore(k.storeKey)
it := sdk.KVStorePrefixIterator(store, []byte{types.VersionMapByte})
vm := make(module.VersionMap)
defer it.Close()
for ; it.Valid(); it.Next() {
moduleBytes := it.Key()
// first byte is prefix key, so we remove it here
name := string(moduleBytes[1:])
moduleVersion := binary.BigEndian.Uint64(it.Value())
vm[name] = moduleVersion
}
return vm
}
// GetModuleVersions gets a slice of module consensus versions
func (k Keeper) GetModuleVersions(ctx sdk.Context) []*types.ModuleVersion {
store := ctx.KVStore(k.storeKey)
it := sdk.KVStorePrefixIterator(store, []byte{types.VersionMapByte})
defer it.Close()
mv := make([]*types.ModuleVersion, 0)
for ; it.Valid(); it.Next() {
moduleBytes := it.Key()
name := string(moduleBytes[1:])
moduleVersion := binary.BigEndian.Uint64(it.Value())
mv = append(mv, &types.ModuleVersion{
Name: name,
Version: moduleVersion,
})
}
return mv
}
// gets the version for a given module, and returns true if it exists, false otherwise
func (k Keeper) getModuleVersion(ctx sdk.Context, name string) (uint64, bool) {
store := ctx.KVStore(k.storeKey)
it := sdk.KVStorePrefixIterator(store, []byte{types.VersionMapByte})
defer it.Close()
for ; it.Valid(); it.Next() {
moduleName := string(it.Key()[1:])
if moduleName == name {
version := binary.BigEndian.Uint64(it.Value())
return version, true
}
}
return 0, false
}
// ScheduleUpgrade schedules an upgrade based on the specified plan.
// If there is another Plan already scheduled, it will overwrite it
// (implicitly cancelling the current plan)
// ScheduleUpgrade will also write the upgraded client to the upgraded client path
// if an upgraded client is specified in the plan
func (k Keeper) ScheduleUpgrade(ctx sdk.Context, plan types.Plan) error {
if err := plan.ValidateBasic(); err != nil {
return err
}
// NOTE: allow for the possibility of chains to schedule upgrades in begin block of the same block
// as a strategy for emergency hard fork recoveries
if plan.Height < ctx.BlockHeight() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "upgrade cannot be scheduled in the past")
}
if k.GetDoneHeight(ctx, plan.Name) != 0 {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "upgrade with name %s has already been completed", plan.Name)
}
store := ctx.KVStore(k.storeKey)
// clear any old IBC state stored by previous plan
oldPlan, found := k.GetUpgradePlan(ctx)
if found {
k.ClearIBCState(ctx, oldPlan.Height)
}
bz := k.cdc.MustMarshal(&plan)
store.Set(types.PlanKey(), bz)
return nil
}
// SetUpgradedClient sets the expected upgraded client for the next version of this chain at the last height the current chain will commit.
func (k Keeper) SetUpgradedClient(ctx sdk.Context, planHeight int64, bz []byte) error {
store := ctx.KVStore(k.storeKey)
store.Set(types.UpgradedClientKey(planHeight), bz)
return nil
}
// GetUpgradedClient gets the expected upgraded client for the next version of this chain
func (k Keeper) GetUpgradedClient(ctx sdk.Context, height int64) ([]byte, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.UpgradedClientKey(height))
if len(bz) == 0 {
return nil, false
}
return bz, true
}
// SetUpgradedConsensusState set the expected upgraded consensus state for the next version of this chain
// using the last height committed on this chain.
func (k Keeper) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error {
store := ctx.KVStore(k.storeKey)
store.Set(types.UpgradedConsStateKey(planHeight), bz)
return nil
}
// GetUpgradedConsensusState set the expected upgraded consensus state for the next version of this chain
func (k Keeper) GetUpgradedConsensusState(ctx sdk.Context, lastHeight int64) ([]byte, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.UpgradedConsStateKey(lastHeight))
if len(bz) == 0 {
return nil, false
}
return bz, true
}
// GetLastCompletedUpgrade returns the last applied upgrade name and height.
func (k Keeper) GetLastCompletedUpgrade(ctx sdk.Context) (string, int64) {
iter := sdk.KVStoreReversePrefixIterator(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
defer iter.Close()
var upgrades []upgrade
for ; iter.Valid(); iter.Next() {
name := parseDoneKey(iter.Key())
value := int64(sdk.BigEndianToUint64(iter.Value()))
upgrades = append(upgrades, upgrade{Name: name, BlockHeight: value})
}
// sort upgrades in descending order by block height
sort.SliceStable(upgrades, func(i, j int) bool {
return upgrades[i].BlockHeight > upgrades[j].BlockHeight
})
if len(upgrades) > 0 {
return upgrades[0].Name, upgrades[0].BlockHeight
}
return "", 0
}
// parseDoneKey - split upgrade name from the done key
func parseDoneKey(key []byte) string {
if len(key) < 2 {
panic(fmt.Sprintf("expected key of length at least %d, got %d", 2, len(key)))
}
return string(key[1:])
}
// GetDoneHeight returns the height at which the given upgrade was executed
func (k Keeper) GetDoneHeight(ctx sdk.Context, name string) int64 {
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
bz := store.Get([]byte(name))
if len(bz) == 0 {
return 0
}
return int64(binary.BigEndian.Uint64(bz))
}
// ClearIBCState clears any planned IBC state
func (k Keeper) ClearIBCState(ctx sdk.Context, lastHeight int64) {
// delete IBC client and consensus state from store if this is IBC plan
store := ctx.KVStore(k.storeKey)
store.Delete(types.UpgradedClientKey(lastHeight))
store.Delete(types.UpgradedConsStateKey(lastHeight))
}
// ClearUpgradePlan clears any schedule upgrade and associated IBC states.
func (k Keeper) ClearUpgradePlan(ctx sdk.Context) {
// clear IBC states everytime upgrade plan is removed
oldPlan, found := k.GetUpgradePlan(ctx)
if found {
k.ClearIBCState(ctx, oldPlan.Height)
}
store := ctx.KVStore(k.storeKey)
store.Delete(types.PlanKey())
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+types.ModuleName)
}
// GetUpgradePlan returns the currently scheduled Plan if any, setting havePlan to true if there is a scheduled
// upgrade or false if there is none
func (k Keeper) GetUpgradePlan(ctx sdk.Context) (plan types.Plan, havePlan bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.PlanKey())
if bz == nil {
return plan, false
}
k.cdc.MustUnmarshal(bz, &plan)
return plan, true
}
// setDone marks this upgrade name as being done so the name can't be reused accidentally
func (k Keeper) setDone(ctx sdk.Context, name string) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, uint64(ctx.BlockHeight()))
store.Set([]byte(name), bz)
}
// HasHandler returns true iff there is a handler registered for this name
func (k Keeper) HasHandler(name string) bool {
_, ok := k.upgradeHandlers[name]
return ok
}
// ApplyUpgrade will execute the handler associated with the Plan and mark the plan as done.
func (k Keeper) ApplyUpgrade(ctx sdk.Context, plan types.Plan) {
handler := k.upgradeHandlers[plan.Name]
if handler == nil {
panic("ApplyUpgrade should never be called without first checking HasHandler")
}
updatedVM, err := handler(ctx, plan, k.GetModuleVersionMap(ctx))
if err != nil {
panic(err)
}
k.SetModuleVersionMap(ctx, updatedVM)
// incremement the protocol version and set it in state and baseapp
nextProtocolVersion := k.getProtocolVersion(ctx) + 1
k.setProtocolVersion(ctx, nextProtocolVersion)
if k.versionSetter != nil {
// set protocol version on BaseApp
k.versionSetter.SetProtocolVersion(nextProtocolVersion)
}
// Must clear IBC state after upgrade is applied as it is stored separately from the upgrade plan.
// This will prevent resubmission of upgrade msg after upgrade is already completed.
k.ClearIBCState(ctx, plan.Height)
k.ClearUpgradePlan(ctx)
k.setDone(ctx, plan.Name)
}
// IsSkipHeight checks if the given height is part of skipUpgradeHeights
func (k Keeper) IsSkipHeight(height int64) bool {
return k.skipUpgradeHeights[height]
}
// DumpUpgradeInfoToDisk writes upgrade information to UpgradeInfoFileName. The function
// doesn't save the `Plan.Info` data, hence it won't support auto download functionality
// by cosmvisor.
// NOTE: this function will be update in the next release.
func (k Keeper) DumpUpgradeInfoToDisk(height int64, name string) error {
return k.DumpUpgradeInfoWithInfoToDisk(height, name, "")
}
// Deprecated: DumpUpgradeInfoWithInfoToDisk writes upgrade information to UpgradeInfoFileName.
// `info` should be provided and contain Plan.Info data in order to support
// auto download functionality by cosmovisor and other tools using upgarde-info.json
// (GetUpgradeInfoPath()) file.
func (k Keeper) DumpUpgradeInfoWithInfoToDisk(height int64, name string, info string) error {
upgradeInfoFilePath, err := k.GetUpgradeInfoPath()
if err != nil {
return err
}
upgradeInfo := upgradeInfo{
Name: name,
Height: height,
Info: info,
}
bz, err := json.Marshal(upgradeInfo)
if err != nil {
return err
}
return os.WriteFile(upgradeInfoFilePath, bz, 0o600)
}
// GetUpgradeInfoPath returns the upgrade info file path
func (k Keeper) GetUpgradeInfoPath() (string, error) {
upgradeInfoFileDir := path.Join(k.getHomeDir(), "data")
err := tmos.EnsureDir(upgradeInfoFileDir, os.ModePerm)
if err != nil {
return "", err
}
return filepath.Join(upgradeInfoFileDir, UpgradeInfoFileName), nil
}
// getHomeDir returns the height at which the given upgrade was executed
func (k Keeper) getHomeDir() string {
return k.homePath
}
// ReadUpgradeInfoFromDisk returns the name and height of the upgrade which is
// written to disk by the old binary when panicking. An error is returned if
// the upgrade path directory cannot be created or if the file exists and
// cannot be read or if the upgrade info fails to unmarshal.
func (k Keeper) ReadUpgradeInfoFromDisk() (store.UpgradeInfo, error) {
var upgradeInfo store.UpgradeInfo
upgradeInfoPath, err := k.GetUpgradeInfoPath()
if err != nil {
return upgradeInfo, err
}
data, err := ioutil.ReadFile(upgradeInfoPath)
if err != nil {
// if file does not exist, assume there are no upgrades
if os.IsNotExist(err) {
return upgradeInfo, nil
}
return upgradeInfo, err
}
if err := json.Unmarshal(data, &upgradeInfo); err != nil {
return upgradeInfo, err
}
return upgradeInfo, nil
}
// upgradeInfo is stripped types.Plan structure used to dump upgrade plan data.
type upgradeInfo struct {
// Name has types.Plan.Name value
Name string `json:"name,omitempty"`
// Height has types.Plan.Height value
Height int64 `json:"height,omitempty"`
// Height has types.Plan.Height value
Info string `json:"info,omitempty"`
}
// SetDowngradeVerified updates downgradeVerified.
func (k *Keeper) SetDowngradeVerified(v bool) {
k.downgradeVerified = v
}
// DowngradeVerified returns downgradeVerified.
func (k Keeper) DowngradeVerified() bool {
return k.downgradeVerified
}