From 41e4b31be8d9ebe37ad411c3b7173cf4d4e75398 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Mon, 29 Apr 2024 12:27:31 -0700 Subject: [PATCH] feat(zetacore): add develop store upgrde tracker --- app/setup_handlers.go | 99 ++++++++++++++++++++++++++++++++++++-- app/setup_handlers_test.go | 63 ++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 app/setup_handlers_test.go diff --git a/app/setup_handlers.go b/app/setup_handlers.go index 19fcc96860..5568929256 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -1,6 +1,13 @@ package app import ( + "fmt" + "os" + "path" + "strconv" + "strings" + + "github.com/cosmos/cosmos-sdk/baseapp" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" @@ -24,19 +31,40 @@ func SetupHandlers(app *App) { return app.mm.RunMigrations(ctx, app.configurator, vm) }) + // add each store upgrade to this slice with a monotonically increasing index. + // you must use a index that is greater than any migration that has ever been run. + allStoreUpgrades := upgradeTracker{ + { + idx: 1000, + upgrade: storetypes.StoreUpgrades{ + Added: []string{authoritytypes.ModuleName}, + }, + }, + { + idx: 2000, + upgrade: storetypes.StoreUpgrades{ + Added: []string{lightclienttypes.ModuleName}, + }, + }, + } + + // if this is a develop release, use the special develop store loader functionality + if strings.Contains(releaseVersion, "develop") { + app.SetStoreLoader(allStoreUpgrades.getDevelopStoreLoader()) + return + } + upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() if err != nil { panic(err) } if upgradeInfo.Name == releaseVersion && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { - storeUpgrades := storetypes.StoreUpgrades{ - Added: []string{authoritytypes.ModuleName, lightclienttypes.ModuleName}, - } + storeUpgrades := allStoreUpgrades.mergeAllUpgrades() // Use upgrade store loader for the initial loading of all stores when app starts, // it checks if version == upgradeHeight and applies store upgrades before loading the stores, // so that new stores start with the correct version (the current height of chain), // instead the default which is the latest version that store last committed i.e 0 for new stores. - app.SetStoreLoader(types.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + app.SetStoreLoader(types.UpgradeStoreLoader(upgradeInfo.Height, storeUpgrades)) } } @@ -48,3 +76,66 @@ func (v VersionMigrator) TriggerMigration(moduleName string) module.VersionMap { v.v[moduleName] = v.v[moduleName] - 1 return v.v } + +type upgradeTrackerItem struct { + idx int64 + upgrade storetypes.StoreUpgrades +} + +type upgradeTracker []upgradeTrackerItem + +func (t upgradeTracker) getDevelopStoreUpgrades() *storetypes.StoreUpgrades { + neededUpgrades := &storetypes.StoreUpgrades{} + stateFilePath := path.Join(DefaultNodeHome, "storeupgradetracker") + + currentIdx := int64(0) + if stateFileContents, err := os.ReadFile(stateFilePath); err == nil { + currentIdx, err = strconv.ParseInt(string(stateFileContents), 10, 64) + if err != nil { + panic("unable to decode store upgrade tracker") + } + } else { + fmt.Printf("unable to load store upgrade tracker: %v\n", err) + } + + maxIdx := currentIdx + for _, item := range t { + idx := item.idx + upgrade := item.upgrade + if idx <= currentIdx { + continue + } + neededUpgrades.Added = append(neededUpgrades.Added, upgrade.Added...) + neededUpgrades.Deleted = append(neededUpgrades.Deleted, upgrade.Deleted...) + neededUpgrades.Renamed = append(neededUpgrades.Renamed, upgrade.Renamed...) + maxIdx = idx + } + err := os.WriteFile(stateFilePath, []byte(strconv.FormatInt(maxIdx, 10)), os.ModePerm) + if err != nil { + panic(fmt.Sprintf("unable to write upgrade state file: %v", err)) + } + return neededUpgrades +} + +func (t upgradeTracker) getDevelopStoreLoader() baseapp.StoreLoader { + neededUpgrades := t.getDevelopStoreUpgrades() + return func(ms sdk.CommitMultiStore) error { + if len(neededUpgrades.Renamed) > 0 || len(neededUpgrades.Deleted) > 0 || len(neededUpgrades.Added) > 0 { + return ms.LoadLatestVersionAndUpgrade(neededUpgrades) + } + + // Otherwise load default store loader + return baseapp.DefaultStoreLoader(ms) + } +} + +func (t upgradeTracker) mergeAllUpgrades() *storetypes.StoreUpgrades { + upgrades := &storetypes.StoreUpgrades{} + for _, item := range t { + upgrade := item.upgrade + upgrades.Added = append(upgrades.Added, upgrade.Added...) + upgrades.Deleted = append(upgrades.Deleted, upgrade.Deleted...) + upgrades.Renamed = append(upgrades.Renamed, upgrade.Renamed...) + } + return upgrades +} diff --git a/app/setup_handlers_test.go b/app/setup_handlers_test.go new file mode 100644 index 0000000000..78fb52e723 --- /dev/null +++ b/app/setup_handlers_test.go @@ -0,0 +1,63 @@ +package app + +import ( + "testing" + "time" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/stretchr/testify/require" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" +) + +func TestStoreUpgradeTracker(t *testing.T) { + r := require.New(t) + + // use timestamp for tracker idx so migrations work regardless of system state + now := time.Now().UnixMicro() + + allStoreUpgrades := upgradeTracker{ + { + idx: now, + upgrade: storetypes.StoreUpgrades{ + Added: []string{authoritytypes.ModuleName}, + }, + }, + { + idx: now + 1, + upgrade: storetypes.StoreUpgrades{ + Added: []string{lightclienttypes.ModuleName}, + }, + }, + } + + r.Len(allStoreUpgrades.mergeAllUpgrades().Added, 2) + r.Len(allStoreUpgrades.mergeAllUpgrades().Renamed, 0) + r.Len(allStoreUpgrades.mergeAllUpgrades().Deleted, 0) + + // should return all migrations on first call + upgrades := allStoreUpgrades.getDevelopStoreUpgrades() + r.Len(upgrades.Added, 2) + r.Len(upgrades.Renamed, 0) + r.Len(upgrades.Deleted, 0) + + // should return no upgrades on second call + upgrades = allStoreUpgrades.getDevelopStoreUpgrades() + r.Len(upgrades.Added, 0) + r.Len(upgrades.Renamed, 0) + r.Len(upgrades.Deleted, 0) + + // now add a upgrade and ensure that it gets run without running + // the other upgrades + allStoreUpgrades = append(allStoreUpgrades, upgradeTrackerItem{ + idx: now + 3, + upgrade: storetypes.StoreUpgrades{ + Deleted: []string{"example"}, + }, + }) + + upgrades = allStoreUpgrades.getDevelopStoreUpgrades() + r.Len(upgrades.Added, 0) + r.Len(upgrades.Renamed, 0) + r.Len(upgrades.Deleted, 1) +}