Skip to content

Commit

Permalink
refactor/comment/panic
Browse files Browse the repository at this point in the history
  • Loading branch information
gartnera committed May 2, 2024
1 parent dcc0c79 commit b822b3c
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 106 deletions.
94 changes: 7 additions & 87 deletions app/setup_handlers.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package app

import (
"fmt"
"os"
"path"
"strconv"
"strings"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
Expand All @@ -20,17 +16,17 @@ const releaseVersion = "v15"
func SetupHandlers(app *App) {

// add each 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.
// the easiest index to use is the current unix epoch timestamp.
allUpgrades := upgradeTracker{
upgrades: []upgradeTrackerItem{

Check warning on line 21 in app/setup_handlers.go

View check run for this annotation

Codecov / codecov/patch

app/setup_handlers.go#L20-L21

Added lines #L20 - L21 were not covered by tests
{
idx: 1000,
index: 1713000000,
storeUpgrade: &storetypes.StoreUpgrades{
Added: []string{authoritytypes.ModuleName},
},
},

Check warning on line 27 in app/setup_handlers.go

View check run for this annotation

Codecov / codecov/patch

app/setup_handlers.go#L23-L27

Added lines #L23 - L27 were not covered by tests
{
idx: 2000,
index: 1714664193,
storeUpgrade: &storetypes.StoreUpgrades{
Added: []string{lightclienttypes.ModuleName},
},
Expand All @@ -40,7 +36,10 @@ func SetupHandlers(app *App) {
}

isDevelopBuild := strings.Contains(releaseVersion, "develop")
versionModifierFns, storeUpgrades := allUpgrades.getUpgrades(isDevelopBuild)
versionModifierFns, storeUpgrades, err := allUpgrades.getUpgrades(isDevelopBuild)
if err != nil {
panic(err)

Check warning on line 41 in app/setup_handlers.go

View check run for this annotation

Codecov / codecov/patch

app/setup_handlers.go#L38-L41

Added lines #L38 - L41 were not covered by tests
}

app.UpgradeKeeper.SetUpgradeHandler(releaseVersion, func(ctx sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
app.Logger().Info("Running upgrade handler for " + releaseVersion)
Expand Down Expand Up @@ -68,82 +67,3 @@ func SetupHandlers(app *App) {
app.SetStoreLoader(types.UpgradeStoreLoader(upgradeInfo.Height, storeUpgrades))

Check warning on line 67 in app/setup_handlers.go

View check run for this annotation

Codecov / codecov/patch

app/setup_handlers.go#L67

Added line #L67 was not covered by tests
}
}

type versionModiferFn func(ctx sdk.Context, vm module.VersionMap) (module.VersionMap, error)

type upgradeTrackerItem struct {
idx int64
versionModifier versionModiferFn
storeUpgrade *storetypes.StoreUpgrades
}

type upgradeTracker struct {
upgrades []upgradeTrackerItem
stateFileDir string
}

func (t upgradeTracker) getDevelopUpgrades() ([]versionModiferFn, *storetypes.StoreUpgrades) {
neededUpgrades := &storetypes.StoreUpgrades{}
neededVersionModifiers := []versionModiferFn{}
stateFilePath := path.Join(t.stateFileDir, "developupgradetracker")

currentIdx := int64(0)
// #nosec G304 stateFilePath is not user controllable
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.upgrades {
idx := item.idx
upgrade := item.storeUpgrade
versionModifier := item.versionModifier
if idx <= currentIdx {
continue
}
if versionModifier != nil {
neededVersionModifiers = append(neededVersionModifiers, versionModifier)
}
if upgrade != nil {
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)), 0o600)
if err != nil {
panic(fmt.Sprintf("unable to write upgrade state file: %v", err))
}
return neededVersionModifiers, neededUpgrades
}

func (t upgradeTracker) mergeAllUpgrades() ([]versionModiferFn, *storetypes.StoreUpgrades) {
upgrades := &storetypes.StoreUpgrades{}
versionModifiers := []versionModiferFn{}
for _, item := range t.upgrades {
upgrade := item.storeUpgrade
versionModifier := item.versionModifier
if versionModifier != nil {
versionModifiers = append(versionModifiers, versionModifier)
}
if upgrade != nil {
upgrades.Added = append(upgrades.Added, upgrade.Added...)
upgrades.Deleted = append(upgrades.Deleted, upgrade.Deleted...)
upgrades.Renamed = append(upgrades.Renamed, upgrade.Renamed...)
}
}
return versionModifiers, upgrades
}

func (t upgradeTracker) getUpgrades(isDevelop bool) ([]versionModiferFn, *storetypes.StoreUpgrades) {
if isDevelop {
return t.getDevelopUpgrades()
}
return t.mergeAllUpgrades()
}
95 changes: 95 additions & 0 deletions app/upgrade_tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package app

import (
"fmt"
"os"
"path"
"strconv"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
)

type upgradeHandlerFn func(ctx sdk.Context, vm module.VersionMap) (module.VersionMap, error)

type upgradeTrackerItem struct {
// Monotonically increasing index to order and track migrations. Typically the current unix epoch timestamp.
index int64
// Function that will run during the SetUpgradeHandler callback. The VersionMap must always be returned.
upgradeHandler upgradeHandlerFn
// StoreUpgrades that will be provided to UpgradeStoreLoader
storeUpgrade *storetypes.StoreUpgrades
}

// upgradeTracker allows us to track needed upgrades/migrations across both release and develop builds
type upgradeTracker struct {
upgrades []upgradeTrackerItem
stateFileDir string
}

func (t upgradeTracker) getDevelopUpgrades() ([]upgradeHandlerFn, *storetypes.StoreUpgrades, error) {
neededUpgrades := &storetypes.StoreUpgrades{}
neededUpgradeHandlers := []upgradeHandlerFn{}
stateFilePath := path.Join(t.stateFileDir, "developupgradetracker")

currentIndex := int64(0)
// #nosec G304 stateFilePath is not user controllable
if stateFileContents, err := os.ReadFile(stateFilePath); err == nil {
currentIndex, err = strconv.ParseInt(string(stateFileContents), 10, 64)
if err != nil {
return nil, nil, fmt.Errorf("unable to decode upgrade tracker: %w", err)

Check warning on line 41 in app/upgrade_tracker.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade_tracker.go#L41

Added line #L41 was not covered by tests
}
} else {
fmt.Printf("unable to load upgrade tracker: %v\n", err)
}

maxIndex := currentIndex
for _, item := range t.upgrades {
index := item.index
upgrade := item.storeUpgrade
versionModifier := item.upgradeHandler
if index <= currentIndex {
continue
}
if versionModifier != nil {
neededUpgradeHandlers = append(neededUpgradeHandlers, versionModifier)
}
if upgrade != nil {
neededUpgrades.Added = append(neededUpgrades.Added, upgrade.Added...)
neededUpgrades.Deleted = append(neededUpgrades.Deleted, upgrade.Deleted...)
neededUpgrades.Renamed = append(neededUpgrades.Renamed, upgrade.Renamed...)
}
maxIndex = index
}
err := os.WriteFile(stateFilePath, []byte(strconv.FormatInt(maxIndex, 10)), 0o600)
if err != nil {
return nil, nil, fmt.Errorf("unable to write upgrade state file: %w", err)

Check warning on line 67 in app/upgrade_tracker.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade_tracker.go#L67

Added line #L67 was not covered by tests
}
return neededUpgradeHandlers, neededUpgrades, nil
}

func (t upgradeTracker) mergeAllUpgrades() ([]upgradeHandlerFn, *storetypes.StoreUpgrades, error) {
upgrades := &storetypes.StoreUpgrades{}
upgradeHandlers := []upgradeHandlerFn{}
for _, item := range t.upgrades {
upgrade := item.storeUpgrade
versionModifier := item.upgradeHandler
if versionModifier != nil {
upgradeHandlers = append(upgradeHandlers, versionModifier)
}
if upgrade != nil {
upgrades.Added = append(upgrades.Added, upgrade.Added...)
upgrades.Deleted = append(upgrades.Deleted, upgrade.Deleted...)
upgrades.Renamed = append(upgrades.Renamed, upgrade.Renamed...)
}
}
return upgradeHandlers, upgrades, nil
}

func (t upgradeTracker) getUpgrades(isDevelop bool) ([]upgradeHandlerFn, *storetypes.StoreUpgrades, error) {
if isDevelop {
return t.getDevelopUpgrades()

Check warning on line 92 in app/upgrade_tracker.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade_tracker.go#L90-L92

Added lines #L90 - L92 were not covered by tests
}
return t.mergeAllUpgrades()

Check warning on line 94 in app/upgrade_tracker.go

View check run for this annotation

Codecov / codecov/patch

app/upgrade_tracker.go#L94

Added line #L94 was not covered by tests
}
38 changes: 19 additions & 19 deletions app/setup_handlers_test.go → app/upgrade_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package app
import (
"os"
"testing"
"time"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -13,74 +12,75 @@ import (
lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types"
)

func TestStoreUpgradeTracker(t *testing.T) {
func TestUpgradeTracker(t *testing.T) {
r := require.New(t)

// use timestamp for tracker idx so migrations work regardless of system state
now := time.Now().UnixMicro()

tmpdir, err := os.MkdirTemp("", "storeupgradetracker-*")
r.NoError(err)

allUpgrades := upgradeTracker{
upgrades: []upgradeTrackerItem{
{
idx: 1000,
index: 1000,
storeUpgrade: &storetypes.StoreUpgrades{
Added: []string{authoritytypes.ModuleName},
},
},
{
idx: 2000,
index: 2000,
storeUpgrade: &storetypes.StoreUpgrades{
Added: []string{lightclienttypes.ModuleName},
},
versionModifier: func(ctx sdk.Context, vm module.VersionMap) (module.VersionMap, error) {
upgradeHandler: func(ctx sdk.Context, vm module.VersionMap) (module.VersionMap, error) {
return vm, nil
},
},
{
idx: 3000,
versionModifier: func(ctx sdk.Context, vm module.VersionMap) (module.VersionMap, error) {
index: 3000,
upgradeHandler: func(ctx sdk.Context, vm module.VersionMap) (module.VersionMap, error) {
return vm, nil
},
},
},
stateFileDir: tmpdir,
}

versionModifierFns, storeUpgrades := allUpgrades.mergeAllUpgrades()
upgradeHandlers, storeUpgrades, err := allUpgrades.mergeAllUpgrades()
r.NoError(err)
r.Len(storeUpgrades.Added, 2)
r.Len(storeUpgrades.Renamed, 0)
r.Len(storeUpgrades.Deleted, 0)
r.Len(versionModifierFns, 2)
r.Len(upgradeHandlers, 2)

// should return all migrations on first call
versionModifierFns, storeUpgrades = allUpgrades.getDevelopUpgrades()
upgradeHandlers, storeUpgrades, err = allUpgrades.getDevelopUpgrades()
r.NoError(err)
r.Len(storeUpgrades.Added, 2)
r.Len(storeUpgrades.Renamed, 0)
r.Len(storeUpgrades.Deleted, 0)
r.Len(versionModifierFns, 2)
r.Len(upgradeHandlers, 2)

// should return no upgrades on second call
versionModifierFns, storeUpgrades = allUpgrades.getDevelopUpgrades()
upgradeHandlers, storeUpgrades, err = allUpgrades.getDevelopUpgrades()
r.NoError(err)
r.Len(storeUpgrades.Added, 0)
r.Len(storeUpgrades.Renamed, 0)
r.Len(storeUpgrades.Deleted, 0)
r.Len(versionModifierFns, 0)
r.Len(upgradeHandlers, 0)

// now add a upgrade and ensure that it gets run without running
// the other upgrades
allUpgrades.upgrades = append(allUpgrades.upgrades, upgradeTrackerItem{
idx: now + 3,
index: 4000,
storeUpgrade: &storetypes.StoreUpgrades{
Deleted: []string{"example"},
},
})

versionModifierFns, storeUpgrades = allUpgrades.getDevelopUpgrades()
upgradeHandlers, storeUpgrades, err = allUpgrades.getDevelopUpgrades()
r.NoError(err)
r.Len(storeUpgrades.Added, 0)
r.Len(storeUpgrades.Renamed, 0)
r.Len(storeUpgrades.Deleted, 1)
r.Len(versionModifierFns, 0)
r.Len(upgradeHandlers, 0)
}

0 comments on commit b822b3c

Please sign in to comment.