Skip to content
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

Upgrade handler #684

Merged
merged 18 commits into from
Jul 28, 2022
Merged
36 changes: 35 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package app

import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"

upgradev46 "github.com/Pylons-tech/pylons/app/upgrade"
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
Expand Down Expand Up @@ -238,6 +240,9 @@ type PylonsApp struct {

// simulation manager
sm *module.SimulationManager

// module migration manager
configurator module.Configurator
}

// New returns a reference to an initialized Pylons.
Expand Down Expand Up @@ -407,6 +412,8 @@ func New(
app.TransferKeeper,
app.GetSubspace(pylonsmoduletypes.ModuleName),
)
// upgrade handlers
cfg := module.NewConfigurator(appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())

pylonsModule := pylonsmodule.NewAppModule(appCodec, app.PylonsKeeper, app.BankKeeper)

Expand All @@ -418,6 +425,7 @@ func New(

// NOTE: Any module instantiated in the module manager that is later modified
// must be passed by reference here.
app.setupUpgradeStoreLoaders()

app.mm = module.NewManager(
genutil.NewAppModule(
Expand Down Expand Up @@ -517,7 +525,8 @@ func New(

app.mm.RegisterInvariants(&app.CrisisKeeper)
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
app.mm.RegisterServices(module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()))
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
app.mm.RegisterServices(app.configurator)

// create the simulation manager and define the order of the modules for deterministic simulations
//
Expand All @@ -542,6 +551,9 @@ func New(

app.sm.RegisterStoreDecoders()

// register upgrade
app.RegisterUpgradeHandlers(cfg)

// initialize stores
app.MountKVStores(keys)
app.MountTransientStores(tkeys)
Expand Down Expand Up @@ -703,6 +715,28 @@ func (app *PylonsApp) RegisterTendermintService(clientCtx client.Context) {
)
}

// RegisterUpgradeHandlers returns upgrade handlers
func (app *PylonsApp) RegisterUpgradeHandlers(cfg module.Configurator) {
app.UpgradeKeeper.SetUpgradeHandler(upgradev46.UpgradeName, upgradev46.CreateUpgradeHandler(app.mm, app.configurator, &app.StakingKeeper, app.keys[pylonsmoduletypes.StoreKey], app.appCodec))
}

func (app *PylonsApp) setupUpgradeStoreLoaders() {
upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk()
if err != nil {
panic(fmt.Sprintf("failed to read upgrade info from disk %s", err))
}

// TODO: add module name.
if upgradeInfo.Name == upgradev46.UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
storeUpgrades := storetypes.StoreUpgrades{
Deleted: []string{"epoch"},
}

// configure store loader that checks if version == upgradeHeight and applies store upgrades
app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades))
}
}

// GetMaccPerms returns a copy of the module account permissions
func GetMaccPerms() map[string][]string {
dupMaccPerms := make(map[string][]string)
Expand Down
3 changes: 3 additions & 0 deletions app/upgrade/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package upgrade

const UpgradeName = "sdk-46"
60 changes: 60 additions & 0 deletions app/upgrade/fork.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package upgrade

import (
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

v046 "github.com/Pylons-tech/pylons/x/pylons/migrations/v046"
)

// CreateUpgradeHandler make upgrade handler
func CreateUpgradeHandler(mm *module.Manager, configurator module.Configurator, staking *stakingkeeper.Keeper, pylonStoreKey storetypes.StoreKey, cdc codec.Codec) upgradetypes.UpgradeHandler {
return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) {
newVM, err := mm.RunMigrations(ctx, configurator, vm)
if err != nil {
return newVM, err
}
FixMinCommissionRate(ctx, staking)
err = MigrateStore(ctx, pylonStoreKey, cdc)
if err != nil {
return newVM, err
}

// override here
return newVM, err
}
}

// Fixes an error where validators can be created with a commission rate
// less than the network minimum rate.
func FixMinCommissionRate(ctx sdk.Context, staking *stakingkeeper.Keeper) {
// Upgrade every validators min-commission rate
validators := staking.GetAllValidators(ctx)
minCommissionRate := staking.GetParams(ctx).MinCommissionRate
for _, v := range validators {
if v.Commission.Rate.LT(minCommissionRate) {
comm, err := staking.UpdateValidatorCommission(
ctx, v, minCommissionRate)
if err != nil {
panic(err)
}
v.Commission = comm

// call the before-modification hook since we're about to update the commission
err = staking.BeforeValidatorModified(ctx, v.GetOperator())
if err != nil {
panic(err)
}

staking.SetValidator(ctx, v)
}
}
}

