-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Regen network/multistore upgrades #5500
Regen network/multistore upgrades #5500
Conversation
…ork/multistore-upgrades
This pull request introduces 1 alert when merging 62ff38f into 869531c - view on LGTM.com new alerts:
|
…ork/multistore-upgrades
This pull request introduces 1 alert when merging 3a71d54 into 3df3887 - view on LGTM.com new alerts:
|
…network/cosmos-sdk into sahith/multistore
Sahith/multistore
This pull request introduces 1 alert when merging 28f7bca into d0a5b9b - view on LGTM.com new alerts:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A number of minor cleanup comments. However, two major design questions:
- I see the two loaders:
StoreLoaderWithUpgrade
andUpgradeableStoreLoader
. And a bit confused about usage.UpgradeableStoreLoader
requires loading the migration paths from disks, which I believe is now a deprecated approach.StoreLoaderWithUpgrade
seems to do what the PR requests, but is marked as only for testing. The usage should be clarified.
The other issue I have is how the upgrade handler is supposed to set this. I think @aaronc needs to answer this (ideally showing a code sample that compiles). From the issue:
Change the upgrade.Keeper SetUpgradeHandler
method to:
SetUpgradeHandler(name string, upgradeHandler types.UpgradeHandler, storeUpgrades []storytypes.StoreUpgrades)`
so that whenever store key renames/deletions are needed they can be registered with the upgrade handler
Add a hook to the upgrade module that configures BaseApp and the multi-store before starting the ABCI app. The multi-store upgrades should only be performed when there is an upgrade-info.json present and the version of the store matches the upgrade height in the json file. This will prevent store upgrades from happening too early.
Clearly the argument is still not added to the SetHandler
method. However, I see no clean way to add such a callback. I doubt BaseApp is ever supposed to be passed into a module, and not sure about import cycles and such. Also, the issue is that this info must be available without any calls to the KVStore. (Thus the original design to load from json file). If there is zero or one SetHandler
registered, then we can use the data (or nil) as the pending store upgrades. And expose some call GetPendingStoreUpgrades
that just inspects the map set up by SetHandler
. BUT, if there are multiple SetHandler
calls in the app, we cannot check the db to see which is pending to properly implement this.
I would recommend NOT doing this in SetHandler, and just adding this directly in app.go
. In fact app.go
must be updated for each upgrade, and is in the new binary, so it can store this info.
So, when preparing for an upgrade, we would have two lines:
# prepare the next update upgrade handler
upgradeKeeper.SetHandler("next-update", migrationFunc)
# only if we want to do upgrades in next update
storeLoader := upgrade.StoreLoaderWithUpgrade(storeUpgradesForNextUpdate)
app.SetStoreLoader(storeLoader)
simapp/app.go
Outdated
@@ -196,7 +196,7 @@ func NewSimApp( | |||
app.CrisisKeeper = crisis.NewKeeper( | |||
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName, | |||
) | |||
app.UpgradeKeeper = upgrade.NewKeeper(skipUpgradeHeights, keys[upgrade.StoreKey], app.cdc) | |||
app.UpgradeKeeper = upgrade.NewKeeper(skipUpgradeHeights, keys[upgrade.StoreKey], app.cdc, DefaultNodeHome) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just the default home. What happens when running with a different home flag (--home=/foo/bar
)? I think you need to read the home path with viper, like I did in wasmd: https://github.com/cosmwasm/wasmd/blob/master/app/app.go#L213-L215
If you have a better way to get this info, let me know and we can fix both binaries. But this was the best way I could think of to get the home dir dynamically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ethanfrey that is up to the specific application. You can see how that is handled in gaia
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the whole home processing stuff is burried in the client libs in cosmos-sdk not in the app. The default is set by app, but how we override with --home
or --home-client
is standardized
x/upgrade/internal/keeper/keeper.go
Outdated
return err | ||
} | ||
|
||
var upgradeInfo store.UpgradeInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So UpgradeInfo
includes StoreUpgrades
. Where do we set those? I never see those set by searching the diff (but maybe I missed it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea I had was for just Plan
to get serialized to json. I think that's likely how it should work. The store upgrades wouldn't get serialized to disk here.
|
||
// StoreLoaderWithUpgrade is used to prepare baseapp with a fixed StoreLoader | ||
// pattern. This is useful in test cases, or with custom upgrade loading logic. | ||
func StoreLoaderWithUpgrade(storeUpgrades *storetypes.StoreUpgrades) baseapp.StoreLoader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is called in app.go
(which I think it is), then we should do the viper stuff there, and just pass this as a second argument. You need to update app.go
to use viper.GetString(flags.FlagHome)
instead of DefaultNodeHome
when creating the upgrade keeper as well, so let's just use that value and pass it in here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keeping viper calls out of the modules and pushing them up into the cobra cli code is preferred.
} | ||
|
||
// Check if the current commit version and upgrade height matches | ||
if (len(upgrades.StoreUpgrades.Renamed) > 0 || len(upgrades.StoreUpgrades.Deleted) > 0) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like similar logic (but cleaned up) from the original upgrade handler.
Big issue here, is that we now never write the store upgrades from the pre-upgrade binary (design was that they would be in the proposal), but use them from the post-upgrade binary (written in code). I understand the reason behind this change, however, here we check value from file.
I would actually remove the StoreUpgrades
field form UpgradeInfo as it is confusing and not used elsewhere in the code (I think). And you should be checking eg. len(storeUpgrades.Renamed)
here. I see you do use the proper storeUpgrades
as an argument to ms.LoadLatestVersionAndUpgrade
, so it seems that you just missed to upgrade the if statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah,sorry missed this. Changed in next commit
// | ||
// This is useful for in place migrations when a store key is renamed between | ||
// two versions of the software. | ||
func UpgradeableStoreLoader(upgradeInfoPath string) baseapp.StoreLoader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an implementation of the older technique, right?
The code looks correct to me, but I am a bit confused when and where we would use one or the other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was to address store changes saved to disk, but as that is deprecated I'll remove the one which takes path
|
||
upgradeInfoFilePath := filepath.Join(homeDir, "upgrade-info.json") | ||
|
||
data := []byte(`{"height": 0, "store_upgrades": {"renamed":[{"old_key": "bnk", "new_key": "bank"}]}}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume to use a non-zero height? We want to use the same height on BeginBlock, right? They are supposed to check equality.
If this is eg. height=2
, then we mount as below and show that no migrations happen (even with the migration handler installed). Then commit eg. 2 blocks, so the iavl tree is now stored at height 2. Then mount the store again (with committed height =2) and validate the stores were renamed properly.
A bit more complicated, but important to ensure the checks for "only at height" work out properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, will clean this up
x/upgrade/internal/keeper/keeper.go
Outdated
return err | ||
} | ||
|
||
var upgradeInfo store.UpgradeInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea I had was for just Plan
to get serialized to json. I think that's likely how it should work. The store upgrades wouldn't get serialized to disk here.
Since this is a pretty infrequent operation, I think something that puts the burden in
So this could be as simple as something like: upgradeName, upgradeHeight := upgradeKeeper.ReadUpgradeInfoFromDisk()
if upgradeName == "myUpgrade" {
// configure store loader that checks if version == upgradeHeight and applies store upgrades
app.SetStoreLoader(UpgradeStoreLoader(upgradeHeight, storeUpgrades)
} This could also be more self-contained if we added a store upgrades parameter to upgradeKeeper.SetHandler("next-update", migrationFunc, storeUpgrades)
app.SetStoreLoader(upgradeKeeper.UpgradeStoreLoader()) Happy to get on a quick call to talk this through if that would help. |
This pull request introduces 1 alert when merging 3cc4be0 into 6890feb - view on LGTM.com new alerts:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK
@alexanderbez can you check this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good @anilcse! I left some minor feedback on the PR. Take a look :)
simapp/app.go
Outdated
@@ -189,7 +192,7 @@ func NewSimApp( | |||
app.CrisisKeeper = crisis.NewKeeper( | |||
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName, | |||
) | |||
app.UpgradeKeeper = upgrade.NewKeeper(skipUpgradeHeights, keys[upgrade.StoreKey], app.cdc) | |||
app.UpgradeKeeper = upgrade.NewKeeper(skipUpgradeHeights, keys[upgrade.StoreKey], app.cdc, viper.GetString(flags.FlagHome)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The viper dep should not be used here. We need to figure out a way to provide the home value app constructor instead.
Something we need to address in another PR is really cleaning up the app constructor. We should use variadic option functions maybe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might help: #5548
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should address that issue. But I would suggest doing it separately to keep this PR as lean as possible. For the time being, simply have the SimApp constructor take a new parameter or even introduce variadic options.
store/types/store.go
Outdated
@@ -44,6 +44,13 @@ type StoreUpgrades struct { | |||
Deleted []string `json:"deleted"` | |||
} | |||
|
|||
// UpgradeInfo defines height and name of the upgrade | |||
// to ensure multistore upgrades happens only at matching height |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// to ensure multistore upgrades happens only at matching height | |
// to ensure multistore upgrades happen only at matching height. |
x/upgrade/abci.go
Outdated
// Write upgrade info to disk | ||
// UpgradeStoreLoader uses this info to perform or skip store migrations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Write the upgrade info to disk. The UpgradeStoreLoader uses this info to perform or skip
// store migrations.
x/upgrade/internal/keeper/keeper.go
Outdated
@@ -130,3 +140,66 @@ func (k Keeper) ApplyUpgrade(ctx sdk.Context, plan types.Plan) { | |||
func (k Keeper) IsSkipHeight(height int64) bool { | |||
return k.skipUpgradeHeights[height] | |||
} | |||
|
|||
// DumpUpgradeInfoToDisk adds plan height to upgrade-info.json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// DumpUpgradeInfoToDisk adds plan height to upgrade-info.json | |
// DumpUpgradeInfoToDisk writes upgrade information to UpgradeInfoFileName. |
x/upgrade/internal/keeper/keeper.go
Outdated
func (k Keeper) GetUpgradeInfoPath() (string, error) { | ||
upgradeInfoFileDir := path.Join(k.GetHomeDir(), "data") | ||
|
||
if _, err := os.Stat(upgradeInfoFileDir); os.IsNotExist(err) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import tmos "github.com/tendermint/tendermint/libs/os"
Use tmos.EnsureDir
here instead of duplicating code 👍
x/upgrade/internal/keeper/keeper.go
Outdated
} | ||
|
||
// GetHomeDir returns the height at which the given upgrade was executed | ||
func (k Keeper) GetHomeDir() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be exposed/used anywhere outside the keeper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really. I will change that
x/upgrade/internal/keeper/keeper.go
Outdated
|
||
var upgradeInfo store.UpgradeInfo | ||
json.Unmarshal(data, &upgradeInfo) | ||
return upgradeInfo.Name, upgradeInfo.Height |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're returning all the values from the UpgradeInfo
type. You might as well just return the entire UpgradeInfo
struct which will cleanup the method signature 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the required changes
@@ -56,6 +56,17 @@ During each `EndBlock` execution, the `x/upgrade` module checks if there exists | |||
`Handler` is executed. If the `Plan` is expected to execute but no `Handler` is registered | |||
or if the binary was upgraded too early, the node will gracefully panic and exit. | |||
|
|||
## StoreLoader | |||
The `x/upgrade` module also facilitates store migrations as part of the upgrade. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we certainly need way more detail and explanation here. This is a core component of the upgrade procedure. I'd elaborate and explain concepts and procedures such as when and how a file is written to disk. What the file contains and why. How the file is used to migrate state....etc.
Fix review updates
…network/cosmos-sdk into regen-network/multistore-upgrades
…ork/multistore-upgrades # Conflicts: # simapp/app.go # x/upgrade/keeper/keeper.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK -- just need to resolve the merge conflict ;)
…ork/multistore-upgrades # Conflicts: # x/upgrade/keeper/keeper.go
…network/cosmos-sdk into regen-network/multistore-upgrades
Updated |
Closes: regen-network#7
Description
For contributor use:
docs/
) or specification (x/<module>/spec/
)godoc
comments.Unreleased
section inCHANGELOG.md
Files changed
in the Github PR explorerFor admin use:
WIP
,R4R
,docs
, etc)