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  }