func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.Codec) error {
return v046.MigrateStore(ctx, storeKey, cdc)
}
12 changes: 12 additions & 0 deletions proto/pylons/v1/old_apple_in_app_purchase_order.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
syntax = "proto3";
package pylons.pylons;

option go_package = "github.com/Pylons-tech/pylons/x/pylons/types";

message AppleInAppPurchaseOrder {
string quantity = 1;
string productID = 2;
string purchaseID = 3;
string purchaseDate = 4;
string Creator = 5;
}
40 changes: 40 additions & 0 deletions scripts/upgrade.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Create a genesis.json for testing. The node that you this on will be your "validator"
# It should be on version v3.0.0-rc0
pylonsd init --chain-id=testing testing --home=$HOME/.pylonsd
pylonsd keys add validator --keyring-backend=test --home=$HOME/.pylonsd
pylonsd add-genesis-account $(pylonsd keys show validator -a --keyring-backend=test --home=$HOME/.pylonsd) 1000000000upylon,1000000000valtoken --home=$HOME/.pylonsd
sed -i -e "s/stake/upylon/g" $HOME/.pylonsd/config/genesis.json
pylonsd gentx validator 500000000upylon --commission-rate="0.0" --keyring-backend=test --home=$HOME/.pylonsd --chain-id=testing
pylonsd collect-gentxs --home=$HOME/.pylonsd

cat $HOME/.pylonsd/config/genesis.json | jq '.initial_height="139"' > $HOME/.pylonsd/config/tmp_genesis.json && mv $HOME/.pylonsd/config/tmp_genesis.json $HOME/.pylonsd/config/genesis.json
cat $HOME/.pylonsd/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["min_deposit"]["denom"]="valtoken"' > $HOME/.pylonsd/config/tmp_genesis.json && mv $HOME/.pylonsd/config/tmp_genesis.json $HOME/.pylonsd/config/genesis.json
cat $HOME/.pylonsd/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["min_deposit"]["amount"]="100"' > $HOME/.pylonsd/config/tmp_genesis.json && mv $HOME/.pylonsd/config/tmp_genesis.json $HOME/.pylonsd/config/genesis.json
cat $HOME/.pylonsd/config/genesis.json | jq '.app_state["gov"]["voting_params"]["voting_period"]="120s"' > $HOME/.pylonsd/config/tmp_genesis.json && mv $HOME/.pylonsd/config/tmp_genesis.json $HOME/.pylonsd/config/genesis.json
cat $HOME/.pylonsd/config/genesis.json | jq '.app_state["staking"]["params"]["min_commission_rate"]="0.050000000000000000"' > $HOME/.pylonsd/config/tmp_genesis.json && mv $HOME/.pylonsd/config/tmp_genesis.json $HOME/.pylonsd/config/genesis.json

# Now setup a second full node, and peer it with this v3.0.0-rc0 node.

# start the chain on both machines
pylonsd start
# Create proposals

pylonsd tx gov submit-proposal --title="existing passing prop" --description="passing prop" --from=validator --deposit=1000valtoken --chain-id=testing --keyring-backend=test --broadcast-mode=block --type="Text"
pylonsd tx gov vote 1 yes --from=validator --keyring-backend=test --chain-id=testing --yes
pylonsd tx gov submit-proposal --title="prop with enough osmo deposit" --description="prop w/ enough deposit" --from=validator --deposit=500000000upylon --chain-id=testing --keyring-backend=test --broadcast-mode=block --type="Text"
# Check that we have proposal 1 passed, and proposal 2 in deposit period
pylonsd q gov proposals
# CHeck that validator commission is under min_commission_rate
pylonsd q staking validators
# Wait for upgrade block.
# Upgrade happened
# your full node should have crashed with consensus failure

# Now we test post-upgrade behavior is as intended

# Everything in deposit stayed in deposit
pylonsd q gov proposals
# Check that commissions was bumped to min_commission_rate
pylonsd q staking validators
# pushes 2 into voting period
pylonsd tx gov deposit 2 1valtoken --from=validator --keyring-backend=test --chain-id=testing --yes
20 changes: 20 additions & 0 deletions x/pylons/migrations/v046/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package v046

import (
"github.com/Pylons-tech/pylons/x/pylons/types"
v1 "github.com/Pylons-tech/pylons/x/pylons/types/v1"
)

