diff --git a/meta.json b/meta.json index f270dbd2c3..2d93bb20c3 100644 --- a/meta.json +++ b/meta.json @@ -9,6 +9,11 @@ "skipped": false, "from_binary": "v0.22.8", "from_version": "v0.22.0" + }, + "v0.26.0": { + "skipped": false, + "from_binary": "v0.24.2", + "from_version": "v0.24.0" } } } diff --git a/tests/upgrade/upgrade_test.go b/tests/upgrade/upgrade_test.go index 34f92682d7..7874783526 100644 --- a/tests/upgrade/upgrade_test.go +++ b/tests/upgrade/upgrade_test.go @@ -91,12 +91,6 @@ var ( nodeTestStagePostUpgrade: "postupgrade", } - testStageMapStr = map[testStage]string{ - testStagePreUpgrade: "preupgrade", - testStageUpgrade: "upgrade", - testStagePostUpgrade: "postupgrade", - } - testModuleStatusMapStr = map[testModuleStatus]string{ testModuleStatusUnexpected: "unexpected", testModuleStatusNotChecked: "notchecked", @@ -563,13 +557,12 @@ loop: if stageCount == 0 { l.t.Log("all nodes performed upgrade") - for _, val := range l.validators { - _ = val.pubsub.Publish(eventShutdown{}) - } postUpgradeWorker := uttypes.GetPostUpgradeWorker(l.upgradeName) if postUpgradeWorker == nil { l.t.Log("no post upgrade handlers found. submitting shutdown") + _ = bus.Publish(postUpgradeTestDone{}) + break loop } @@ -593,6 +586,10 @@ loop: }) } case postUpgradeTestDone: + for _, val := range l.validators { + _ = val.pubsub.Publish(eventShutdown{}) + } + break loop } } @@ -764,7 +761,7 @@ func executeCommand(ctx context.Context, env []string, cmd string, args ...strin } func (l *validator) run() error { - lStdout, err := os.Create(fmt.Sprintf("%s/%s-stdout.log", l.params.home, l.params.name)) + lStdout, err := os.Create(fmt.Sprintf("%s/logs/%s-stdout.log", l.params.home, l.params.name)) if err != nil { return err } @@ -773,7 +770,7 @@ func (l *validator) run() error { _ = lStdout.Close() }() - lStderr, err := os.Create(fmt.Sprintf("%s/%s-stderr.log", l.params.home, l.params.name)) + lStderr, err := os.Create(fmt.Sprintf("%s/logs/%s-stderr.log", l.params.home, l.params.name)) if err != nil { return err } diff --git a/tests/upgrade/v0.26.0/postupgrade.go b/tests/upgrade/v0.26.0/postupgrade.go new file mode 100644 index 0000000000..7931e3da2d --- /dev/null +++ b/tests/upgrade/v0.26.0/postupgrade.go @@ -0,0 +1,176 @@ +//go:build e2e.upgrade + +// Package v0_26_0 +// nolint revive +package v0_26_0 + +import ( + "context" + "fmt" + "strings" + "testing" + + // v1beta2dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta2" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + astaking "github.com/akash-network/akash-api/go/node/staking/v1beta3" + + "github.com/akash-network/node/app" + uttypes "github.com/akash-network/node/tests/upgrade/types" +) + +func init() { + uttypes.RegisterPostUpgradeWorker("v0.26.0", &postUpgrade{}) +} + +type postUpgrade struct{} + +var _ uttypes.TestWorker = (*postUpgrade)(nil) + +func (pu *postUpgrade) Run(ctx context.Context, t *testing.T, params uttypes.TestParams) { + encodingConfig := app.MakeEncodingConfig() + + rpcClient, err := client.NewClientFromNode(params.Node) + require.NoError(t, err) + + cctx := client.Context{}. + WithCodec(encodingConfig.Marshaler). + WithInterfaceRegistry(encodingConfig.InterfaceRegistry). + WithTxConfig(encodingConfig.TxConfig). + WithLegacyAmino(encodingConfig.Amino). + WithAccountRetriever(authtypes.AccountRetriever{}). + WithBroadcastMode(flags.BroadcastBlock). + WithHomeDir(params.Home). + WithChainID(params.ChainID). + WithNodeURI(params.Node). + WithClient(rpcClient) + + kr, err := client.NewKeyringFromBackend(cctx, params.KeyringBackend) + require.NoError(t, err) + + cctx = cctx.WithKeyring(kr) + + validateValidatorsCommission(ctx, cctx, t) + validateDeploymentAuthz(ctx, cctx, t) +} + +func validateDeploymentAuthz(ctx context.Context, cctx client.Context, t *testing.T) { + authQc := authtypes.NewQueryClient(cctx) + authzQc := authz.NewQueryClient(cctx) + + var pkey []byte + + accs := make([]sdk.AccAddress, 0, 100000) + + for { + var pgn *query.PageRequest + if pkey != nil { + pgn = &query.PageRequest{ + Key: pkey, + } + } + + result, err := authQc.Accounts(ctx, &authtypes.QueryAccountsRequest{ + Pagination: pgn, + }) + + require.NoError(t, err) + + for _, accI := range result.Accounts { + var acc authtypes.AccountI + err := cctx.Codec.UnpackAny(accI, &acc) + require.NoError(t, err) + + accs = append(accs, acc.GetAddress()) + } + + if pg := result.Pagination; pg != nil && len(pg.NextKey) > 0 { + pkey = pg.NextKey + } else { + break + } + } + + for _, acc := range accs { + result, err := authzQc.GranterGrants(ctx, &authz.QueryGranterGrantsRequest{Granter: acc.String()}) + require.NoError(t, err) + + if len(result.Grants) == 0 { + continue + } + + for _, grant := range result.Grants { + assert.NotEqual(t, + "/akash.deployment.v1beta2.DepositDeploymentAuthorization", + grant.Authorization.GetTypeUrl(), + "detected non-migrated v1beta2.DepositDeploymentAuthorization. granter: (%s), grantee: (%s)", grant.Granter, grant.Grantee) + // authzOld := &v1beta2dtypes.DepositDeploymentAuthorization{} + // + // err := cctx.Codec.UnpackAny(grant.Authorization, authzOld) + // assert.Error(t, err, "detected non-migrated v1beta2.DepositDeploymentAuthorization. granter: (%s), grantee: (%s)", grant.Granter, grant.Grantee) + } + } +} + +func validateValidatorsCommission(ctx context.Context, cctx client.Context, t *testing.T) { + pqc := proposal.NewQueryClient(cctx) + res, err := pqc.Params(ctx, &proposal.QueryParamsRequest{ + Subspace: stakingtypes.ModuleName, + Key: string(stakingtypes.KeyMaxValidators), + }) + require.NoError(t, err) + + require.NoError(t, err) + + res, err = pqc.Params(ctx, &proposal.QueryParamsRequest{ + Subspace: astaking.ModuleName, + Key: string(astaking.KeyMinCommissionRate), + }) + require.NoError(t, err) + + minCommission, err := sdk.NewDecFromStr(strings.Trim(res.Param.Value, "\"")) + require.NoError(t, err) + + sqc := stakingtypes.NewQueryClient(cctx) + + var pkey []byte + + for { + var pgn *query.PageRequest + if pkey != nil { + pgn = &query.PageRequest{ + Key: pkey, + } + } + + result, err := sqc.Validators(ctx, &stakingtypes.QueryValidatorsRequest{ + Pagination: pgn, + }) + require.NoError(t, err) + + for _, validator := range result.Validators { + assert.True(t, validator.Commission.Rate.GTE(minCommission), + fmt.Sprintf("invalid commission Rate for validator (%s). (%s%%) < (%s%%)MinCommission", + validator.OperatorAddress, validator.Commission.Rate.String(), minCommission.String())) + + assert.True(t, validator.Commission.MaxRate.GTE(minCommission), + fmt.Sprintf("invalid commission MaxRate for validator (%s). (%s%%) < (%s%%)MinCommission", + validator.OperatorAddress, validator.Commission.MaxRate.String(), minCommission.String())) + } + + if pg := result.Pagination; pg != nil && len(pg.NextKey) > 0 { + pkey = pg.NextKey + } else { + break + } + } +} diff --git a/tests/upgrade/workers_test.go b/tests/upgrade/workers_test.go new file mode 100644 index 0000000000..5207ee2852 --- /dev/null +++ b/tests/upgrade/workers_test.go @@ -0,0 +1,7 @@ +//go:build e2e.upgrade + +package upgrade + +import ( + _ "github.com/akash-network/node/tests/upgrade/v0.26.0" +) diff --git a/upgrades/CHANGELOG.md b/upgrades/CHANGELOG.md index 6dfa418511..82eb780e75 100644 --- a/upgrades/CHANGELOG.md +++ b/upgrades/CHANGELOG.md @@ -43,6 +43,10 @@ Goal of the upgrade here Add new upgrades after this line based on the template above ----- +##### v0.26.0 + +1. Enforce **Minimum Validators commission** using onchain parameter. Default value is set to 5%. This is carry-over from v0.24.0 upgrade, as this change was dry-run + ##### v0.24.0 1. Update following stores to the `v1beta3`: diff --git a/upgrades/software/v0.26.0/init.go b/upgrades/software/v0.26.0/init.go new file mode 100644 index 0000000000..e1df58ebd3 --- /dev/null +++ b/upgrades/software/v0.26.0/init.go @@ -0,0 +1,11 @@ +// Package v0_26_0 +// nolint revive +package v0_26_0 + +import ( + utypes "github.com/akash-network/node/upgrades/types" +) + +func init() { + utypes.RegisterUpgrade(UpgradeName, initUpgrade) +} diff --git a/upgrades/software/v0.26.0/upgrade.go b/upgrades/software/v0.26.0/upgrade.go new file mode 100644 index 0000000000..78ac996eef --- /dev/null +++ b/upgrades/software/v0.26.0/upgrade.go @@ -0,0 +1,206 @@ +// Package v0_26_0 +// nolint revive +package v0_26_0 + +import ( + "fmt" + "reflect" + "time" + + "github.com/tendermint/tendermint/libs/log" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/authz" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + v1beta2dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta2" + dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" + + apptypes "github.com/akash-network/node/app/types" + utypes "github.com/akash-network/node/upgrades/types" +) + +const ( + UpgradeName = "v0.26.0" +) + +type upgrade struct { + *apptypes.App + log log.Logger +} + +var _ utypes.IUpgrade = (*upgrade)(nil) + +func initUpgrade(log log.Logger, app *apptypes.App) (utypes.IUpgrade, error) { + up := &upgrade{ + App: app, + log: log.With("module", fmt.Sprintf("upgrade/%s", UpgradeName)), + } + + return up, nil +} + +func (up *upgrade) StoreLoader() *storetypes.StoreUpgrades { + return &storetypes.StoreUpgrades{} +} + +func (up *upgrade) UpgradeHandler() upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + if err := up.enforceMinValidatorCommission(ctx); err != nil { + return nil, err + } + + if err := up.migrateDeploymentAuthz(ctx); err != nil { + return nil, err + } + + return up.MM.RunMigrations(ctx, up.Configurator, fromVM) + } +} + +type grantBackup struct { + granter sdk.AccAddress + grantee sdk.AccAddress + grant authz.Grant +} + +func (up *upgrade) migrateDeploymentAuthz(ctx sdk.Context) error { + msgUrlOld := v1beta2dtypes.DepositDeploymentAuthorization{}.MsgTypeURL() + + grants := make([]grantBackup, 0, 10000) + + up.Keepers.Cosmos.Authz.IterateGrants(ctx, func(granterAddr sdk.AccAddress, granteeAddr sdk.AccAddress, grant authz.Grant) bool { + authorization := grant.GetAuthorization() + if authorization.MsgTypeURL() == msgUrlOld { + grants = append(grants, grantBackup{ + granter: granterAddr, + grantee: granteeAddr, + grant: grant, + }) + } + return false + }) + + expiredCnt := 0 + + for _, grant := range grants { + authzGen := grant.grant.GetAuthorization() + authzOld, valid := authzGen.(*v1beta2dtypes.DepositDeploymentAuthorization) + if !valid { + return fmt.Errorf("unexpected authorization type. expected (%s), actual (%s)", reflect.TypeOf(&v1beta2dtypes.DepositDeploymentAuthorization{}), reflect.TypeOf(authzGen)) + } + + err := up.Keepers.Cosmos.Authz.DeleteGrant(ctx, grant.grantee, grant.granter, msgUrlOld) + if err != nil { + return err + } + + // Save only grants that are still active at upgrade block + if grant.grant.Expiration.Before(ctx.BlockHeader().Time) { + expiredCnt++ + continue + } + + authzNew := dtypes.NewDepositDeploymentAuthorization(authzOld.SpendLimit) + + err = up.Keepers.Cosmos.Authz.SaveGrant(ctx, grant.grantee, grant.granter, authzNew, grant.grant.Expiration) + if err != nil { + return err + } + } + + up.log.Info("updated DepositDeploymentAuthorizations from v1beta2 to v1beta3", "total", len(grants), "expired", expiredCnt) + + return nil +} + +func (up *upgrade) enforceMinValidatorCommission(ctx sdk.Context) error { + minRate := up.Keepers.Akash.Staking.MinCommissionRate(ctx) + validators := up.Keepers.Cosmos.Staking.GetAllValidators(ctx) + + for _, validator := range validators { + if validator.Commission.MaxRate.LT(minRate) || validator.GetCommission().LT(minRate) { + // update MaxRate if it is less than minimum required rate + if validator.Commission.MaxRate.LT(minRate) { + up.log.Info(fmt.Sprintf("force updating validator commission MaxRate to %s", minRate), + "address", + validator.OperatorAddress, + "currentMaxRate", + validator.Commission.MaxRate, + ) + + validator.Commission.MaxRate = minRate + } + + if validator.GetCommission().LT(minRate) { + up.log.Info(fmt.Sprintf("force updating validator commission Rate to %s", minRate), + "address", + validator.OperatorAddress, + "currentRate", + validator.Commission.Rate, + ) + + // set max change rate temporarily to 100% + maxRateCh := validator.Commission.MaxChangeRate + validator.Commission.MaxChangeRate = sdk.NewDecWithPrec(1, 0) + + newCommission, err := updateValidatorCommission(ctx, validator, minRate) + if err != nil { + return err + } + + validator.Commission = newCommission + validator.Commission.MaxChangeRate = maxRateCh + } + + up.Keepers.Cosmos.Staking.BeforeValidatorModified(ctx, validator.GetOperator()) + up.Keepers.Cosmos.Staking.SetValidator(ctx, validator) + } + } + + return nil +} + +// updateValidatorCommission use custom implementation of update commission, +// this prevents panic during upgrade if any of validators have changed their +// commission within 24h of upgrade height +func updateValidatorCommission( + ctx sdk.Context, + validator stakingtypes.Validator, + newRate sdk.Dec, +) (stakingtypes.Commission, error) { + commission := validator.Commission + blockTime := ctx.BlockHeader().Time + + if err := validateNewRate(commission, newRate, blockTime); err != nil { + return commission, err + } + + commission.Rate = newRate + commission.UpdateTime = blockTime + + return commission, nil +} + +// validateNewRate performs basic sanity validation checks of a new commission +// rate. If validation fails, an SDK error is returned. +func validateNewRate(commission stakingtypes.Commission, newRate sdk.Dec, _ time.Time) error { + switch { + case newRate.IsNegative(): + // new rate cannot be negative + return stakingtypes.ErrCommissionNegative + + case newRate.GT(commission.MaxRate): + // new rate cannot be greater than the max rate + return stakingtypes.ErrCommissionGTMaxRate + + case newRate.Sub(commission.Rate).GT(commission.MaxChangeRate): + // new rate % points change cannot be greater than the max change rate + return stakingtypes.ErrCommissionGTMaxChangeRate + } + + return nil +} diff --git a/upgrades/upgrades.go b/upgrades/upgrades.go index a9540b0dc3..e5d26fa708 100644 --- a/upgrades/upgrades.go +++ b/upgrades/upgrades.go @@ -1,6 +1,8 @@ package upgrades import ( + // nolint: revive + _ "github.com/akash-network/node/upgrades/software/v0.26.0" // nolint: revive _ "github.com/akash-network/node/upgrades/software/v0.24.0" // nolint: revive