// ConvertToLegacyProposal takes a new proposal and attempts to convert it to the
// legacy proposal format. This conversion is best effort. New proposal types that
// don't have a legacy message will return a "nil" content

func convertToNewAppleInAppPurchaseOrder(oldProp v1.AppleInAppPurchaseOrder) types.AppleInAppPurchaseOrder {
return types.AppleInAppPurchaseOrder{
Quantity: oldProp.Quantity,
ProductId: oldProp.ProductID,
PurchaseId: oldProp.PurchaseID,
PurchaseDate: oldProp.PurchaseDate,
Creator: oldProp.Creator,
}
}
49 changes: 49 additions & 0 deletions x/pylons/migrations/v046/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package v046

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/Pylons-tech/pylons/x/pylons/types"
v1 "github.com/Pylons-tech/pylons/x/pylons/types/v1"
)

// migrateProposals migrates all legacy proposals into MsgExecLegacyContent
// proposals.
func migrateAppleInAppPurchaseOrder(store sdk.KVStore, cdc codec.BinaryCodec) error {
orderStore := prefix.NewStore(store, []byte(types.AppleInAppPurchaseOrderKey))

iter := orderStore.Iterator(nil, nil)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
var oldProp v1.AppleInAppPurchaseOrder
err := cdc.Unmarshal(iter.Value(), &oldProp)
if err != nil {
return err
}

newProp := convertToNewAppleInAppPurchaseOrder(oldProp)
bz, err := cdc.Marshal(&newProp)
if err != nil {
return err
}

// Set new value on store.
orderStore.Set(iter.Key(), bz)
}

return nil
}

// MigrateStore performs in-place store migrations from v0.43 to v0.46. The
// migration includes:
//
// - Migrate proposals to be Msg-based.
func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) error {
store := ctx.KVStore(storeKey)

return migrateAppleInAppPurchaseOrder(store, cdc)
}
66 changes: 66 additions & 0 deletions x/pylons/migrations/v046/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package v046_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"

v046 "github.com/Pylons-tech/pylons/x/pylons/migrations/v046"
"github.com/Pylons-tech/pylons/x/pylons/types"
v1 "github.com/Pylons-tech/pylons/x/pylons/types/v1"
)

func TestMigrateStore(t *testing.T) {
cdc := simapp.MakeTestEncodingConfig().Codec
key := sdk.NewKVStoreKey(types.AppleInAppPurchaseOrderKey)
ctx := testutil.DefaultContext(key, sdk.NewTransientStoreKey("transient_test"))
store := ctx.KVStore(key)

// Create 2 AppleInAppPurchaseOrder
prop1 := v1.AppleInAppPurchaseOrder{
Quantity: "1",
ProductID: "test_prod_1",
PurchaseID: "test_purchase_1",
PurchaseDate: "28-07-2022",
Creator: types.GenTestBech32FromString("test1"),
}
prop1Bz, err := cdc.Marshal(&prop1)

prop2 := v1.AppleInAppPurchaseOrder{
Quantity: "2",
ProductID: "test_prod_2",
PurchaseID: "test_purchase_2",
PurchaseDate: "28-07-2022",
Creator: types.GenTestBech32FromString("test2"),
}
prop2Bz, err := cdc.Marshal(&prop2)

store.Set(types.KeyPrefix(prop1.PurchaseID), prop1Bz)
store.Set(types.KeyPrefix(prop2.PurchaseID), prop2Bz)

// Run migrations.
err = v046.MigrateStore(ctx, key, cdc)
require.NoError(t, err)

var newProp1 types.AppleInAppPurchaseOrder
err = cdc.Unmarshal(store.Get(types.KeyPrefix(prop1.PurchaseID)), &newProp1)
require.NoError(t, err)
compareProps(t, prop1, newProp1)

var newProp2 types.AppleInAppPurchaseOrder
err = cdc.Unmarshal(store.Get(types.KeyPrefix(prop2.PurchaseID)), &newProp2)
require.NoError(t, err)
compareProps(t, prop2, newProp2)
}

func compareProps(t *testing.T, oldProp v1.AppleInAppPurchaseOrder, newProp types.AppleInAppPurchaseOrder) {
require.Equal(t, oldProp.PurchaseID, newProp.PurchaseId)
require.Equal(t, oldProp.Quantity, newProp.Quantity)
require.Equal(t, oldProp.ProductID, newProp.ProductId)
require.Equal(t, oldProp.PurchaseDate, newProp.PurchaseDate)
require.Equal(t, oldProp.Creator, newProp.Creator)
}
Loading