diff --git a/.changelog/unreleased/bug-fixes/provider/1925-apply-audit-suggestions.md b/.changelog/unreleased/bug-fixes/provider/1925-apply-audit-suggestions.md index 3d12033a4e..018c0eafb1 100644 --- a/.changelog/unreleased/bug-fixes/provider/1925-apply-audit-suggestions.md +++ b/.changelog/unreleased/bug-fixes/provider/1925-apply-audit-suggestions.md @@ -1,3 +1,2 @@ - Apply audit suggestions that include a bug fix in the way we compute the - maximum capped power. ([\#1925](https://github.com/cosmos/interchain- - security/pull/1925)) + maximum capped power. ([\#1925](https://github.com/cosmos/interchain-security/pull/1925)) diff --git a/.changelog/unreleased/features/provider/2035-min-stake-max-rank.md b/.changelog/unreleased/features/provider/2035-min-stake-max-rank.md new file mode 100644 index 0000000000..bd9f0b9eb3 --- /dev/null +++ b/.changelog/unreleased/features/provider/2035-min-stake-max-rank.md @@ -0,0 +1,2 @@ +- Add minimum stake and maximum validator rank requirements to let consumer chains + determine requirements for validators that validate their chain. ([\#2035](https://github.com/cosmos/interchain-security/pull/2035)) diff --git a/.changelog/unreleased/features/provider/2066-inactive-vals.md b/.changelog/unreleased/features/provider/2066-inactive-vals.md new file mode 100644 index 0000000000..f54d88fc29 --- /dev/null +++ b/.changelog/unreleased/features/provider/2066-inactive-vals.md @@ -0,0 +1 @@ +- Add the `allow_inactive_vals` parameter for consumer chains to choose whether inactive validators can validate their chain ([\#2066](https://github.com/cosmos/interchain-security/pull/2066)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1925-apply-audit-suggestions.md b/.changelog/unreleased/state-breaking/provider/1925-apply-audit-suggestions.md index 3d12033a4e..018c0eafb1 100644 --- a/.changelog/unreleased/state-breaking/provider/1925-apply-audit-suggestions.md +++ b/.changelog/unreleased/state-breaking/provider/1925-apply-audit-suggestions.md @@ -1,3 +1,2 @@ - Apply audit suggestions that include a bug fix in the way we compute the - maximum capped power. ([\#1925](https://github.com/cosmos/interchain- - security/pull/1925)) + maximum capped power. ([\#1925](https://github.com/cosmos/interchain-security/pull/1925)) diff --git a/.changelog/unreleased/state-breaking/provider/2035-min-stake-max-rank.md b/.changelog/unreleased/state-breaking/provider/2035-min-stake-max-rank.md new file mode 100644 index 0000000000..bd9f0b9eb3 --- /dev/null +++ b/.changelog/unreleased/state-breaking/provider/2035-min-stake-max-rank.md @@ -0,0 +1,2 @@ +- Add minimum stake and maximum validator rank requirements to let consumer chains + determine requirements for validators that validate their chain. ([\#2035](https://github.com/cosmos/interchain-security/pull/2035)) diff --git a/.changelog/unreleased/state-breaking/provider/2066-inactive-vals.md b/.changelog/unreleased/state-breaking/provider/2066-inactive-vals.md new file mode 100644 index 0000000000..f54d88fc29 --- /dev/null +++ b/.changelog/unreleased/state-breaking/provider/2066-inactive-vals.md @@ -0,0 +1 @@ +- Add the `allow_inactive_vals` parameter for consumer chains to choose whether inactive validators can validate their chain ([\#2066](https://github.com/cosmos/interchain-security/pull/2066)) \ No newline at end of file diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index b279c59714..ffc8ff363f 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -310,6 +310,23 @@ jobs: - name: E2E active set changes run: go run ./tests/e2e/... --tc active-set-changes + inactive-provider-validators-on-consumer-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.22" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" # The Go version to download (if necessary) and use. + - name: E2E inactive provider validators on consumer + run: go run ./tests/e2e/... --tc inactive-provider-validators-on-consumer + nightly-test-fail: needs: - happy-path-test diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation.yml new file mode 100644 index 0000000000..72c3a70b2c --- /dev/null +++ b/.github/workflows/simulation.yml @@ -0,0 +1,44 @@ +name: Simulation +on: + workflow_call: + pull_request: + merge_group: + push: + branches: + - main + - release/v* + - feat/* + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }}-tests + cancel-in-progress: true + +jobs: + simulation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.22" + check-latest: true + cache: true + cache-dependency-path: go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - name: simulation test + if: env.GIT_DIFF + run: | + make sim-full \ No newline at end of file diff --git a/Makefile b/Makefile index e68df6fd63..c00d2fcb23 100644 --- a/Makefile +++ b/Makefile @@ -145,6 +145,13 @@ verify-models: ../run_invariants.sh +############################################################################### +### Simulation tests ### + +# Run a full simulation test +sim-full: + cd app/provider;\ + go test -mod=readonly -run=^TestFullAppSimulation$ -Enabled=true -NumBlocks=500 -BlockSize=200 -Commit=true -timeout 24h github.com/cosmos/interchain-security/v5/app/provider -v ############################################################################### ### Linting ### diff --git a/app/consumer-democracy/proposals_whitelisting.go b/app/consumer-democracy/proposals_whitelisting.go index fb333dc014..56f514c0b8 100644 --- a/app/consumer-democracy/proposals_whitelisting.go +++ b/app/consumer-democracy/proposals_whitelisting.go @@ -32,7 +32,7 @@ type legacyParamChangeKey struct { // these parameters don't exist in the consumer app -- keeping them as an var LegacyWhitelistedParams = map[legacyParamChangeKey]struct{}{ // add whitlisted legacy parameters here [cosmos-sdk <= 0.47] - // commented parameters are just an example - most params have been moved to their respecitve modules + // commented parameters are just an example - most params have been moved to their respective modules // and they cannot be changed through legacy governance proposals {Subspace: banktypes.ModuleName, Key: "SendEnabled"}: {}, } diff --git a/app/provider/app.go b/app/provider/app.go index 4c998bdc13..f791c4c953 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -8,7 +8,11 @@ import ( "os" "path/filepath" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/ibc-go/modules/capability" + capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" "github.com/cosmos/ibc-go/v8/modules/apps/transfer" ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" @@ -26,7 +30,7 @@ import ( reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" "cosmossdk.io/client/v2/autocli" "cosmossdk.io/core/appmodule" - + "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/evidence" evidencekeeper "cosmossdk.io/x/evidence/keeper" @@ -35,6 +39,7 @@ import ( "cosmossdk.io/x/upgrade" upgradekeeper "cosmossdk.io/x/upgrade/keeper" upgradetypes "cosmossdk.io/x/upgrade/types" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -50,16 +55,20 @@ import ( "github.com/cosmos/cosmos-sdk/server/config" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/msgservice" + sigtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/ante" authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" "github.com/cosmos/cosmos-sdk/x/auth/posthandler" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/auth/vesting" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" @@ -93,29 +102,21 @@ import ( "github.com/cosmos/cosmos-sdk/x/slashing" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - "github.com/cosmos/cosmos-sdk/x/staking" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/ibc-go/modules/capability" - capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" - capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" - "cosmossdk.io/log" abci "github.com/cometbft/cometbft/abci/types" tmjson "github.com/cometbft/cometbft/libs/json" tmos "github.com/cometbft/cometbft/libs/os" - dbm "github.com/cosmos/cosmos-db" appencoding "github.com/cosmos/interchain-security/v5/app/encoding" testutil "github.com/cosmos/interchain-security/v5/testutil/integration" + no_valupdates_genutil "github.com/cosmos/interchain-security/v5/x/ccv/no_valupdates_genutil" + no_valupdates_staking "github.com/cosmos/interchain-security/v5/x/ccv/no_valupdates_staking" ibcprovider "github.com/cosmos/interchain-security/v5/x/ccv/provider" ibcproviderclient "github.com/cosmos/interchain-security/v5/x/ccv/provider/client" ibcproviderkeeper "github.com/cosmos/interchain-security/v5/x/ccv/provider/keeper" providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" - - "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" - sigtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" - txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" ) const ( @@ -152,7 +153,7 @@ var ( mint.AppModuleBasic{}, slashing.AppModuleBasic{}, distr.AppModuleBasic{}, - staking.AppModuleBasic{}, + no_valupdates_staking.AppModuleBasic{}, upgrade.AppModuleBasic{}, evidence.AppModuleBasic{}, @@ -352,9 +353,7 @@ func New( // Remove the ConsumerRewardsPool from the group of blocked recipient addresses in bank // this is required for the provider chain to be able to receive tokens from // the consumer chain - bankBlockedAddrs := app.ModuleAccountAddrs() - delete(bankBlockedAddrs, authtypes.NewModuleAddress( - providertypes.ConsumerRewardsPool).String()) + bankBlockedAddrs := BankBlockedAddrs(app) app.BankKeeper = bankkeeper.NewBaseKeeper( appCodec, @@ -374,15 +373,6 @@ func New( authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()), authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()), ) - app.MintKeeper = mintkeeper.NewKeeper( - appCodec, - runtime.NewKVStoreService(keys[minttypes.StoreKey]), - app.StakingKeeper, - app.AccountKeeper, - app.BankKeeper, - authtypes.FeeCollectorName, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) app.DistrKeeper = distrkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), @@ -457,19 +447,6 @@ func New( runtime.ProvideCometInfoService(), ) - govConfig := govtypes.DefaultConfig() - app.GovKeeper = govkeeper.NewKeeper( - appCodec, - runtime.NewKVStoreService(keys[govtypes.StoreKey]), - app.AccountKeeper, - app.BankKeeper, - app.StakingKeeper, - app.DistrKeeper, - app.MsgServiceRouter(), - govConfig, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) - app.ProviderKeeper = ibcproviderkeeper.NewKeeper( appCodec, keys[providertypes.StoreKey], @@ -484,13 +461,41 @@ func New( app.AccountKeeper, app.DistrKeeper, app.BankKeeper, - *app.GovKeeper, + govkeeper.Keeper{}, // will be set after the GovKeeper is created authtypes.NewModuleAddress(govtypes.ModuleName).String(), authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()), authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()), authtypes.FeeCollectorName, ) + govConfig := govtypes.DefaultConfig() + app.GovKeeper = govkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[govtypes.StoreKey]), + app.AccountKeeper, + app.BankKeeper, + app.ProviderKeeper, + app.DistrKeeper, + app.MsgServiceRouter(), + govConfig, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // set the GovKeeper in the ProviderKeeper + app.ProviderKeeper.SetGovKeeper(*app.GovKeeper) + + app.MintKeeper = mintkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[minttypes.StoreKey]), + // use the ProviderKeeper as StakingKeeper for mint + // because minting should be based on the consensus-active validators + app.ProviderKeeper, + app.AccountKeeper, + app.BankKeeper, + authtypes.FeeCollectorName, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + // gov router must be set after the provider keeper is created // otherwise the provider keeper will not be able to handle proposals (will be nil) govRouter := govv1beta1.NewRouter() @@ -536,7 +541,7 @@ func New( // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. app.MM = module.NewManager( - genutil.NewAppModule( + no_valupdates_genutil.NewAppModule( app.AccountKeeper, app.StakingKeeper, app, @@ -552,7 +557,7 @@ func New( mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName), app.interfaceRegistry), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), - staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + no_valupdates_staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), upgrade.NewAppModule(&app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), evidence.NewAppModule(app.EvidenceKeeper), @@ -679,6 +684,13 @@ func New( } // create the simulation manager and define the order of the modules for deterministic simulations + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.sm = module.NewSimulationManagerFromAppModules(app.MM.Modules, overrideModules) + + // register the store decoders for simulation tests + app.sm.RegisterStoreDecoders() // Note this upgrade handler is just an example and may not be exactly what you need to implement. // See https://docs.cosmos.network/v0.45/building-modules/upgrade.html @@ -780,6 +792,13 @@ func New( return app } +func BankBlockedAddrs(app *App) map[string]bool { + bankBlockedAddrs := app.ModuleAccountAddrs() + delete(bankBlockedAddrs, authtypes.NewModuleAddress( + providertypes.ConsumerRewardsPool).String()) + return bankBlockedAddrs +} + // Name returns the name of the App func (app *App) Name() string { return app.BaseApp.Name() } diff --git a/app/provider/sim_test.go b/app/provider/sim_test.go new file mode 100644 index 0000000000..e9d23807c7 --- /dev/null +++ b/app/provider/sim_test.go @@ -0,0 +1,85 @@ +package app_test + +import ( + "os" + "testing" + + "cosmossdk.io/store" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/cosmos/cosmos-sdk/x/simulation" + simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + providerapp "github.com/cosmos/interchain-security/v5/app/provider" +) + +func init() { + simcli.GetSimulatorFlags() +} + +// interBlockCacheOpt returns a BaseApp option function that sets the persistent +// inter-block write-through cache. +func interBlockCacheOpt() func(*baseapp.BaseApp) { + return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) +} + +// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of +// an IAVLStore for faster simulation speed. +func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { + bapp.SetFauxMerkleMode() +} + +func TestFullAppSimulation(t *testing.T) { + config := simcli.NewConfigFromFlags() + config.ChainID = "provi" + + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) + if skip { + t.Skip("skipping application simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = providerapp.DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + app := providerapp.New(logger, db, nil, true, appOptions, fauxMerkleModeOpt, interBlockCacheOpt(), baseapp.SetChainID("provi")) + require.Equal(t, "interchain-security-p", app.Name()) + + encoding := providerapp.MakeTestEncodingConfig() + + genesisState := providerapp.NewDefaultGenesisState(encoding.Codec) + + // run randomized simulation + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simtestutil.AppStateFn(encoding.Codec, app.SimulationManager(), genesisState), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(app, app.AppCodec(), config), + providerapp.BankBlockedAddrs(app), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simtestutil.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simtestutil.PrintStats(db) + } +} diff --git a/app/sovereign/export.go b/app/sovereign/export.go index 53e9f6fee1..6c7750d4c2 100644 --- a/app/sovereign/export.go +++ b/app/sovereign/export.go @@ -81,7 +81,6 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []str // withdraw all validator commission app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) - if err != nil { panic(err) } diff --git a/docs/docs/adrs/adr-017-allowing-inactive-validators.md b/docs/docs/adrs/adr-017-allowing-inactive-validators.md index 9a3715f135..25ee270827 100644 --- a/docs/docs/adrs/adr-017-allowing-inactive-validators.md +++ b/docs/docs/adrs/adr-017-allowing-inactive-validators.md @@ -62,15 +62,111 @@ The following changes to the state are required: * Store the provider consensus validator set in the provider module state under the `LastProviderConsensusValsPrefix` key. This is the last set of validators that the provider sent to the consensus engine. This is needed to compute the ValUpdates to send to the consensus engine (by diffing the current set with this last sent set). * Increase the `MaxValidators` parameter of the staking module to the desired size of the potential validator set of consumer chains. +* Introduce extra per-consumer-chain parameters: + * `MinStake`: is the minimum amount of stake a validator must have to be considered for validation on the consumer chain. + * `MaxValidatorRank`: is the maximum rank in the provider validator set that can validate on the consumer chain. For example, setting this to 1 means only the validator with the most stake can validate on the consumer chain. + * `AllowInactiveVals`: is a boolean that determines whether validators that are not part of the active set on the provider chain can validate on the consumer chain. If this is set to `true`, validators outside the active set on the provider chain can validate on the consumer chain. If this is set to `true`, validators outside the active set on the provider chain cannot validate on the consumer chain. ## Risk Mitigations To mitigate risks from validators with little stake, we introduce a minimum stake requirement for validators to be able to validate on consumer chains, which can be set by each consumer chain independently, with a default value set by the provider chain. -Additionally, we indepdently allow individual consumer chains to disable this feature, which will disallow validators from outside the provider active set from validating on the consumer chain and revert them to the previous behaviour of only considering validators of the provider that are part of the active consensus validator set. +Additionally, we independently allow individual consumer chains to set a maximum rank for validators. +This means that validators above a certain position in the validator set cannot validate on the consumer chain. +Setting this to be equal to `MaxProviderConsensusValidators` effectively disables inactive validators from validating on the consumer chain and thus +disables the main feature described in this ADR. Additional risk mitigations are to increase the active set size slowly, and to monitor the effects on the network closely. For the first iteration, we propose to increase the active set size to 200 validators (while keeping the consensus validators to 180), thus letting the 20 validators with the most stake outside of the active set validate on consumer chains. +## Testing Scenarios + +In the following, +- bonded validators refers to all validators that have bonded stake, +- active validators refers to the validators that take part in consensus, +- inactive validators refers to bonded validators that are not active validators. + +### Scenario 1: Inactive validators should not be considered by governance + +Inactive validators should not be considered for the purpose of governance. +In particular, the quorum should depend only on active validators. + +We test this by: +* creating a provider chain (either with 3 active validators, or with only 1 active validator), a quorum of 50%, and 3 validators with alice=300, bob=299, charlie=299 stake +* we create a governance proposal +* alice votes for the proposal +* we check that the proposal has the right status: + * in the scenario where we have 3 active validators, the proposal *should not* have passed, because alice alone is not enough to fulfill the quorum + * in the scenario where we have 1 active validator, the proposal *should* have passed, because alice is the only active validator, and thus fulfills the quorum + +Tested by the e2e tests `inactive-provider-validators-governance` (scenario with 1 active val) and `inactive-provider-validators-governance-basecase` (scenario with 3 active vals). + +### Scenario 2: Inactive validators should not get rewards from the provider chain + +Inactive validators should not get rewards from the provider chain. + +This can be tested by starting a provider chain with inactive validators and checking the rewards of inactive validators. + +Checked as part of the e2e test `inactive-provider-validators-on-consumer`. + +### Scenario 3: Inactive validators should get rewards from consumer chains + +An inactive validator that is validating on a consumer chain should receive rewards in the consumer chain token. + +Checked as part of the e2e test `inactive-provider-validators-on-consumer`. + +### Scenario 4: Inactive validators should not get slashed/jailed for downtime on the provider chain + +This can be tested by having an inactive validator go offline on the provider chain for long enough to accrue downtime. +The validator should be neither slashed nor jailed for downtime. + +Checked as part of the e2e test `inactive-provider-validators-on-consumer`. + +### Scenario 5: Inactive validators *should* get jailed for downtime on the provider chain + +This can be tested by having an inactive validator go offline on a consumer chain for long enough to accrue downtime. +The consumer chain should send a SlashPacket to the provider chain, which should jail the validator. + +Checked as part of the e2e test `inactive-provider-validators-on-consumer`. + +### Scenario 6: Inactive validators should not be counted when computing the minimum power in the top N + +This can be tested like this: +* Start a provider chain with validator powers alice=300, bob=200, charlie=100 and 2 max provider consensus validators + * So alice and bob will validate on the provider +* Start a consumer chain with top N = 51%. + * Without inactive validators, this means both alice and bob have to validate. But since charlie is inactive, this means bob is *not* in the top N +* Verify that alice is in the top N, but bob is not + +Checked as part of the e2e test `inactive-vals-topN`. + +### Scenario 7: Mint does not consider inactive validators + +To compute the inflation rate, only the active validators should be considered. + +We can check this by querying the inflation rate change over subsequent blocks. + +We start a provider chain with these arguments +* 3 validators with powers alice=290, bob=280, charlie=270 +* either 1 or 3 active validators +* a bonded goal of 300 tokens (this is given in percent, but we simplify here) + +If we have 3 validators active, then the inflation rate should *decrease* between blocks, because the bonded goal is exceeded as all validators are bonded. +If we have only 1 validator active, then the inflation rate should *increase* between blocks, because the bonded goal is not met. + +Checked as part of the e2e tests `inactive-vals-mint` (scenario with 1 active val) and `mint-basecase` (scenario with 3 active vals). + +### Scenarios 8: Inactive validators can validate on consumer chains + +An inactive validator can opt in and validate on consumer chains (if min stake and max rank allow it) + +Checked as part of the e2e test `inactive-provider-validators-on-consumer`. + +### Scenario 9: MinStake and MaxRank parameters are respected + +Validators that don't meet the criteria for a consumer chain cannot validate on it. + +Checked in the e2e tests `min-stake` and `max-rank`. + ## Consequences ### Positive diff --git a/docs/docs/features/power-shaping.md b/docs/docs/features/power-shaping.md index 2f51aef8f0..9d76300a82 100644 --- a/docs/docs/features/power-shaping.md +++ b/docs/docs/features/power-shaping.md @@ -12,7 +12,7 @@ maximum, will validate the consumer chain. :::info This is only applicable to Opt In chains (chains with Top N = 0). ::: -1) **Capping the fraction of power any single validator can have**: The consumer chain can specify a maximum fraction +2) **Capping the fraction of power any single validator can have**: The consumer chain can specify a maximum fraction of the total voting power that any single validator in its validator set should have. This is a security measure with the intention of making it harder for a single large validator to take over a consumer chain. This mitigates the risk of an Opt In chain with only a few validators being dominated by a validator with a large amount of stake opting in. For example, setting this fraction to e.g. 33% would mean that no single validator can have more than 33% of the total voting power, @@ -24,7 +24,7 @@ This is a soft cap, and the actual power of a validator can exceed this fraction Rewards are distributed proportionally to validators with respect to their capped voting power on the consumer, not their total voting power on the provider. ::: -1) **Allowlist and denylist**: The consumer chain can specify a list of validators that are allowed or disallowed from participating in the validator set. If an allowlist is set, all validators not on the allowlist cannot validate the consumer chain. If a validator is on both lists, the denylist takes precedence, that is, they cannot validate the consumer chain. If neither list is set, all validators are able to validate the consumer chain. +3) **Allowlist and denylist**: The consumer chain can specify a list of validators that are allowed or disallowed from participating in the validator set. If an allowlist is set, all validators not on the allowlist cannot validate the consumer chain. If a validator is on both lists, the denylist takes precedence, that is, they cannot validate the consumer chain. If neither list is set, all validators are able to validate the consumer chain. :::warning Note that if denylisting is used in a Top N consumer chain, then the chain might not be secured by N% of the total provider's @@ -32,13 +32,21 @@ power. For example, consider that the top validator `V` on the provider chain ha then if `V` is denylisted, the consumer chain would only be secured by at least 40% of the provider's power. ::: +4) **Maximum validator rank**: The consumer chain can specify a maximum position in the validator set that a validator can have on the provider chain to be able to validate the consumer chain. This can be used to ensure that only validators with relatively large amounts of stake can validate the consumer chain. For example, setting this to 20 would mean only the 20 validators with the most voting stake on the provider chain can validate the consumer chain. + +5) **Minimum validator stake**: The consumer chain can specify a minimum amount of stake that a validator must have on the provider chain to be able to validate the consumer chain. This can be used to ensure that only validators with a certain amount of stake can validate the consumer chain. For example, setting this to 1000 would mean only validators with at least 1000 tokens staked on the provider chain can validate the consumer chain. + +6) **Allow inactive validators**: The consumer chain can specify whether provider validators that are *not* active in consensus may validate on the consumer chain or not. If this is set to `false`, only active validators on the provider chain can validate the consumer chain. If this is set to `true`, inactive validators can also validate the consumer chain. This can be useful for chains that want to have a larger validator set than the active validators on the provider chain, or for chains that want to have a more decentralized validator set. COnsumer chains that enable this feature should strongly consider setting a maximum validator rank and/or a minimum validator stake to ensure that only validators with some reputation/stake can validate the chain. + All these mechanisms are set by the consumer chain in the `ConsumerAdditionProposal`. They operate *solely on the provider chain*, meaning the consumer chain simply receives the validator set after these rules have been applied and does not have any knowledge about whether they are applied. -Each of these mechanisms is *set during the consumer addition proposal* (see [Onboarding](../consumer-development/onboarding.md#3-submit-a-governance-proposal)), and is currently *immutable* after the chain has been added. +Each of these mechanisms is *set during the consumer addition proposal* (see [Onboarding](../consumer-development/onboarding.md#3-submit-a-governance-proposal)). The values can be seen by querying the list of consumer chains: ```bash interchain-security-pd query provider list-consumer-chains + + ``` ## Guidelines for setting power shaping parameters diff --git a/proto/interchain_security/ccv/provider/v1/genesis.proto b/proto/interchain_security/ccv/provider/v1/genesis.proto index 269743721e..80e9ee0cf8 100644 --- a/proto/interchain_security/ccv/provider/v1/genesis.proto +++ b/proto/interchain_security/ccv/provider/v1/genesis.proto @@ -48,6 +48,9 @@ message GenesisState { repeated interchain_security.ccv.provider.v1.ExportedVscSendTimestamp exported_vsc_send_timestamps = 13 [ (gogoproto.nullable) = false ]; + + repeated ConsensusValidator last_provider_consensus_validators = 14 + [ (gogoproto.nullable) = false ]; } // The provider CCV module's knowledge of consumer state. diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index 339d94a833..390de31224 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -106,6 +106,12 @@ message ConsumerAdditionProposal { repeated string allowlist = 18; // Corresponds to a list of provider consensus addresses of validators that CANNOT validate the consumer chain. repeated string denylist = 19; + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + uint64 min_stake = 20; + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + uint32 max_rank = 21; + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + bool allow_inactive_vals = 22; } // ConsumerRemovalProposal is a governance proposal on the provider chain to @@ -156,6 +162,12 @@ message ConsumerModificationProposal { repeated string allowlist = 7; // Corresponds to a list of provider consensus addresses of validators that CANNOT validate the consumer chain. repeated string denylist = 8; + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + uint64 min_stake = 9; + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + uint32 max_rank = 10; + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + bool allow_inactive_vals = 11; } @@ -251,6 +263,10 @@ message Params { // The number of blocks that comprise an epoch. int64 blocks_per_epoch = 10; + + // The maximal number of validators that will be passed + // to the consensus engine on the provider. + int64 max_provider_consensus_validators = 11; } // SlashAcks contains cons addresses of consumer chain validators @@ -359,15 +375,18 @@ message ConsumerAddrsToPrune { AddressList consumer_addrs = 3; } -// ConsumerValidator is used to facilitate epoch-based transitions. It contains relevant info for -// a validator that is expected to validate on a consumer chain during an epoch. -message ConsumerValidator { +// ConsensusValidator is used to express a validator that +// should be validating on a chain. +// It contains relevant info for +// a validator that is expected to validate on +// either the provider or a consumer chain. +message ConsensusValidator { // validator's consensus address on the provider chain bytes provider_cons_addr = 1; // voting power the validator has during this epoch int64 power = 2; - // public key the validator uses on the consumer chain during this epoch - tendermint.crypto.PublicKey consumer_public_key = 3; + // public key the validator uses on the chain it is validating on + tendermint.crypto.PublicKey public_key = 3; } // ConsumerRewardsAllocation stores the rewards allocated by a consumer chain // to the consumer rewards pool. It is used to allocate the tokens to the consumer diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 4f8aaaf9ad..8440bf6a4f 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -183,6 +183,12 @@ message MsgConsumerAddition { repeated string denylist = 17; // signer address string authority = 18 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + uint64 min_stake = 19; + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + uint32 max_rank = 20; + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + bool allow_inactive_vals = 21; } // MsgConsumerAdditionResponse defines response type for MsgConsumerAddition messages @@ -320,6 +326,12 @@ message MsgConsumerModification { repeated string denylist = 8; // signer address string authority = 9 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + uint64 min_stake = 10; + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + uint32 max_rank = 11; + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + bool allow_inactive_vals = 12; } message MsgConsumerModificationResponse {} diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index f50f23b41a..b52f5b73c7 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -263,6 +263,9 @@ type SubmitConsumerAdditionProposalAction struct { ValidatorSetCap uint32 Allowlist []string Denylist []string + MaxValidatorRank uint32 + MinStake uint64 + AllowInactiveVals bool } func (tr Chain) submitConsumerAdditionProposal( @@ -292,6 +295,9 @@ func (tr Chain) submitConsumerAdditionProposal( ValidatorSetCap: action.ValidatorSetCap, Allowlist: action.Allowlist, Denylist: action.Denylist, + MaxValidatorRank: action.MaxValidatorRank, + MinStake: action.MinStake, + AllowInactiveVals: action.AllowInactiveVals, } bz, err := json.Marshal(prop) @@ -334,7 +340,6 @@ func (tr Chain) submitConsumerAdditionProposal( fmt.Println("submitConsumerAdditionProposal json:", jsonStr) } bz, err = cmd.CombinedOutput() - if err != nil { log.Fatal(err, "\n", string(bz)) } @@ -468,7 +473,6 @@ func (tr Chain) submitConsumerModificationProposal( } bz, err = cmd.CombinedOutput() - if err != nil { log.Fatal(err, "\n", string(bz)) } @@ -1005,7 +1009,6 @@ func (tr Chain) addChainToHermes( action AddChainToRelayerAction, verbose bool, ) { - bz, err := tr.target.ExecCommand("bash", "-c", "hermes", "version").CombinedOutput() if err != nil { log.Fatal(err, "\n error getting hermes version", string(bz)) @@ -1911,7 +1914,7 @@ func (tr Chain) registerRepresentative( panic(fmt.Sprintf("failed writing ccv consumer file : %v", err)) } defer file.Close() - err = os.WriteFile(file.Name(), []byte(fileContent), 0600) + err = os.WriteFile(file.Name(), []byte(fileContent), 0o600) if err != nil { log.Fatalf("Failed writing consumer genesis to file: %v", err) } diff --git a/tests/e2e/config.go b/tests/e2e/config.go index bbfe7a4d0b..2296a07b8e 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -92,6 +92,11 @@ const ( ConsumerMisbehaviourTestCfg TestConfigType = "consumer-misbehaviour" CompatibilityTestCfg TestConfigType = "compatibility" SmallMaxValidatorsTestCfg TestConfigType = "small-max-validators" + InactiveProviderValsTestCfg TestConfigType = "inactive-provider-vals" + GovTestCfg TestConfigType = "gov" + InactiveValsGovTestCfg TestConfigType = "inactive-vals-gov" + InactiveValsMintTestCfg TestConfigType = "inactive-vals-mint" + MintTestCfg TestConfigType = "mint" ) type TestConfig struct { @@ -180,6 +185,16 @@ func GetTestConfig(cfgType TestConfigType, providerVersion, consumerVersion stri testCfg = CompatibilityTestConfig(pv, cv) case SmallMaxValidatorsTestCfg: testCfg = SmallMaxValidatorsTestConfig() + case InactiveProviderValsTestCfg: + testCfg = InactiveProviderValsTestConfig() + case GovTestCfg: + testCfg = GovTestConfig() + case InactiveValsGovTestCfg: + testCfg = InactiveValsGovTestConfig() + case InactiveValsMintTestCfg: + testCfg = InactiveValsMintTestConfig() + case MintTestCfg: + testCfg = MintTestConfig() default: panic(fmt.Sprintf("Invalid test config: %s", cfgType)) } @@ -417,7 +432,7 @@ func CompatibilityTestConfig(providerVersion, consumerVersion string) TestConfig ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling ".app_state.provider.params.slash_meter_replenish_period = \"3s\"", } - } else if semver.Compare(providerVersion, "v5.0.0-alpha1") < 0 { //TODO: MOV THIS BACK TO "v5.0.0" + } else if semver.Compare(providerVersion, "v5.0.0-alpha1") < 0 { // TODO: MOV THIS BACK TO "v5.0.0" fmt.Println("Using provider chain config for v4.1.x") providerConfig = ChainConfig{ ChainId: ChainID("provi"), @@ -557,6 +572,27 @@ func DemocracyTestConfig(allowReward bool) TestConfig { return tr } +func InactiveProviderValsTestConfig() TestConfig { + tr := DefaultTestConfig() + tr.name = "InactiveValsConfig" + // set the MaxProviderConsensusValidators param to 2 + proviConfig := tr.chainConfigs[ChainID("provi")] + proviConfig.GenesisChanges += " | .app_state.provider.params.max_provider_consensus_validators = \"2\"" + + consuConfig := tr.chainConfigs[ChainID("consu")] + // set the soft_opt_out threshold to 0% to make sure all validators are slashed for downtime + consuConfig.GenesisChanges += " | .app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.0\"" + tr.chainConfigs[ChainID("provi")] = proviConfig + tr.chainConfigs[ChainID("consu")] = consuConfig + + // make it so that carol does not use a consumer key + carolConfig := tr.validatorConfigs[ValidatorID("carol")] + carolConfig.UseConsumerKey = false + tr.validatorConfigs[ValidatorID("carol")] = carolConfig + + return tr +} + func SmallMaxValidatorsTestConfig() TestConfig { cfg := DefaultTestConfig() @@ -573,6 +609,60 @@ func SmallMaxValidatorsTestConfig() TestConfig { return cfg } +func GovTestConfig() TestConfig { + cfg := DefaultTestConfig() + + // set the quorum to 50% + proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig.GenesisChanges += "| .app_state.gov.params.quorum = \"0.5\"" + cfg.chainConfigs[ChainID("provi")] = proviConfig + + carolConfig := cfg.validatorConfigs["carol"] + // make carol use her own key + carolConfig.UseConsumerKey = false + cfg.validatorConfigs["carol"] = carolConfig + + return cfg +} + +func InactiveValsGovTestConfig() TestConfig { + cfg := GovTestConfig() + + // set the MaxValidators to 1 + proviConfig := cfg.chainConfigs[ChainID("provi")] + proviConfig.GenesisChanges += "| .app_state.staking.params.max_validators = 1" + cfg.chainConfigs[ChainID("provi")] = proviConfig + + return cfg +} + +func MintTestConfig() TestConfig { + cfg := GovTestConfig() + AdjustMint(cfg) + + return cfg +} + +func InactiveValsMintTestConfig() TestConfig { + cfg := InactiveValsGovTestConfig() + AdjustMint(cfg) + + return cfg +} + +// AdjustMint adjusts the mint parameters to have a very low goal bonded amount +// and a high inflation rate change +func AdjustMint(cfg TestConfig) { + proviConfig := cfg.chainConfigs[ChainID("provi")] + // total supply is 30000000000stake; we want to set the mint bonded goal to + // a small fraction of that + proviConfig.GenesisChanges += "| .app_state.mint.params.goal_bonded = \"0.001\"" + + "| .app_state.mint.params.inflation_rate_change = \"1\"" + + "| .app_state.mint.params.inflation_max = \"0.5\"" + + "| .app_state.mint.params.inflation_min = \"0.1\"" + cfg.chainConfigs[ChainID("provi")] = proviConfig +} + func MultiConsumerTestConfig() TestConfig { tr := TestConfig{ name: string(MulticonsumerTestCfg), diff --git a/tests/e2e/main.go b/tests/e2e/main.go index cf8fe70d89..1f73d68ed7 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -204,6 +204,54 @@ var stepChoices = map[string]StepChoice{ description: "This is a regression test related to the issue discussed here: https://forum.cosmos.network/t/cosmos-hub-v17-1-chain-halt-post-mortem/13899. The test ensures that the protocol works as expected when MaxValidators is smaller than the number of potential validators.", testConfig: SmallMaxValidatorsTestCfg, }, + "inactive-provider-validators-on-consumer": { + name: "inactive-provider-validators-on-consumer", + steps: stepsInactiveProviderValidators(), + description: "test inactive validators on consumer", + testConfig: InactiveProviderValsTestCfg, + }, + "inactive-vals-topN": { + name: "inactive-vals-topN", + steps: stepsInactiveValsWithTopN(), + description: "test inactive validators on topN chain", + testConfig: InactiveProviderValsTestCfg, + }, + "inactive-provider-validators-governance": { + name: "inactive-provider-validators-governance", + steps: stepsInactiveProviderValidatorsGovernance(), + description: "test governance with inactive validators", + testConfig: InactiveValsGovTestCfg, + }, + "inactive-provider-validators-governance-basecase": { + name: "inactive-provider-validators-governance-basecase", + steps: stepsInactiveProviderValidatorsGovernanceBasecase(), + description: "comparison for governance when there are *no* inactive validators, to verify the difference to the governance test *with* inactive validators", + testConfig: GovTestCfg, + }, + "max-rank": { + name: "max-rank", + steps: stepsMaxRank(), + description: "checks that the max rank parameter for consumer chains is respected", + testConfig: GovTestCfg, // can reuse the GovTestCfg because all parameters there are ok to use here + }, + "min-stake": { + name: "min-stake", + steps: stepsMinStake(), + description: "checks that the min stake parameter for consumer chains is respected", + testConfig: GovTestCfg, // see above: we reuse the GovTestCfg for convenience + }, + "inactive-vals-mint": { + name: "inactive-vals-mint", + steps: stepsInactiveValsMint(), + description: "test minting with inactive validators", + testConfig: InactiveValsMintTestCfg, + }, + "mint-basecase": { + name: "mint-basecase", + steps: stepsMintBasecase(), + description: "test minting without inactive validators as a sanity check", + testConfig: MintTestCfg, + }, } func getTestCaseUsageString() string { @@ -293,7 +341,9 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet, providerVe "partial-set-security-validator-set-cap", "partial-set-security-validators-power-cap", "partial-set-security-validators-allowlisted", "partial-set-security-validators-denylisted", "partial-set-security-modification-proposal", - "active-set-changes", + "active-set-changes", "inactive-vals-topN", + "inactive-provider-validators-on-consumer", "inactive-provider-validators-governance", + "max-rank", "min-stake", "inactive-vals-mint", } if includeMultiConsumer != nil && *includeMultiConsumer { selectedPredefinedTests = append(selectedPredefinedTests, "multiconsumer") diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 1c1be7e95a..3caee9ce5c 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -116,6 +116,29 @@ func (tr Chain) GetChainState(chain ChainID, modelState ChainState) ChainState { chainState.HasToValidate = &hasToValidate } + if modelState.InflationRateChange != nil { + // get the inflation rate now + inflationRateNow := tr.target.GetInflationRate(chain) + + // wait a block + tr.waitBlocks(chain, 1, 10*time.Second) + + // get the new inflation rate + inflationRateAfter := tr.target.GetInflationRate(chain) + + // calculate the change + inflationRateChange := inflationRateAfter - inflationRateNow + var inflationRateChangeDirection int + if inflationRateChange > 0 { + inflationRateChangeDirection = 1 + } else if inflationRateChange < 0 { + inflationRateChangeDirection = -1 + } else { + inflationRateChangeDirection = 0 + } + chainState.InflationRateChange = &inflationRateChangeDirection + } + if modelState.ConsumerPendingPacketQueueSize != nil { pendingPacketQueueSize := tr.target.GetPendingPacketQueueSize(chain) chainState.ConsumerPendingPacketQueueSize = &pendingPacketQueueSize @@ -302,6 +325,10 @@ func uintPtr(i uint) *uint { return &i } +func intPtr(i int) *int { + return &i +} + type Commands struct { containerConfig ContainerConfig // FIXME only needed for 'Now' time tracking validatorConfigs map[ValidatorID]ValidatorConfig @@ -357,15 +384,19 @@ func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight u binaryName := tr.chainConfigs[chain].BinaryName cmd := tr.target.ExecCommand(binaryName, - "query", "distribution", "delegation-total-rewards", - "--delegator-address", delAddresss, + "query", "distribution", "rewards", + delAddresss, `--height`, fmt.Sprint(blockHeight), `--node`, tr.GetQueryNode(chain), `-o`, `json`, ) - bz, err := cmd.CombinedOutput() + if *verbose { + log.Println("getting rewards for chain: ", chain, " validator: ", validator, " blockHeight: ", blockHeight) + log.Println(cmd) + } + bz, err := cmd.CombinedOutput() if err != nil { log.Fatal("failed getting rewards: ", err, "\n", string(bz)) } @@ -380,7 +411,7 @@ func (tr Commands) GetReward(chain ChainID, validator ValidatorID, blockHeight u // interchain-securityd query gov proposals func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { - var noProposalRegex = regexp.MustCompile(`doesn't exist: key not found`) + noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) binaryName := tr.chainConfigs[chain].BinaryName bz, err := tr.target.ExecCommand(binaryName, @@ -863,6 +894,23 @@ func (tr Commands) GetHasToValidate( return chains } +func (tr Commands) GetInflationRate( + chain ChainID, +) float64 { + binaryName := tr.chainConfigs[chain].BinaryName + bz, err := tr.target.ExecCommand(binaryName, + "query", "mint", "inflation", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + inflationRate := gjson.Get(string(bz), "inflation").Float() + return inflationRate +} + func (tr Commands) GetTrustedHeight( chain ChainID, clientID string, diff --git a/tests/e2e/steps_inactive_vals.go b/tests/e2e/steps_inactive_vals.go new file mode 100644 index 0000000000..1fb145e47c --- /dev/null +++ b/tests/e2e/steps_inactive_vals.go @@ -0,0 +1,1085 @@ +package main + +import ( + "strconv" + + gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" +) + +// stepsInactiveValidatorsOnConsumer tests situations where validators that are *not* in the active set on the +// provider chain validate on the consumer chain. +// The provider chain is set to have at most *2* validators active in consensus, and there are 3 validators in total. +// high-level, this test does: +// - start the provider chain +// - start a consumer chain +// - check that non-consensus validators do not get slashed for downtime on the provider; and that they don't get rewards +// - check that active validators *do* get slashed for downtime on the provider, and don't get rewards while they are down +// - check that non-consensus validators *do* get jailed for downtime on consumer chains +// - check that non-consensus validators *become* consensus validators when they have enough power +func stepsInactiveProviderValidators() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // we need to get incremental rewards + // if we would look at total rewards, alice would trivially also get rewards, + // because she gets rewards in the first block due to being in the genesis + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + }, + setupOptInChain(), + []Step{ + // check that active-but-not-consensus validators do not get slashed for downtime + { + // alices provider node goes offline + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // still 0 consensus power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, // but alice does not get jailed or slashed + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + // give carol more power so that she has enough power to validate if bob goes down + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 700000000, // carol needs to have more than 2/3rds of power(alice) + power(carol) + power(bob) to run both chains alone, so we stake some more to her + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 200, + ValidatorID("carol"): 1000, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 1000000000, + }, + // check that bob and carol get rewards, but alice does not + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards since block 1 + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + // bob goes offline + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // alice gets into the active set + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 198000000, // 1% slash + ValidatorID("carol"): 1000000000, + }, + }, + }, + }, + { + // relay packets so that the consumer gets up to date with the provider + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): true, // alice is participating right now, so gets rewards + ValidatorID("bob"): false, // bob does not get rewards since he is not participating in consensus + ValidatorID("carol"): true, + }, + }, + }, + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 198, // bob was slashed 1% + ValidatorID("carol"): 1000, + }, + // check that between two blocks now, alice does not get rewarded with the native denom + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // check rewards for currently produced blocks only + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + // bob is still at 0 power on the consumer chain + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 0, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay packets so that the consumer gets up to date with the provider + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // alice goes offline on the consumer chain + { + Action: DowntimeSlashAction{ + Chain: ChainID("consu"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // power not affected yet + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is not consensus-active anyways, since we allow two vals at maximum + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the provider chain knows about alice's downtime + { + Action: RelayPacketsAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + Port: "consumer", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is still not in the active set, and should now be jailed too. + // we cannot test directly whether alice is jailed, but we will test this below + ValidatorID("bob"): 198, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // we need to double-check that alice is actually jailed, so we get bob jailed, too, which usually would mean alice gets into power + { + Action: DowntimeSlashAction{ + Chain: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is jailed + ValidatorID("bob"): 0, // bob is jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail alice + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("alice"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + // alice was not slashed because consumer downtime just jails without slashing tokens + ValidatorID("alice"): 100, // alice is back as an active consensus validator. + ValidatorID("bob"): 0, // bob is still jailed + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // unjail bob + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice is back out because only 2 validators can be active in consensus + ValidatorID("bob"): 196, // bob is back as an active consensus validator and lost 2 more power due to the second downtime + ValidatorID("carol"): 1000, + }, + }, + }, + }, + // relay the packets so that the consumer chain is in sync again + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID("consu"), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, // both alice and bob are validating the consumer + ValidatorID("bob"): 196, + ValidatorID("carol"): 1000, + }, + }, + }, + }, + }, + ) + + return s +} + +// Precondition: The provider chain is running. +// Postcondition: A consumer chain with Top N = 0 is running, including an up-and-running IBC connection to the provider. +// "alice", "bob", "carol" have opted in and are validating. +func setupOptInChain() []Step { + return concatSteps([]Step{ + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + AllowInactiveVals: true, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, + ValidatorID("carol"): {}, + }, + }, + }, + }, + }, + stepsOptInValidators("alice", "bob", "carol"), + []Step{ + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + { + // we start all the validators but only "alice" and "bob" have opted in and hence + // only "alice" and "bob" are validating blocks + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 100, + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + }, + }, + }, + { + Action: AddIbcConnectionAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ClientA: 0, + ClientB: 0, + }, + State: State{}, + }, + { + Action: AddIbcChannelAction{ + ChainA: ChainID("consu"), + ChainB: ChainID("provi"), + ConnectionA: 0, + PortA: "consumer", + PortB: "provider", + Order: "ordered", + }, + State: State{}, + }, + }, + ) +} + +func stepsOptInValidators(validators ...ValidatorID) []Step { + s := make([]Step, 0) + for _, val := range validators { + // Οpt in all validators + s = append(s, Step{ + Action: OptInAction{ + Chain: ChainID("consu"), + Validator: val, + }, + State: State{ + ChainID("provi"): ChainState{}, + }, + }, + ) + } + return s +} + +// stepsInactiveProviderValidatorsGovernance validates that inactive validators +// are not included in the calculation of the quorum for governance proposals. +// It checks that when the quorum is met *among active validators*, +// the proposal can pass, even though the quorum would not be met if inactive validators +// would be counted. +func stepsInactiveProviderValidatorsGovernance() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 1, so alice and bob should not be in power + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + // the proposal should have passed because carol voted for it. + // carol alone is enough to pass the quorum, because stake of the other validators is not counted + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + }, + ) + + return s +} + +// stepsInactiveProviderValidatorsGovernanceBasecase is a sanity check to go along with +// stepsInactiveProviderValidatorsGovernance. It tests that with all validators being active, +// the proposal does not pass if it does not meet the quorum among validators. +func stepsInactiveProviderValidatorsGovernanceBasecase() []Step { + s := concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + }, + []Step{ + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + // vote for it with carol + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("carol")}, + Vote: []string{"yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + // the proposal should *not* have passed because only carol voted for it, + // and carol is not enough to pass the quorum + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_REJECTED)), + }, + }, + }, + }, + }, + }, + ) + + return s +} + +// stepsMaxRank validates that a validator with a rank higher than the specified maxRank parameter +// cannot validate the consumer chain. +func stepsMaxRank() []Step { + return concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + MaxValidatorRank: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + }, + stepsOptInValidators("alice", "bob", "carol"), + []Step{ + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + { + // we start all the validators, but due to max rank of 1, only carol can validate + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, // due to max rank of 1, only carol can validate + }, + }, + }, + }, + }, + ) +} + +// stepsMinStake validates that a validator with less stake than the specified minStake parameter +// cannot validate the consumer chain. +func stepsMinStake() []Step { + return concatSteps( + []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 290000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 290, + ValidatorID("bob"): 290, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 290000000, + ValidatorID("bob"): 290000000, + ValidatorID("carol"): 300000000, + }, + }, + }, + }, + // create a governance proposal + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 0, + MinStake: 300000000, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + }, + stepsOptInValidators("alice", "bob", "carol"), + []Step{ + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + }, + }, + }, + { + // we start all the validators, but due to max rank of 1, only carol can validate + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, // due to min stake of 300000000, only carol can validate + }, + }, + }, + }, + }, + ) +} + +// This test case validates that inactive validators are not included when computing +// the top N. +func stepsInactiveValsWithTopN() []Step { + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // max consensus validators is 2, so alice should not be in power + ValidatorID("bob"): 200, + ValidatorID("carol"): 300, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 100000000, + ValidatorID("bob"): 200000000, + ValidatorID("carol"): 300000000, + }, + Rewards: &Rewards{ + IsNativeDenom: true, // check for rewards in the provider denom + IsIncrementalReward: true, // we need to get incremental rewards + // if we would look at total rewards, alice would trivially also get rewards, + // because she gets rewards in the first block due to being in the genesis + IsRewarded: map[ValidatorID]bool{ + ValidatorID("alice"): false, + ValidatorID("bob"): true, + ValidatorID("carol"): true, + }, + }, + }, + }, + }, + { + Action: SubmitConsumerAdditionProposalAction{ + Chain: ChainID("provi"), + From: ValidatorID("alice"), + Deposit: 10000001, + ConsumerChain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + TopN: 51, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_VOTING_PERIOD)), + }, + }, + }, + }, + }, + { + Action: VoteGovProposalAction{ + Chain: ChainID("provi"), + From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob")}, + Vote: []string{"yes", "yes"}, + PropNumber: 1, + }, + State: State{ + ChainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: ChainID("consu"), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: strconv.Itoa(int(gov.ProposalStatus_PROPOSAL_STATUS_PASSED)), + }, + }, + HasToValidate: &map[ValidatorID][]ChainID{ + ValidatorID("alice"): {}, + ValidatorID("bob"): {}, // bob doesn't have to validate because he is not in the top N + ValidatorID("carol"): {"consu"}, + }, + }, + }, + }, + { + Action: StartConsumerChainAction{ + ConsumerChain: ChainID("consu"), + ProviderChain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 100000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 200000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 300000000, Allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + GenesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + State: State{ + ChainID("consu"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, // alice and bob are not in the top N, so aren't in the validator set + ValidatorID("bob"): 0, + ValidatorID("carol"): 300, + }, + }, + }, + }, + } +} + +// stepsInactiveValsMint tests the minting of tokens with inactive validators +// It checks that inactive validators are not counted when computing whether the +// inflation rate should go up or down. +func stepsInactiveValsMint() []Step { + // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 29, // other validators are not in power since only 1 can be active + }, + InflationRateChange: intPtr(1), // inflation rate goes up because less than the goal is bonded, since only carol is active + }, + }, + }, + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 50000000, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 0, + ValidatorID("carol"): 79, + }, + InflationRateChange: intPtr(-1), // inflation rate goes down now, because carol has more bonded than the goal + }, + }, + }, + } +} + +// stepsMintBasecase tests the minting of tokens without inactive validators. +// This is done as a sanity check to complement stepsInactiveValsMint. +func stepsMintBasecase() []Step { + // total supply is 30000000000, bonded goal ratio makes it so we want 30000000 tokens bonded + return []Step{ + { + Action: StartChainAction{ + Chain: ChainID("provi"), + Validators: []StartChainValidator{ + {Id: ValidatorID("alice"), Stake: 27000000, Allocation: 10000000000}, + {Id: ValidatorID("bob"), Stake: 28000000, Allocation: 10000000000}, + {Id: ValidatorID("carol"), Stake: 29000000, Allocation: 10000000000}, + }, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 27, + ValidatorID("bob"): 28, + ValidatorID("carol"): 29, + }, + InflationRateChange: intPtr(-1), // inflation rate goes down because more than the goal is bonded + }, + }, + }, + { + Action: DelegateTokensAction{ + Chain: ChainID("provi"), + From: ValidatorID("carol"), + To: ValidatorID("carol"), + Amount: 50000000, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 27, + ValidatorID("bob"): 28, + ValidatorID("carol"): 79, + }, + InflationRateChange: intPtr(-1), // inflation rate *still* goes down + }, + }, + }, + } +} diff --git a/tests/e2e/testlib/types.go b/tests/e2e/testlib/types.go index 135f07a6a8..725c4e6951 100644 --- a/tests/e2e/testlib/types.go +++ b/tests/e2e/testlib/types.go @@ -37,6 +37,7 @@ type ChainCommands interface { GetValPower(chain ChainID, validator ValidatorID) uint GetValStakedTokens(chain ChainID, validatorAddress string) uint GetQueryNodeIP(chain ChainID) string + GetInflationRate(chain ChainID) float64 } // TODO: replace ExecutionTarget with new TargetDriver interface @@ -153,7 +154,6 @@ type ProposalAndType struct { RawProposal json.RawMessage Type string } - type ChainState struct { ValBalances *map[ValidatorID]uint Proposals *map[uint]Proposal @@ -170,6 +170,7 @@ type ChainState struct { RegisteredConsumerRewardDenoms *[]string ClientsFrozenHeights *map[string]clienttypes.Height HasToValidate *map[ValidatorID][]ChainID // only relevant to provider chain + InflationRateChange *int // whether the inflation rate between two blocks changes negatively (any negative number), is equal (0), or changes positively (any positive number) } // custom marshal and unmarshal functions for the chainstate that convert proposals to/from the auxiliary type with type info diff --git a/tests/e2e/v4/state.go b/tests/e2e/v4/state.go index 70ca8afe7c..ab8fc58267 100644 --- a/tests/e2e/v4/state.go +++ b/tests/e2e/v4/state.go @@ -236,7 +236,7 @@ func (tr Commands) GetBalance(chain ChainID, validator ValidatorID) uint { // interchain-securityd query gov proposals func (tr Commands) GetProposal(chain ChainID, proposal uint) Proposal { - var noProposalRegex = regexp.MustCompile(`doesn't exist: key not found`) + noProposalRegex := regexp.MustCompile(`doesn't exist: key not found`) binaryName := tr.ChainConfigs[chain].BinaryName bz, err := tr.Target.ExecCommand(binaryName, @@ -411,6 +411,7 @@ func (tr Commands) GetConsumerChains(chain ChainID) map[ChainID]bool { return chains } + func (tr Commands) GetConsumerAddress(consumerChain ChainID, validator ValidatorID) string { binaryName := tr.ChainConfigs[ChainID("provi")].BinaryName cmd := tr.Target.ExecCommand(binaryName, @@ -656,3 +657,20 @@ func (tr Commands) GetHasToValidate(validator ValidatorID) []ChainID { func uintPtr(i uint) *uint { return &i } + +func (tr Commands) GetInflationRate( + chain ChainID, +) float64 { + binaryName := tr.ChainConfigs[chain].BinaryName + bz, err := tr.Target.ExecCommand(binaryName, + "query", "mint", "inflation", + `--node`, tr.GetQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + inflationRate := gjson.Get(string(bz), "inflation").Float() + return inflationRate +} diff --git a/tests/integration/partial_set_security_test.go b/tests/integration/partial_set_security_test.go new file mode 100644 index 0000000000..d363dbe39b --- /dev/null +++ b/tests/integration/partial_set_security_test.go @@ -0,0 +1,232 @@ +package integration + +import ( + "testing" + + "cosmossdk.io/math" + ccv "github.com/cosmos/interchain-security/v5/x/ccv/types" + "github.com/stretchr/testify/require" + + icstestingutils "github.com/cosmos/interchain-security/v5/testutil/ibc_testing" + + appConsumer "github.com/cosmos/interchain-security/v5/app/consumer" + appProvider "github.com/cosmos/interchain-security/v5/app/provider" +) + +// we need a stake multiplier because tokens do not directly correspond to voting power +// this is needed because 1000000 tokens = 1 voting power, so lower multipliers +// will be verbose and harder to read because small token numbers +// won't correspond to at least one voting power +const stake_multiplier = 1000000 + +// TestMinStakeMaxRank tests the min stake and max rank parameters. +// It starts a provider and single consumer chain, +// sets the initial powers according to the input, and then +// sets the min stake and max rank parameters according to the test case. +// Finally, it checks that the validator set on the consumer chain is as expected +// according to the min stake and max rank parameters. +func TestMinStakeMaxRank(t *testing.T) { + testCases := []struct { + name string + stakedTokens []int64 + minStake uint64 + maxRank uint32 + expectedConsuValSet []int64 + }{ + { + name: "disabled min stake and max rank", + stakedTokens: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + minStake: 0, + maxRank: 0, + expectedConsuValSet: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + }, + { + name: "stake multiplier - standard case", + stakedTokens: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + minStake: 3 * stake_multiplier, + maxRank: 0, + expectedConsuValSet: []int64{ + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + }, + { + name: "validator rank - standard case", + stakedTokens: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + minStake: 0, + maxRank: 2, + expectedConsuValSet: []int64{ + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + }, + { + name: "check max rank precedence over min stake", + stakedTokens: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + minStake: 4 * stake_multiplier, + maxRank: 2, + expectedConsuValSet: []int64{ + 4 * stake_multiplier, + }, + }, + { + name: "check min stake precedence over max rank", + stakedTokens: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 3 * stake_multiplier, + 4 * stake_multiplier, + }, + minStake: 2 * stake_multiplier, + maxRank: 1, + expectedConsuValSet: []int64{ + 4 * stake_multiplier, + }, + }, + { + name: "check min stake with multiple equal stakes", + stakedTokens: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 2 * stake_multiplier, + 2 * stake_multiplier, + }, + minStake: 2 * stake_multiplier, + maxRank: 0, + expectedConsuValSet: []int64{ + 2 * stake_multiplier, + 2 * stake_multiplier, + 2 * stake_multiplier, + }, + }, + { + name: "check max rank with multiple equal stakes", + stakedTokens: []int64{ + 1 * stake_multiplier, + 2 * stake_multiplier, + 2 * stake_multiplier, + 2 * stake_multiplier, + }, + minStake: 0, + maxRank: 1, + expectedConsuValSet: []int64{ + 2 * stake_multiplier, + 2 * stake_multiplier, + 2 * stake_multiplier, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s := NewCCVTestSuite[*appProvider.App, *appConsumer.App]( + // Pass in ibctesting.AppIniters for provider and consumer. + icstestingutils.ProviderAppIniter, icstestingutils.ConsumerAppIniter, []string{}) + s.SetT(t) + s.SetupTest() + + providerKeeper := s.providerApp.GetProviderKeeper() + s.SetupCCVChannel(s.path) + + // set validator powers + vals, err := providerKeeper.GetLastBondedValidators(s.providerChain.GetContext()) + s.Require().NoError(err) + + delegatorAccount := s.providerChain.SenderAccounts[0] + + for i, val := range vals { + power := tc.stakedTokens[i] + valAddr, err := providerKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + s.Require().NoError(err) + undelegate(s, delegatorAccount.SenderAccount.GetAddress(), valAddr, math.LegacyOneDec()) + + // set validator power + delegateByIdx(s, delegatorAccount.SenderAccount.GetAddress(), math.NewInt(power), i) + } + + // end the epoch to apply the updates + s.nextEpoch() + + // Relay 1 VSC packet from provider to consumer + relayAllCommittedPackets(s, s.providerChain, s.path, ccv.ProviderPortID, s.path.EndpointB.ChannelID, 1) + + // end the block on the consumer to apply the updates + s.consumerChain.NextBlock() + + // get the last bonded validators + lastVals, err := providerKeeper.GetLastBondedValidators(s.providerChain.GetContext()) + s.Require().NoError(err) + + for i, val := range lastVals { + // check that the intiial state was set correctly + require.Equal(s.T(), math.NewInt(tc.stakedTokens[i]), val.Tokens) + } + + // check the validator set on the consumer chain is the original one + consuValSet := s.consumerChain.LastHeader.ValidatorSet + s.Require().Equal(len(consuValSet.Validators), 4) + + // get just the powers of the consu val set + consuValPowers := make([]int64, len(consuValSet.Validators)) + for i, consuVal := range consuValSet.Validators { + // voting power corresponds to staked tokens at a 1:stake_multiplier ratio + consuValPowers[i] = consuVal.VotingPower * stake_multiplier + } + + s.Require().ElementsMatch(consuValPowers, tc.stakedTokens) + + // adjust parameters + + // set the maxRank and minStake according to the test case + providerKeeper.SetMaxValidatorRank(s.providerChain.GetContext(), s.consumerChain.ChainID, tc.maxRank) + providerKeeper.SetMinStake(s.providerChain.GetContext(), s.consumerChain.ChainID, tc.minStake) + + // undelegate and delegate to trigger a vscupdate + delegateAndUndelegate(s, delegatorAccount.SenderAccount.GetAddress(), math.NewInt(1*stake_multiplier), 1) + + // end the epoch to apply the updates + s.nextEpoch() + + // Relay 1 VSC packet from provider to consumer + relayAllCommittedPackets(s, s.providerChain, s.path, ccv.ProviderPortID, s.path.EndpointB.ChannelID, 1) + + // end the block on the consumer to apply the updates + s.consumerChain.NextBlock() + + // construct the new val powers + newConsuValSet := s.consumerChain.LastHeader.ValidatorSet + newConsuValPowers := make([]int64, len(newConsuValSet.Validators)) + for i, consuVal := range newConsuValSet.Validators { + // voting power corresponds to staked tokens at a 1:stake_multiplier ratio + newConsuValPowers[i] = consuVal.VotingPower * stake_multiplier + } + + // check that the new validator set is as expected + s.Require().ElementsMatch(newConsuValPowers, tc.expectedConsuValSet) + }) + } +} diff --git a/tests/integration/slashing.go b/tests/integration/slashing.go index 9f29c2806c..f2eebd5ef5 100644 --- a/tests/integration/slashing.go +++ b/tests/integration/slashing.go @@ -419,7 +419,7 @@ func (suite *CCVTestSuite) TestOnRecvSlashPacketErrors() { suite.Require().NoError(err, "no error expected") suite.Require().Equal(ccv.SlashPacketHandledResult, ackResult, "expected successful ack") - providerKeeper.SetConsumerValidator(ctx, firstBundle.Chain.ChainID, providertypes.ConsumerValidator{ + providerKeeper.SetConsumerValidator(ctx, firstBundle.Chain.ChainID, providertypes.ConsensusValidator{ ProviderConsAddr: validAddress, }) diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 8e2c552c95..8be9418b97 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -9,6 +9,7 @@ import ( reflect "reflect" time "time" + address "cosmossdk.io/core/address" math "cosmossdk.io/math" types "cosmossdk.io/store/types" types0 "github.com/cometbft/cometbft/abci/types" @@ -62,6 +63,21 @@ func (mr *MockStakingKeeperMockRecorder) BondDenom(ctx interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BondDenom", reflect.TypeOf((*MockStakingKeeper)(nil).BondDenom), ctx) } +// BondedRatio mocks base method. +func (m *MockStakingKeeper) BondedRatio(ctx context.Context) (math.LegacyDec, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BondedRatio", ctx) + ret0, _ := ret[0].(math.LegacyDec) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BondedRatio indicates an expected call of BondedRatio. +func (mr *MockStakingKeeperMockRecorder) BondedRatio(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BondedRatio", reflect.TypeOf((*MockStakingKeeper)(nil).BondedRatio), ctx) +} + // Delegation mocks base method. func (m *MockStakingKeeper) Delegation(ctx context.Context, addr types1.AccAddress, valAddr types1.ValAddress) (types3.DelegationI, error) { m.ctrl.T.Helper() @@ -77,6 +93,21 @@ func (mr *MockStakingKeeperMockRecorder) Delegation(ctx, addr, valAddr interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delegation", reflect.TypeOf((*MockStakingKeeper)(nil).Delegation), ctx, addr, valAddr) } +// GetBondedValidatorsByPower mocks base method. +func (m *MockStakingKeeper) GetBondedValidatorsByPower(ctx context.Context) ([]types3.Validator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBondedValidatorsByPower", ctx) + ret0, _ := ret[0].([]types3.Validator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBondedValidatorsByPower indicates an expected call of GetBondedValidatorsByPower. +func (mr *MockStakingKeeperMockRecorder) GetBondedValidatorsByPower(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBondedValidatorsByPower", reflect.TypeOf((*MockStakingKeeper)(nil).GetBondedValidatorsByPower), ctx) +} + // GetLastTotalPower mocks base method. func (m *MockStakingKeeper) GetLastTotalPower(ctx context.Context) (math.Int, error) { m.ctrl.T.Helper() @@ -272,6 +303,34 @@ func (mr *MockStakingKeeperMockRecorder) IsValidatorJailed(ctx, addr interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsValidatorJailed", reflect.TypeOf((*MockStakingKeeper)(nil).IsValidatorJailed), ctx, addr) } +// IterateBondedValidatorsByPower mocks base method. +func (m *MockStakingKeeper) IterateBondedValidatorsByPower(arg0 context.Context, arg1 func(int64, types3.ValidatorI) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IterateBondedValidatorsByPower", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// IterateBondedValidatorsByPower indicates an expected call of IterateBondedValidatorsByPower. +func (mr *MockStakingKeeperMockRecorder) IterateBondedValidatorsByPower(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateBondedValidatorsByPower", reflect.TypeOf((*MockStakingKeeper)(nil).IterateBondedValidatorsByPower), arg0, arg1) +} + +// IterateDelegations mocks base method. +func (m *MockStakingKeeper) IterateDelegations(ctx context.Context, delegator types1.AccAddress, fn func(int64, types3.DelegationI) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IterateDelegations", ctx, delegator, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// IterateDelegations indicates an expected call of IterateDelegations. +func (mr *MockStakingKeeperMockRecorder) IterateDelegations(ctx, delegator, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateDelegations", reflect.TypeOf((*MockStakingKeeper)(nil).IterateDelegations), ctx, delegator, fn) +} + // IterateLastValidatorPowers mocks base method. func (m *MockStakingKeeper) IterateLastValidatorPowers(ctx context.Context, cb func(types1.ValAddress, int64) bool) error { m.ctrl.T.Helper() @@ -432,6 +491,36 @@ func (mr *MockStakingKeeperMockRecorder) SlashWithInfractionReason(ctx, consAddr return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashWithInfractionReason", reflect.TypeOf((*MockStakingKeeper)(nil).SlashWithInfractionReason), ctx, consAddr, infractionHeight, power, slashFactor, infraction) } +// StakingTokenSupply mocks base method. +func (m *MockStakingKeeper) StakingTokenSupply(ctx context.Context) (math.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StakingTokenSupply", ctx) + ret0, _ := ret[0].(math.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StakingTokenSupply indicates an expected call of StakingTokenSupply. +func (mr *MockStakingKeeperMockRecorder) StakingTokenSupply(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StakingTokenSupply", reflect.TypeOf((*MockStakingKeeper)(nil).StakingTokenSupply), ctx) +} + +// TotalBondedTokens mocks base method. +func (m *MockStakingKeeper) TotalBondedTokens(ctx context.Context) (math.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TotalBondedTokens", ctx) + ret0, _ := ret[0].(math.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TotalBondedTokens indicates an expected call of TotalBondedTokens. +func (mr *MockStakingKeeperMockRecorder) TotalBondedTokens(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalBondedTokens", reflect.TypeOf((*MockStakingKeeper)(nil).TotalBondedTokens), ctx) +} + // UnbondingCanComplete mocks base method. func (m *MockStakingKeeper) UnbondingCanComplete(ctx context.Context, id uint64) error { m.ctrl.T.Helper() @@ -490,6 +579,20 @@ func (mr *MockStakingKeeperMockRecorder) Validator(ctx, addr interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validator", reflect.TypeOf((*MockStakingKeeper)(nil).Validator), ctx, addr) } +// ValidatorAddressCodec mocks base method. +func (m *MockStakingKeeper) ValidatorAddressCodec() address.Codec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidatorAddressCodec") + ret0, _ := ret[0].(address.Codec) + return ret0 +} + +// ValidatorAddressCodec indicates an expected call of ValidatorAddressCodec. +func (mr *MockStakingKeeperMockRecorder) ValidatorAddressCodec() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatorAddressCodec", reflect.TypeOf((*MockStakingKeeper)(nil).ValidatorAddressCodec)) +} + // ValidatorByConsAddr mocks base method. func (m *MockStakingKeeper) ValidatorByConsAddr(ctx context.Context, consAddr types1.ConsAddress) (types3.ValidatorI, error) { m.ctrl.T.Helper() diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index a80138551a..a2052c14b6 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -296,6 +296,9 @@ func GetTestConsumerAdditionProp() *providertypes.ConsumerAdditionProposal { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal) return prop diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index 644cfc33a7..a7f644ced8 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -716,5 +716,9 @@ func (k Keeper) IsPrevStandaloneChain(ctx sdk.Context) bool { // GetLastBondedValidators iterates the last validator powers in the staking module // and returns the first MaxValidators many validators with the largest powers. func (k Keeper) GetLastBondedValidators(ctx sdk.Context) ([]stakingtypes.Validator, error) { - return ccv.GetLastBondedValidatorsUtil(ctx, k.standaloneStakingKeeper, k.Logger(ctx)) + maxVals, err := k.standaloneStakingKeeper.MaxValidators(ctx) + if err != nil { + return nil, err + } + return ccv.GetLastBondedValidatorsUtil(ctx, k.standaloneStakingKeeper, k.Logger(ctx), maxVals) } diff --git a/x/ccv/consumer/types/codec.go b/x/ccv/consumer/types/codec.go index 243fc63f46..947fb4feb0 100644 --- a/x/ccv/consumer/types/codec.go +++ b/x/ccv/consumer/types/codec.go @@ -9,7 +9,6 @@ import ( // RegisterInterfaces registers the consumer Tx message types to the interface registry func RegisterInterfaces(registry codectypes.InterfaceRegistry) { - registry.RegisterImplementations( (*sdk.Msg)(nil), &MsgUpdateParams{}, diff --git a/x/ccv/democracy/governance/module.go b/x/ccv/democracy/governance/module.go index c4bf930066..57f243822f 100644 --- a/x/ccv/democracy/governance/module.go +++ b/x/ccv/democracy/governance/module.go @@ -74,7 +74,6 @@ func (am AppModule) EndBlock(c context.Context) error { deleteForbiddenProposal(ctx, am, proposal) return false, nil }) - if err != nil { return err } diff --git a/x/ccv/no_valupdates_genutil/doc.go b/x/ccv/no_valupdates_genutil/doc.go new file mode 100644 index 0000000000..08ad53a8cb --- /dev/null +++ b/x/ccv/no_valupdates_genutil/doc.go @@ -0,0 +1,8 @@ +/* +Package staking defines a "wrapper" module around the Cosmos SDK's native +x/genutil module. In other words, it provides the exact same functionality as +the native module in that it simply embeds the native module. However, it +overrides `InitGenesis` which will return no validator set updates. Instead, +it is assumed that some other module will provide the validator set updates. +*/ +package genutil diff --git a/x/ccv/no_valupdates_genutil/module.go b/x/ccv/no_valupdates_genutil/module.go new file mode 100644 index 0000000000..9a7528c071 --- /dev/null +++ b/x/ccv/no_valupdates_genutil/module.go @@ -0,0 +1,58 @@ +package genutil + +import ( + "encoding/json" + + "cosmossdk.io/core/genesis" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/genutil/types" + + abci "github.com/cometbft/cometbft/abci/types" +) + +var ( + _ module.AppModuleGenesis = AppModule{} + _ module.AppModuleBasic = genutil.AppModuleBasic{} +) + +// AppModule implements an application module for the genutil module. +type AppModule struct { + genutil.AppModule + + stakingKeeper types.StakingKeeper + deliverTx genesis.TxHandler + txEncodingConfig client.TxEncodingConfig +} + +// NewAppModule creates a new AppModule object +func NewAppModule(accountKeeper types.AccountKeeper, + stakingKeeper types.StakingKeeper, deliverTx genesis.TxHandler, + txEncodingConfig client.TxEncodingConfig, +) module.GenesisOnlyAppModule { + genutilAppModule := genutil.NewAppModule(accountKeeper, stakingKeeper, deliverTx, txEncodingConfig) + genutilAppModule.AppModuleGenesis = AppModule{ + AppModule: genutilAppModule.AppModuleGenesis.(genutil.AppModule), + stakingKeeper: stakingKeeper, + deliverTx: deliverTx, + txEncodingConfig: txEncodingConfig, + } + return genutilAppModule +} + +// InitGenesis delegates the InitGenesis call to the underlying x/genutil module, +// however, it returns no validator updates as validator updates will be provided by the provider module. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + + cdc.MustUnmarshalJSON(data, &genesisState) + _, err := genutil.InitGenesis(ctx, am.stakingKeeper, am.deliverTx, genesisState, am.txEncodingConfig) + if err != nil { + panic(err) + } + + return []abci.ValidatorUpdate{} +} diff --git a/x/ccv/no_valupdates_staking/doc.go b/x/ccv/no_valupdates_staking/doc.go new file mode 100644 index 0000000000..0cd53a9166 --- /dev/null +++ b/x/ccv/no_valupdates_staking/doc.go @@ -0,0 +1,8 @@ +/* +Package staking defines a "wrapper" module around the Cosmos SDK's native +x/staking module. In other words, it provides the exact same functionality as +the native module in that it simply embeds the native module. However, it +overrides `EndBlock` which will return no validator set updates. Instead, +it is assumed that some other module will provide the validator set updates. +*/ +package staking diff --git a/x/ccv/no_valupdates_staking/module.go b/x/ccv/no_valupdates_staking/module.go new file mode 100644 index 0000000000..cda755621e --- /dev/null +++ b/x/ccv/no_valupdates_staking/module.go @@ -0,0 +1,84 @@ +package staking + +import ( + "context" + "encoding/json" + + "cosmossdk.io/core/appmodule" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/exported" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} + + _ module.HasServices = AppModule{} + _ module.HasInvariants = AppModule{} + _ module.HasABCIGenesis = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} +) + +// AppModule embeds the Cosmos SDK's x/staking AppModuleBasic. +type AppModuleBasic struct { + staking.AppModuleBasic +} + +// AppModule embeds the Cosmos SDK's x/staking AppModule where we only override +// specific methods. +type AppModule struct { + // embed the Cosmos SDK's x/staking AppModule + staking.AppModule + + keeper keeper.Keeper + accKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +// NewAppModule creates a new AppModule object using the native x/staking module +// AppModule constructor. +func NewAppModule( + cdc codec.Codec, + keeper *keeper.Keeper, + ak types.AccountKeeper, + bk types.BankKeeper, + ls exported.Subspace, +) AppModule { + stakingAppMod := staking.NewAppModule(cdc, keeper, ak, bk, ls) + return AppModule{ + AppModule: stakingAppMod, + keeper: *keeper, + accKeeper: ak, + bankKeeper: bk, + } +} + +// InitGenesis delegates the InitGenesis call to the underlying x/staking module, +// however, it returns no validator updates as validator updates will be provided by the provider module. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + + cdc.MustUnmarshalJSON(data, &genesisState) + _ = am.keeper.InitGenesis(ctx, &genesisState) + + return []abci.ValidatorUpdate{} +} + +// EndBlock delegates the EndBlock call to the underlying x/staking module, +// however, it returns no validator updates as validator updates will be provided by the provider module. +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { + _, err := am.keeper.EndBlocker(ctx) + // return the error, but no validator updates + return []abci.ValidatorUpdate{}, err +} diff --git a/x/ccv/provider/client/legacy_proposal_handler.go b/x/ccv/provider/client/legacy_proposal_handler.go index f837e7bab2..78e144ae87 100644 --- a/x/ccv/provider/client/legacy_proposal_handler.go +++ b/x/ccv/provider/client/legacy_proposal_handler.go @@ -61,7 +61,10 @@ Where proposal.json contains: "validators_power_cap": 32, "validator_set_cap": 50, "allowlist": [], - "denylist": ["validatorAConsensusAddress", "validatorBConsensusAddress"] + "denylist": ["validatorAConsensusAddress", "validatorBConsensusAddress"], + "min_stake": 100000000000, + "max_validator_rank": 180, + "allow_inactive_vals": false } `, RunE: func(cmd *cobra.Command, args []string) error { @@ -84,7 +87,8 @@ Where proposal.json contains: proposal.ConsumerRedistributionFraction, proposal.BlocksPerDistributionTransmission, proposal.DistributionTransmissionChannel, proposal.HistoricalEntries, proposal.CcvTimeoutPeriod, proposal.TransferTimeoutPeriod, proposal.UnbondingPeriod, proposal.TopN, - proposal.ValidatorsPowerCap, proposal.ValidatorSetCap, proposal.Allowlist, proposal.Denylist) + proposal.ValidatorsPowerCap, proposal.ValidatorSetCap, proposal.Allowlist, proposal.Denylist, + proposal.MinStake, proposal.MaxValidatorRank, proposal.AllowInactiveVals) from := clientCtx.GetFromAddress() @@ -245,7 +249,10 @@ Where proposal.json contains: "validators_power_cap": 32, "validator_set_cap": 50, "allowlist": [], - "denylist": ["validatorAConsensusAddress", "validatorBConsensusAddress"] + "denylist": ["validatorAConsensusAddress", "validatorBConsensusAddress"], + "min_stake": 100000000000, + "max_validator_rank": 180, + "allow_inactive_vals": false } `, RunE: func(cmd *cobra.Command, args []string) error { @@ -261,7 +268,7 @@ Where proposal.json contains: content := types.NewConsumerModificationProposal( proposal.Title, proposal.Summary, proposal.ChainId, proposal.TopN, - proposal.ValidatorsPowerCap, proposal.ValidatorSetCap, proposal.Allowlist, proposal.Denylist) + proposal.ValidatorsPowerCap, proposal.ValidatorSetCap, proposal.Allowlist, proposal.Denylist, proposal.MinStake, proposal.MaxValidatorRank, proposal.AllowInactiveVals) from := clientCtx.GetFromAddress() diff --git a/x/ccv/provider/client/legacy_proposals.go b/x/ccv/provider/client/legacy_proposals.go index b19528b1b7..d6708dcdb2 100644 --- a/x/ccv/provider/client/legacy_proposals.go +++ b/x/ccv/provider/client/legacy_proposals.go @@ -41,6 +41,9 @@ type ConsumerAdditionProposalJSON struct { ValidatorSetCap uint32 `json:"validator_set_cap"` Allowlist []string `json:"allowlist"` Denylist []string `json:"denylist"` + MinStake uint64 `json:"min_stake"` + MaxValidatorRank uint32 `json:"max_validator_rank"` + AllowInactiveVals bool `json:"allow_inactive_vals"` } type ConsumerAdditionProposalReq struct { @@ -172,6 +175,9 @@ type ConsumerModificationProposalJSON struct { ValidatorSetCap uint32 `json:"validator_set_cap"` Allowlist []string `json:"allowlist"` Denylist []string `json:"denylist"` + MinStake uint64 `json:"min_stake"` + MaxValidatorRank uint32 `json:"max_validator_rank"` + AllowInactiveVals bool `json:"allow_inactive_vals"` Deposit string `json:"deposit"` } diff --git a/x/ccv/provider/keeper/consumer_equivocation.go b/x/ccv/provider/keeper/consumer_equivocation.go index 12f964ebd4..023ec00219 100644 --- a/x/ccv/provider/keeper/consumer_equivocation.go +++ b/x/ccv/provider/keeper/consumer_equivocation.go @@ -359,7 +359,7 @@ func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.Pr if err != nil && errors.Is(err, stakingtypes.ErrNoValidatorFound) { return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) } else if err != nil { - return errorsmod.Wrapf(slashingtypes.ErrBadValidatorAddr, "unkown error looking for provider consensus address: %s", providerAddr.String()) + return errorsmod.Wrapf(slashingtypes.ErrBadValidatorAddr, "unknown error looking for provider consensus address: %s", providerAddr.String()) } if validator.IsUnbonded() { @@ -429,7 +429,7 @@ func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsA if err != nil && errors.Is(err, stakingtypes.ErrNoValidatorFound) { return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) } else if err != nil { - return errorsmod.Wrapf(slashingtypes.ErrBadValidatorAddr, "unkown error looking for provider consensus address: %s", providerAddr.String()) + return errorsmod.Wrapf(slashingtypes.ErrBadValidatorAddr, "unknown error looking for provider consensus address: %s", providerAddr.String()) } if validator.IsUnbonded() { diff --git a/x/ccv/provider/keeper/distribution.go b/x/ccv/provider/keeper/distribution.go index 9ac016abbc..d2505e7dbe 100644 --- a/x/ccv/provider/keeper/distribution.go +++ b/x/ccv/provider/keeper/distribution.go @@ -1,11 +1,11 @@ package keeper import ( + "context" + storetypes "cosmossdk.io/store/types" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" - "context" - errorsmod "cosmossdk.io/errors" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" diff --git a/x/ccv/provider/keeper/distribution_test.go b/x/ccv/provider/keeper/distribution_test.go index 0b22260a2b..2b09beb4fb 100644 --- a/x/ccv/provider/keeper/distribution_test.go +++ b/x/ccv/provider/keeper/distribution_test.go @@ -46,7 +46,7 @@ func TestComputeConsumerTotalVotingPower(t *testing.T) { keeper.SetConsumerValidator( ctx, chainID, - types.ConsumerValidator{ + types.ConsensusValidator{ ProviderConsAddr: val.Address, Power: val.VotingPower, }, diff --git a/x/ccv/provider/keeper/genesis.go b/x/ccv/provider/keeper/genesis.go index b8d6d179fc..3804e405f2 100644 --- a/x/ccv/provider/keeper/genesis.go +++ b/x/ccv/provider/keeper/genesis.go @@ -5,12 +5,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v5/x/ccv/types" ) // InitGenesis initializes the CCV provider state and binds to PortID. -func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { +func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) []abci.ValidatorUpdate { k.SetPort(ctx, ccv.ProviderPortID) // Only try to bind to port if it is not already bound, since we may already own @@ -103,6 +105,50 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) { k.SetParams(ctx, genState.Params) k.InitializeSlashMeter(ctx) + + // Set the last provider consensus validator set + k.SetLastProviderConsensusValSet(ctx, genState.LastProviderConsensusValidators) + + return k.InitGenesisValUpdates(ctx) +} + +// InitGenesisValUpdates returns the genesis validator set updates +// for the provider module by selecting the first MaxProviderConsensusValidators +// from the staking module's validator set. +func (k Keeper) InitGenesisValUpdates(ctx sdk.Context) []abci.ValidatorUpdate { + // get the staking validator set + valSet, err := k.stakingKeeper.GetBondedValidatorsByPower(ctx) + if err != nil { + panic(fmt.Errorf("retrieving validator set: %w", err)) + } + + // restrict the set to the first MaxProviderConsensusValidators + maxVals := k.GetParams(ctx).MaxProviderConsensusValidators + if int64(len(valSet)) > maxVals { + k.Logger(ctx).Info(fmt.Sprintf("reducing validator set from %d to %d", len(valSet), maxVals)) + valSet = valSet[:maxVals] + } + + reducedValSet := make([]types.ConsensusValidator, len(valSet)) + for i, val := range valSet { + consensusVal, err := k.CreateProviderConsensusValidator(ctx, val) + if err != nil { + k.Logger(ctx).Error(fmt.Sprintf("failed to create provider consensus validator: %v", err)) + continue + } + reducedValSet[i] = consensusVal + } + + k.SetLastProviderConsensusValSet(ctx, reducedValSet) + + valUpdates := make([]abci.ValidatorUpdate, len(reducedValSet)) + for i, val := range reducedValSet { + valUpdates[i] = abci.ValidatorUpdate{ + PubKey: *val.PublicKey, + Power: val.Power, + } + } + return valUpdates } // ExportGenesis returns the CCV provider module's exported genesis @@ -171,5 +217,6 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { consumerAddrsToPrune, k.GetAllInitTimeoutTimestamps(ctx), exportedVscSendTimestamps, + k.GetLastProviderConsensusValSet(ctx), ) } diff --git a/x/ccv/provider/keeper/genesis_test.go b/x/ccv/provider/keeper/genesis_test.go index 8fd22824ef..07fd2ba229 100644 --- a/x/ccv/provider/keeper/genesis_test.go +++ b/x/ccv/provider/keeper/genesis_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/interchain-security/v5/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper" @@ -32,6 +33,9 @@ func TestInitAndExportGenesis(t *testing.T) { // create validator keys and addresses for key assignment providerCryptoId := crypto.NewCryptoIdentityFromIntSeed(7896) provAddr := providerCryptoId.ProviderConsAddress() + provVal := providerCryptoId.SDKStakingValidator() + provPubKey, err := provVal.TmConsPublicKey() + require.NoError(t, err) consumerCryptoId := crypto.NewCryptoIdentityFromIntSeed(7897) consumerTmPubKey := consumerCryptoId.TMProtoCryptoPublicKey() @@ -63,6 +67,13 @@ func TestInitAndExportGenesis(t *testing.T) { exportedVscSendTimeStampsAll = append(exportedVscSendTimeStampsAll, exportedVscSendTimeStampsC0) exportedVscSendTimeStampsAll = append(exportedVscSendTimeStampsAll, exportedVscSendTimeStampsC1) + // at genesis, the validator has 100 power + lastProviderConsensusValidators := []providertypes.ConsensusValidator{{ + ProviderConsAddr: provAddr.Address, + Power: 100, + PublicKey: &provPubKey, + }} + // create genesis struct provGenesis := providertypes.NewGenesisState(vscID, []providertypes.ValsetUpdateIdToHeight{{ValsetUpdateId: vscID, Height: initHeight}}, @@ -127,6 +138,7 @@ func TestInitAndExportGenesis(t *testing.T) { }, initTimeoutTimeStamps, exportedVscSendTimeStampsAll, + lastProviderConsensusValidators, ) // Instantiate in-mem provider keeper with mocks @@ -141,6 +153,16 @@ func TestInitAndExportGenesis(t *testing.T) { ctx).Return(math.NewInt(100), nil).Times(1), // Return total voting power as 100 ) + mocks.MockStakingKeeper.EXPECT().GetBondedValidatorsByPower(gomock.Any()).Return( + []stakingtypes.Validator{ + provVal, + }, nil).AnyTimes() + + valAddr, err := sdk.ValAddressFromBech32(provVal.GetOperator()) + require.NoError(t, err) + mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), valAddr). + Return(int64(100), nil).AnyTimes() + // init provider chain pk.InitGenesis(ctx, provGenesis) @@ -175,6 +197,16 @@ func TestInitAndExportGenesis(t *testing.T) { require.True(t, pk.PendingConsumerRemovalPropExists(ctx, cChainIDs[0], oneHourFromNow)) require.Equal(t, provGenesis.Params, pk.GetParams(ctx)) + providerConsensusValSet := pk.GetLastProviderConsensusValSet(ctx) + require.Equal(t, + []providertypes.ConsensusValidator{{ + ProviderConsAddr: provAddr.Address, + Power: 100, + PublicKey: &provPubKey, + }}, + providerConsensusValSet, + ) + gotConsTmPubKey, found := pk.GetValidatorConsumerPubKey(ctx, cChainIDs[0], provAddr) require.True(t, found) require.Equal(t, consumerTmPubKey, gotConsTmPubKey) diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index d2a36c686d..27c9450afb 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -328,7 +328,7 @@ func (k Keeper) QueryConsumerValidators(goCtx context.Context, req *types.QueryC for _, v := range k.GetConsumerValSet(ctx, consumerChainID) { validators = append(validators, &types.QueryConsumerValidatorsValidator{ ProviderAddress: sdk.ConsAddress(v.ProviderConsAddr).String(), - ConsumerKey: v.ConsumerPublicKey, + ConsumerKey: v.PublicKey, Power: v.Power, }) } diff --git a/x/ccv/provider/keeper/grpc_query_test.go b/x/ccv/provider/keeper/grpc_query_test.go index 3d9f9ee86d..37cc335b03 100644 --- a/x/ccv/provider/keeper/grpc_query_test.go +++ b/x/ccv/provider/keeper/grpc_query_test.go @@ -5,18 +5,21 @@ import ( "testing" "time" - "cosmossdk.io/math" - "github.com/cometbft/cometbft/proto/tendermint/crypto" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdktypes "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cometbft/cometbft/proto/tendermint/crypto" + cryptotestutil "github.com/cosmos/interchain-security/v5/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper" "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v5/x/ccv/types" - "github.com/stretchr/testify/require" ) func TestQueryAllPairsValConAddrByConsumerChainID(t *testing.T) { @@ -154,11 +157,11 @@ func TestQueryConsumerValidators(t *testing.T) { providerAddr1 := types.NewProviderConsAddress([]byte("providerAddr1")) consumerKey1 := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() - consumerValidator1 := types.ConsumerValidator{ProviderConsAddr: providerAddr1.ToSdkConsAddr(), Power: 1, ConsumerPublicKey: &consumerKey1} + consumerValidator1 := types.ConsensusValidator{ProviderConsAddr: providerAddr1.ToSdkConsAddr(), Power: 1, PublicKey: &consumerKey1} providerAddr2 := types.NewProviderConsAddress([]byte("providerAddr2")) consumerKey2 := cryptotestutil.NewCryptoIdentityFromIntSeed(2).TMProtoCryptoPublicKey() - consumerValidator2 := types.ConsumerValidator{ProviderConsAddr: providerAddr2.ToSdkConsAddr(), Power: 2, ConsumerPublicKey: &consumerKey2} + consumerValidator2 := types.ConsensusValidator{ProviderConsAddr: providerAddr2.ToSdkConsAddr(), Power: 2, PublicKey: &consumerKey2} expectedResponse := types.QueryConsumerValidatorsResponse{ Validators: []*types.QueryConsumerValidatorsValidator{ @@ -169,7 +172,7 @@ func TestQueryConsumerValidators(t *testing.T) { // set up the client id so the chain looks like it "started" pk.SetConsumerClientId(ctx, chainID, "clientID") - pk.SetConsumerValSet(ctx, chainID, []types.ConsumerValidator{consumerValidator1, consumerValidator2}) + pk.SetConsumerValSet(ctx, chainID, []types.ConsensusValidator{consumerValidator1, consumerValidator2}) res, err := pk.QueryConsumerValidators(ctx, &req) require.NoError(t, err) @@ -197,10 +200,10 @@ func TestQueryConsumerChainsValidatorHasToValidate(t *testing.T) { } // set `providerAddr` as a consumer validator on "chain1" - pk.SetConsumerValidator(ctx, "chain1", types.ConsumerValidator{ + pk.SetConsumerValidator(ctx, "chain1", types.ConsensusValidator{ ProviderConsAddr: providerAddr.ToSdkConsAddr(), Power: 1, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{1}, }, @@ -210,6 +213,11 @@ func TestQueryConsumerChainsValidatorHasToValidate(t *testing.T) { // set `providerAddr` as an opted-in validator on "chain3" pk.SetOptedIn(ctx, "chain3", providerAddr) + // set max provider consensus vals to include all validators + params := pk.GetParams(ctx) + params.MaxProviderConsensusValidators = 180 + pk.SetParams(ctx, params) + // `providerAddr` has to validate "chain1" because it is a consumer validator in this chain, as well as "chain3" // because it opted in, in "chain3" and `providerAddr` belongs to the bonded validators expectedChains := []string{"chain1", "chain3"} diff --git a/x/ccv/provider/keeper/invariants.go b/x/ccv/provider/keeper/invariants.go new file mode 100644 index 0000000000..ca73ffad25 --- /dev/null +++ b/x/ccv/provider/keeper/invariants.go @@ -0,0 +1,32 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" +) + +// RegisterInvariants registers all staking invariants +func RegisterInvariants(ir sdk.InvariantRegistry, k *Keeper) { + ir.RegisterRoute(types.ModuleName, "max-provider-validators", + MaxProviderConsensusValidatorsInvariant(k)) +} + +// MaxProviderConsensusValidatorsInvariant checks that the number of provider consensus validators +// is less than or equal to the maximum number of provider consensus validators +func MaxProviderConsensusValidatorsInvariant(k *Keeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + params := k.GetParams(ctx) + maxProviderConsensusValidators := params.MaxProviderConsensusValidators + + consensusValidators := k.GetLastProviderConsensusValSet(ctx) + if int64(len(consensusValidators)) > maxProviderConsensusValidators { + return sdk.FormatInvariant(types.ModuleName, "max-provider-validators", + fmt.Sprintf("number of provider consensus validators: %d, exceeds max: %d", + len(consensusValidators), maxProviderConsensusValidators)), true + } + + return "", false + } +} diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 22f5f33626..97bd7888e7 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -143,6 +143,10 @@ func (k Keeper) mustValidateFields() { // ccv.PanicIfZeroOrNil(k.govKeeper, "govKeeper") // 17 } +func (k *Keeper) SetGovKeeper(govKeeper govkeeper.Keeper) { + k.govKeeper = govKeeper +} + // Logger returns a module-specific logger. func (k Keeper) Logger(ctx context.Context) log.Logger { sdkCtx := sdk.UnwrapSDKContext(ctx) @@ -1582,3 +1586,124 @@ func (k Keeper) DeleteMinimumPowerInTopN( store := ctx.KVStore(k.storeKey) store.Delete(types.MinimumPowerInTopNKey(chainID)) } + +// SetMinStake sets the minimum stake required for a validator to validate +// a given consumer chain. +func (k Keeper) SetMinStake( + ctx sdk.Context, + chainID string, + minStake uint64, +) { + store := ctx.KVStore(k.storeKey) + + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, minStake) + + store.Set(types.MinStakeKey(chainID), buf) +} + +// GetMinStake returns the minimum stake required for a validator to validate +// a given consumer chain. +func (k Keeper) GetMinStake( + ctx sdk.Context, + chainID string, +) (uint64, bool) { + store := ctx.KVStore(k.storeKey) + buf := store.Get(types.MinStakeKey(chainID)) + if buf == nil { + return 0, false + } + return binary.BigEndian.Uint64(buf), true +} + +// DeleteMinStake removes the minimum stake required for a validator to validate +// a given consumer chain. +func (k Keeper) DeleteMinStake( + ctx sdk.Context, + chainID string, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.MinStakeKey(chainID)) +} + +// SetMaxValidatorRank sets the maximum position in the validator set a validator can have to validate +// a given consumer chain. +func (k Keeper) SetMaxValidatorRank( + ctx sdk.Context, + chainID string, + maxRank uint32, +) { + store := ctx.KVStore(k.storeKey) + + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, maxRank) + + store.Set(types.MaxValidatorRankKey(chainID), buf) +} + +// GetMaxValidatorRank returns the maximum position in the validator set a validator can have to validate +// a given consumer chain. +func (k Keeper) GetMaxValidatorRank( + ctx sdk.Context, + chainID string, +) (uint32, bool) { + store := ctx.KVStore(k.storeKey) + buf := store.Get(types.MaxValidatorRankKey(chainID)) + if buf == nil { + return 0, false + } + return binary.BigEndian.Uint32(buf), true +} + +// DeleteMaxValidatorRank removes the maximum position in the validator set a validator can have to validate +// a given consumer chain. +func (k Keeper) DeleteMaxValidatorRank( + ctx sdk.Context, + chainID string, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.MaxValidatorRankKey(chainID)) +} + +// SetAllowInactiveValidators sets whether inactive validators are allowed to validate +// a given consumer chain. +func (k Keeper) SetAllowInactiveValidators( + ctx sdk.Context, + chainID string, + allowed bool, +) { + store := ctx.KVStore(k.storeKey) + + var buf []byte + if allowed { + buf = []byte{1} + } else { + buf = []byte{0} + } + + store.Set(types.AllowInactiveValidatorsKey(chainID), buf) +} + +// GetAllowInactiveValidators returns whether inactive validators are allowed to validate +// a given consumer chain. +func (k Keeper) GetAllowInactiveValidators( + ctx sdk.Context, + chainID string, +) (bool, bool) { + store := ctx.KVStore(k.storeKey) + buf := store.Get(types.AllowInactiveValidatorsKey(chainID)) + if buf == nil { + return false, false + } + return buf[0] == 1, true +} + +// DeleteAllowInactiveValidators removes the flag of whether inactive validators are allowed to validate +// a given consumer chain. +func (k Keeper) DeleteAllowInactiveValidators( + ctx sdk.Context, + chainID string, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.AllowInactiveValidatorsKey(chainID)) +} diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index e61a93cda1..ec7f783e64 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/cometbft/cometbft/abci/types" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" @@ -744,38 +745,6 @@ func TestConsumerCommissionRate(t *testing.T) { require.False(t, found) } -// TestValidatorsPowerCap tests the `SetValidatorsPowerCap`, `GetValidatorsPowerCap`, and `DeleteValidatorsPowerCap` methods -func TestValidatorsPowerCap(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - expectedPowerCap := uint32(10) - providerKeeper.SetValidatorsPowerCap(ctx, "chainID", expectedPowerCap) - powerCap, found := providerKeeper.GetValidatorsPowerCap(ctx, "chainID") - require.Equal(t, expectedPowerCap, powerCap) - require.True(t, found) - - providerKeeper.DeleteValidatorsPowerCap(ctx, "chainID") - _, found = providerKeeper.GetValidatorsPowerCap(ctx, "chainID") - require.False(t, found) -} - -// TestValidatorSetCap tests the `SetValidatorSetCap`, `GetValidatorSetCap`, and `DeleteValidatorSetCap` methods -func TestValidatorSetCap(t *testing.T) { - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - defer ctrl.Finish() - - expectedPowerCap := uint32(10) - providerKeeper.SetValidatorSetCap(ctx, "chainID", expectedPowerCap) - powerCap, found := providerKeeper.GetValidatorSetCap(ctx, "chainID") - require.Equal(t, expectedPowerCap, powerCap) - require.True(t, found) - - providerKeeper.DeleteValidatorSetCap(ctx, "chainID") - _, found = providerKeeper.GetValidatorSetCap(ctx, "chainID") - require.False(t, found) -} - // TestAllowlist tests the `SetAllowlist`, `IsAllowlisted`, `DeleteAllowlist`, and `IsAllowlistEmpty` methods func TestAllowlist(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) @@ -832,32 +801,109 @@ func TestDenylist(t *testing.T) { require.True(t, providerKeeper.IsDenylistEmpty(ctx, chainID)) } -func TestMinimumPowerInTopN(t *testing.T) { +// Tests setting, getting and deleting number parameters that are stored per-consumer chain. +// The tests cover the following parameters: +// - MinimumPowerInTopN +// - MinStake +// - MaxValidatorRank +// - ValidatorSetCap +// - ValidatorPowersCap +// - AllowInactiveValidators +func TestKeeperConsumerParams(t *testing.T) { k, ctx, _, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) - chainID := "testChain" - minPower := int64(1000) + tests := []struct { + name string + settingFunc func(sdk.Context, string, int64) + getFunc func(sdk.Context, string) (int64, bool) + initialValue int64 + updatedValue int64 + }{ + { + name: "Minimum Power In Top N", + settingFunc: func(ctx sdk.Context, id string, val int64) { k.SetMinimumPowerInTopN(ctx, id, val) }, + getFunc: func(ctx sdk.Context, id string) (int64, bool) { return k.GetMinimumPowerInTopN(ctx, id) }, + initialValue: 1000, + updatedValue: 2000, + }, + { + name: "Minimum Stake", + settingFunc: func(ctx sdk.Context, id string, val int64) { k.SetMinStake(ctx, id, uint64(val)) }, + getFunc: func(ctx sdk.Context, id string) (int64, bool) { + val, found := k.GetMinStake(ctx, id) + return int64(val), found + }, + initialValue: 1000, + updatedValue: 2000, + }, + { + name: "Maximum Validator Rank", + settingFunc: func(ctx sdk.Context, id string, val int64) { k.SetMaxValidatorRank(ctx, id, uint32(val)) }, + getFunc: func(ctx sdk.Context, id string) (int64, bool) { + val, found := k.GetMaxValidatorRank(ctx, id) + return int64(val), found + }, + initialValue: 100, + updatedValue: 200, + }, + { + name: "Validator Set Cap", + settingFunc: func(ctx sdk.Context, id string, val int64) { k.SetValidatorSetCap(ctx, id, uint32(val)) }, + getFunc: func(ctx sdk.Context, id string) (int64, bool) { + val, found := k.GetValidatorSetCap(ctx, id) + return int64(val), found + }, + initialValue: 10, + updatedValue: 200, + }, + { + name: "Validator Powers Cap", + settingFunc: func(ctx sdk.Context, id string, val int64) { k.SetValidatorsPowerCap(ctx, id, uint32(val)) }, + getFunc: func(ctx sdk.Context, id string) (int64, bool) { + val, found := k.GetValidatorsPowerCap(ctx, id) + return int64(val), found + }, + initialValue: 10, + updatedValue: 11, + }, + { + name: "Allow Inactive Validators", + settingFunc: func(ctx sdk.Context, id string, val int64) { k.SetAllowInactiveValidators(ctx, id, val == 1) }, + getFunc: func(ctx sdk.Context, id string) (int64, bool) { + val, found := k.GetAllowInactiveValidators(ctx, id) + res := int64(0) // default value + if val { + res = 1 + } + return int64(res), found + }, + initialValue: 1, + updatedValue: 0, + }, + } - // Set the minimum power in top N - k.SetMinimumPowerInTopN(ctx, chainID, minPower) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + chainID := "chainID" + // Set initial value + tt.settingFunc(ctx, chainID, int64(tt.initialValue)) - // Retrieve the minimum power in top N - actualMinPower, found := k.GetMinimumPowerInTopN(ctx, chainID) - require.True(t, found) - require.Equal(t, minPower, actualMinPower) + // Retrieve and check initial value + actualValue, found := tt.getFunc(ctx, chainID) + require.True(t, found) + require.EqualValues(t, tt.initialValue, actualValue) - // Update the minimum power - newMinPower := int64(2000) - k.SetMinimumPowerInTopN(ctx, chainID, newMinPower) + // Update value + tt.settingFunc(ctx, chainID, int64(tt.updatedValue)) - // Retrieve the updated minimum power in top N - newActualMinPower, found := k.GetMinimumPowerInTopN(ctx, chainID) - require.True(t, found) - require.Equal(t, newMinPower, newActualMinPower) + // Retrieve and check updated value + newActualValue, found := tt.getFunc(ctx, chainID) + require.True(t, found) + require.EqualValues(t, tt.updatedValue, newActualValue) - // Test when the chain ID does not exist - nonExistentChainID := "nonExistentChain" - nonExistentMinPower, found := k.GetMinimumPowerInTopN(ctx, nonExistentChainID) - require.False(t, found) - require.Equal(t, int64(0), nonExistentMinPower) + // Check non-existent chain ID + _, found = tt.getFunc(ctx, "not the chainID") + require.False(t, found) + }) + } } diff --git a/x/ccv/provider/keeper/legacy_proposal.go b/x/ccv/provider/keeper/legacy_proposal.go index 19ad553038..7b85f572ce 100644 --- a/x/ccv/provider/keeper/legacy_proposal.go +++ b/x/ccv/provider/keeper/legacy_proposal.go @@ -92,6 +92,9 @@ func (k Keeper) HandleLegacyConsumerModificationProposal(ctx sdk.Context, p *typ k.SetTopN(ctx, p.ChainId, p.Top_N) k.SetValidatorsPowerCap(ctx, p.ChainId, p.ValidatorsPowerCap) k.SetValidatorSetCap(ctx, p.ChainId, p.ValidatorSetCap) + k.SetMinStake(ctx, p.ChainId, p.MinStake) + k.SetMaxValidatorRank(ctx, p.ChainId, p.MaxRank) + k.SetAllowInactiveValidators(ctx, p.ChainId, p.AllowInactiveVals) k.DeleteAllowlist(ctx, p.ChainId) for _, address := range p.Allowlist { @@ -123,11 +126,11 @@ func (k Keeper) HandleLegacyConsumerModificationProposal(ctx sdk.Context, p *typ if p.Top_N != oldTopN { if p.Top_N > 0 { // if the chain receives a non-zero top N value, store the minimum power in the top N - bondedValidators, err := k.GetLastBondedValidators(ctx) + activeValidators, err := k.GetLastActiveBondedValidators(ctx) if err != nil { return err } - minPower, err := k.ComputeMinPowerInTopN(ctx, bondedValidators, p.Top_N) + minPower, err := k.ComputeMinPowerInTopN(ctx, activeValidators, p.Top_N) if err != nil { return err } diff --git a/x/ccv/provider/keeper/legacy_proposal_test.go b/x/ccv/provider/keeper/legacy_proposal_test.go index 06533a2503..0684f4ca63 100644 --- a/x/ccv/provider/keeper/legacy_proposal_test.go +++ b/x/ccv/provider/keeper/legacy_proposal_test.go @@ -62,6 +62,9 @@ func TestHandleLegacyConsumerAdditionProposal(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), blockTime: now, expAppendProp: true, @@ -92,6 +95,9 @@ func TestHandleLegacyConsumerAdditionProposal(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), blockTime: now, expAppendProp: false, @@ -282,18 +288,27 @@ func TestHandleConsumerModificationProposal(t *testing.T) { providerKeeper.SetAllowlist(ctx, chainID, providertypes.NewProviderConsAddress([]byte("allowlistedAddr1"))) providerKeeper.SetAllowlist(ctx, chainID, providertypes.NewProviderConsAddress([]byte("allowlistedAddr2"))) providerKeeper.SetDenylist(ctx, chainID, providertypes.NewProviderConsAddress([]byte("denylistedAddr1"))) + providerKeeper.SetMinStake(ctx, chainID, 1000) + providerKeeper.SetMaxValidatorRank(ctx, chainID, 180) + providerKeeper.SetAllowInactiveValidators(ctx, chainID, true) expectedTopN := uint32(75) expectedValidatorsPowerCap := uint32(67) expectedValidatorSetCap := uint32(20) expectedAllowlistedValidator := "cosmosvalcons1wpex7anfv3jhystyv3eq20r35a" expectedDenylistedValidator := "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39" + expectedMinStake := uint64(0) + expectedMaxValidatorRank := uint32(20) + expectedAllowInactiveValidators := false proposal := providertypes.NewConsumerModificationProposal("title", "description", chainID, expectedTopN, expectedValidatorsPowerCap, expectedValidatorSetCap, []string{expectedAllowlistedValidator}, []string{expectedDenylistedValidator}, + expectedMinStake, + expectedMaxValidatorRank, + expectedAllowInactiveValidators, ).(*providertypes.ConsumerModificationProposal) err := providerKeeper.HandleLegacyConsumerModificationProposal(ctx, proposal) @@ -315,4 +330,13 @@ func TestHandleConsumerModificationProposal(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, len(providerKeeper.GetDenyList(ctx, chainID))) require.Equal(t, providertypes.NewProviderConsAddress(denylistedValidator), providerKeeper.GetDenyList(ctx, chainID)[0]) + + actualMinStake, _ := providerKeeper.GetMinStake(ctx, chainID) + require.Equal(t, expectedMinStake, actualMinStake) + + actualMaxValidatorRank, _ := providerKeeper.GetMaxValidatorRank(ctx, chainID) + require.Equal(t, expectedMaxValidatorRank, actualMaxValidatorRank) + + actualAllowInactiveValidators, _ := providerKeeper.GetAllowInactiveValidators(ctx, chainID) + require.Equal(t, expectedAllowInactiveValidators, actualAllowInactiveValidators) } diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 4239a7cf41..b97ebdb235 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -100,7 +100,8 @@ func (k msgServer) ConsumerAddition(goCtx context.Context, msg *types.MsgConsume // ConsumerRemoval defines a rpc handler method for MsgConsumerRemoval func (k msgServer) ConsumerRemoval( goCtx context.Context, - msg *types.MsgConsumerRemoval) (*types.MsgConsumerRemovalResponse, error) { + msg *types.MsgConsumerRemoval, +) (*types.MsgConsumerRemovalResponse, error) { if k.GetAuthority() != msg.Authority { return nil, errorsmod.Wrapf(types.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Authority) } diff --git a/x/ccv/provider/keeper/params.go b/x/ccv/provider/keeper/params.go index 9c1d276d4f..4b7cb8ee0d 100644 --- a/x/ccv/provider/keeper/params.go +++ b/x/ccv/provider/keeper/params.go @@ -78,6 +78,13 @@ func (k Keeper) GetBlocksPerEpoch(ctx sdk.Context) int64 { return params.BlocksPerEpoch } +// GetMaxProviderConsensusValidators returns the number of validators that will be passed on from the staking module +// to the consensus engine on the provider +func (k Keeper) GetMaxProviderConsensusValidators(ctx sdk.Context) int64 { + params := k.GetParams(ctx) + return params.MaxProviderConsensusValidators +} + // GetParams returns the paramset for the provider module func (k Keeper) GetParams(ctx sdk.Context) types.Params { store := ctx.KVStore(k.storeKey) diff --git a/x/ccv/provider/keeper/params_test.go b/x/ccv/provider/keeper/params_test.go index 6450102c2a..e6a9db59bf 100644 --- a/x/ccv/provider/keeper/params_test.go +++ b/x/ccv/provider/keeper/params_test.go @@ -50,6 +50,7 @@ func TestParams(t *testing.T) { Amount: math.NewInt(10000000), }, 600, + 10, ) providerKeeper.SetParams(ctx, newParams) params = providerKeeper.GetParams(ctx) diff --git a/x/ccv/provider/keeper/partial_set_security.go b/x/ccv/provider/keeper/partial_set_security.go index 069eb219dc..dd62613cb3 100644 --- a/x/ccv/provider/keeper/partial_set_security.go +++ b/x/ccv/provider/keeper/partial_set_security.go @@ -165,7 +165,7 @@ func (k Keeper) ComputeMinPowerInTopN(ctx sdk.Context, bondedValidators []stakin // CapValidatorSet caps the provided `validators` if chain `chainID` is an Opt In chain with a validator-set cap. If cap // is `k`, `CapValidatorSet` returns the first `k` validators from `validators` with the highest power. -func (k Keeper) CapValidatorSet(ctx sdk.Context, chainID string, validators []types.ConsumerValidator) []types.ConsumerValidator { +func (k Keeper) CapValidatorSet(ctx sdk.Context, chainID string, validators []types.ConsensusValidator) []types.ConsensusValidator { if topN, found := k.GetTopN(ctx, chainID); found && topN > 0 { // is a no-op if the chain is a Top N chain return validators @@ -186,7 +186,7 @@ func (k Keeper) CapValidatorSet(ctx sdk.Context, chainID string, validators []ty // with their new powers. Works on a best-basis effort because there are cases where we cannot guarantee that all validators // on the consumer chain have less power than the set validators-power cap. For example, if we have 10 validators and // the power cap is set to 5%, we need at least one validator to have more than 10% of the voting power on the consumer chain. -func (k Keeper) CapValidatorsPower(ctx sdk.Context, chainID string, validators []types.ConsumerValidator) []types.ConsumerValidator { +func (k Keeper) CapValidatorsPower(ctx sdk.Context, chainID string, validators []types.ConsensusValidator) []types.ConsensusValidator { if p, found := k.GetValidatorsPowerCap(ctx, chainID); found && p > 0 { return NoMoreThanPercentOfTheSum(validators, p) } else { @@ -196,7 +196,7 @@ func (k Keeper) CapValidatorsPower(ctx sdk.Context, chainID string, validators [ } // sum is a helper function to sum all the validators' power -func sum(validators []types.ConsumerValidator) int64 { +func sum(validators []types.ConsensusValidator) int64 { s := int64(0) for _, v := range validators { s = s + v.Power @@ -206,7 +206,7 @@ func sum(validators []types.ConsumerValidator) int64 { // NoMoreThanPercentOfTheSum returns a set of validators with updated powers such that no validator has more than the // provided `percent` of the sum of all validators' powers. Operates on a best-effort basis. -func NoMoreThanPercentOfTheSum(validators []types.ConsumerValidator, percent uint32) []types.ConsumerValidator { +func NoMoreThanPercentOfTheSum(validators []types.ConsensusValidator, percent uint32) []types.ConsensusValidator { // Algorithm's idea // ---------------- // Consider the validators' powers to be `a_1, a_2, ... a_n` and `p` to be the percent in [1, 100]. Now, consider @@ -262,7 +262,7 @@ func NoMoreThanPercentOfTheSum(validators []types.ConsumerValidator, percent uin } } - updatedValidators := make([]types.ConsumerValidator, len(validators)) + updatedValidators := make([]types.ConsensusValidator, len(validators)) powerPerValidator := int64(0) remainingValidators := int64(validatorsWithPowerLessThanMaxPower) @@ -308,11 +308,62 @@ func (k Keeper) CanValidateChain(ctx sdk.Context, chainID string, providerAddr t !k.IsDenylisted(ctx, chainID, providerAddr)) } +// FulfillsMinStake returns true if the validator `providerAddr` has enough stake to validate chain `chainID` +// by checking its staked tokens against the minimum stake required to validate the chain. +func (k Keeper) FulfillsMinStake(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) bool { + minStake, found := k.GetMinStake(ctx, chainID) + if !found { + return true + } + + validator, err := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.Address) + if err != nil { + return false + } + + // validator has enough stake to validate the chain + return validator.GetTokens().GTE(math.NewIntFromUint64(minStake)) +} + // ComputeNextValidators computes the validators for the upcoming epoch based on the currently `bondedValidators`. -func (k Keeper) ComputeNextValidators(ctx sdk.Context, chainID string, bondedValidators []stakingtypes.Validator) []types.ConsumerValidator { +func (k Keeper) ComputeNextValidators(ctx sdk.Context, chainID string, bondedValidators []stakingtypes.Validator) []types.ConsensusValidator { + // sort the bonded validators by number of staked tokens in descending order + sort.Slice(bondedValidators, func(i, j int) bool { + return bondedValidators[i].GetTokens().GT(bondedValidators[j].GetTokens()) + }) + + // if inactive validators are not allowed, only consider the first `MaxProviderConsensusValidators` validators + // since those are the ones that participate in consensus + allowInactiveVals, found := k.GetAllowInactiveValidators(ctx, chainID) + if !allowInactiveVals || !found { + // only leave the first MaxProviderConsensusValidators bonded validators + maxProviderConsensusVals := k.GetMaxProviderConsensusValidators(ctx) + if len(bondedValidators) > int(maxProviderConsensusVals) { + bondedValidators = bondedValidators[:maxProviderConsensusVals] + } + } + + // take only the first `MaxValidatorRank` many validators; others are not allowed to validate + maxRank, found := k.GetMaxValidatorRank(ctx, chainID) + if found && maxRank > 0 && int(maxRank) < len(bondedValidators) { + tmpValidators := bondedValidators[:maxRank] + + // also include other validators that have the same number of tokens as the last validator in the list + for i := int(maxRank); i < len(bondedValidators); i++ { + // if the validator has the same number of tokens as the last validator in the list, include it + if bondedValidators[i].GetTokens().Equal(bondedValidators[maxRank-1].GetTokens()) { + tmpValidators = append(tmpValidators, bondedValidators[i]) + } else { + // since validators are sorted, we can break if we get to a validator with less tokens + break + } + } + bondedValidators = tmpValidators + } + nextValidators := k.FilterValidators(ctx, chainID, bondedValidators, func(providerAddr types.ProviderConsAddress) bool { - return k.CanValidateChain(ctx, chainID, providerAddr) + return k.CanValidateChain(ctx, chainID, providerAddr) && k.FulfillsMinStake(ctx, chainID, providerAddr) }) nextValidators = k.CapValidatorSet(ctx, chainID, nextValidators) diff --git a/x/ccv/provider/keeper/partial_set_security_test.go b/x/ccv/provider/keeper/partial_set_security_test.go index 2dfbfd3b82..e015af850d 100644 --- a/x/ccv/provider/keeper/partial_set_security_test.go +++ b/x/ccv/provider/keeper/partial_set_security_test.go @@ -389,24 +389,24 @@ func TestCapValidatorSet(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - validatorA := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddrA"), - Power: 1, - ConsumerPublicKey: &crypto.PublicKey{}, + validatorA := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddrA"), + Power: 1, + PublicKey: &crypto.PublicKey{}, } - validatorB := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddrB"), - Power: 2, - ConsumerPublicKey: &crypto.PublicKey{}, + validatorB := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddrB"), + Power: 2, + PublicKey: &crypto.PublicKey{}, } - validatorC := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddrC"), - Power: 3, - ConsumerPublicKey: &crypto.PublicKey{}, + validatorC := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddrC"), + Power: 3, + PublicKey: &crypto.PublicKey{}, } - validators := []types.ConsumerValidator{validatorA, validatorB, validatorC} + validators := []types.ConsensusValidator{validatorA, validatorB, validatorC} consumerValidators := providerKeeper.CapValidatorSet(ctx, "chainID", validators) require.Equal(t, validators, consumerValidators) @@ -421,55 +421,55 @@ func TestCapValidatorSet(t *testing.T) { providerKeeper.SetValidatorSetCap(ctx, "chainID", 1) consumerValidators = providerKeeper.CapValidatorSet(ctx, "chainID", validators) - require.Equal(t, []types.ConsumerValidator{validatorC}, consumerValidators) + require.Equal(t, []types.ConsensusValidator{validatorC}, consumerValidators) providerKeeper.SetValidatorSetCap(ctx, "chainID", 2) consumerValidators = providerKeeper.CapValidatorSet(ctx, "chainID", validators) - require.Equal(t, []types.ConsumerValidator{validatorC, validatorB}, consumerValidators) + require.Equal(t, []types.ConsensusValidator{validatorC, validatorB}, consumerValidators) providerKeeper.SetValidatorSetCap(ctx, "chainID", 3) consumerValidators = providerKeeper.CapValidatorSet(ctx, "chainID", validators) - require.Equal(t, []types.ConsumerValidator{validatorC, validatorB, validatorA}, consumerValidators) + require.Equal(t, []types.ConsensusValidator{validatorC, validatorB, validatorA}, consumerValidators) } func TestCapValidatorsPower(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - validatorA := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddrA"), - Power: 1, - ConsumerPublicKey: &crypto.PublicKey{}, + validatorA := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddrA"), + Power: 1, + PublicKey: &crypto.PublicKey{}, } - validatorB := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddrB"), - Power: 2, - ConsumerPublicKey: &crypto.PublicKey{}, + validatorB := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddrB"), + Power: 2, + PublicKey: &crypto.PublicKey{}, } - validatorC := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddrC"), - Power: 3, - ConsumerPublicKey: &crypto.PublicKey{}, + validatorC := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddrC"), + Power: 3, + PublicKey: &crypto.PublicKey{}, } - validatorD := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddrD"), - Power: 4, - ConsumerPublicKey: &crypto.PublicKey{}, + validatorD := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddrD"), + Power: 4, + PublicKey: &crypto.PublicKey{}, } - validators := []types.ConsumerValidator{validatorA, validatorB, validatorC, validatorD} + validators := []types.ConsensusValidator{validatorA, validatorB, validatorC, validatorD} - expectedValidators := make([]types.ConsumerValidator, len(validators)) + expectedValidators := make([]types.ConsensusValidator, len(validators)) copy(expectedValidators, validators) expectedValidators[0].Power = 2 expectedValidators[1].Power = 2 expectedValidators[2].Power = 3 expectedValidators[3].Power = 3 - sortValidators := func(validators []types.ConsumerValidator) { + sortValidators := func(validators []types.ConsensusValidator) { sort.Slice(validators, func(i, j int) bool { return bytes.Compare(validators[i].ProviderConsAddr, validators[j].ProviderConsAddr) < 0 }) @@ -523,13 +523,13 @@ func TestNoMoreThanPercentOfTheSum(t *testing.T) { require.True(t, noMoreThanPercent(keeper.NoMoreThanPercentOfTheSum(createConsumerValidators(powers), percent), percent)) } -func createConsumerValidators(powers []int64) []types.ConsumerValidator { - var validators []types.ConsumerValidator +func createConsumerValidators(powers []int64) []types.ConsensusValidator { + var validators []types.ConsensusValidator for _, p := range powers { - validators = append(validators, types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddr"), - Power: p, - ConsumerPublicKey: &crypto.PublicKey{}, + validators = append(validators, types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr"), + Power: p, + PublicKey: &crypto.PublicKey{}, }) } return validators @@ -537,7 +537,7 @@ func createConsumerValidators(powers []int64) []types.ConsumerValidator { // returns `true` if no validator in `validators` corresponds to more than `percent` of the total sum of all // validators' powers -func noMoreThanPercent(validators []types.ConsumerValidator, percent uint32) bool { +func noMoreThanPercent(validators []types.ConsensusValidator, percent uint32) bool { sum := int64(0) for _, v := range validators { sum = sum + v.Power @@ -551,7 +551,7 @@ func noMoreThanPercent(validators []types.ConsumerValidator, percent uint32) boo return true } -func sumPowers(vals []types.ConsumerValidator) int64 { +func sumPowers(vals []types.ConsensusValidator) int64 { sum := int64(0) for _, v := range vals { sum += v.Power @@ -559,7 +559,7 @@ func sumPowers(vals []types.ConsumerValidator) int64 { return sum } -func CapSatisfiable(vals []types.ConsumerValidator, percent uint32) bool { +func CapSatisfiable(vals []types.ConsensusValidator, percent uint32) bool { // 100 / len(vals) is what each validator gets if each has the same power. // if this is more than the cap, it cannot be satisfied. return float64(100)/float64(len(vals)) < float64(percent) @@ -569,14 +569,14 @@ func TestNoMoreThanPercentOfTheSumProps(t *testing.T) { // define properties to test // capRespectedIfSatisfiable: if the cap can be respected, then it will be respected - capRespectedIfSatisfiable := func(valsBefore, valsAfter []types.ConsumerValidator, percent uint32) bool { + capRespectedIfSatisfiable := func(valsBefore, valsAfter []types.ConsensusValidator, percent uint32) bool { if CapSatisfiable(valsBefore, percent) { return noMoreThanPercent(valsAfter, percent) } return true } - evenPowersIfCapCannotBeSatisfied := func(valsBefore, valsAfter []types.ConsumerValidator, percent uint32) bool { + evenPowersIfCapCannotBeSatisfied := func(valsBefore, valsAfter []types.ConsensusValidator, percent uint32) bool { if !CapSatisfiable(valsBefore, percent) { // if the cap cannot be satisfied, each validator should have the same power for _, valAfter := range valsAfter { @@ -590,7 +590,7 @@ func TestNoMoreThanPercentOfTheSumProps(t *testing.T) { // fairness: if before, v1 has more power than v2, then afterwards v1 will not have less power than v2 // (they might get the same power if they are both capped) - fairness := func(valsBefore, valsAfter []types.ConsumerValidator) bool { + fairness := func(valsBefore, valsAfter []types.ConsensusValidator) bool { for i, v := range valsBefore { // find the validator after with the same address vAfter := findConsumerValidator(t, v, valsAfter) @@ -619,7 +619,7 @@ func TestNoMoreThanPercentOfTheSumProps(t *testing.T) { } // non-zero: v has non-zero power before IFF it has non-zero power after - nonZero := func(valsBefore, valsAfter []types.ConsumerValidator) bool { + nonZero := func(valsBefore, valsAfter []types.ConsensusValidator) bool { for _, v := range valsBefore { vAfter := findConsumerValidator(t, v, valsAfter) if (v.Power == 0) != (vAfter.Power == 0) { @@ -631,7 +631,7 @@ func TestNoMoreThanPercentOfTheSumProps(t *testing.T) { // equalSumIfCapSatisfiable: the sum of the powers of the validators will not change if the cap can be satisfied // (except for small changes by rounding errors) - equalSumIfCapSatisfiable := func(valsBefore, valsAfter []types.ConsumerValidator, percent uint32) bool { + equalSumIfCapSatisfiable := func(valsBefore, valsAfter []types.ConsensusValidator, percent uint32) bool { if CapSatisfiable(valsBefore, percent) { difference := gomath.Abs(float64(sumPowers(valsBefore) - sumPowers(valsAfter))) if difference > 1 { @@ -643,7 +643,7 @@ func TestNoMoreThanPercentOfTheSumProps(t *testing.T) { } // num validators: the number of validators will not change - equalNumVals := func(valsBefore, valsAfter []types.ConsumerValidator) bool { + equalNumVals := func(valsBefore, valsAfter []types.ConsensusValidator) bool { return len(valsBefore) == len(valsAfter) } @@ -669,8 +669,8 @@ func TestNoMoreThanPercentOfTheSumProps(t *testing.T) { }) } -func findConsumerValidator(t *testing.T, v types.ConsumerValidator, valsAfter []types.ConsumerValidator) *types.ConsumerValidator { - var vAfter *types.ConsumerValidator +func findConsumerValidator(t *testing.T, v types.ConsensusValidator, valsAfter []types.ConsensusValidator) *types.ConsensusValidator { + var vAfter *types.ConsensusValidator for _, vA := range valsAfter { if bytes.Equal(v.ProviderConsAddr, vA.ProviderConsAddr) { vAfter = &vA @@ -682,3 +682,195 @@ func findConsumerValidator(t *testing.T, v types.ConsumerValidator, valsAfter [] } return vAfter } + +func createStakingValidatorsAndMocks(ctx sdk.Context, mocks testkeeper.MockedKeepers, powers ...int64) ([]stakingtypes.Validator, []types.ProviderConsAddress) { + var validators []stakingtypes.Validator + for i, power := range powers { + val := createStakingValidator(ctx, mocks, i, power, i) + val.Tokens = math.NewInt(power) + validators = append(validators, val) + } + + var consAddrs []types.ProviderConsAddress + for _, val := range validators { + consAddr, err := val.GetConsAddr() + if err != nil { + panic(err) + } + consAddrs = append(consAddrs, types.NewProviderConsAddress(consAddr)) + } + // set up mocks + for index, val := range validators { + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, consAddrs[index].Address).Return(val, nil).AnyTimes() + } + + return validators, consAddrs +} + +// TestMinStake checks that FulfillsMinStake returns true if the validator has more than the min stake +// and false otherwise +func TestMinStake(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // create two validators with powers 1 and 2 + _, consAddrs := createStakingValidatorsAndMocks(ctx, mocks, 1, 2) + + testCases := []struct { + name string + minStake uint64 + expectedFulfil []bool + }{ + { + name: "No min stake", + minStake: 0, + expectedFulfil: []bool{true, true}, + }, + { + name: "Min stake set to 2", + minStake: 2, + expectedFulfil: []bool{false, true}, + }, + { + name: "Min stake set to 3", + minStake: 3, + expectedFulfil: []bool{false, false}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + providerKeeper.SetMinStake(ctx, "chainID", tc.minStake) + for i, valAddr := range consAddrs { + result := providerKeeper.FulfillsMinStake(ctx, "chainID", valAddr) + require.Equal(t, tc.expectedFulfil[i], result) + } + }) + } +} + +func TestMaxValidatorRank(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // create validators + vals, consAddrs := createStakingValidatorsAndMocks(ctx, mocks, 1, 2, 3, 4, 5, 5) + + // opt the validators in + for _, valAddr := range consAddrs { + providerKeeper.SetOptedIn(ctx, "chainID", valAddr) + } + + testCases := []struct { + name string + maxRank uint32 + expectedProviderConsAddrs []types.ProviderConsAddress + }{ + { + name: "No max rank", + maxRank: 0, + expectedProviderConsAddrs: consAddrs, + }, + { + name: "Max rank set to 1", + maxRank: 1, + // include the last two validators which each have 5 power + expectedProviderConsAddrs: consAddrs[len(consAddrs)-2:], + }, + { + name: "Max rank set to 2", + maxRank: 2, + // still only include the last two validators + expectedProviderConsAddrs: consAddrs[len(consAddrs)-2:], + }, + { + name: "Max rank set to 3", + maxRank: 3, + // now include the third to last validator as well + expectedProviderConsAddrs: consAddrs[len(consAddrs)-3:], + }, + { + name: "Max rank set to 6", + maxRank: 6, + expectedProviderConsAddrs: consAddrs, + }, + { + name: "Max rank set to 10", + maxRank: 10, + expectedProviderConsAddrs: consAddrs, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + providerKeeper.SetMaxValidatorRank(ctx, "chainID", tc.maxRank) + + // set max provider consensus vals to include all validators + params := providerKeeper.GetParams(ctx) + params.MaxProviderConsensusValidators = 180 + providerKeeper.SetParams(ctx, params) + + nextVals := providerKeeper.ComputeNextValidators(ctx, "chainID", vals) + nextConsAddrs := make([]types.ProviderConsAddress, len(nextVals)) + for i, val := range nextVals { + nextConsAddrs[i] = types.NewProviderConsAddress(val.ProviderConsAddr) + } + // check that the expected validators are the same as the next validator set, disregarding ordering + require.ElementsMatch(t, tc.expectedProviderConsAddrs, nextConsAddrs) + }) + } +} + +// TestMaxProviderConsensusValidators checks that the number of validators in the next validator set is at most +// the maxProviderConsensusValidators parameter if the consumer chain does not allow inactive validators to validate. +func TestIfInactiveValsDisallowedProperty(t *testing.T) { + rapid.Check(t, func(r *rapid.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // Generate a random number of validators with random powers + valPowers := rapid.SliceOfN(rapid.Int64Range(1, 100), 1, 100).Draw(r, "valPowers") + vals, consAddrs := createStakingValidatorsAndMocks(ctx, mocks, valPowers...) + + // opt the validators in + for _, valAddr := range consAddrs { + providerKeeper.SetOptedIn(ctx, "chainID", valAddr) + } + + // Randomly choose values for parameters + minStake := rapid.Uint64Range(0, 101).Draw(r, "minStake") + maxRank := rapid.Uint32Range(0, 11).Draw(r, "maxRank") + maxProviderConsensusVals := rapid.Uint32Range(1, 11).Draw(r, "maxProviderConsensusVals") + + // Set up the parameters in the provider keeper + providerKeeper.SetAllowInactiveValidators(ctx, "chainID", false) // do not allow inactive validators + providerKeeper.SetMinStake(ctx, "chainID", minStake) + providerKeeper.SetMaxValidatorRank(ctx, "chainID", maxRank) + params := providerKeeper.GetParams(ctx) + params.MaxProviderConsensusValidators = int64(maxProviderConsensusVals) + providerKeeper.SetParams(ctx, params) + + // Compute the next validators + nextVals := providerKeeper.ComputeNextValidators(ctx, "chainID", vals) + + // Check that the length of nextVals is at most maxProviderConsensusVals + require.LessOrEqual(t, len(nextVals), int(maxProviderConsensusVals), "The length of nextVals should be at most maxProviderConsensusVals") + + // Sanity check: we only get 0 next validators if either: + // - maxProviderConsensusVals is 0 + // - the maximal validator power is less than the min stake + if len(nextVals) == 0 { + maxValPower := int64(0) + for _, power := range valPowers { + if power > maxValPower { + maxValPower = power + } + } + require.True( + t, + maxProviderConsensusVals == 0 || maxValPower < int64(minStake), + "The length of nextVals should only be 0 if either maxProviderConsensusVals is 0 or the maximal validator power is less than the min stake", + ) + } + }) +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 887251c87a..e1da24de18 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -78,6 +78,9 @@ func (k Keeper) HandleConsumerModificationProposal(ctx sdk.Context, proposal *ty ValidatorSetCap: proposal.ValidatorSetCap, Allowlist: proposal.Allowlist, Denylist: proposal.Denylist, + MinStake: proposal.MinStake, + MaxRank: proposal.MaxRank, + AllowInactiveVals: proposal.AllowInactiveVals, } return k.HandleLegacyConsumerModificationProposal(ctx, &p) @@ -238,6 +241,9 @@ func (k Keeper) StopConsumerChain(ctx sdk.Context, chainID string, closeChan boo k.DeleteValidatorSetCap(ctx, chainID) k.DeleteAllowlist(ctx, chainID) k.DeleteDenylist(ctx, chainID) + k.DeleteMaxValidatorRank(ctx, chainID) + k.DeleteAllowInactiveValidators(ctx, chainID) + k.DeleteMinStake(ctx, chainID) k.DeleteAllOptedIn(ctx, chainID) k.DeleteConsumerValSet(ctx, chainID) @@ -284,20 +290,26 @@ func (k Keeper) MakeConsumerGenesis( } if prop.Top_N > 0 { + // get the consensus active validators + activeValidators, err := k.GetLastActiveBondedValidators(ctx) + if err != nil { + return gen, nil, errorsmod.Wrapf(stakingtypes.ErrNoValidatorFound, "error getting last active bonded validators: %s", err) + } // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerInTopN(ctx, bondedValidators, prop.Top_N) + minPower, err := k.ComputeMinPowerInTopN(ctx, activeValidators, prop.Top_N) if err != nil { return gen, nil, err } - k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) + k.OptInTopNValidators(ctx, chainID, activeValidators, minPower) k.SetMinimumPowerInTopN(ctx, chainID, minPower) } + // need to use the bondedValidators, not activeValidators, here since the chain might be opt-in and allow inactive vals nextValidators := k.ComputeNextValidators(ctx, chainID, bondedValidators) k.SetConsumerValSet(ctx, chainID, nextValidators) // get the initial updates with the latest set consumer public keys - initialUpdatesWithConsumerKeys := DiffValidators([]types.ConsumerValidator{}, nextValidators) + initialUpdatesWithConsumerKeys := DiffValidators([]types.ConsensusValidator{}, nextValidators) // Get a hash of the consumer validator set from the update with applied consumer assigned keys updatesAsValSet, err := tmtypes.PB2TM.ValidatorUpdates(initialUpdatesWithConsumerKeys) @@ -384,6 +396,9 @@ func (k Keeper) BeginBlockInit(ctx sdk.Context) { k.SetTopN(cachedCtx, prop.ChainId, prop.Top_N) k.SetValidatorSetCap(cachedCtx, prop.ChainId, prop.ValidatorSetCap) k.SetValidatorsPowerCap(cachedCtx, prop.ChainId, prop.ValidatorsPowerCap) + k.SetMinStake(cachedCtx, prop.ChainId, prop.MinStake) + k.SetMaxValidatorRank(cachedCtx, prop.ChainId, prop.MaxRank) + k.SetAllowInactiveValidators(cachedCtx, prop.ChainId, prop.AllowInactiveVals) for _, address := range prop.Allowlist { consAddr, err := sdk.ConsAddressFromBech32(address) diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index 650179c274..a06a40e809 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -73,6 +73,9 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), blockTime: now, expAppendProp: true, @@ -103,6 +106,9 @@ func TestHandleConsumerAdditionProposal(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), blockTime: now, expAppendProp: false, @@ -809,6 +815,9 @@ func TestBeginBlockInit(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "spawn time passed", "chain2", clienttypes.NewHeight(3, 4), []byte{}, []byte{}, @@ -825,6 +834,9 @@ func TestBeginBlockInit(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "spawn time not passed", "chain3", clienttypes.NewHeight(3, 4), []byte{}, []byte{}, @@ -841,6 +853,9 @@ func TestBeginBlockInit(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "invalid proposal: chain id already exists", "chain2", clienttypes.NewHeight(4, 5), []byte{}, []byte{}, @@ -857,6 +872,9 @@ func TestBeginBlockInit(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "opt-in chain with at least one validator opted in", "chain5", clienttypes.NewHeight(3, 4), []byte{}, []byte{}, @@ -873,6 +891,9 @@ func TestBeginBlockInit(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), providertypes.NewConsumerAdditionProposal( "title", "opt-in chain with no validator opted in", "chain6", clienttypes.NewHeight(3, 4), []byte{}, []byte{}, @@ -889,6 +910,9 @@ func TestBeginBlockInit(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ).(*providertypes.ConsumerAdditionProposal), } @@ -914,6 +938,10 @@ func TestBeginBlockInit(t *testing.T) { valAddr, _ := sdk.ValAddressFromBech32(validator.GetOperator()) mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower(gomock.Any(), valAddr).Return(int64(1), nil).AnyTimes() + + // for each validator, expect a call to GetValidatorByConsAddr with its consensus address + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(gomock.Any(), consAddr).Return(validator, nil).AnyTimes() + providerKeeper.SetOptedIn(ctx, pendingProps[4].ChainId, providertypes.NewProviderConsAddress(consAddr)) providerKeeper.BeginBlockInit(ctx) diff --git a/x/ccv/provider/keeper/provider_consensus.go b/x/ccv/provider/keeper/provider_consensus.go new file mode 100644 index 0000000000..462c4d4ae3 --- /dev/null +++ b/x/ccv/provider/keeper/provider_consensus.go @@ -0,0 +1,87 @@ +package keeper + +import ( + "fmt" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" +) + +// SetLastProviderConsensusValidator sets the given validator to be stored +// as part of the last provider consensus validator set +func (k Keeper) SetLastProviderConsensusValidator( + ctx sdk.Context, + validator types.ConsensusValidator, +) { + k.setValidator(ctx, []byte{types.LastProviderConsensusValsPrefix}, validator) +} + +// SetLastProviderConsensusValSet resets the stored last validator set sent to the consensus engine on the provider +// to the provided `nextValidators“. +func (k Keeper) SetLastProviderConsensusValSet(ctx sdk.Context, nextValidators []types.ConsensusValidator) { + k.setValSet(ctx, []byte{types.LastProviderConsensusValsPrefix}, nextValidators) +} + +// DeleteLastProviderConsensusValidator removes the validator with `providerConsAddr` address +// from the stored last provider consensus validator set +func (k Keeper) DeleteLastProviderConsensusValidator( + ctx sdk.Context, + providerConsAddr types.ProviderConsAddress, +) { + k.deleteValidator(ctx, []byte{types.LastProviderConsensusValsPrefix}, providerConsAddr) +} + +// DeleteLastProviderConsensusValSet deletes all the stored validators from the +// last provider consensus validator set +func (k Keeper) DeleteLastProviderConsensusValSet( + ctx sdk.Context, +) { + k.deleteValSet(ctx, []byte{types.LastProviderConsensusValsPrefix}) +} + +// GetLastProviderConsensusValSet returns the last stored +// validator set sent to the consensus engine on the provider +func (k Keeper) GetLastProviderConsensusValSet( + ctx sdk.Context, +) []types.ConsensusValidator { + return k.getValSet(ctx, []byte{types.LastProviderConsensusValsPrefix}) +} + +// GetLastTotalProviderConsensusPower returns the total power of the last stored +// validator set sent to the consensus engine on the provider +func (k Keeper) GetLastTotalProviderConsensusPower( + ctx sdk.Context, +) math.Int { + return k.getTotalPower(ctx, []byte{types.LastProviderConsensusValsPrefix}) +} + +// CreateProviderConsensusValidator creates a new ConsensusValidator from the given staking validator +func (k Keeper) CreateProviderConsensusValidator(ctx sdk.Context, val stakingtypes.Validator) (types.ConsensusValidator, error) { + consAddr, err := val.GetConsAddr() + if err != nil { + return types.ConsensusValidator{}, fmt.Errorf("getting consensus address: %w", err) + } + pubKey, err := val.TmConsPublicKey() + if err != nil { + return types.ConsensusValidator{}, fmt.Errorf("getting consensus public key: %w", err) + } + valAddr, err := sdk.ValAddressFromBech32(val.GetOperator()) + if err != nil { + return types.ConsensusValidator{}, fmt.Errorf("getting validator address: %w", err) + } + + power, err := k.stakingKeeper.GetLastValidatorPower(ctx, valAddr) + if err != nil { + return types.ConsensusValidator{}, fmt.Errorf("getting validator power: %w", err) + } + + return types.ConsensusValidator{ + ProviderConsAddr: consAddr, + PublicKey: &pubKey, + Power: power, + }, nil +} diff --git a/x/ccv/provider/keeper/provider_consensus_test.go b/x/ccv/provider/keeper/provider_consensus_test.go new file mode 100644 index 0000000000..329fd10f13 --- /dev/null +++ b/x/ccv/provider/keeper/provider_consensus_test.go @@ -0,0 +1,124 @@ +package keeper_test + +import ( + "testing" + + "cosmossdk.io/math" + "github.com/cometbft/cometbft/proto/tendermint/crypto" + "github.com/stretchr/testify/require" + + testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper" + "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" +) + +func TestSetLastProviderConsensusValidator(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr"), + Power: 2, + PublicKey: &crypto.PublicKey{}, + } + + providerKeeper.SetLastProviderConsensusValidator(ctx, validator) + + // Retrieve the stored validator + storedValidator := providerKeeper.GetLastProviderConsensusValSet(ctx)[0] + + require.Equal(t, validator, storedValidator, "stored validator does not match") +} + +func TestSetLastProviderConsensusValSet(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator1 := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr1"), + Power: 2, + PublicKey: &crypto.PublicKey{}, + } + + validator2 := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr2"), + Power: 3, + PublicKey: &crypto.PublicKey{}, + } + + nextValidators := []types.ConsensusValidator{validator1, validator2} + + providerKeeper.SetLastProviderConsensusValSet(ctx, nextValidators) + + // Retrieve the stored validator set + storedValidators := providerKeeper.GetLastProviderConsensusValSet(ctx) + require.Equal(t, nextValidators, storedValidators, "stored validator set does not match") +} + +func TestDeleteLastProviderConsensusValidator(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr"), + Power: 2, + PublicKey: &crypto.PublicKey{}, + } + + providerKeeper.SetLastProviderConsensusValidator(ctx, validator) + + // Delete the stored validator + providerKeeper.DeleteLastProviderConsensusValidator(ctx, types.NewProviderConsAddress(validator.ProviderConsAddr)) + + // Ensure the validator is deleted + storedValidators := providerKeeper.GetLastProviderConsensusValSet(ctx) + require.Empty(t, storedValidators, "validator set should be empty") +} + +func TestDeleteLastProviderConsensusValSet(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + validator1 := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr1"), + Power: 2, + PublicKey: &crypto.PublicKey{}, + } + + validator2 := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr2"), + Power: 3, + PublicKey: &crypto.PublicKey{}, + } + + nextValidators := []types.ConsensusValidator{validator1, validator2} + + providerKeeper.SetLastProviderConsensusValSet(ctx, nextValidators) + + // Delete the stored validator set + providerKeeper.DeleteLastProviderConsensusValSet(ctx) + + // Ensure the validator set is empty + storedValidators := providerKeeper.GetLastProviderConsensusValSet(ctx) + require.Empty(t, storedValidators, "validator set should be empty") +} + +func TestGetLastTotalProviderConsensusPower(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + validator1 := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr1"), + Power: 2, + PublicKey: &crypto.PublicKey{}, + } + validator2 := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr2"), + Power: 3, + PublicKey: &crypto.PublicKey{}, + } + nextValidators := []types.ConsensusValidator{validator1, validator2} + providerKeeper.SetLastProviderConsensusValSet(ctx, nextValidators) + // Get the total power of the last stored validator set + totalPower := providerKeeper.GetLastTotalProviderConsensusPower(ctx) + expectedTotalPower := math.NewInt(5) + require.Equal(t, expectedTotalPower, totalPower, "total power does not match") +} diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 6373e6a7ce..a0544fb5e2 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -13,6 +13,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v5/x/ccv/types" ) @@ -145,7 +148,13 @@ func (k Keeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet) err // EndBlockVSU contains the EndBlock logic needed for // the Validator Set Update sub-protocol -func (k Keeper) EndBlockVSU(ctx sdk.Context) { +func (k Keeper) EndBlockVSU(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { + // logic to update the provider consensus validator set. + // Important: must be called before the rest of EndBlockVSU, because + // we need to know the updated provider validator set + // to compute the minimum power in the top N + valUpdates := k.ProviderValidatorUpdates(ctx) + // notify the staking module to complete all matured unbonding ops k.completeMaturedUnbondingOps(ctx) @@ -160,6 +169,46 @@ func (k Keeper) EndBlockVSU(ctx sdk.Context) { // the updates will remain queued until the channel is established k.SendVSCPackets(ctx) } + + return valUpdates, nil +} + +// ProviderValidatorUpdates returns changes in the provider consensus validator set +// from the last block to the current one. +// It retrieves the bonded validators from the staking module and creates a `ConsumerValidator` object for each validator. +// The maximum number of validators is determined by the `maxValidators` parameter. +// The function returns the difference between the current validator set and the next validator set as a list of `abci.ValidatorUpdate` objects. +func (k Keeper) ProviderValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate { + // get the bonded validators from the staking module + bondedValidators, err := k.stakingKeeper.GetBondedValidatorsByPower(ctx) + if err != nil { + panic(fmt.Errorf("failed to get bonded validators: %w", err)) + } + + // get the last validator set sent to consensus + currentValidators := k.GetLastProviderConsensusValSet(ctx) + + nextValidators := []types.ConsensusValidator{} + maxValidators := k.GetMaxProviderConsensusValidators(ctx) + // avoid out of range errors by bounding the max validators to the number of bonded validators + if maxValidators > int64(len(bondedValidators)) { + maxValidators = int64(len(bondedValidators)) + } + for _, val := range bondedValidators[:maxValidators] { + nextValidator, err := k.CreateProviderConsensusValidator(ctx, val) + if err != nil { + k.Logger(ctx).Error("error when creating provider consensus validator", "error", err) + continue + } + nextValidators = append(nextValidators, nextValidator) + } + + // store the validator set we will send to consensus + k.SetLastProviderConsensusValSet(ctx, nextValidators) + + valUpdates := DiffValidators(currentValidators, nextValidators) + + return valUpdates } // SendVSCPackets iterates over all registered consumers and sends pending @@ -233,12 +282,18 @@ func (k Keeper) QueueVSCPackets(ctx sdk.Context) { if topN > 0 { // in a Top-N chain, we automatically opt in all validators that belong to the top N - minPower, err := k.ComputeMinPowerInTopN(ctx, bondedValidators, topN) + // of the active validators + activeValidators, err := k.GetLastActiveBondedValidators(ctx) + if err != nil { + // we just log here and do not panic because panic-ing would halt the provider chain + k.Logger(ctx).Error("failed to get active validators", "error", err) + } + minPower, err := k.ComputeMinPowerInTopN(ctx, activeValidators, topN) if err == nil { // set the minimal power of validators in the top N in the store k.SetMinimumPowerInTopN(ctx, chainID, minPower) - k.OptInTopNValidators(ctx, chainID, bondedValidators, minPower) + k.OptInTopNValidators(ctx, chainID, activeValidators, minPower) } else { // we just log here and do not panic because panic-ing would halt the provider chain k.Logger(ctx).Error("failed to compute min power to opt in for chain", "chain", chainID, "error", err) diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index 59626f7038..1cb48df6ad 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "sort" "strings" "testing" @@ -124,7 +125,7 @@ func TestOnRecvDowntimeSlashPacket(t *testing.T) { providerKeeper.SetValsetUpdateBlockHeight(ctx, packetData.ValsetUpdateId, uint64(15)) // Set consumer validator - providerKeeper.SetConsumerValidator(ctx, "chain-1", providertypes.ConsumerValidator{ + providerKeeper.SetConsumerValidator(ctx, "chain-1", providertypes.ConsensusValidator{ ProviderConsAddr: packetData.Validator.Address, }) @@ -135,7 +136,7 @@ func TestOnRecvDowntimeSlashPacket(t *testing.T) { require.NoError(t, err) // Set consumer validator - providerKeeper.SetConsumerValidator(ctx, "chain-2", providertypes.ConsumerValidator{ + providerKeeper.SetConsumerValidator(ctx, "chain-2", providertypes.ConsensusValidator{ ProviderConsAddr: packetData.Validator.Address, }) @@ -148,7 +149,7 @@ func TestOnRecvDowntimeSlashPacket(t *testing.T) { providerKeeper.SetSlashMeter(ctx, math.NewInt(5)) // Set the consumer validator - providerKeeper.SetConsumerValidator(ctx, "chain-1", providertypes.ConsumerValidator{ProviderConsAddr: packetData.Validator.Address}) + providerKeeper.SetConsumerValidator(ctx, "chain-1", providertypes.ConsensusValidator{ProviderConsAddr: packetData.Validator.Address}) // Mock call to GetEffectiveValPower, so that it returns 2. providerAddr := providertypes.NewProviderConsAddress(packetData.Validator.Address) @@ -442,7 +443,7 @@ func TestHandleSlashPacket(t *testing.T) { // Setup consumer address to provider address mapping. require.NotEmpty(t, tc.packetData.Validator.Address) providerKeeper.SetValidatorByConsumerAddr(ctx, chainId, consumerConsAddr, providerConsAddr) - providerKeeper.SetConsumerValidator(ctx, chainId, providertypes.ConsumerValidator{ProviderConsAddr: providerConsAddr.Address.Bytes()}) + providerKeeper.SetConsumerValidator(ctx, chainId, providertypes.ConsensusValidator{ProviderConsAddr: providerConsAddr.Address.Bytes()}) // Execute method and assert expected mock calls. providerKeeper.HandleSlashPacket(ctx, chainId, tc.packetData) @@ -502,7 +503,7 @@ func TestHandleVSCMaturedPacket(t *testing.T) { } // Opt-in one validator to consumer - pk.SetConsumerValidator(ctx, "chain-1", providertypes.ConsumerValidator{ProviderConsAddr: valsPk[0].Address()}) + pk.SetConsumerValidator(ctx, "chain-1", providertypes.ConsensusValidator{ProviderConsAddr: valsPk[0].Address()}) // Start second unbonding unbondingOpId = 2 @@ -538,8 +539,8 @@ func TestHandleVSCMaturedPacket(t *testing.T) { pk.SetConsumerClientId(ctx, "chain-2", "client-2") // Opt-in both validators to second consumer - pk.SetConsumerValidator(ctx, "chain-2", providertypes.ConsumerValidator{ProviderConsAddr: valsPk[0].Address()}) - pk.SetConsumerValidator(ctx, "chain-2", providertypes.ConsumerValidator{ProviderConsAddr: valsPk[1].Address()}) + pk.SetConsumerValidator(ctx, "chain-2", providertypes.ConsensusValidator{ProviderConsAddr: valsPk[0].Address()}) + pk.SetConsumerValidator(ctx, "chain-2", providertypes.ConsensusValidator{ProviderConsAddr: valsPk[1].Address()}) // Start third and fourth unbonding unbondingOpIds := []uint64{3, 4} @@ -773,6 +774,12 @@ func TestEndBlockVSU(t *testing.T) { testkeeper.SetupMocksForLastBondedValidatorsExpectation(mocks.MockStakingKeeper, 5, lastValidators, powers, -1) + sort.Slice(lastValidators, func(i, j int) bool { + return lastValidators[i].GetConsensusPower(sdk.DefaultPowerReduction) > + lastValidators[j].GetConsensusPower(sdk.DefaultPowerReduction) + }) + mocks.MockStakingKeeper.EXPECT().GetBondedValidatorsByPower(gomock.Any()).Return(lastValidators, nil).AnyTimes() + // set a sample client for a consumer chain so that `GetAllConsumerChains` in `QueueVSCPackets` iterates at least once providerKeeper.SetConsumerClientId(ctx, chainID, "clientID") @@ -845,6 +852,11 @@ func TestQueueVSCPacketsWithPowerCapping(t *testing.T) { // set a power-capping of 40% providerKeeper.SetValidatorsPowerCap(ctx, "chainID", 40) + // set max provider consensus vals to include all validators + params := providerKeeper.GetParams(ctx) + params.MaxProviderConsensusValidators = 180 + providerKeeper.SetParams(ctx, params) + providerKeeper.QueueVSCPackets(ctx) actualQueuedVSCPackets := providerKeeper.GetPendingVSCPackets(ctx, "chainID") diff --git a/x/ccv/provider/keeper/staking_keeper_interface.go b/x/ccv/provider/keeper/staking_keeper_interface.go new file mode 100644 index 0000000000..60786bb030 --- /dev/null +++ b/x/ccv/provider/keeper/staking_keeper_interface.go @@ -0,0 +1,80 @@ +package keeper + +import ( + "context" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// IterateBondedValidatorsByPower iterates over the consensus-active validators by power. +// The same as IterateBondedValidatorsByPower in the StakingKeeper, +// but only returns the first MaxProviderConsensusValidators validators. +// This is used to implement the interface of the staking keeper to interface with +// modules that need to reference the consensus-active validators. +func (k Keeper) IterateBondedValidatorsByPower(ctx context.Context, fn func(index int64, validator stakingtypes.ValidatorI) (stop bool)) error { + maxProviderConsensusVals := k.GetMaxProviderConsensusValidators(sdk.UnwrapSDKContext(ctx)) + counter := int64(0) + return k.stakingKeeper.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { + if counter >= maxProviderConsensusVals { + return true + } + counter++ + return fn(index, validator) + }) +} + +// TotalBondedTokens gets the amount of tokens of the consensus-active validators. +// The same as TotalBondedTokens in the StakingKeeper, but only counts bonded tokens +// of the first MaxProviderConsensusValidators bonded validators. +// This is used to implement the interface of the staking keeper to interface with +// modules that need to reference the consensus-active validators. +func (k Keeper) TotalBondedTokens(ctx context.Context) (math.Int, error) { + // iterate through the bonded validators + totalBondedTokens := math.ZeroInt() + + k.IterateBondedValidatorsByPower(ctx, func(_ int64, validator stakingtypes.ValidatorI) (stop bool) { + tokens := validator.GetTokens() + totalBondedTokens = totalBondedTokens.Add(tokens) + return false + }) + + return totalBondedTokens, nil +} + +// IterateDelegations is the same as IterateDelegations in the StakingKeeper. +// Necessary to implement the interface of the staking keeper to interface with +// other modules. +func (k Keeper) IterateDelegations(ctx context.Context, delegator sdk.AccAddress, fn func(index int64, delegation stakingtypes.DelegationI) (stop bool)) error { + return k.stakingKeeper.IterateDelegations(ctx, delegator, fn) +} + +// StakingTokenSupply is the same as StakingTotalSupply in the StakingKeeper. +// Necessary to implement the interface of the staking keeper to interface with +// other modules. +func (k Keeper) StakingTokenSupply(ctx context.Context) (math.Int, error) { + return k.stakingKeeper.StakingTokenSupply(ctx) +} + +// BondedRatio gets the ratio of tokens staked to validators active in the consensus +// to the total supply of tokens. +// Same as BondedRatio in the StakingKeeper, but only counts +// tokens of the first MaxProviderConsensusValidators bonded validators. +func (k Keeper) BondedRatio(ctx context.Context) (math.LegacyDec, error) { + totalSupply, err := k.StakingTokenSupply(ctx) + if err != nil { + return math.LegacyZeroDec(), err + } + + bondedTokens, err := k.TotalBondedTokens(ctx) + if err != nil { + return math.LegacyZeroDec(), err + } + + if !totalSupply.IsPositive() { + return math.LegacyZeroDec(), nil + } + + return math.LegacyNewDecFromInt(bondedTokens).QuoInt(totalSupply), nil +} diff --git a/x/ccv/provider/keeper/staking_keeper_interface_test.go b/x/ccv/provider/keeper/staking_keeper_interface_test.go new file mode 100644 index 0000000000..510756bf12 --- /dev/null +++ b/x/ccv/provider/keeper/staking_keeper_interface_test.go @@ -0,0 +1,131 @@ +package keeper_test + +import ( + "sort" + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +// TestStakingKeeperInterface tests +// * IterateBondedValidatorsByPower +// * TotalBondedTokens +// * BondedRatio +func TestStakingKeeperInterface(t *testing.T) { + tests := []struct { + name string + maxValidators int64 + powers []int64 + expectedPowers []int64 + mockTotalSupply int64 + expectedTotalBonded math.Int + expectedBondedRatio math.LegacyDec + }{ + { + name: "no validators", + maxValidators: 5, + powers: []int64{}, + expectedPowers: []int64{}, + mockTotalSupply: 100, + expectedTotalBonded: math.ZeroInt(), + expectedBondedRatio: math.LegacyZeroDec(), + }, + { + name: "validators less than max", + maxValidators: 5, + powers: []int64{10, 20, 30}, + expectedPowers: []int64{10, 20, 30}, // get all validators back + mockTotalSupply: 100, + expectedTotalBonded: math.NewInt(60), + expectedBondedRatio: math.LegacyNewDec(60).Quo(math.LegacyNewDec(100)), // 60% bonded + }, + { + name: "validators more than max", + maxValidators: 2, + powers: []int64{10, 20, 30}, + expectedPowers: []int64{30, 20}, // get the top 2 validators back + mockTotalSupply: 100, + // 30 + 20 = 50 + expectedTotalBonded: math.NewInt(50), + expectedBondedRatio: math.LegacyNewDec(50).Quo(math.LegacyNewDec(100)), // 50% bonded + }, + { + name: "validators equal to max", + maxValidators: 3, + powers: []int64{10, 20, 30}, + expectedPowers: []int64{30, 20, 10}, // get all validators back + mockTotalSupply: 60, + // 30 + 20 + 10 = 60 + expectedTotalBonded: math.NewInt(60), + expectedBondedRatio: math.LegacyNewDec(100).Quo(math.LegacyNewDec(100)), // 100% bonded + }, + { + name: "validators with equal powers", + maxValidators: 3, + powers: []int64{10, 10, 10, 20, 20, 30, 30}, + expectedPowers: []int64{30, 30, 20}, // get the top 3 validators back + mockTotalSupply: 1000, + // 30 + 30 + 20 = 80 + expectedTotalBonded: math.NewInt(80), + expectedBondedRatio: math.LegacyNewDec(8).Quo(math.LegacyNewDec(100)), // 8% bonded + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + counter := int64(0) + vals, _ := createStakingValidatorsAndMocks(ctx, mocks, tt.powers...) + + params := providerKeeper.GetParams(ctx) + params.MaxProviderConsensusValidators = tt.maxValidators + providerKeeper.SetParams(ctx, params) + + // sort vals by descending number of tokens + sort.Slice( + vals, + func(i, j int) bool { + return vals[i].GetTokens().Int64() > vals[j].GetTokens().Int64() + }, + ) + + mocks.MockStakingKeeper.EXPECT().IterateBondedValidatorsByPower(gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx sdk.Context, cb func(int64, stakingtypes.ValidatorI) bool) error { + for i, val := range vals { + if stop := cb(int64(i), val); stop { + break + } + } + return nil + }).AnyTimes() + actualValPowers := []int64{} + err := providerKeeper.IterateBondedValidatorsByPower(ctx, func(index int64, validator types.ValidatorI) (stop bool) { + counter++ + actualValPowers = append(actualValPowers, validator.GetTokens().Int64()) + return false + }) + require.NoError(t, err) + // we don't check that the elements exactly match; just matching the powers is good enough + require.ElementsMatch(t, tt.expectedPowers, actualValPowers) + + // check bonded ratio and total bonded + mocks.MockStakingKeeper.EXPECT().StakingTokenSupply(gomock.Any()).Return(math.NewInt(tt.mockTotalSupply), nil).AnyTimes() + + totalBonded, err := providerKeeper.TotalBondedTokens(ctx) + require.NoError(t, err) + require.Equal(t, tt.expectedTotalBonded, totalBonded) + + bondedRatio, err := providerKeeper.BondedRatio(ctx) + require.NoError(t, err) + require.Equal(t, tt.expectedBondedRatio, bondedRatio) + }) + } +} diff --git a/x/ccv/provider/keeper/validator_set_storage.go b/x/ccv/provider/keeper/validator_set_storage.go new file mode 100644 index 0000000000..3e271fec86 --- /dev/null +++ b/x/ccv/provider/keeper/validator_set_storage.go @@ -0,0 +1,106 @@ +package keeper + +import ( + "fmt" + + "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" +) + +// getValidatorKey constructs the key to access a given validator, stored under a given prefix. +func (k Keeper) getValidatorKey(prefix []byte, providerAddr types.ProviderConsAddress) []byte { + return append(prefix, providerAddr.ToSdkConsAddr()...) +} + +// setValidator stores the given `validator` in the validator set stored under the given prefix. +func (k Keeper) setValidator( + ctx sdk.Context, + prefix []byte, + validator types.ConsensusValidator, +) { + store := ctx.KVStore(k.storeKey) + bz, err := validator.Marshal() + if err != nil { + panic(fmt.Errorf("failed to marshal ConsumerValidator: %w", err)) + } + + store.Set(k.getValidatorKey(prefix, types.NewProviderConsAddress(validator.ProviderConsAddr)), bz) +} + +// setValSet resets the validator set stored under the given prefix to the provided `nextValidators`. +func (k Keeper) setValSet(ctx sdk.Context, prefix []byte, nextValidators []types.ConsensusValidator) { + k.deleteValSet(ctx, prefix) + for _, val := range nextValidators { + k.setValidator(ctx, prefix, val) + } +} + +// deleteValidator removes validator with `providerAddr` address from the +// validator set stored under the given prefix. +func (k Keeper) deleteValidator( + ctx sdk.Context, + prefix []byte, + providerConsAddr types.ProviderConsAddress, +) { + store := ctx.KVStore(k.storeKey) + store.Delete(k.getValidatorKey(prefix, providerConsAddr)) +} + +// deleteValSet deletes all the stored consumer validators under the given prefix. +func (k Keeper) deleteValSet( + ctx sdk.Context, + prefix []byte, +) { + store := ctx.KVStore(k.storeKey) + iterator := storetypes.KVStorePrefixIterator(store, prefix) + + var keysToDel [][]byte + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keysToDel = append(keysToDel, iterator.Key()) + } + for _, delKey := range keysToDel { + store.Delete(delKey) + } +} + +// isValidator returns `true` if the validator with `providerAddr` exists +// in the validator set stored under the given prefix. +func (k Keeper) isValidator(ctx sdk.Context, prefix []byte, providerAddr types.ProviderConsAddress) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(k.getValidatorKey(prefix, providerAddr)) != nil +} + +// getValSet returns all the validators stored under the given prefix. +func (k Keeper) getValSet( + ctx sdk.Context, + prefix []byte, +) (validators []types.ConsensusValidator) { + store := ctx.KVStore(k.storeKey) + iterator := storetypes.KVStorePrefixIterator(store, prefix) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + iterator.Value() + var validator types.ConsensusValidator + if err := validator.Unmarshal(iterator.Value()); err != nil { + panic(fmt.Errorf("failed to unmarshal ConsumerValidator: %w", err)) + } + validators = append(validators, validator) + } + + return validators +} + +// getTotalPower computes the total power of all the consumer validators stored under the given prefix. +func (k Keeper) getTotalPower(ctx sdk.Context, prefix []byte) math.Int { + totalPower := math.ZeroInt() + validators := k.getValSet(ctx, prefix) + for _, val := range validators { + totalPower = totalPower.Add(math.NewInt(val.Power)) + } + return totalPower +} diff --git a/x/ccv/provider/keeper/validator_set_update.go b/x/ccv/provider/keeper/validator_set_update.go index 4f8a127884..0a0a4444d8 100644 --- a/x/ccv/provider/keeper/validator_set_update.go +++ b/x/ccv/provider/keeper/validator_set_update.go @@ -3,7 +3,6 @@ package keeper import ( "fmt" - storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -13,28 +12,24 @@ import ( ccv "github.com/cosmos/interchain-security/v5/x/ccv/types" ) +// GetConsumerChainConsensusValidatorsKey returns the store key for consumer validators of the consumer chain with `chainID` +func (k Keeper) GetConsumerChainConsensusValidatorsKey(ctx sdk.Context, chainID string) []byte { + return types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) +} + // SetConsumerValidator sets provided consumer `validator` on the consumer chain with `chainID` func (k Keeper) SetConsumerValidator( ctx sdk.Context, chainID string, - validator types.ConsumerValidator, + validator types.ConsensusValidator, ) { - store := ctx.KVStore(k.storeKey) - bz, err := validator.Marshal() - if err != nil { - panic(fmt.Errorf("failed to marshal ConsumerValidator: %w", err)) - } - - store.Set(types.ConsumerValidatorKey(chainID, validator.ProviderConsAddr), bz) + k.setValidator(ctx, k.GetConsumerChainConsensusValidatorsKey(ctx, chainID), validator) } // SetConsumerValSet resets the current consumer validators with the `nextValidators` computed by // `FilterValidators` and hence this method should only be called after `FilterValidators` has completed. -func (k Keeper) SetConsumerValSet(ctx sdk.Context, chainID string, nextValidators []types.ConsumerValidator) { - k.DeleteConsumerValSet(ctx, chainID) - for _, val := range nextValidators { - k.SetConsumerValidator(ctx, chainID, val) - } +func (k Keeper) SetConsumerValSet(ctx sdk.Context, chainID string, nextValidators []types.ConsensusValidator) { + k.setValSet(ctx, k.GetConsumerChainConsensusValidatorsKey(ctx, chainID), nextValidators) } // DeleteConsumerValidator removes consumer validator with `providerAddr` address @@ -43,8 +38,7 @@ func (k Keeper) DeleteConsumerValidator( chainID string, providerConsAddr types.ProviderConsAddress, ) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.ConsumerValidatorKey(chainID, providerConsAddr.ToSdkConsAddr())) + k.deleteValidator(ctx, k.GetConsumerChainConsensusValidatorsKey(ctx, chainID), providerConsAddr) } // DeleteConsumerValSet deletes all the stored consumer validators for chain `chainID` @@ -52,84 +46,58 @@ func (k Keeper) DeleteConsumerValSet( ctx sdk.Context, chainID string, ) { - store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) - iterator := storetypes.KVStorePrefixIterator(store, key) - - var keysToDel [][]byte - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - keysToDel = append(keysToDel, iterator.Key()) - } - for _, delKey := range keysToDel { - store.Delete(delKey) - } + k.deleteValSet(ctx, k.GetConsumerChainConsensusValidatorsKey(ctx, chainID)) } // IsConsumerValidator returns `true` if the consumer validator with `providerAddr` exists for chain `chainID` // and `false` otherwise func (k Keeper) IsConsumerValidator(ctx sdk.Context, chainID string, providerAddr types.ProviderConsAddress) bool { - store := ctx.KVStore(k.storeKey) - return store.Get(types.ConsumerValidatorKey(chainID, providerAddr.ToSdkConsAddr())) != nil + return k.isValidator(ctx, k.GetConsumerChainConsensusValidatorsKey(ctx, chainID), providerAddr) } // GetConsumerValSet returns all the consumer validators for chain `chainID` func (k Keeper) GetConsumerValSet( ctx sdk.Context, chainID string, -) (validators []types.ConsumerValidator) { - store := ctx.KVStore(k.storeKey) - key := types.ChainIdWithLenKey(types.ConsumerValidatorBytePrefix, chainID) - iterator := storetypes.KVStorePrefixIterator(store, key) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - iterator.Value() - var validator types.ConsumerValidator - if err := validator.Unmarshal(iterator.Value()); err != nil { - panic(fmt.Errorf("failed to unmarshal ConsumerValidator: %w", err)) - } - validators = append(validators, validator) - } - - return validators +) []types.ConsensusValidator { + return k.getValSet(ctx, k.GetConsumerChainConsensusValidatorsKey(ctx, chainID)) } // DiffValidators compares the current and the next epoch's consumer validators and returns the `ValidatorUpdate` diff // needed by CometBFT to update the validator set on a chain. func DiffValidators( - currentValidators []types.ConsumerValidator, - nextValidators []types.ConsumerValidator, + currentValidators []types.ConsensusValidator, + nextValidators []types.ConsensusValidator, ) []abci.ValidatorUpdate { var updates []abci.ValidatorUpdate - isCurrentValidator := make(map[string]types.ConsumerValidator, len(currentValidators)) + isCurrentValidator := make(map[string]types.ConsensusValidator, len(currentValidators)) for _, val := range currentValidators { - isCurrentValidator[val.ConsumerPublicKey.String()] = val + isCurrentValidator[val.PublicKey.String()] = val } - isNextValidator := make(map[string]types.ConsumerValidator, len(nextValidators)) + isNextValidator := make(map[string]types.ConsensusValidator, len(nextValidators)) for _, val := range nextValidators { - isNextValidator[val.ConsumerPublicKey.String()] = val + isNextValidator[val.PublicKey.String()] = val } for _, currentVal := range currentValidators { - if nextVal, found := isNextValidator[currentVal.ConsumerPublicKey.String()]; !found { + if nextVal, found := isNextValidator[currentVal.PublicKey.String()]; !found { // this consumer public key does not appear in the next validators and hence we remove the validator // with that consumer public key by creating an update with 0 power - updates = append(updates, abci.ValidatorUpdate{PubKey: *currentVal.ConsumerPublicKey, Power: 0}) + updates = append(updates, abci.ValidatorUpdate{PubKey: *currentVal.PublicKey, Power: 0}) } else if currentVal.Power != nextVal.Power { // validator did not modify its consumer public key but has changed its voting power, so we // have to create an update with the new power - updates = append(updates, abci.ValidatorUpdate{PubKey: *nextVal.ConsumerPublicKey, Power: nextVal.Power}) + updates = append(updates, abci.ValidatorUpdate{PubKey: *nextVal.PublicKey, Power: nextVal.Power}) } // else no update is needed because neither the consumer public key changed, nor the power of the validator } for _, nextVal := range nextValidators { - if _, found := isCurrentValidator[nextVal.ConsumerPublicKey.String()]; !found { + if _, found := isCurrentValidator[nextVal.PublicKey.String()]; !found { // this consumer public key does not exist in the current validators and hence we introduce this validator - updates = append(updates, abci.ValidatorUpdate{PubKey: *nextVal.ConsumerPublicKey, Power: nextVal.Power}) + updates = append(updates, abci.ValidatorUpdate{PubKey: *nextVal.PublicKey, Power: nextVal.Power}) } } @@ -137,30 +105,30 @@ func DiffValidators( } // CreateConsumerValidator creates a consumer validator for `chainID` from the given staking `validator` -func (k Keeper) CreateConsumerValidator(ctx sdk.Context, chainID string, validator stakingtypes.Validator) (types.ConsumerValidator, error) { +func (k Keeper) CreateConsumerValidator(ctx sdk.Context, chainID string, validator stakingtypes.Validator) (types.ConsensusValidator, error) { valAddr, err := sdk.ValAddressFromBech32(validator.GetOperator()) if err != nil { - return types.ConsumerValidator{}, err + return types.ConsensusValidator{}, err } power, err := k.stakingKeeper.GetLastValidatorPower(ctx, valAddr) consAddr, err := validator.GetConsAddr() if err != nil { - return types.ConsumerValidator{}, fmt.Errorf("could not retrieve validator's (%+v) consensus address: %w", + return types.ConsensusValidator{}, fmt.Errorf("could not retrieve validator's (%+v) consensus address: %w", validator, err) } - consumerPublicKey, foundConsumerPublicKey := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(consAddr)) - if !foundConsumerPublicKey { + consumerPublicKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(consAddr)) + if !found { consumerPublicKey, err = validator.TmConsPublicKey() if err != nil { - return types.ConsumerValidator{}, fmt.Errorf("could not retrieve validator's (%+v) public key: %w", validator, err) + return types.ConsensusValidator{}, fmt.Errorf("could not retrieve validator's (%+v) public key: %w", validator, err) } } - return types.ConsumerValidator{ - ProviderConsAddr: consAddr, - Power: power, - ConsumerPublicKey: &consumerPublicKey, + return types.ConsensusValidator{ + ProviderConsAddr: consAddr, + Power: power, + PublicKey: &consumerPublicKey, }, nil } @@ -171,8 +139,8 @@ func (k Keeper) FilterValidators( chainID string, bondedValidators []stakingtypes.Validator, predicate func(providerAddr types.ProviderConsAddress) bool, -) []types.ConsumerValidator { - var nextValidators []types.ConsumerValidator +) []types.ConsensusValidator { + var nextValidators []types.ConsensusValidator for _, val := range bondedValidators { consAddr, err := val.GetConsAddr() if err != nil { @@ -199,5 +167,14 @@ func (k Keeper) FilterValidators( // GetLastBondedValidators iterates the last validator powers in the staking module // and returns the first MaxValidators many validators with the largest powers. func (k Keeper) GetLastBondedValidators(ctx sdk.Context) ([]stakingtypes.Validator, error) { - return ccv.GetLastBondedValidatorsUtil(ctx, k.stakingKeeper, k.Logger(ctx)) + maxVals, err := k.stakingKeeper.MaxValidators(ctx) + if err != nil { + return nil, err + } + return ccv.GetLastBondedValidatorsUtil(ctx, k.stakingKeeper, k.Logger(ctx), maxVals) +} + +func (k Keeper) GetLastActiveBondedValidators(ctx sdk.Context) ([]stakingtypes.Validator, error) { + maxVals := k.GetMaxProviderConsensusValidators(ctx) + return ccv.GetLastBondedValidatorsUtil(ctx, k.stakingKeeper, k.Logger(ctx), uint32(maxVals)) } diff --git a/x/ccv/provider/keeper/validator_set_update_test.go b/x/ccv/provider/keeper/validator_set_update_test.go index 43bc5a0370..b4de4ba416 100644 --- a/x/ccv/provider/keeper/validator_set_update_test.go +++ b/x/ccv/provider/keeper/validator_set_update_test.go @@ -26,10 +26,10 @@ func TestConsumerValidator(t *testing.T) { providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() - validator := types.ConsumerValidator{ - ProviderConsAddr: []byte("providerConsAddr"), - Power: 2, - ConsumerPublicKey: &crypto.PublicKey{}, + validator := types.ConsensusValidator{ + ProviderConsAddr: []byte("providerConsAddr"), + Power: 2, + PublicKey: &crypto.PublicKey{}, } require.False(t, providerKeeper.IsConsumerValidator(ctx, "chainID", types.NewProviderConsAddress(validator.ProviderConsAddr))) @@ -44,11 +44,11 @@ func TestGetConsumerValSet(t *testing.T) { defer ctrl.Finish() // create 3 validators and set them as current validators - expectedValidators := []types.ConsumerValidator{ + expectedValidators := []types.ConsensusValidator{ { ProviderConsAddr: []byte("providerConsAddr1"), Power: 1, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{1}, }, @@ -57,7 +57,7 @@ func TestGetConsumerValSet(t *testing.T) { { ProviderConsAddr: []byte("providerConsAddr2"), Power: 2, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{2}, }, @@ -66,7 +66,7 @@ func TestGetConsumerValSet(t *testing.T) { { ProviderConsAddr: []byte("providerConsAddr3"), Power: 3, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{3}, }, @@ -76,17 +76,17 @@ func TestGetConsumerValSet(t *testing.T) { for _, expectedValidator := range expectedValidators { providerKeeper.SetConsumerValidator(ctx, "chainID", - types.ConsumerValidator{ - ProviderConsAddr: expectedValidator.ProviderConsAddr, - Power: expectedValidator.Power, - ConsumerPublicKey: expectedValidator.ConsumerPublicKey, + types.ConsensusValidator{ + ProviderConsAddr: expectedValidator.ProviderConsAddr, + Power: expectedValidator.Power, + PublicKey: expectedValidator.PublicKey, }) } actualValidators := providerKeeper.GetConsumerValSet(ctx, "chainID") // sort validators first to be able to compare - sortValidators := func(validators []types.ConsumerValidator) { + sortValidators := func(validators []types.ConsensusValidator) { sort.Slice(validators, func(i, j int) bool { return bytes.Compare(validators[i].ProviderConsAddr, validators[j].ProviderConsAddr) < 0 }) @@ -99,13 +99,13 @@ func TestGetConsumerValSet(t *testing.T) { // createConsumerValidator is a helper function to create a consumer validator with the given `power`. It uses `index` as // the `ProviderConsAddr` of the validator, and the `seed` to generate the consumer public key. Returns the validator // and its consumer public key. -func createConsumerValidator(index int, power int64, seed int) (types.ConsumerValidator, crypto.PublicKey) { +func createConsumerValidator(index int, power int64, seed int) (types.ConsensusValidator, crypto.PublicKey) { publicKey := cryptotestutil.NewCryptoIdentityFromIntSeed(seed).TMProtoCryptoPublicKey() - return types.ConsumerValidator{ - ProviderConsAddr: []byte{byte(index)}, - Power: power, - ConsumerPublicKey: &publicKey, + return types.ConsensusValidator{ + ProviderConsAddr: []byte{byte(index)}, + Power: power, + PublicKey: &publicKey, }, publicKey } @@ -173,8 +173,8 @@ func TestDiff(t *testing.T) { nextF, nextPublicKeyF := createConsumerValidator(6, 1, 8) expectedUpdates = append(expectedUpdates, abci.ValidatorUpdate{PubKey: nextPublicKeyF, Power: 1}) - currentValidators := []types.ConsumerValidator{currentA, currentB, currentC, currentD, currentE} - nextValidators := []types.ConsumerValidator{nextB, nextC, nextD, nextE, nextF} + currentValidators := []types.ConsensusValidator{currentA, currentB, currentC, currentD, currentE} + nextValidators := []types.ConsensusValidator{nextB, nextC, nextD, nextE, nextF} actualUpdates := keeper.DiffValidators(currentValidators, nextValidators) @@ -194,12 +194,12 @@ func TestDiff(t *testing.T) { } func TestDiffEdgeCases(t *testing.T) { - require.Empty(t, len(keeper.DiffValidators([]types.ConsumerValidator{}, []types.ConsumerValidator{}))) + require.Empty(t, len(keeper.DiffValidators([]types.ConsensusValidator{}, []types.ConsensusValidator{}))) valA, publicKeyA := createConsumerValidator(1, 1, 1) valB, publicKeyB := createConsumerValidator(2, 2, 2) valC, publicKeyC := createConsumerValidator(3, 3, 3) - validators := []types.ConsumerValidator{valA, valB, valC} + validators := []types.ConsensusValidator{valA, valB, valC} // we do not expect any validator updates if the `currentValidators` are the same with the `nextValidators` require.Empty(t, len(keeper.DiffValidators(validators, validators))) @@ -210,7 +210,7 @@ func TestDiffEdgeCases(t *testing.T) { {PubKey: publicKeyB, Power: 2}, {PubKey: publicKeyC, Power: 3}, } - actualUpdates := keeper.DiffValidators([]types.ConsumerValidator{}, validators) + actualUpdates := keeper.DiffValidators([]types.ConsensusValidator{}, validators) // sort validators first to be able to compare sortUpdates := func(updates []abci.ValidatorUpdate) { sort.Slice(updates, func(i, j int) bool { @@ -231,7 +231,7 @@ func TestDiffEdgeCases(t *testing.T) { {PubKey: publicKeyB, Power: 0}, {PubKey: publicKeyC, Power: 0}, } - actualUpdates = keeper.DiffValidators(validators, []types.ConsumerValidator{}) + actualUpdates = keeper.DiffValidators(validators, []types.ConsensusValidator{}) sortUpdates(expectedUpdates) sortUpdates(actualUpdates) require.Equal(t, expectedUpdates, actualUpdates) @@ -254,11 +254,11 @@ func TestSetConsumerValSet(t *testing.T) { chainID := "chainID" - currentValidators := []types.ConsumerValidator{ + currentValidators := []types.ConsensusValidator{ { ProviderConsAddr: []byte("currentProviderConsAddr1"), Power: 2, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{2}, }, @@ -267,7 +267,7 @@ func TestSetConsumerValSet(t *testing.T) { { ProviderConsAddr: []byte("currentProviderConsAddr2"), Power: 3, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{3}, }, @@ -276,7 +276,7 @@ func TestSetConsumerValSet(t *testing.T) { { ProviderConsAddr: []byte("currentProviderConsAddr3"), Power: 4, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{4}, }, @@ -284,11 +284,11 @@ func TestSetConsumerValSet(t *testing.T) { }, } - nextValidators := []types.ConsumerValidator{ + nextValidators := []types.ConsensusValidator{ { ProviderConsAddr: []byte("nextProviderConsAddr1"), Power: 2, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{2}, }, @@ -297,7 +297,7 @@ func TestSetConsumerValSet(t *testing.T) { { ProviderConsAddr: []byte("nextProviderConsAddr2"), Power: 3, - ConsumerPublicKey: &crypto.PublicKey{ + PublicKey: &crypto.PublicKey{ Sum: &crypto.PublicKey_Ed25519{ Ed25519: []byte{3}, }, @@ -316,7 +316,7 @@ func TestSetConsumerValSet(t *testing.T) { nextCurrentValidators := providerKeeper.GetConsumerValSet(ctx, chainID) // sort validators first to be able to compare - sortValidators := func(validators []types.ConsumerValidator) { + sortValidators := func(validators []types.ConsensusValidator) { sort.Slice(validators, func(i, j int) bool { return bytes.Compare(validators[i].ProviderConsAddr, validators[j].ProviderConsAddr) < 0 }) @@ -337,29 +337,29 @@ func TestFilterValidatorsConsiderAll(t *testing.T) { considerAll := func(providerAddr types.ProviderConsAddress) bool { return true } require.Empty(t, providerKeeper.FilterValidators(ctx, chainID, []stakingtypes.Validator{}, considerAll)) - var expectedValidators []types.ConsumerValidator + var expectedValidators []types.ConsensusValidator // create a staking validator A that has not set a consumer public key valA := createStakingValidator(ctx, mocks, 1, 1, 1) - // because validator A has no consumer key set, the `ConsumerPublicKey` we expect is the key on the provider chain + // because validator A has no consumer key set, the `PublicKey` we expect is the key on the provider chain valAConsAddr, _ := valA.GetConsAddr() valAPublicKey, _ := valA.TmConsPublicKey() - expectedValidators = append(expectedValidators, types.ConsumerValidator{ - ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), - Power: 1, - ConsumerPublicKey: &valAPublicKey, + expectedValidators = append(expectedValidators, types.ConsensusValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), + Power: 1, + PublicKey: &valAPublicKey, }) // create a staking validator B that has set a consumer public key valB := createStakingValidator(ctx, mocks, 2, 2, 2) - // validator B has set a consumer key, the `ConsumerPublicKey` we expect is the key set by `SetValidatorConsumerPubKey` + // validator B has set a consumer key, the `PublicKey` we expect is the key set by `SetValidatorConsumerPubKey` valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() valBConsAddr, _ := valB.GetConsAddr() providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(valBConsAddr), valBConsumerKey) - expectedValidators = append(expectedValidators, types.ConsumerValidator{ - ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), - Power: 2, - ConsumerPublicKey: &valBConsumerKey, + expectedValidators = append(expectedValidators, types.ConsensusValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), + Power: 2, + PublicKey: &valBConsumerKey, }) bondedValidators := []stakingtypes.Validator{valA, valB} @@ -379,30 +379,30 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { return providerKeeper.IsOptedIn(ctx, chainID, providerAddr) })) - var expectedValidators []types.ConsumerValidator + var expectedValidators []types.ConsensusValidator // create a staking validator A that has not set a consumer public key valA := createStakingValidator(ctx, mocks, 1, 1, 1) - // because validator A has no consumer key set, the `ConsumerPublicKey` we expect is the key on the provider chain + // because validator A has no consumer key set, the `PublicKey` we expect is the key on the provider chain valAConsAddr, _ := valA.GetConsAddr() valAPublicKey, _ := valA.TmConsPublicKey() - expectedValAConsumerValidator := types.ConsumerValidator{ - ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), - Power: 1, - ConsumerPublicKey: &valAPublicKey, + expectedValAConsumerValidator := types.ConsensusValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valAConsAddr).Address.Bytes(), + Power: 1, + PublicKey: &valAPublicKey, } expectedValidators = append(expectedValidators, expectedValAConsumerValidator) // create a staking validator B that has set a consumer public key valB := createStakingValidator(ctx, mocks, 2, 2, 2) - // validator B has set a consumer key, the `ConsumerPublicKey` we expect is the key set by `SetValidatorConsumerPubKey` + // validator B has set a consumer key, the `PublicKey` we expect is the key set by `SetValidatorConsumerPubKey` valBConsumerKey := cryptotestutil.NewCryptoIdentityFromIntSeed(1).TMProtoCryptoPublicKey() valBConsAddr, _ := valB.GetConsAddr() providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(valBConsAddr), valBConsumerKey) - expectedValBConsumerValidator := types.ConsumerValidator{ - ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), - Power: 2, - ConsumerPublicKey: &valBConsumerKey, + expectedValBConsumerValidator := types.ConsensusValidator{ + ProviderConsAddr: types.NewProviderConsAddress(valBConsAddr).Address.Bytes(), + Power: 2, + PublicKey: &valBConsumerKey, } expectedValidators = append(expectedValidators, expectedValBConsumerValidator) @@ -418,7 +418,7 @@ func TestFilterValidatorsConsiderOnlyOptIn(t *testing.T) { }) // sort validators first to be able to compare - sortValidators := func(validators []types.ConsumerValidator) { + sortValidators := func(validators []types.ConsensusValidator) { sort.Slice(validators, func(i, j int) bool { return bytes.Compare(validators[i].ProviderConsAddr, validators[j].ProviderConsAddr) < 0 }) @@ -454,10 +454,10 @@ func TestCreateConsumerValidator(t *testing.T) { valAProviderConsAddr := types.NewProviderConsAddress(valAConsAddr) providerKeeper.SetValidatorConsumerPubKey(ctx, chainID, valAProviderConsAddr, valAConsumerKey) actualConsumerValidatorA, err := providerKeeper.CreateConsumerValidator(ctx, chainID, valA) - expectedConsumerValidatorA := types.ConsumerValidator{ - ProviderConsAddr: valAProviderConsAddr.ToSdkConsAddr(), - Power: 1, - ConsumerPublicKey: &valAConsumerKey, + expectedConsumerValidatorA := types.ConsensusValidator{ + ProviderConsAddr: valAProviderConsAddr.ToSdkConsAddr(), + Power: 1, + PublicKey: &valAConsumerKey, } require.Equal(t, expectedConsumerValidatorA, actualConsumerValidatorA) require.NoError(t, err) @@ -468,10 +468,10 @@ func TestCreateConsumerValidator(t *testing.T) { valBProviderConsAddr := types.NewProviderConsAddress(valBConsAddr) valBPublicKey, _ := valB.TmConsPublicKey() actualConsumerValidatorB, err := providerKeeper.CreateConsumerValidator(ctx, chainID, valB) - expectedConsumerValidatorB := types.ConsumerValidator{ - ProviderConsAddr: valBProviderConsAddr.ToSdkConsAddr(), - Power: 2, - ConsumerPublicKey: &valBPublicKey, + expectedConsumerValidatorB := types.ConsensusValidator{ + ProviderConsAddr: valBProviderConsAddr.ToSdkConsAddr(), + Power: 2, + PublicKey: &valBPublicKey, } require.Equal(t, expectedConsumerValidatorB, actualConsumerValidatorB) require.NoError(t, err) diff --git a/x/ccv/provider/migrations/v6/legacy_params.go b/x/ccv/provider/migrations/v6/legacy_params.go index 1841497bce..b1e0d0ed05 100644 --- a/x/ccv/provider/migrations/v6/legacy_params.go +++ b/x/ccv/provider/migrations/v6/legacy_params.go @@ -88,5 +88,7 @@ func GetParamsLegacy(ctx sdk.Context, paramspace ccvtypes.LegacyParamSubspace) t getSlashMeterReplenishFraction(ctx, paramspace), getConsumerRewardDenomRegistrationFee(ctx, paramspace), getBlocksPerEpoch(ctx, paramspace), + // this parameter is new so it doesn't need to be migrated, just initialized + types.DefaultMaxProviderConsensusValidators, ) } diff --git a/x/ccv/provider/migrations/vX/migrations.go b/x/ccv/provider/migrations/vX/migrations.go new file mode 100644 index 0000000000..b2b89f0fad --- /dev/null +++ b/x/ccv/provider/migrations/vX/migrations.go @@ -0,0 +1,24 @@ +package v6 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + providerkeeper "github.com/cosmos/interchain-security/v5/x/ccv/provider/keeper" +) + +// InitializeMaxValidatorsForExistingConsumers initializes the max validators +// parameter for existing consumers to the MaxProviderConsensusValidators parameter. +// This is necessary to avoid those consumer chains having an excessive amount of validators. +func InitializeMaxValidatorsForExistingConsumers(ctx sdk.Context, providerKeeper providerkeeper.Keeper) { + maxVals := providerKeeper.GetParams(ctx).MaxProviderConsensusValidators + for _, chainID := range providerKeeper.GetAllRegisteredConsumerChainIDs(ctx) { + providerKeeper.SetValidatorSetCap(ctx, chainID, uint32(maxVals)) + } +} + +// InitializeAllowInactiveVals initializes the allow inactive validators parameter to be false +// for all existing consumer chains. +func InitializeAllowInactiveVals(ctx sdk.Context, providerKeeper providerkeeper.Keeper) { + for _, chainID := range providerKeeper.GetAllRegisteredConsumerChainIDs(ctx) { + providerKeeper.SetAllowInactiveValidators(ctx, chainID, false) + } +} diff --git a/x/ccv/provider/module.go b/x/ccv/provider/module.go index d670ee2828..6f37d2eff6 100644 --- a/x/ccv/provider/module.go +++ b/x/ccv/provider/module.go @@ -5,10 +5,11 @@ import ( "encoding/json" "fmt" - "cosmossdk.io/core/appmodule" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" + "cosmossdk.io/core/appmodule" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -17,9 +18,12 @@ import ( simtypes "github.com/cosmos/cosmos-sdk/types/simulation" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/interchain-security/v5/x/ccv/provider/client/cli" "github.com/cosmos/interchain-security/v5/x/ccv/provider/keeper" "github.com/cosmos/interchain-security/v5/x/ccv/provider/migrations" + "github.com/cosmos/interchain-security/v5/x/ccv/provider/simulation" providertypes "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" ) @@ -27,14 +31,14 @@ var ( _ module.AppModule = (*AppModule)(nil) _ module.AppModuleBasic = (*AppModuleBasic)(nil) _ module.AppModuleSimulation = (*AppModule)(nil) - _ module.HasGenesis = (*AppModule)(nil) _ module.HasName = (*AppModule)(nil) _ module.HasConsensusVersion = (*AppModule)(nil) _ module.HasInvariants = (*AppModule)(nil) _ module.HasServices = (*AppModule)(nil) + _ module.HasABCIGenesis = (*AppModule)(nil) + _ module.HasABCIEndBlock = (*AppModule)(nil) _ appmodule.AppModule = (*AppModule)(nil) _ appmodule.HasBeginBlocker = (*AppModule)(nil) - _ appmodule.HasEndBlocker = (*AppModule)(nil) ) // AppModuleBasic is the IBC Provider AppModuleBasic @@ -112,8 +116,8 @@ func NewAppModule(k *keeper.Keeper, paramSpace paramtypes.Subspace) AppModule { } // RegisterInvariants implements the AppModule interface -func (AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { - // TODO +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { + keeper.RegisterInvariants(ir, am.keeper) } // RegisterServices registers module services. @@ -139,11 +143,11 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { // InitGenesis performs genesis initialization for the provider module. It returns no validator updates. // Note: This method along with ValidateGenesis satisfies the CCV spec: // https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#ccv-pcf-initg1 -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { var genesisState providertypes.GenesisState cdc.MustUnmarshalJSON(data, &genesisState) - am.keeper.InitGenesis(ctx, &genesisState) + return am.keeper.InitGenesis(ctx, &genesisState) } // ExportGenesis returns the exported genesis state as raw bytes for the provider @@ -172,7 +176,7 @@ func (am AppModule) BeginBlock(ctx context.Context) error { } // EndBlock implements the AppModule interface -func (am AppModule) EndBlock(ctx context.Context) error { +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) // EndBlock logic needed for the Consumer Initiated Slashing sub-protocol. @@ -181,15 +185,14 @@ func (am AppModule) EndBlock(ctx context.Context) error { // EndBlock logic needed for the Consumer Chain Removal sub-protocol am.keeper.EndBlockCCR(sdkCtx) // EndBlock logic needed for the Validator Set Update sub-protocol - am.keeper.EndBlockVSU(sdkCtx) - - return nil + return am.keeper.EndBlockVSU(sdkCtx) } // AppModuleSimulation functions // GenerateGenesisState creates a randomized GenState of the transfer module. func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) } // RegisterStoreDecoder registers a decoder for provider module's types diff --git a/x/ccv/provider/module_test.go b/x/ccv/provider/module_test.go index b37c34d1da..5e8584f15b 100644 --- a/x/ccv/provider/module_test.go +++ b/x/ccv/provider/module_test.go @@ -3,14 +3,18 @@ package provider_test import ( "testing" - "cosmossdk.io/math" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "cosmossdk.io/math" + + "github.com/cosmos/interchain-security/v5/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v5/testutil/keeper" "github.com/cosmos/interchain-security/v5/x/ccv/provider" "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" @@ -110,6 +114,7 @@ func TestInitGenesis(t *testing.T) { nil, nil, nil, + nil, ) cdc := keeperParams.Cdc @@ -141,9 +146,19 @@ func TestInitGenesis(t *testing.T) { // Last total power is queried in InitGenesis, only if method has not // already panicked from unowned capability. if !tc.expPanic { + // create a mock validator + cId := crypto.NewCryptoIdentityFromIntSeed(234234) + validator := cId.SDKStakingValidator() + valAddr, err := sdk.ValAddressFromBech32(validator.GetOperator()) + require.NoError(t, err) + orderedCalls = append(orderedCalls, mocks.MockStakingKeeper.EXPECT().GetLastTotalPower( ctx).Return(math.NewInt(100), nil).Times(1), // Return total voting power as 100 + mocks.MockStakingKeeper.EXPECT().GetBondedValidatorsByPower( + ctx).Return([]stakingtypes.Validator{validator}, nil).Times(1), // Return a single validator + mocks.MockStakingKeeper.EXPECT().GetLastValidatorPower( + ctx, valAddr).Return(int64(100), nil).Times(1), // Return total power as power of the single validator ) } diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index 2494da8c60..89475fa4e0 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -51,6 +51,9 @@ func TestProviderProposalHandler(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), blockTime: hourFromNow, // ctx blocktime is after proposal's spawn time expValidConsumerAddition: true, diff --git a/x/ccv/provider/simulation/genesis.go b/x/ccv/provider/simulation/genesis.go new file mode 100644 index 0000000000..f27573cc7f --- /dev/null +++ b/x/ccv/provider/simulation/genesis.go @@ -0,0 +1,44 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/interchain-security/v5/x/ccv/provider/types" +) + +// Simulation parameter constants +const ( + // only includes params that make sense even with a single + maxProviderConsensusValidators = "max_provider_consensus_validators" +) + +// genMaxProviderConsensusValidators returns randomized maxProviderConsensusValidators +func genMaxProviderConsensusValidators(r *rand.Rand) int64 { + return int64(r.Intn(250) + 1) +} + +// RandomizedGenState generates a random GenesisState for staking +func RandomizedGenState(simState *module.SimulationState) { + // params + var ( + maxProviderConsensusVals int64 + ) + + simState.AppParams.GetOrGenerate(maxProviderConsensusValidators, &maxProviderConsensusVals, simState.Rand, func(r *rand.Rand) { maxProviderConsensusVals = genMaxProviderConsensusValidators(r) }) + + providerParams := types.DefaultParams() + providerParams.MaxProviderConsensusValidators = maxProviderConsensusVals + + providerGenesis := types.DefaultGenesisState() + providerGenesis.Params = providerParams + + bz, err := json.MarshalIndent(&providerGenesis.Params, "", " ") + if err != nil { + panic(err) + } + fmt.Printf("Selected randomly generated provider parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(providerGenesis) +} diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 1d75778bb1..a3593fb22f 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -1,13 +1,14 @@ package types import ( + "github.com/cosmos/ibc-go/v8/modules/core/exported" + tendermint "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - "github.com/cosmos/ibc-go/v8/modules/core/exported" - tendermint "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { diff --git a/x/ccv/provider/types/genesis.go b/x/ccv/provider/types/genesis.go index 7ef643970b..345bd20537 100644 --- a/x/ccv/provider/types/genesis.go +++ b/x/ccv/provider/types/genesis.go @@ -26,21 +26,23 @@ func NewGenesisState( consumerAddrsToPrune []ConsumerAddrsToPrune, initTimeoutTimestamps []InitTimeoutTimestamp, exportedVscSendTimestamps []ExportedVscSendTimestamp, + lastProviderConsensusValidators []ConsensusValidator, ) *GenesisState { return &GenesisState{ - ValsetUpdateId: vscID, - ValsetUpdateIdToHeight: vscIdToHeights, - ConsumerStates: consumerStates, - UnbondingOps: unbondingOps, - MatureUnbondingOps: matureUbdOps, - ConsumerAdditionProposals: additionProposals, - ConsumerRemovalProposals: removalProposals, - Params: params, - ValidatorConsumerPubkeys: validatorConsumerPubkeys, - ValidatorsByConsumerAddr: validatorsByConsumerAddr, - ConsumerAddrsToPrune: consumerAddrsToPrune, - InitTimeoutTimestamps: initTimeoutTimestamps, - ExportedVscSendTimestamps: exportedVscSendTimestamps, + ValsetUpdateId: vscID, + ValsetUpdateIdToHeight: vscIdToHeights, + ConsumerStates: consumerStates, + UnbondingOps: unbondingOps, + MatureUnbondingOps: matureUbdOps, + ConsumerAdditionProposals: additionProposals, + ConsumerRemovalProposals: removalProposals, + Params: params, + ValidatorConsumerPubkeys: validatorConsumerPubkeys, + ValidatorsByConsumerAddr: validatorsByConsumerAddr, + ConsumerAddrsToPrune: consumerAddrsToPrune, + InitTimeoutTimestamps: initTimeoutTimestamps, + ExportedVscSendTimestamps: exportedVscSendTimestamps, + LastProviderConsensusValidators: lastProviderConsensusValidators, } } diff --git a/x/ccv/provider/types/genesis.pb.go b/x/ccv/provider/types/genesis.pb.go index d885dbc667..78d2aec6fb 100644 --- a/x/ccv/provider/types/genesis.pb.go +++ b/x/ccv/provider/types/genesis.pb.go @@ -46,9 +46,10 @@ type GenesisState struct { // empty for a new chain ValidatorsByConsumerAddr []ValidatorByConsumerAddr `protobuf:"bytes,10,rep,name=validators_by_consumer_addr,json=validatorsByConsumerAddr,proto3" json:"validators_by_consumer_addr"` // empty for a new chain - ConsumerAddrsToPrune []ConsumerAddrsToPrune `protobuf:"bytes,11,rep,name=consumer_addrs_to_prune,json=consumerAddrsToPrune,proto3" json:"consumer_addrs_to_prune"` - InitTimeoutTimestamps []InitTimeoutTimestamp `protobuf:"bytes,12,rep,name=init_timeout_timestamps,json=initTimeoutTimestamps,proto3" json:"init_timeout_timestamps"` - ExportedVscSendTimestamps []ExportedVscSendTimestamp `protobuf:"bytes,13,rep,name=exported_vsc_send_timestamps,json=exportedVscSendTimestamps,proto3" json:"exported_vsc_send_timestamps"` + ConsumerAddrsToPrune []ConsumerAddrsToPrune `protobuf:"bytes,11,rep,name=consumer_addrs_to_prune,json=consumerAddrsToPrune,proto3" json:"consumer_addrs_to_prune"` + InitTimeoutTimestamps []InitTimeoutTimestamp `protobuf:"bytes,12,rep,name=init_timeout_timestamps,json=initTimeoutTimestamps,proto3" json:"init_timeout_timestamps"` + ExportedVscSendTimestamps []ExportedVscSendTimestamp `protobuf:"bytes,13,rep,name=exported_vsc_send_timestamps,json=exportedVscSendTimestamps,proto3" json:"exported_vsc_send_timestamps"` + LastProviderConsensusValidators []ConsensusValidator `protobuf:"bytes,14,rep,name=last_provider_consensus_validators,json=lastProviderConsensusValidators,proto3" json:"last_provider_consensus_validators"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -175,6 +176,13 @@ func (m *GenesisState) GetExportedVscSendTimestamps() []ExportedVscSendTimestamp return nil } +func (m *GenesisState) GetLastProviderConsensusValidators() []ConsensusValidator { + if m != nil { + return m.LastProviderConsensusValidators + } + return nil +} + // The provider CCV module's knowledge of consumer state. // // Note this type is only used internally to the provider CCV module. @@ -352,64 +360,67 @@ func init() { } var fileDescriptor_48411d9c7900d48e = []byte{ - // 908 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xdd, 0x6e, 0x1b, 0x45, - 0x14, 0xce, 0x26, 0x69, 0x1a, 0x4f, 0x7e, 0x08, 0x43, 0x70, 0x37, 0x09, 0xb8, 0x91, 0x51, 0xa5, - 0x48, 0x80, 0xdd, 0x04, 0x90, 0xca, 0x4f, 0x2f, 0x9a, 0x16, 0x81, 0x85, 0x10, 0x96, 0x93, 0x06, - 0xa9, 0x5c, 0x8c, 0xc6, 0x33, 0x23, 0x7b, 0x1a, 0x7b, 0x66, 0x35, 0x67, 0x76, 0x13, 0x0b, 0x21, - 0x15, 0xc1, 0x03, 0xf0, 0x58, 0xbd, 0xcc, 0x25, 0x57, 0x15, 0x4a, 0xde, 0x80, 0x27, 0x40, 0x3b, - 0x3b, 0xbb, 0x5d, 0x07, 0xa7, 0xb2, 0x7b, 0x95, 0x78, 0xbe, 0x39, 0xdf, 0xf7, 0x9d, 0x39, 0x33, - 0xe7, 0x2c, 0xda, 0x97, 0xca, 0x0a, 0xc3, 0xfa, 0x54, 0x2a, 0x02, 0x82, 0xc5, 0x46, 0xda, 0x51, - 0x93, 0xb1, 0xa4, 0x19, 0x19, 0x9d, 0x48, 0x2e, 0x4c, 0x33, 0xd9, 0x6f, 0xf6, 0x84, 0x12, 0x20, - 0xa1, 0x11, 0x19, 0x6d, 0x35, 0xfe, 0x68, 0x42, 0x48, 0x83, 0xb1, 0xa4, 0x91, 0x87, 0x34, 0x92, - 0xfd, 0xed, 0xcd, 0x9e, 0xee, 0x69, 0xb7, 0xbf, 0x99, 0xfe, 0x97, 0x85, 0x6e, 0xdf, 0xbf, 0x49, - 0x2d, 0xd9, 0x6f, 0x42, 0x9f, 0x1a, 0xc1, 0x09, 0xd3, 0x0a, 0xe2, 0xa1, 0x30, 0x3e, 0xe2, 0xde, - 0x1b, 0x22, 0xce, 0xa4, 0x11, 0x7e, 0xdb, 0xc1, 0x34, 0x69, 0x14, 0xfe, 0x5c, 0x4c, 0xfd, 0x62, - 0x05, 0xad, 0x7e, 0x97, 0x65, 0x76, 0x64, 0xa9, 0x15, 0x78, 0x0f, 0x6d, 0x24, 0x74, 0x00, 0xc2, - 0x92, 0x38, 0xe2, 0xd4, 0x0a, 0x22, 0x79, 0x18, 0xec, 0x06, 0x7b, 0x8b, 0x9d, 0xf5, 0x6c, 0xfd, - 0xa9, 0x5b, 0x6e, 0x71, 0xfc, 0x2b, 0x7a, 0x27, 0xf7, 0x49, 0x20, 0x8d, 0x85, 0x70, 0x7e, 0x77, - 0x61, 0x6f, 0xe5, 0xe0, 0xa0, 0x31, 0xc5, 0xe1, 0x34, 0x1e, 0xfb, 0x58, 0x27, 0x7b, 0x58, 0x7b, - 0xf9, 0xea, 0xee, 0xdc, 0xbf, 0xaf, 0xee, 0x56, 0x47, 0x74, 0x38, 0xf8, 0xaa, 0x7e, 0x8d, 0xb8, - 0xde, 0x59, 0x67, 0xe5, 0xed, 0x80, 0x7f, 0x41, 0x6b, 0xb1, 0xea, 0x6a, 0xc5, 0xa5, 0xea, 0x11, - 0x1d, 0x41, 0xb8, 0xe0, 0xa4, 0xef, 0x4f, 0x25, 0xfd, 0x34, 0x8f, 0xfc, 0x29, 0x3a, 0x5c, 0x4c, - 0x85, 0x3b, 0xab, 0xf1, 0xeb, 0x25, 0xc0, 0xcf, 0xd1, 0xe6, 0x90, 0xda, 0xd8, 0x08, 0x32, 0xae, - 0xb1, 0xb8, 0x1b, 0xec, 0xad, 0x1c, 0x3c, 0x98, 0x4a, 0xe3, 0x47, 0x47, 0xc0, 0x4b, 0x52, 0xd0, - 0xc1, 0x19, 0x6b, 0x79, 0x0d, 0xff, 0x86, 0xb6, 0xaf, 0x9f, 0x37, 0xb1, 0x9a, 0xf4, 0x85, 0xec, - 0xf5, 0x6d, 0x78, 0xcb, 0x65, 0xf5, 0xf5, 0x54, 0x8a, 0x27, 0x63, 0xe5, 0x39, 0xd6, 0xdf, 0x3b, - 0x0a, 0x9f, 0x60, 0x35, 0x99, 0x88, 0xe2, 0x3f, 0x02, 0xb4, 0x53, 0x1c, 0x36, 0xe5, 0x5c, 0x5a, - 0xa9, 0x15, 0x89, 0x8c, 0x8e, 0x34, 0xd0, 0x01, 0x84, 0x4b, 0xce, 0xc0, 0xc3, 0x99, 0x2a, 0xfa, - 0xc8, 0xd3, 0xb4, 0x3d, 0x8b, 0xb7, 0xb0, 0xc5, 0x6e, 0xc0, 0x01, 0xbf, 0x08, 0xd0, 0x76, 0xe1, - 0xc2, 0x88, 0xa1, 0x4e, 0xe8, 0xa0, 0x64, 0xe2, 0xb6, 0x33, 0xf1, 0xcd, 0x4c, 0x26, 0x3a, 0x19, - 0xcb, 0x35, 0x0f, 0x21, 0x9b, 0x0c, 0x03, 0x6e, 0xa1, 0xa5, 0x88, 0x1a, 0x3a, 0x84, 0x70, 0xd9, - 0x55, 0xf9, 0xe3, 0xa9, 0xd4, 0xda, 0x2e, 0xc4, 0x93, 0x7b, 0x02, 0x97, 0x4d, 0x42, 0x07, 0x92, - 0x53, 0xab, 0x4d, 0xf1, 0x96, 0x49, 0x14, 0x77, 0x4f, 0xc5, 0x08, 0xc2, 0xca, 0x0c, 0xd9, 0x9c, - 0xe4, 0x34, 0x79, 0x5a, 0xed, 0xb8, 0xfb, 0x83, 0x18, 0xe5, 0xd9, 0x24, 0x13, 0xe0, 0x54, 0x03, - 0xff, 0x1e, 0xa0, 0x9d, 0x02, 0x04, 0xd2, 0x1d, 0x91, 0x72, 0x91, 0x4d, 0x88, 0xde, 0xc6, 0xc3, - 0xe1, 0xa8, 0x54, 0x61, 0xf3, 0x3f, 0x0f, 0x30, 0x8e, 0xe3, 0x04, 0xdd, 0x19, 0x13, 0x85, 0xf4, - 0x5e, 0x47, 0x26, 0x56, 0x22, 0x5c, 0x71, 0xf2, 0x5f, 0xce, 0x7a, 0xab, 0x0c, 0x1c, 0xeb, 0x76, - 0x4a, 0xe0, 0xb5, 0x37, 0xd9, 0x04, 0x0c, 0x9f, 0xa1, 0x3b, 0x52, 0x49, 0x4b, 0xac, 0x1c, 0x0a, - 0x1d, 0x67, 0x7f, 0xc1, 0xd2, 0x61, 0x04, 0xe1, 0xea, 0x0c, 0xba, 0x2d, 0x25, 0xed, 0x71, 0x46, - 0x71, 0x9c, 0x33, 0x78, 0xdd, 0xf7, 0xe5, 0x04, 0x0c, 0xf0, 0x9f, 0x01, 0xfa, 0x40, 0x9c, 0x47, - 0xda, 0x58, 0xc1, 0x49, 0x02, 0x8c, 0x80, 0x50, 0xbc, 0x2c, 0xbf, 0x36, 0xc3, 0x63, 0xfa, 0xd6, - 0x13, 0x9d, 0x00, 0x3b, 0x12, 0x8a, 0x5f, 0xb7, 0xb0, 0x25, 0x6e, 0xc0, 0xa1, 0xfe, 0x62, 0x11, - 0xad, 0x8d, 0x35, 0x57, 0xbc, 0x85, 0x96, 0x33, 0x35, 0xdf, 0xcb, 0x2b, 0x9d, 0xdb, 0xee, 0x77, - 0x8b, 0xe3, 0x0f, 0x11, 0x62, 0x7d, 0xaa, 0x94, 0x18, 0xa4, 0xe0, 0xbc, 0x03, 0x2b, 0x7e, 0xa5, - 0xc5, 0xf1, 0x0e, 0xaa, 0xb0, 0x81, 0x14, 0xca, 0xa6, 0xe8, 0x82, 0x43, 0x97, 0xb3, 0x85, 0x16, - 0xc7, 0xf7, 0xd0, 0x7a, 0x7a, 0x10, 0x92, 0x0e, 0xf2, 0x76, 0xb5, 0xe8, 0x06, 0xc5, 0x9a, 0x5f, - 0xf5, 0x2d, 0x86, 0xa2, 0x8d, 0xe2, 0x1e, 0xf8, 0x21, 0x1a, 0xde, 0x72, 0x6f, 0xec, 0xe6, 0x6e, - 0x5d, 0xaa, 0x7b, 0x79, 0x3a, 0xf9, 0xe4, 0x8b, 0xb9, 0xe3, 0x31, 0x6c, 0x51, 0x35, 0x12, 0x59, - 0x9f, 0xf6, 0xcd, 0x34, 0x4d, 0xa1, 0x27, 0xf2, 0xfe, 0xf5, 0xe0, 0x4d, 0x42, 0xc5, 0xfd, 0x3e, - 0x12, 0xf6, 0xb1, 0x0b, 0x6b, 0x53, 0x76, 0x2a, 0xec, 0x13, 0x6a, 0x69, 0x7e, 0xd1, 0x3c, 0x7b, - 0xd6, 0x62, 0xb3, 0x4d, 0x80, 0x3f, 0x41, 0x18, 0x06, 0x14, 0xfa, 0x84, 0xeb, 0x33, 0x95, 0x96, - 0x99, 0x50, 0x76, 0xea, 0x9a, 0x55, 0xa5, 0xb3, 0xe1, 0x90, 0x27, 0x1e, 0x78, 0xc4, 0x4e, 0xf1, - 0x73, 0xf4, 0xde, 0xd8, 0x34, 0x21, 0x52, 0x71, 0x71, 0x1e, 0x2e, 0x3b, 0x83, 0x9f, 0x4f, 0xf7, - 0x12, 0x81, 0x95, 0x67, 0x87, 0x37, 0xf7, 0x6e, 0x79, 0x76, 0xb5, 0x52, 0xd2, 0xfa, 0x33, 0x54, - 0x9d, 0x3c, 0x0d, 0x66, 0x18, 0xef, 0x55, 0xb4, 0xe4, 0xab, 0x3a, 0xef, 0x70, 0xff, 0xeb, 0xf0, - 0xe7, 0x97, 0x97, 0xb5, 0xe0, 0xe2, 0xb2, 0x16, 0xfc, 0x73, 0x59, 0x0b, 0xfe, 0xba, 0xaa, 0xcd, - 0x5d, 0x5c, 0xd5, 0xe6, 0xfe, 0xbe, 0xaa, 0xcd, 0x3d, 0x7b, 0xd8, 0x93, 0xb6, 0x1f, 0x77, 0x1b, - 0x4c, 0x0f, 0x9b, 0x4c, 0xc3, 0x50, 0x43, 0xf3, 0x75, 0x56, 0x9f, 0x16, 0x5f, 0x24, 0xc9, 0x17, - 0xcd, 0xf3, 0xf1, 0xcf, 0x12, 0x3b, 0x8a, 0x04, 0x74, 0x97, 0xdc, 0x17, 0xc9, 0x67, 0xff, 0x05, - 0x00, 0x00, 0xff, 0xff, 0xae, 0xb1, 0x22, 0x59, 0x8e, 0x09, 0x00, 0x00, + // 951 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0x5f, 0x6f, 0x1b, 0xc5, + 0x17, 0xcd, 0x26, 0x6e, 0x1a, 0x4f, 0xfe, 0xfc, 0xf2, 0x1b, 0x82, 0xbb, 0x49, 0xc0, 0x89, 0x8c, + 0x2a, 0x45, 0x02, 0xec, 0x26, 0x80, 0x28, 0x7f, 0xfa, 0xd0, 0xb4, 0x08, 0x2c, 0x84, 0xb0, 0x9c, + 0x34, 0x48, 0xe5, 0x61, 0x34, 0x9e, 0x1d, 0xd9, 0xd3, 0xac, 0x67, 0x56, 0x73, 0x67, 0x37, 0xb1, + 0x10, 0x52, 0x11, 0x3c, 0xf1, 0xc4, 0xc7, 0xea, 0x63, 0x1f, 0x79, 0xa1, 0x42, 0xc9, 0x37, 0xe0, + 0x13, 0xa0, 0x9d, 0x9d, 0xdd, 0xac, 0x53, 0xa7, 0xb2, 0x79, 0x4a, 0x3c, 0x67, 0xee, 0x39, 0xe7, + 0xce, 0x9d, 0xbd, 0x77, 0xd0, 0xbe, 0x90, 0x86, 0x6b, 0x36, 0xa0, 0x42, 0x12, 0xe0, 0x2c, 0xd6, + 0xc2, 0x8c, 0x5a, 0x8c, 0x25, 0xad, 0x48, 0xab, 0x44, 0x04, 0x5c, 0xb7, 0x92, 0xfd, 0x56, 0x9f, + 0x4b, 0x0e, 0x02, 0x9a, 0x91, 0x56, 0x46, 0xe1, 0xf7, 0x26, 0x84, 0x34, 0x19, 0x4b, 0x9a, 0x79, + 0x48, 0x33, 0xd9, 0xdf, 0xda, 0xe8, 0xab, 0xbe, 0xb2, 0xfb, 0x5b, 0xe9, 0x7f, 0x59, 0xe8, 0xd6, + 0xbd, 0x9b, 0xd4, 0x92, 0xfd, 0x16, 0x0c, 0xa8, 0xe6, 0x01, 0x61, 0x4a, 0x42, 0x3c, 0xe4, 0xda, + 0x45, 0xdc, 0x7d, 0x43, 0xc4, 0x99, 0xd0, 0xdc, 0x6d, 0x3b, 0x98, 0x26, 0x8d, 0xc2, 0x9f, 0x8d, + 0x69, 0xfc, 0xb5, 0x82, 0x56, 0xbe, 0xce, 0x32, 0x3b, 0x32, 0xd4, 0x70, 0xbc, 0x87, 0xd6, 0x13, + 0x1a, 0x02, 0x37, 0x24, 0x8e, 0x02, 0x6a, 0x38, 0x11, 0x81, 0xef, 0xed, 0x7a, 0x7b, 0x95, 0xee, + 0x5a, 0xb6, 0xfe, 0xc4, 0x2e, 0xb7, 0x03, 0xfc, 0x13, 0xfa, 0x5f, 0xee, 0x93, 0x40, 0x1a, 0x0b, + 0xfe, 0xfc, 0xee, 0xc2, 0xde, 0xf2, 0xc1, 0x41, 0x73, 0x8a, 0xc3, 0x69, 0x3e, 0x72, 0xb1, 0x56, + 0xf6, 0xb0, 0xfe, 0xe2, 0xd5, 0xce, 0xdc, 0x3f, 0xaf, 0x76, 0x6a, 0x23, 0x3a, 0x0c, 0x3f, 0x6f, + 0x5c, 0x23, 0x6e, 0x74, 0xd7, 0x58, 0x79, 0x3b, 0xe0, 0x1f, 0xd1, 0x6a, 0x2c, 0x7b, 0x4a, 0x06, + 0x42, 0xf6, 0x89, 0x8a, 0xc0, 0x5f, 0xb0, 0xd2, 0xf7, 0xa6, 0x92, 0x7e, 0x92, 0x47, 0x7e, 0x1f, + 0x1d, 0x56, 0x52, 0xe1, 0xee, 0x4a, 0x7c, 0xb5, 0x04, 0xf8, 0x19, 0xda, 0x18, 0x52, 0x13, 0x6b, + 0x4e, 0xc6, 0x35, 0x2a, 0xbb, 0xde, 0xde, 0xf2, 0xc1, 0xfd, 0xa9, 0x34, 0xbe, 0xb3, 0x04, 0x41, + 0x49, 0x0a, 0xba, 0x38, 0x63, 0x2d, 0xaf, 0xe1, 0x9f, 0xd1, 0xd6, 0xf5, 0xf3, 0x26, 0x46, 0x91, + 0x01, 0x17, 0xfd, 0x81, 0xf1, 0x6f, 0xd9, 0xac, 0xbe, 0x98, 0x4a, 0xf1, 0x64, 0xac, 0x3c, 0xc7, + 0xea, 0x1b, 0x4b, 0xe1, 0x12, 0xac, 0x25, 0x13, 0x51, 0xfc, 0xab, 0x87, 0xb6, 0x8b, 0xc3, 0xa6, + 0x41, 0x20, 0x8c, 0x50, 0x92, 0x44, 0x5a, 0x45, 0x0a, 0x68, 0x08, 0xfe, 0xa2, 0x35, 0xf0, 0x60, + 0xa6, 0x8a, 0x3e, 0x74, 0x34, 0x1d, 0xc7, 0xe2, 0x2c, 0x6c, 0xb2, 0x1b, 0x70, 0xc0, 0xcf, 0x3d, + 0xb4, 0x55, 0xb8, 0xd0, 0x7c, 0xa8, 0x12, 0x1a, 0x96, 0x4c, 0xdc, 0xb6, 0x26, 0xbe, 0x9c, 0xc9, + 0x44, 0x37, 0x63, 0xb9, 0xe6, 0xc1, 0x67, 0x93, 0x61, 0xc0, 0x6d, 0xb4, 0x18, 0x51, 0x4d, 0x87, + 0xe0, 0x2f, 0xd9, 0x2a, 0xbf, 0x3f, 0x95, 0x5a, 0xc7, 0x86, 0x38, 0x72, 0x47, 0x60, 0xb3, 0x49, + 0x68, 0x28, 0x02, 0x6a, 0x94, 0x2e, 0xbe, 0x65, 0x12, 0xc5, 0xbd, 0x53, 0x3e, 0x02, 0xbf, 0x3a, + 0x43, 0x36, 0x27, 0x39, 0x4d, 0x9e, 0x56, 0x27, 0xee, 0x7d, 0xcb, 0x47, 0x79, 0x36, 0xc9, 0x04, + 0x38, 0xd5, 0xc0, 0xbf, 0x78, 0x68, 0xbb, 0x00, 0x81, 0xf4, 0x46, 0xa4, 0x5c, 0x64, 0xed, 0xa3, + 0xff, 0xe2, 0xe1, 0x70, 0x54, 0xaa, 0xb0, 0x7e, 0xcd, 0x03, 0x8c, 0xe3, 0x38, 0x41, 0x77, 0xc6, + 0x44, 0x21, 0xbd, 0xd7, 0x91, 0x8e, 0x25, 0xf7, 0x97, 0xad, 0xfc, 0x67, 0xb3, 0xde, 0x2a, 0x0d, + 0xc7, 0xaa, 0x93, 0x12, 0x38, 0xed, 0x0d, 0x36, 0x01, 0xc3, 0x67, 0xe8, 0x8e, 0x90, 0xc2, 0x10, + 0x23, 0x86, 0x5c, 0xc5, 0xd9, 0x5f, 0x30, 0x74, 0x18, 0x81, 0xbf, 0x32, 0x83, 0x6e, 0x5b, 0x0a, + 0x73, 0x9c, 0x51, 0x1c, 0xe7, 0x0c, 0x4e, 0xf7, 0x6d, 0x31, 0x01, 0x03, 0xfc, 0x9b, 0x87, 0xde, + 0xe1, 0xe7, 0x91, 0xd2, 0x86, 0x07, 0x24, 0x01, 0x46, 0x80, 0xcb, 0xa0, 0x2c, 0xbf, 0x3a, 0xc3, + 0xc7, 0xf4, 0x95, 0x23, 0x3a, 0x01, 0x76, 0xc4, 0x65, 0x70, 0xdd, 0xc2, 0x26, 0xbf, 0x01, 0x07, + 0xfc, 0xbb, 0x87, 0x1a, 0x21, 0x05, 0x43, 0x72, 0x3a, 0x5b, 0x7b, 0x2e, 0x21, 0x06, 0x72, 0x55, + 0x2c, 0x7f, 0xcd, 0x9a, 0xf9, 0x74, 0xea, 0x1a, 0x58, 0x82, 0xab, 0xbb, 0x90, 0xd9, 0xd8, 0x49, + 0x85, 0x3a, 0x6e, 0xe7, 0xeb, 0xbb, 0xa0, 0xf1, 0xbc, 0x82, 0x56, 0xc7, 0x3a, 0x3d, 0xde, 0x44, + 0x4b, 0x99, 0x9a, 0x1b, 0x2c, 0xd5, 0xee, 0x6d, 0xfb, 0xbb, 0x1d, 0xe0, 0x77, 0x11, 0x62, 0x03, + 0x2a, 0x25, 0x0f, 0x53, 0x70, 0xde, 0x82, 0x55, 0xb7, 0xd2, 0x0e, 0xf0, 0x36, 0xaa, 0xb2, 0x50, + 0x70, 0x69, 0x52, 0x74, 0xc1, 0xa2, 0x4b, 0xd9, 0x42, 0x3b, 0xc0, 0x77, 0xd1, 0x5a, 0x5a, 0x15, + 0x41, 0xc3, 0xbc, 0x77, 0x56, 0xec, 0xd4, 0x5a, 0x75, 0xab, 0xae, 0xdf, 0x51, 0xb4, 0x5e, 0x5c, + 0x4a, 0x37, 0xd1, 0xfd, 0x5b, 0xf6, 0x83, 0xbf, 0x79, 0x74, 0x94, 0x2e, 0x61, 0x79, 0x54, 0xba, + 0x23, 0x28, 0x86, 0xa0, 0xc3, 0xb0, 0x41, 0xb5, 0x88, 0x67, 0x43, 0xc3, 0x75, 0xf6, 0x34, 0x85, + 0x3e, 0xcf, 0x9b, 0xe9, 0xfd, 0x37, 0x09, 0x15, 0x47, 0x77, 0xc4, 0xcd, 0x23, 0x1b, 0xd6, 0xa1, + 0xec, 0x94, 0x9b, 0xc7, 0xd4, 0xd0, 0xfc, 0xd6, 0x3b, 0xf6, 0xac, 0xdf, 0x67, 0x9b, 0x00, 0x7f, + 0x80, 0x30, 0x84, 0x14, 0x06, 0x24, 0x50, 0x67, 0x32, 0xbd, 0x73, 0x84, 0xb2, 0x53, 0xdb, 0x39, + 0xab, 0xdd, 0x75, 0x8b, 0x3c, 0x76, 0xc0, 0x43, 0x76, 0x8a, 0x9f, 0xa1, 0xb7, 0xc6, 0x46, 0x1b, + 0x11, 0x32, 0xe0, 0xe7, 0xfe, 0x92, 0x35, 0xf8, 0xf1, 0x74, 0x6d, 0x01, 0x58, 0x79, 0x90, 0x39, + 0x73, 0xff, 0x2f, 0x0f, 0xd2, 0x76, 0x4a, 0xda, 0x78, 0x8a, 0x6a, 0x93, 0x47, 0xd3, 0x0c, 0x6f, + 0x8d, 0x1a, 0x5a, 0x74, 0x55, 0x9d, 0xb7, 0xb8, 0xfb, 0x75, 0xf8, 0xc3, 0x8b, 0x8b, 0xba, 0xf7, + 0xf2, 0xa2, 0xee, 0xfd, 0x7d, 0x51, 0xf7, 0xfe, 0xb8, 0xac, 0xcf, 0xbd, 0xbc, 0xac, 0xcf, 0xfd, + 0x79, 0x59, 0x9f, 0x7b, 0xfa, 0xa0, 0x2f, 0xcc, 0x20, 0xee, 0x35, 0x99, 0x1a, 0xb6, 0x98, 0x82, + 0xa1, 0x82, 0xd6, 0x55, 0x56, 0x1f, 0x16, 0xcf, 0xa3, 0xe4, 0x93, 0xd6, 0xf9, 0xf8, 0x1b, 0xc9, + 0x8c, 0x22, 0x0e, 0xbd, 0x45, 0xfb, 0x3c, 0xfa, 0xe8, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x54, + 0xf5, 0x38, 0x0c, 0x1b, 0x0a, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -432,6 +443,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.LastProviderConsensusValidators) > 0 { + for iNdEx := len(m.LastProviderConsensusValidators) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.LastProviderConsensusValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x72 + } + } if len(m.ExportedVscSendTimestamps) > 0 { for iNdEx := len(m.ExportedVscSendTimestamps) - 1; iNdEx >= 0; iNdEx-- { { @@ -817,6 +842,12 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } + if len(m.LastProviderConsensusValidators) > 0 { + for _, e := range m.LastProviderConsensusValidators { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -1342,6 +1373,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastProviderConsensusValidators", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LastProviderConsensusValidators = append(m.LastProviderConsensusValidators, ConsensusValidator{}) + if err := m.LastProviderConsensusValidators[len(m.LastProviderConsensusValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/ccv/provider/types/genesis_test.go b/x/ccv/provider/types/genesis_test.go index f040a2ee08..23671723ae 100644 --- a/x/ccv/provider/types/genesis_test.go +++ b/x/ccv/provider/types/genesis_test.go @@ -43,6 +43,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), true, }, @@ -67,6 +68,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), true, }, @@ -82,7 +84,8 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -103,7 +106,8 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -124,7 +128,8 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -145,7 +150,8 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -172,7 +178,8 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -199,7 +206,8 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -226,7 +234,8 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: math.NewInt(1000000)}, 600), + sdk.Coin{Denom: "stake", Amount: math.NewInt(1000000)}, 600, 180), + nil, nil, nil, nil, @@ -253,7 +262,8 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -280,7 +290,8 @@ func TestValidateGenesisState(t *testing.T) { 0, // 0 vsc timeout here types.DefaultSlashMeterReplenishPeriod, types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -307,7 +318,8 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, 0, // 0 slash meter replenish period here types.DefaultSlashMeterReplenishFraction, - sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -334,7 +346,8 @@ func TestValidateGenesisState(t *testing.T) { types.DefaultVscTimeoutPeriod, types.DefaultSlashMeterReplenishPeriod, "1.15", - sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600), + sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -359,6 +372,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -378,6 +392,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -397,6 +412,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -416,6 +432,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -438,6 +455,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -461,6 +479,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -484,6 +503,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -511,6 +531,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -538,6 +559,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -565,6 +587,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -601,6 +624,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -649,6 +673,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -671,6 +696,7 @@ func TestValidateGenesisState(t *testing.T) { nil, nil, nil, + nil, ), false, }, @@ -686,7 +712,8 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: math.NewInt(10000000)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: math.NewInt(10000000)}, 600, 180), + nil, nil, nil, nil, @@ -707,7 +734,8 @@ func TestValidateGenesisState(t *testing.T) { nil, types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(-1000000)}, 600), + types.DefaultTrustingPeriodFraction, time.Hour, time.Hour, 30*time.Minute, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(-1000000)}, 600, 180), + nil, nil, nil, nil, diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 47231c219f..0adff80bd4 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -187,6 +187,21 @@ const ( // minimum power required to be in the top N per consumer chain. MinimumPowerInTopNBytePrefix + // LastProviderConsensusValsPrefix is the byte prefix for storing the last validator set + // sent to the consensus engine of the provider chain + LastProviderConsensusValsPrefix + + // MinStakePrefix is the byte prefix for storing the mapping from consumer chains to the minimum stake required to be a validator on the consumer chain + MinStakePrefix + + // MaxValidatorRankPrefix is the byte prefix for storing the mapping from consumer chains to the maximum position in the validator set + // a validator can have to be a validator on the consumer chain + MaxValidatorRankPrefix + + // AllowInactiveValidatorsPrefix is the byte prefix for storing the mapping from consumer chains to the boolean value + // that determines whether inactive validators can validate on that chain + AllowInactiveValidatorsPrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -621,6 +636,27 @@ func MinimumPowerInTopNKey(chainID string) []byte { return ChainIdWithLenKey(MinimumPowerInTopNBytePrefix, chainID) } +// LastProviderConsensusValidatorKey returns the key of the validator with `providerAddr` +// in the last validator set sent to the consensus engine of the provider chain +func LastProviderConsensusValidatorKey(providerAddr []byte) []byte { + return append([]byte{LastProviderConsensusValsPrefix}, providerAddr...) +} + +// MinStakeKey returns the key used to store the minimum stake required to validate on consumer chain `chainID` +func MinStakeKey(chainID string) []byte { + return ChainIdWithLenKey(MinStakePrefix, chainID) +} + +// MaxValidatorRankKey returns the key used to store the maximal position in the validator set +// a validator can have to validate on consumer chain `chainID` +func MaxValidatorRankKey(chainID string) []byte { + return ChainIdWithLenKey(MaxValidatorRankPrefix, chainID) +} + +func AllowInactiveValidatorsKey(chainID string) []byte { + return ChainIdWithLenKey(AllowInactiveValidatorsPrefix, chainID) +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 3f6df5cb0a..e4f9b52221 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -62,7 +62,11 @@ func getAllKeyPrefixes() []byte { providertypes.ConsumerRewardsAllocationBytePrefix, providertypes.ConsumerCommissionRatePrefix, providertypes.MinimumPowerInTopNBytePrefix, + providertypes.LastProviderConsensusValsPrefix, providertypes.ParametersByteKey, + providertypes.MinStakePrefix, + providertypes.MaxValidatorRankPrefix, + providertypes.AllowInactiveValidatorsPrefix, } } @@ -107,6 +111,9 @@ func getAllFullyDefinedKeys() [][]byte { providertypes.SlashLogKey(providertypes.NewProviderConsAddress([]byte{0x05})), providertypes.VSCMaturedHandledThisBlockKey(), providertypes.EquivocationEvidenceMinHeightKey("chainID"), + providertypes.MinStakeKey("chainID"), + providertypes.MaxValidatorRankKey("chainID"), + providertypes.AllowInactiveValidatorsKey("chainID"), } } diff --git a/x/ccv/provider/types/legacy_proposal.go b/x/ccv/provider/types/legacy_proposal.go index 37c068e1c2..44da78161d 100644 --- a/x/ccv/provider/types/legacy_proposal.go +++ b/x/ccv/provider/types/legacy_proposal.go @@ -58,6 +58,9 @@ func NewConsumerAdditionProposal(title, description, chainID string, validatorSetCap uint32, allowlist []string, denylist []string, + minStake uint64, + maxValidatorRank uint32, + allowInactiveVals bool, ) govv1beta1.Content { return &ConsumerAdditionProposal{ Title: title, @@ -79,6 +82,9 @@ func NewConsumerAdditionProposal(title, description, chainID string, ValidatorSetCap: validatorSetCap, Allowlist: allowlist, Denylist: denylist, + MinStake: minStake, + MaxRank: maxValidatorRank, + AllowInactiveVals: allowInactiveVals, } } @@ -243,6 +249,9 @@ func NewConsumerModificationProposal(title, description, chainID string, validatorSetCap uint32, allowlist []string, denylist []string, + minStake uint64, + maxValidatorRank uint32, + allowInactiveVals bool, ) govv1beta1.Content { return &ConsumerModificationProposal{ Title: title, @@ -253,6 +262,9 @@ func NewConsumerModificationProposal(title, description, chainID string, ValidatorSetCap: validatorSetCap, Allowlist: allowlist, Denylist: denylist, + MinStake: minStake, + MaxRank: maxValidatorRank, + AllowInactiveVals: allowInactiveVals, } } diff --git a/x/ccv/provider/types/legacy_proposal_test.go b/x/ccv/provider/types/legacy_proposal_test.go index 729a4318ca..8dd392f86b 100644 --- a/x/ccv/provider/types/legacy_proposal_test.go +++ b/x/ccv/provider/types/legacy_proposal_test.go @@ -41,6 +41,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), true, }, @@ -59,6 +62,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), true, }, @@ -77,6 +83,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -95,6 +104,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -133,6 +145,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -150,7 +165,11 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, 0, nil, - nil), + nil, + 0, + 0, + false, + ), false, }, { @@ -168,6 +187,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -186,6 +208,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -204,6 +229,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -222,6 +250,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -240,6 +271,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -258,6 +292,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -276,6 +313,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -294,6 +334,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -312,6 +355,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -330,6 +376,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -348,6 +397,9 @@ func TestConsumerAdditionProposalValidateBasic(t *testing.T) { 101, []string{"addr1"}, []string{"addr2", "addr3"}, + 0, + 0, + false, ), true, }, @@ -377,7 +429,11 @@ func TestMarshalConsumerAdditionProposal(t *testing.T) { 0, 0, nil, - nil) + nil, + 0, + 0, + false, + ) cccp, ok := content.(*types.ConsumerAdditionProposal) require.True(t, ok) @@ -424,7 +480,11 @@ func TestConsumerAdditionProposalString(t *testing.T) { 0, 0, []string{}, - []string{}) + []string{}, + 0, + 0, + false, + ) expect := fmt.Sprintf(`CreateConsumerChain Proposal Title: title @@ -515,6 +575,9 @@ func TestConsumerModificationProposalValidateBasic(t *testing.T) { 34, []string{"addr1"}, nil, + 0, + 0, + false, ), true, }, @@ -526,6 +589,9 @@ func TestConsumerModificationProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -537,6 +603,9 @@ func TestConsumerModificationProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -548,6 +617,9 @@ func TestConsumerModificationProposalValidateBasic(t *testing.T) { 0, nil, nil, + 0, + 0, + false, ), false, }, @@ -559,6 +631,9 @@ func TestConsumerModificationProposalValidateBasic(t *testing.T) { 101, []string{"addr1"}, []string{"addr2", "addr3"}, + 0, + 0, + false, ), true, }, diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 058ee1d2e5..98d17f7474 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -295,7 +295,6 @@ func (msg *MsgConsumerAddition) ValidateBasic() error { } func (msg *MsgConsumerRemoval) ValidateBasic() error { - if strings.TrimSpace(msg.ChainId) == "" { return errorsmod.Wrap(ErrInvalidConsumerRemovalProp, "consumer chain id must not be blank") } diff --git a/x/ccv/provider/types/params.go b/x/ccv/provider/types/params.go index ac677004bc..16fb112bf7 100644 --- a/x/ccv/provider/types/params.go +++ b/x/ccv/provider/types/params.go @@ -42,6 +42,10 @@ const ( // an epoch corresponds to 1 hour (6 * 600 = 3600 seconds). // forcing int64 as the Params KeyTable expects an int64 and not int. DefaultBlocksPerEpoch = int64(600) + + // DefaultMaxProviderConsensusValidators is the default maximum number of validators that will + // be passed on from the staking module to the consensus engine on the provider. + DefaultMaxProviderConsensusValidators = 180 ) // Reflection based keys for params subspace @@ -57,6 +61,7 @@ var ( KeySlashMeterReplenishFraction = []byte("SlashMeterReplenishFraction") KeyConsumerRewardDenomRegistrationFee = []byte("ConsumerRewardDenomRegistrationFee") KeyBlocksPerEpoch = []byte("BlocksPerEpoch") + KeyMaxProviderConsensusValidators = []byte("MaxProviderConsensusValidators") ) // ParamKeyTable returns a key table with the necessary registered provider params @@ -75,6 +80,7 @@ func NewParams( slashMeterReplenishFraction string, consumerRewardDenomRegistrationFee sdk.Coin, blocksPerEpoch int64, + maxProviderConsensusValidators int64, ) Params { return Params{ TemplateClient: cs, @@ -86,6 +92,7 @@ func NewParams( SlashMeterReplenishFraction: slashMeterReplenishFraction, ConsumerRewardDenomRegistrationFee: consumerRewardDenomRegistrationFee, BlocksPerEpoch: blocksPerEpoch, + MaxProviderConsensusValidators: maxProviderConsensusValidators, } } @@ -117,6 +124,7 @@ func DefaultParams() Params { Amount: math.NewInt(10000000), }, DefaultBlocksPerEpoch, + DefaultMaxProviderConsensusValidators, ) } @@ -152,6 +160,9 @@ func (p Params) Validate() error { if err := ccvtypes.ValidateInt64(p.BlocksPerEpoch); err != nil { return fmt.Errorf("blocks per epoch is invalid: %s", err) } + if err := ccvtypes.ValidatePositiveInt64(p.MaxProviderConsensusValidators); err != nil { + return fmt.Errorf("max provider consensus validators is invalid: %s", err) + } return nil } @@ -167,6 +178,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeySlashMeterReplenishFraction, p.SlashMeterReplenishFraction, ccvtypes.ValidateStringFraction), paramtypes.NewParamSetPair(KeyConsumerRewardDenomRegistrationFee, p.ConsumerRewardDenomRegistrationFee, ValidateCoin), paramtypes.NewParamSetPair(KeyBlocksPerEpoch, p.BlocksPerEpoch, ccvtypes.ValidatePositiveInt64), + paramtypes.NewParamSetPair(KeyMaxProviderConsensusValidators, p.MaxProviderConsensusValidators, ccvtypes.ValidatePositiveInt64), } } diff --git a/x/ccv/provider/types/params_test.go b/x/ccv/provider/types/params_test.go index 675ddd0bc5..e4996557c5 100644 --- a/x/ccv/provider/types/params_test.go +++ b/x/ccv/provider/types/params_test.go @@ -25,39 +25,43 @@ func TestValidateParams(t *testing.T) { {"custom valid params", types.NewParams( ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), true}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), true}, {"custom invalid params", types.NewParams( ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, 0, clienttypes.Height{}, nil, []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, {"blank client", types.NewParams(&ibctmtypes.ClientState{}, - "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, - {"nil client", types.NewParams(nil, "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, + {"nil client", types.NewParams(nil, "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, // Check if "0.00" is valid or if a zero dec TrustFraction needs to return an error {"0 trusting period fraction", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), true}, + "0.00", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), true}, {"0 ccv timeout period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", 0, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", 0, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, {"0 init timeout period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, 0, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", time.Hour, 0, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, {"0 vsc timeout period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 0, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", time.Hour, time.Hour, 0, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, {"0 slash meter replenish period", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, 0, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, 0, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, {"slash meter replenish fraction over 1", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "1.5", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "1.5", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 180), false}, {"invalid consumer reward denom registration fee denom", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: math.NewInt(10000000)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "st", Amount: math.NewInt(10000000)}, 1000, 180), false}, {"invalid consumer reward denom registration fee amount", types.NewParams(ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), - "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(-10000000)}, 1000), false}, + "0.33", time.Hour, time.Hour, 24*time.Hour, time.Hour, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(-10000000)}, 1000, 180), false}, + {"0 max provider consensus validators", types.NewParams( + ibctmtypes.NewClientState("", ibctmtypes.DefaultTrustLevel, 0, 0, + time.Second*40, clienttypes.Height{}, commitmenttypes.GetSDKSpecs(), []string{"ibc", "upgradedIBCState"}), + "0.33", time.Hour, time.Hour, time.Hour, 30*time.Minute, "0.1", sdk.Coin{Denom: "stake", Amount: math.NewInt(10000000)}, 1000, 0), false}, } for _, tc := range testCases { diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index dbada484bc..a8bacf0f3f 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -113,6 +113,12 @@ type ConsumerAdditionProposal struct { Allowlist []string `protobuf:"bytes,18,rep,name=allowlist,proto3" json:"allowlist,omitempty"` // Corresponds to a list of provider consensus addresses of validators that CANNOT validate the consumer chain. Denylist []string `protobuf:"bytes,19,rep,name=denylist,proto3" json:"denylist,omitempty"` + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + MinStake uint64 `protobuf:"varint,20,opt,name=min_stake,json=minStake,proto3" json:"min_stake,omitempty"` + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + MaxRank uint32 `protobuf:"varint,21,opt,name=max_rank,json=maxRank,proto3" json:"max_rank,omitempty"` + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + AllowInactiveVals bool `protobuf:"varint,22,opt,name=allow_inactive_vals,json=allowInactiveVals,proto3" json:"allow_inactive_vals,omitempty"` } func (m *ConsumerAdditionProposal) Reset() { *m = ConsumerAdditionProposal{} } @@ -253,6 +259,12 @@ type ConsumerModificationProposal struct { Allowlist []string `protobuf:"bytes,7,rep,name=allowlist,proto3" json:"allowlist,omitempty"` // Corresponds to a list of provider consensus addresses of validators that CANNOT validate the consumer chain. Denylist []string `protobuf:"bytes,8,rep,name=denylist,proto3" json:"denylist,omitempty"` + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + MinStake uint64 `protobuf:"varint,9,opt,name=min_stake,json=minStake,proto3" json:"min_stake,omitempty"` + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + MaxRank uint32 `protobuf:"varint,10,opt,name=max_rank,json=maxRank,proto3" json:"max_rank,omitempty"` + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + AllowInactiveVals bool `protobuf:"varint,11,opt,name=allow_inactive_vals,json=allowInactiveVals,proto3" json:"allow_inactive_vals,omitempty"` } func (m *ConsumerModificationProposal) Reset() { *m = ConsumerModificationProposal{} } @@ -344,6 +356,27 @@ func (m *ConsumerModificationProposal) GetDenylist() []string { return nil } +func (m *ConsumerModificationProposal) GetMinStake() uint64 { + if m != nil { + return m.MinStake + } + return 0 +} + +func (m *ConsumerModificationProposal) GetMaxRank() uint32 { + if m != nil { + return m.MaxRank + } + return 0 +} + +func (m *ConsumerModificationProposal) GetAllowInactiveVals() bool { + if m != nil { + return m.AllowInactiveVals + } + return false +} + // EquivocationProposal is a governance proposal on the provider chain to // punish a validator for equivocation on a consumer chain. // @@ -597,6 +630,9 @@ type Params struct { ConsumerRewardDenomRegistrationFee types2.Coin `protobuf:"bytes,9,opt,name=consumer_reward_denom_registration_fee,json=consumerRewardDenomRegistrationFee,proto3" json:"consumer_reward_denom_registration_fee"` // The number of blocks that comprise an epoch. BlocksPerEpoch int64 `protobuf:"varint,10,opt,name=blocks_per_epoch,json=blocksPerEpoch,proto3" json:"blocks_per_epoch,omitempty"` + // The maximal number of validators that will be passed + // to the consensus engine on the provider. + MaxProviderConsensusValidators int64 `protobuf:"varint,11,opt,name=max_provider_consensus_validators,json=maxProviderConsensusValidators,proto3" json:"max_provider_consensus_validators,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -695,6 +731,13 @@ func (m *Params) GetBlocksPerEpoch() int64 { return 0 } +func (m *Params) GetMaxProviderConsensusValidators() int64 { + if m != nil { + return m.MaxProviderConsensusValidators + } + return 0 +} + // SlashAcks contains cons addresses of consumer chain validators // successfully slashed on the provider chain. type SlashAcks struct { @@ -1538,29 +1581,32 @@ func (m *ConsumerAddrsToPrune) GetConsumerAddrs() *AddressList { return nil } -// ConsumerValidator is used to facilitate epoch-based transitions. It contains relevant info for -// a validator that is expected to validate on a consumer chain during an epoch. -type ConsumerValidator struct { +// ConsensusValidator is used to express a validator that +// should be validating on a chain. +// It contains relevant info for +// a validator that is expected to validate on +// either the provider or a consumer chain. +type ConsensusValidator struct { // validator's consensus address on the provider chain ProviderConsAddr []byte `protobuf:"bytes,1,opt,name=provider_cons_addr,json=providerConsAddr,proto3" json:"provider_cons_addr,omitempty"` // voting power the validator has during this epoch Power int64 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"` - // public key the validator uses on the consumer chain during this epoch - ConsumerPublicKey *crypto.PublicKey `protobuf:"bytes,3,opt,name=consumer_public_key,json=consumerPublicKey,proto3" json:"consumer_public_key,omitempty"` + // public key the validator uses on the chain it is validating on + PublicKey *crypto.PublicKey `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` } -func (m *ConsumerValidator) Reset() { *m = ConsumerValidator{} } -func (m *ConsumerValidator) String() string { return proto.CompactTextString(m) } -func (*ConsumerValidator) ProtoMessage() {} -func (*ConsumerValidator) Descriptor() ([]byte, []int) { +func (m *ConsensusValidator) Reset() { *m = ConsensusValidator{} } +func (m *ConsensusValidator) String() string { return proto.CompactTextString(m) } +func (*ConsensusValidator) ProtoMessage() {} +func (*ConsensusValidator) Descriptor() ([]byte, []int) { return fileDescriptor_f22ec409a72b7b72, []int{23} } -func (m *ConsumerValidator) XXX_Unmarshal(b []byte) error { +func (m *ConsensusValidator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *ConsumerValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *ConsensusValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_ConsumerValidator.Marshal(b, m, deterministic) + return xxx_messageInfo_ConsensusValidator.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -1570,35 +1616,35 @@ func (m *ConsumerValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, e return b[:n], nil } } -func (m *ConsumerValidator) XXX_Merge(src proto.Message) { - xxx_messageInfo_ConsumerValidator.Merge(m, src) +func (m *ConsensusValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusValidator.Merge(m, src) } -func (m *ConsumerValidator) XXX_Size() int { +func (m *ConsensusValidator) XXX_Size() int { return m.Size() } -func (m *ConsumerValidator) XXX_DiscardUnknown() { - xxx_messageInfo_ConsumerValidator.DiscardUnknown(m) +func (m *ConsensusValidator) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusValidator.DiscardUnknown(m) } -var xxx_messageInfo_ConsumerValidator proto.InternalMessageInfo +var xxx_messageInfo_ConsensusValidator proto.InternalMessageInfo -func (m *ConsumerValidator) GetProviderConsAddr() []byte { +func (m *ConsensusValidator) GetProviderConsAddr() []byte { if m != nil { return m.ProviderConsAddr } return nil } -func (m *ConsumerValidator) GetPower() int64 { +func (m *ConsensusValidator) GetPower() int64 { if m != nil { return m.Power } return 0 } -func (m *ConsumerValidator) GetConsumerPublicKey() *crypto.PublicKey { +func (m *ConsensusValidator) GetPublicKey() *crypto.PublicKey { if m != nil { - return m.ConsumerPublicKey + return m.PublicKey } return nil } @@ -1674,7 +1720,7 @@ func init() { proto.RegisterType((*ValidatorConsumerPubKey)(nil), "interchain_security.ccv.provider.v1.ValidatorConsumerPubKey") proto.RegisterType((*ValidatorByConsumerAddr)(nil), "interchain_security.ccv.provider.v1.ValidatorByConsumerAddr") proto.RegisterType((*ConsumerAddrsToPrune)(nil), "interchain_security.ccv.provider.v1.ConsumerAddrsToPrune") - proto.RegisterType((*ConsumerValidator)(nil), "interchain_security.ccv.provider.v1.ConsumerValidator") + proto.RegisterType((*ConsensusValidator)(nil), "interchain_security.ccv.provider.v1.ConsensusValidator") proto.RegisterType((*ConsumerRewardsAllocation)(nil), "interchain_security.ccv.provider.v1.ConsumerRewardsAllocation") } @@ -1683,132 +1729,139 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1988 bytes of a gzipped FileDescriptorProto + // 2098 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcb, 0x6f, 0x1b, 0xc7, - 0x19, 0xd7, 0x92, 0x94, 0x44, 0x0e, 0xf5, 0x1c, 0x29, 0xf1, 0x4a, 0x55, 0x29, 0x7a, 0xd3, 0xb8, - 0x6a, 0x5c, 0x2f, 0x23, 0x07, 0x01, 0x0c, 0xa1, 0x41, 0x20, 0x51, 0x4e, 0x2c, 0x2b, 0xb6, 0x95, - 0x95, 0x2a, 0xa3, 0xed, 0x61, 0x31, 0x9c, 0x1d, 0x93, 0x03, 0x2d, 0x77, 0xd6, 0x33, 0xc3, 0x55, - 0x78, 0xe9, 0xb9, 0x97, 0x02, 0xe9, 0x2d, 0xe8, 0xa1, 0x4d, 0x0b, 0x14, 0x28, 0x72, 0xea, 0xa1, - 0x7f, 0x41, 0x4f, 0x41, 0x2f, 0xcd, 0xb1, 0xa7, 0xa4, 0xb0, 0x0f, 0x3d, 0xf4, 0x9f, 0x28, 0x66, - 0xf6, 0x49, 0x4a, 0xb2, 0x69, 0x24, 0xb9, 0x48, 0xbb, 0xdf, 0xe3, 0xf7, 0x7d, 0x33, 0xdf, 0x93, - 0x0b, 0x6e, 0xd3, 0x40, 0x12, 0x8e, 0x7b, 0x88, 0x06, 0xae, 0x20, 0x78, 0xc0, 0xa9, 0x1c, 0xb6, - 0x30, 0x8e, 0x5a, 0x21, 0x67, 0x11, 0xf5, 0x08, 0x6f, 0x45, 0xdb, 0xd9, 0xb3, 0x1d, 0x72, 0x26, - 0x19, 0x7c, 0xe3, 0x12, 0x1d, 0x1b, 0xe3, 0xc8, 0xce, 0xe4, 0xa2, 0xed, 0xf5, 0x37, 0xaf, 0x02, - 0x8e, 0xb6, 0x5b, 0xe7, 0x94, 0x93, 0x18, 0x6b, 0x7d, 0xb5, 0xcb, 0xba, 0x4c, 0x3f, 0xb6, 0xd4, - 0x53, 0x42, 0xdd, 0xec, 0x32, 0xd6, 0xf5, 0x49, 0x4b, 0xbf, 0x75, 0x06, 0x4f, 0x5a, 0x92, 0xf6, - 0x89, 0x90, 0xa8, 0x1f, 0x26, 0x02, 0x8d, 0x71, 0x01, 0x6f, 0xc0, 0x91, 0xa4, 0x2c, 0x48, 0x01, - 0x68, 0x07, 0xb7, 0x30, 0xe3, 0xa4, 0x85, 0x7d, 0x4a, 0x02, 0xa9, 0xac, 0xc6, 0x4f, 0x89, 0x40, - 0x4b, 0x09, 0xf8, 0xb4, 0xdb, 0x93, 0x31, 0x59, 0xb4, 0x24, 0x09, 0x3c, 0xc2, 0xfb, 0x34, 0x16, - 0xce, 0xdf, 0x12, 0x85, 0x8d, 0x02, 0x1f, 0xf3, 0x61, 0x28, 0x59, 0xeb, 0x8c, 0x0c, 0x45, 0xc2, - 0xbd, 0x81, 0x99, 0xe8, 0x33, 0xd1, 0x22, 0xea, 0xfc, 0x01, 0x26, 0xad, 0x68, 0xbb, 0x43, 0x24, - 0xda, 0xce, 0x08, 0xa9, 0xdf, 0x89, 0x5c, 0x07, 0x89, 0x5c, 0x06, 0x33, 0x9a, 0xfa, 0xbd, 0x16, - 0xf3, 0xdd, 0xf8, 0x46, 0xe2, 0x97, 0x84, 0xb5, 0x8c, 0xfa, 0x34, 0x60, 0x2d, 0xfd, 0x37, 0x26, - 0x59, 0x5f, 0x54, 0x81, 0xd9, 0x66, 0x81, 0x18, 0xf4, 0x09, 0xdf, 0xf5, 0x3c, 0xaa, 0x2e, 0xe0, - 0x88, 0xb3, 0x90, 0x09, 0xe4, 0xc3, 0x55, 0x30, 0x2d, 0xa9, 0xf4, 0x89, 0x69, 0x34, 0x8d, 0xad, - 0x9a, 0x13, 0xbf, 0xc0, 0x26, 0xa8, 0x7b, 0x44, 0x60, 0x4e, 0x43, 0x25, 0x6c, 0x96, 0x34, 0xaf, - 0x48, 0x82, 0x6b, 0xa0, 0x1a, 0x47, 0x8d, 0x7a, 0x66, 0x59, 0xb3, 0x67, 0xf5, 0xfb, 0x81, 0x07, - 0x3f, 0x04, 0x0b, 0x34, 0xa0, 0x92, 0x22, 0xdf, 0xed, 0x11, 0x75, 0x77, 0x66, 0xa5, 0x69, 0x6c, - 0xd5, 0x6f, 0xaf, 0xdb, 0xb4, 0x83, 0x6d, 0x75, 0xdd, 0x76, 0x72, 0xc9, 0xd1, 0xb6, 0x7d, 0x4f, - 0x4b, 0xec, 0x55, 0xbe, 0xfc, 0x7a, 0x73, 0xca, 0x99, 0x4f, 0xf4, 0x62, 0x22, 0xbc, 0x0e, 0xe6, - 0xba, 0x24, 0x20, 0x82, 0x0a, 0xb7, 0x87, 0x44, 0xcf, 0x9c, 0x6e, 0x1a, 0x5b, 0x73, 0x4e, 0x3d, - 0xa1, 0xdd, 0x43, 0xa2, 0x07, 0x37, 0x41, 0xbd, 0x43, 0x03, 0xc4, 0x87, 0xb1, 0xc4, 0x8c, 0x96, - 0x00, 0x31, 0x49, 0x0b, 0xb4, 0x01, 0x10, 0x21, 0x3a, 0x0f, 0x5c, 0x95, 0x1b, 0xe6, 0x6c, 0xe2, - 0x48, 0x9c, 0x17, 0x76, 0x9a, 0x17, 0xf6, 0x49, 0x9a, 0x38, 0x7b, 0x55, 0xe5, 0xc8, 0xa7, 0xdf, - 0x6c, 0x1a, 0x4e, 0x4d, 0xeb, 0x29, 0x0e, 0x7c, 0x08, 0x96, 0x06, 0x41, 0x87, 0x05, 0x1e, 0x0d, - 0xba, 0x6e, 0x48, 0x38, 0x65, 0x9e, 0x59, 0xd5, 0x50, 0x6b, 0x17, 0xa0, 0xf6, 0x93, 0x14, 0x8b, - 0x91, 0x3e, 0x53, 0x48, 0x8b, 0x99, 0xf2, 0x91, 0xd6, 0x85, 0x1f, 0x03, 0x88, 0x71, 0xa4, 0x5d, - 0x62, 0x03, 0x99, 0x22, 0xd6, 0x26, 0x47, 0x5c, 0xc2, 0x38, 0x3a, 0x89, 0xb5, 0x13, 0xc8, 0x5f, - 0x81, 0x6b, 0x92, 0xa3, 0x40, 0x3c, 0x21, 0x7c, 0x1c, 0x17, 0x4c, 0x8e, 0xfb, 0x5a, 0x8a, 0x31, - 0x0a, 0x7e, 0x0f, 0x34, 0x71, 0x92, 0x40, 0x2e, 0x27, 0x1e, 0x15, 0x92, 0xd3, 0xce, 0x40, 0xe9, - 0xba, 0x4f, 0x38, 0xc2, 0x3a, 0x47, 0xea, 0x3a, 0x09, 0x1a, 0xa9, 0x9c, 0x33, 0x22, 0xf6, 0x41, - 0x22, 0x05, 0x1f, 0x81, 0x1f, 0x75, 0x7c, 0x86, 0xcf, 0x84, 0x72, 0xce, 0x1d, 0x41, 0xd2, 0xa6, - 0xfb, 0x54, 0x08, 0x85, 0x36, 0xd7, 0x34, 0xb6, 0xca, 0xce, 0xf5, 0x58, 0xf6, 0x88, 0xf0, 0xfd, - 0x82, 0xe4, 0x49, 0x41, 0x10, 0xde, 0x02, 0xb0, 0x47, 0x85, 0x64, 0x9c, 0x62, 0xe4, 0xbb, 0x24, - 0x90, 0x9c, 0x12, 0x61, 0xce, 0x6b, 0xf5, 0xe5, 0x9c, 0x73, 0x37, 0x66, 0xc0, 0xfb, 0xe0, 0xfa, - 0x95, 0x46, 0x5d, 0xdc, 0x43, 0x41, 0x40, 0x7c, 0x73, 0x41, 0x1f, 0x65, 0xd3, 0xbb, 0xc2, 0x66, - 0x3b, 0x16, 0x83, 0x2b, 0x60, 0x5a, 0xb2, 0xd0, 0x7d, 0x68, 0x2e, 0x36, 0x8d, 0xad, 0x79, 0xa7, - 0x22, 0x59, 0xf8, 0x10, 0xbe, 0x0d, 0x56, 0x23, 0xe4, 0x53, 0x0f, 0x49, 0xc6, 0x85, 0x1b, 0xb2, - 0x73, 0xc2, 0x5d, 0x8c, 0x42, 0x73, 0x49, 0xcb, 0xc0, 0x9c, 0x77, 0xa4, 0x58, 0x6d, 0x14, 0xc2, - 0xb7, 0xc0, 0x72, 0x46, 0x75, 0x05, 0x91, 0x5a, 0x7c, 0x59, 0x8b, 0x2f, 0x66, 0x8c, 0x63, 0x22, - 0x95, 0xec, 0x06, 0xa8, 0x21, 0xdf, 0x67, 0xe7, 0x3e, 0x15, 0xd2, 0x84, 0xcd, 0xf2, 0x56, 0xcd, - 0xc9, 0x09, 0x70, 0x1d, 0x54, 0x3d, 0x12, 0x0c, 0x35, 0x73, 0x45, 0x33, 0xb3, 0xf7, 0x9d, 0x1b, - 0xbf, 0xf9, 0x7c, 0x73, 0xea, 0xb3, 0xcf, 0x37, 0xa7, 0xfe, 0xf9, 0xf7, 0x5b, 0xeb, 0x49, 0xc7, - 0xe8, 0xb2, 0xc8, 0x4e, 0xba, 0x8b, 0xdd, 0x66, 0x81, 0x24, 0x81, 0xb4, 0xfe, 0x65, 0x80, 0x6b, - 0xed, 0x2c, 0x86, 0x7d, 0x16, 0x21, 0xff, 0xfb, 0xec, 0x15, 0xbb, 0xa0, 0x26, 0xd4, 0x25, 0xea, - 0xea, 0xac, 0xbc, 0x42, 0x75, 0x56, 0x95, 0x9a, 0x62, 0xec, 0x34, 0x5e, 0x72, 0xa2, 0x3f, 0x94, - 0xc0, 0x46, 0x7a, 0xa2, 0x07, 0xcc, 0xa3, 0x4f, 0x28, 0x46, 0xdf, 0x77, 0x0b, 0xcc, 0x52, 0xa3, - 0x32, 0x41, 0x6a, 0x4c, 0xbf, 0x5a, 0x6a, 0xcc, 0x4c, 0x90, 0x1a, 0xb3, 0x2f, 0x4a, 0x8d, 0xea, - 0x68, 0x6a, 0x58, 0x7f, 0x34, 0xc0, 0xea, 0xdd, 0xa7, 0x03, 0x1a, 0xb1, 0xef, 0xe8, 0x62, 0x0e, - 0xc1, 0x3c, 0x29, 0xe0, 0x09, 0xb3, 0xdc, 0x2c, 0x6f, 0xd5, 0x6f, 0xbf, 0x69, 0x27, 0x51, 0xca, - 0xa6, 0x5d, 0x1a, 0xaa, 0xa2, 0x75, 0x67, 0x54, 0x77, 0xa7, 0x64, 0x1a, 0xd6, 0x3f, 0x0c, 0xb0, - 0xae, 0xaa, 0xae, 0x4b, 0x1c, 0x72, 0x8e, 0xb8, 0xb7, 0x4f, 0x02, 0xd6, 0x17, 0xdf, 0xda, 0x4f, - 0x0b, 0xcc, 0x7b, 0x1a, 0xc9, 0x95, 0xcc, 0x45, 0x9e, 0xa7, 0xfd, 0xd4, 0x32, 0x8a, 0x78, 0xc2, - 0x76, 0x3d, 0x0f, 0x6e, 0x81, 0xa5, 0x5c, 0x86, 0xab, 0x82, 0x50, 0x79, 0xaa, 0xc4, 0x16, 0x52, - 0x31, 0x5d, 0x26, 0x2f, 0xcf, 0xc3, 0xff, 0x19, 0x60, 0xe9, 0x43, 0x9f, 0x75, 0x90, 0x7f, 0xec, - 0x23, 0xd1, 0x53, 0x1d, 0x69, 0xa8, 0xf2, 0x9f, 0x93, 0x64, 0x14, 0x68, 0xf7, 0x27, 0xce, 0x7f, - 0xa5, 0xa6, 0x87, 0xd3, 0xfb, 0x60, 0x39, 0x6b, 0xce, 0x59, 0x3e, 0xea, 0xd3, 0xee, 0xad, 0x3c, - 0xfb, 0x7a, 0x73, 0x31, 0xcd, 0xfd, 0xb6, 0xce, 0xcd, 0x7d, 0x67, 0x11, 0x8f, 0x10, 0x3c, 0xd8, - 0x00, 0x75, 0xda, 0xc1, 0xae, 0x20, 0x4f, 0xdd, 0x60, 0xd0, 0xd7, 0xa9, 0x5c, 0x71, 0x6a, 0xb4, - 0x83, 0x8f, 0xc9, 0xd3, 0x87, 0x83, 0x3e, 0x7c, 0x07, 0xbc, 0x9e, 0xae, 0x6c, 0x6e, 0x84, 0x7c, - 0x57, 0xe9, 0xab, 0xeb, 0xe2, 0x3a, 0xbb, 0xe7, 0x9c, 0x95, 0x94, 0x7b, 0x8a, 0x7c, 0x65, 0x6c, - 0xd7, 0xf3, 0xb8, 0xf5, 0x7c, 0x1a, 0xcc, 0x1c, 0x21, 0x8e, 0xfa, 0x02, 0x9e, 0x80, 0x45, 0x49, - 0xfa, 0xa1, 0x8f, 0x24, 0x71, 0xe3, 0xc1, 0x9f, 0x9c, 0xf4, 0xa6, 0x5e, 0x08, 0x8a, 0xeb, 0x95, - 0x5d, 0x58, 0xa8, 0xa2, 0x6d, 0xbb, 0xad, 0xa9, 0xc7, 0x12, 0x49, 0xe2, 0x2c, 0xa4, 0x18, 0x31, - 0x11, 0xde, 0x01, 0xa6, 0xe4, 0x03, 0x21, 0xf3, 0x91, 0x9c, 0xcf, 0xa2, 0x38, 0xd6, 0xaf, 0xa7, - 0xfc, 0x78, 0x8a, 0x65, 0x33, 0xe8, 0xf2, 0xe9, 0x5b, 0xfe, 0x36, 0xd3, 0xf7, 0x18, 0xac, 0xa8, - 0xd5, 0x65, 0x1c, 0xb3, 0x32, 0x39, 0xe6, 0xb2, 0xd2, 0x1f, 0x05, 0xfd, 0x18, 0xc0, 0x48, 0xe0, - 0x71, 0xcc, 0xe9, 0x57, 0xf0, 0x33, 0x12, 0x78, 0x14, 0xd2, 0x03, 0x1b, 0x42, 0x25, 0x9f, 0xdb, - 0x27, 0x52, 0xcf, 0xf2, 0xd0, 0x27, 0x01, 0x15, 0xbd, 0x14, 0x7c, 0x66, 0x72, 0xf0, 0x35, 0x0d, - 0xf4, 0x40, 0xe1, 0x38, 0x29, 0x4c, 0x62, 0xa5, 0x0d, 0x1a, 0x97, 0x5b, 0xc9, 0x02, 0x34, 0xab, - 0x03, 0xf4, 0x83, 0x4b, 0x20, 0xb2, 0x28, 0x09, 0x70, 0xa3, 0xb0, 0x73, 0xa8, 0xaa, 0x77, 0x75, - 0xc1, 0xb9, 0x9c, 0x74, 0xd5, 0x60, 0x46, 0xf1, 0xfa, 0x41, 0x48, 0xb6, 0x37, 0x25, 0xb5, 0xa7, - 0x96, 0xe6, 0x42, 0xf1, 0xd1, 0x20, 0x59, 0x2e, 0xad, 0x7c, 0x35, 0xc9, 0x7a, 0x88, 0x53, 0xc0, - 0xfa, 0x80, 0x10, 0x55, 0xed, 0x85, 0xf5, 0x84, 0x84, 0x0c, 0xf7, 0xf4, 0xfa, 0x54, 0x76, 0x16, - 0xb2, 0x55, 0xe4, 0xae, 0xa2, 0xde, 0xaf, 0x54, 0xab, 0x4b, 0x35, 0xeb, 0x27, 0xa0, 0xa6, 0x8b, - 0x79, 0x17, 0x9f, 0x09, 0xdd, 0x81, 0x3d, 0x8f, 0x13, 0x21, 0x88, 0x30, 0x8d, 0xa4, 0x03, 0xa7, - 0x04, 0x4b, 0x82, 0xb5, 0xab, 0x96, 0x70, 0x01, 0x1f, 0x83, 0xd9, 0x90, 0xe8, 0x0d, 0x51, 0x2b, - 0xd6, 0x6f, 0xbf, 0x67, 0x4f, 0xf0, 0xeb, 0xc9, 0xbe, 0x0a, 0xd0, 0x49, 0xd1, 0x2c, 0x9e, 0xaf, - 0xfe, 0x63, 0xd3, 0x5c, 0xc0, 0xd3, 0x71, 0xa3, 0x3f, 0x7b, 0x25, 0xa3, 0x63, 0x78, 0xb9, 0xcd, - 0x9b, 0xa0, 0xbe, 0x1b, 0x1f, 0xfb, 0x23, 0x35, 0x7a, 0x2e, 0x5c, 0xcb, 0x5c, 0xf1, 0x5a, 0xee, - 0x83, 0x85, 0x64, 0x9f, 0x3a, 0x61, 0xba, 0x21, 0xc1, 0x1f, 0x02, 0x90, 0x2c, 0x62, 0xaa, 0x91, - 0xc5, 0x2d, 0xbd, 0x96, 0x50, 0x0e, 0xbc, 0x91, 0xa9, 0x5b, 0x1a, 0x99, 0xba, 0x96, 0x03, 0x16, - 0x4f, 0x05, 0xfe, 0x79, 0xba, 0x6c, 0x3f, 0x0a, 0x05, 0x7c, 0x0d, 0xcc, 0xa8, 0x1a, 0x4a, 0x80, - 0x2a, 0xce, 0x74, 0x24, 0xf0, 0x81, 0xee, 0xea, 0xf9, 0x42, 0xcf, 0x42, 0x97, 0x7a, 0xc2, 0x2c, - 0x35, 0xcb, 0x5b, 0x15, 0x67, 0x61, 0x90, 0xab, 0x1f, 0x78, 0xc2, 0xfa, 0x05, 0xa8, 0x17, 0x00, - 0xe1, 0x02, 0x28, 0x65, 0x58, 0x25, 0xea, 0xc1, 0x1d, 0xb0, 0x96, 0x03, 0x8d, 0xb6, 0xe1, 0x18, - 0xb1, 0xe6, 0x5c, 0xcb, 0x04, 0x46, 0x3a, 0xb1, 0xb0, 0x1e, 0x81, 0xd5, 0x83, 0xbc, 0xe8, 0xb3, - 0x26, 0x3f, 0x72, 0x42, 0x63, 0x74, 0xaf, 0xd8, 0x00, 0xb5, 0xec, 0x37, 0xae, 0x3e, 0x7d, 0xc5, - 0xc9, 0x09, 0x56, 0x1f, 0x2c, 0x9d, 0x0a, 0x7c, 0x4c, 0x02, 0x2f, 0x07, 0xbb, 0xe2, 0x02, 0xf6, - 0xc6, 0x81, 0x26, 0xfe, 0x55, 0x94, 0x9b, 0x63, 0x60, 0xed, 0xb4, 0xb8, 0x84, 0xe8, 0x01, 0x7d, - 0x84, 0xf0, 0x19, 0x91, 0x02, 0x3a, 0xa0, 0xa2, 0x97, 0x8d, 0x38, 0xb3, 0xee, 0x5c, 0x99, 0x59, - 0xd1, 0xb6, 0x7d, 0x15, 0xc8, 0x3e, 0x92, 0x28, 0xa9, 0x5d, 0x8d, 0x65, 0xfd, 0x18, 0xac, 0x3c, - 0x40, 0x72, 0xc0, 0x89, 0x37, 0x12, 0xe3, 0x25, 0x50, 0x56, 0xf1, 0x33, 0x74, 0xfc, 0xd4, 0xa3, - 0xf5, 0x67, 0x03, 0x98, 0x77, 0x3f, 0x09, 0x19, 0x97, 0xc4, 0xbb, 0x70, 0x23, 0x2f, 0xb8, 0xde, - 0x33, 0xb0, 0xa2, 0x2e, 0x4b, 0x90, 0xc0, 0x73, 0xb3, 0x73, 0xc6, 0x71, 0xac, 0xdf, 0x7e, 0x77, - 0xa2, 0xea, 0x18, 0x37, 0x97, 0x1c, 0x60, 0x39, 0x1a, 0xa3, 0x0b, 0xeb, 0x77, 0x06, 0x30, 0x0f, - 0xc9, 0x70, 0x57, 0x08, 0xda, 0x0d, 0xfa, 0x24, 0x90, 0xaa, 0x07, 0x22, 0x4c, 0xd4, 0x23, 0x7c, - 0x03, 0xcc, 0x67, 0x33, 0x57, 0x8f, 0x5a, 0x43, 0x8f, 0xda, 0xb9, 0x94, 0xa8, 0x0a, 0x0c, 0xee, - 0x00, 0x10, 0x72, 0x12, 0xb9, 0xd8, 0x3d, 0x23, 0xc3, 0x24, 0x8a, 0x1b, 0xc5, 0x11, 0x1a, 0x7f, - 0x81, 0xb0, 0x8f, 0x06, 0x1d, 0x9f, 0xe2, 0x43, 0x32, 0x74, 0xaa, 0x4a, 0xbe, 0x7d, 0x48, 0x86, - 0x6a, 0x67, 0xd2, 0x1b, 0xa8, 0x9e, 0x7b, 0x65, 0x27, 0x7e, 0xb1, 0x7e, 0x6f, 0x80, 0x6b, 0x59, - 0x38, 0xd2, 0x74, 0x3d, 0x1a, 0x74, 0x94, 0xc6, 0x0b, 0xee, 0xed, 0x82, 0xb7, 0xa5, 0x4b, 0xbc, - 0x7d, 0x1f, 0xcc, 0x65, 0x05, 0xa2, 0xfc, 0x2d, 0x4f, 0xe0, 0x6f, 0x3d, 0xd5, 0x38, 0x24, 0x43, - 0xeb, 0xd7, 0x05, 0xdf, 0xf6, 0x86, 0x85, 0xde, 0xc7, 0x5f, 0xe2, 0x5b, 0x66, 0xb6, 0xe8, 0x1b, - 0x2e, 0xea, 0x5f, 0x38, 0x40, 0xf9, 0xe2, 0x01, 0xac, 0x3f, 0x19, 0x60, 0xb5, 0x68, 0x55, 0x9c, - 0xb0, 0x23, 0x3e, 0x08, 0xc8, 0x8b, 0xac, 0xe7, 0xe5, 0x57, 0x2a, 0x96, 0xdf, 0x63, 0xb0, 0x30, - 0xe2, 0x94, 0x48, 0x6e, 0xe3, 0xed, 0x89, 0x72, 0xac, 0xd0, 0x5d, 0x9d, 0xf9, 0xe2, 0x39, 0x84, - 0xf5, 0x17, 0x03, 0x2c, 0xa7, 0x3e, 0x66, 0x97, 0x05, 0x7f, 0x0a, 0x60, 0x76, 0xbc, 0x7c, 0x7b, - 0x8b, 0x53, 0x6a, 0x29, 0xe5, 0xa4, 0xab, 0x5b, 0x9e, 0x1a, 0xa5, 0x42, 0x6a, 0xc0, 0x8f, 0xc0, - 0x4a, 0xe6, 0x72, 0xa8, 0x03, 0x34, 0x71, 0x14, 0xb3, 0xfd, 0x34, 0x23, 0x59, 0xbf, 0x35, 0xf2, - 0x71, 0x18, 0xcf, 0x63, 0xb1, 0xeb, 0xfb, 0xc9, 0xd2, 0x0f, 0x43, 0x30, 0x1b, 0x8f, 0x7c, 0x91, - 0xf4, 0x8f, 0x8d, 0x4b, 0x87, 0xfb, 0x3e, 0xc1, 0x7a, 0xbe, 0xdf, 0x51, 0x25, 0xf6, 0xc5, 0x37, - 0x9b, 0x37, 0xbb, 0x54, 0xf6, 0x06, 0x1d, 0x1b, 0xb3, 0x7e, 0xf2, 0x51, 0x2c, 0xf9, 0x77, 0x4b, - 0x78, 0x67, 0x2d, 0x39, 0x0c, 0x89, 0x48, 0x75, 0xc4, 0x5f, 0xff, 0xfb, 0xb7, 0xb7, 0x0c, 0x27, - 0x35, 0xb3, 0xf7, 0xf8, 0xcb, 0x67, 0x0d, 0xe3, 0xab, 0x67, 0x0d, 0xe3, 0x3f, 0xcf, 0x1a, 0xc6, - 0xa7, 0xcf, 0x1b, 0x53, 0x5f, 0x3d, 0x6f, 0x4c, 0xfd, 0xfb, 0x79, 0x63, 0xea, 0x97, 0xef, 0x5d, - 0x04, 0xcd, 0x63, 0x74, 0x2b, 0xfb, 0x66, 0x19, 0xbd, 0xdb, 0xfa, 0x64, 0xf4, 0x8b, 0xa8, 0xb6, - 0xd7, 0x99, 0xd1, 0xdd, 0xf4, 0x9d, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff, 0x62, 0x6f, 0xc0, 0x43, - 0x42, 0x15, 0x00, 0x00, + 0x19, 0xd7, 0x8a, 0x94, 0x44, 0x0e, 0xf5, 0x1c, 0xc9, 0xf6, 0x4a, 0x51, 0x29, 0x7a, 0xd3, 0xb8, + 0x6c, 0x5c, 0x93, 0x91, 0x83, 0x00, 0x86, 0xdb, 0x20, 0x90, 0x28, 0x27, 0x96, 0x5d, 0xdb, 0xca, + 0x4a, 0x95, 0xd1, 0xf6, 0xb0, 0x18, 0xce, 0x8e, 0xc9, 0x81, 0x76, 0x77, 0xd6, 0x33, 0xc3, 0xb5, + 0x79, 0xe9, 0xb1, 0xe8, 0xa5, 0x40, 0x5a, 0xa0, 0x40, 0xd0, 0x4b, 0xd3, 0x9e, 0x8a, 0x1e, 0x8a, + 0x1e, 0xfa, 0x17, 0xf4, 0x14, 0xf4, 0xd2, 0x1c, 0x7b, 0x4a, 0x0a, 0xfb, 0xd0, 0x43, 0xff, 0x89, + 0x62, 0x66, 0x9f, 0xa4, 0x1e, 0xa6, 0x91, 0xe4, 0x22, 0xed, 0x7e, 0x8f, 0xdf, 0xf7, 0xcd, 0x7c, + 0xcf, 0x25, 0xb8, 0x49, 0x03, 0x49, 0x38, 0xee, 0x23, 0x1a, 0x38, 0x82, 0xe0, 0x01, 0xa7, 0x72, + 0xd8, 0xc6, 0x38, 0x6a, 0x87, 0x9c, 0x45, 0xd4, 0x25, 0xbc, 0x1d, 0x6d, 0x67, 0xcf, 0xad, 0x90, + 0x33, 0xc9, 0xe0, 0x9b, 0x67, 0xe8, 0xb4, 0x30, 0x8e, 0x5a, 0x99, 0x5c, 0xb4, 0xbd, 0xf1, 0xd6, + 0x79, 0xc0, 0xd1, 0x76, 0xfb, 0x19, 0xe5, 0x24, 0xc6, 0xda, 0x58, 0xeb, 0xb1, 0x1e, 0xd3, 0x8f, + 0x6d, 0xf5, 0x94, 0x50, 0xb7, 0x7a, 0x8c, 0xf5, 0x3c, 0xd2, 0xd6, 0x6f, 0xdd, 0xc1, 0x93, 0xb6, + 0xa4, 0x3e, 0x11, 0x12, 0xf9, 0x61, 0x22, 0x50, 0x1f, 0x17, 0x70, 0x07, 0x1c, 0x49, 0xca, 0x82, + 0x14, 0x80, 0x76, 0x71, 0x1b, 0x33, 0x4e, 0xda, 0xd8, 0xa3, 0x24, 0x90, 0xca, 0x6a, 0xfc, 0x94, + 0x08, 0xb4, 0x95, 0x80, 0x47, 0x7b, 0x7d, 0x19, 0x93, 0x45, 0x5b, 0x92, 0xc0, 0x25, 0xdc, 0xa7, + 0xb1, 0x70, 0xfe, 0x96, 0x28, 0x6c, 0x16, 0xf8, 0x98, 0x0f, 0x43, 0xc9, 0xda, 0x27, 0x64, 0x28, + 0x12, 0xee, 0x35, 0xcc, 0x84, 0xcf, 0x44, 0x9b, 0xa8, 0xf3, 0x07, 0x98, 0xb4, 0xa3, 0xed, 0x2e, + 0x91, 0x68, 0x3b, 0x23, 0xa4, 0x7e, 0x27, 0x72, 0x5d, 0x24, 0x72, 0x19, 0xcc, 0x68, 0xea, 0xf7, + 0x7a, 0xcc, 0x77, 0xe2, 0x1b, 0x89, 0x5f, 0x12, 0xd6, 0x0a, 0xf2, 0x69, 0xc0, 0xda, 0xfa, 0x6f, + 0x4c, 0xb2, 0x7e, 0x5b, 0x05, 0x66, 0x87, 0x05, 0x62, 0xe0, 0x13, 0xbe, 0xe3, 0xba, 0x54, 0x5d, + 0xc0, 0x01, 0x67, 0x21, 0x13, 0xc8, 0x83, 0x6b, 0x60, 0x46, 0x52, 0xe9, 0x11, 0xd3, 0x68, 0x18, + 0xcd, 0xaa, 0x1d, 0xbf, 0xc0, 0x06, 0xa8, 0xb9, 0x44, 0x60, 0x4e, 0x43, 0x25, 0x6c, 0x4e, 0x6b, + 0x5e, 0x91, 0x04, 0xd7, 0x41, 0x25, 0x8e, 0x1a, 0x75, 0xcd, 0x92, 0x66, 0xcf, 0xe9, 0xf7, 0x7d, + 0x17, 0x7e, 0x04, 0x16, 0x69, 0x40, 0x25, 0x45, 0x9e, 0xd3, 0x27, 0xea, 0xee, 0xcc, 0x72, 0xc3, + 0x68, 0xd6, 0x6e, 0x6e, 0xb4, 0x68, 0x17, 0xb7, 0xd4, 0x75, 0xb7, 0x92, 0x4b, 0x8e, 0xb6, 0x5b, + 0x77, 0xb5, 0xc4, 0x6e, 0xf9, 0xf3, 0x2f, 0xb7, 0xa6, 0xec, 0x85, 0x44, 0x2f, 0x26, 0xc2, 0xab, + 0x60, 0xbe, 0x47, 0x02, 0x22, 0xa8, 0x70, 0xfa, 0x48, 0xf4, 0xcd, 0x99, 0x86, 0xd1, 0x9c, 0xb7, + 0x6b, 0x09, 0xed, 0x2e, 0x12, 0x7d, 0xb8, 0x05, 0x6a, 0x5d, 0x1a, 0x20, 0x3e, 0x8c, 0x25, 0x66, + 0xb5, 0x04, 0x88, 0x49, 0x5a, 0xa0, 0x03, 0x80, 0x08, 0xd1, 0xb3, 0xc0, 0x51, 0xb9, 0x61, 0xce, + 0x25, 0x8e, 0xc4, 0x79, 0xd1, 0x4a, 0xf3, 0xa2, 0x75, 0x94, 0x26, 0xce, 0x6e, 0x45, 0x39, 0xf2, + 0xc9, 0x57, 0x5b, 0x86, 0x5d, 0xd5, 0x7a, 0x8a, 0x03, 0x1f, 0x82, 0xe5, 0x41, 0xd0, 0x65, 0x81, + 0x4b, 0x83, 0x9e, 0x13, 0x12, 0x4e, 0x99, 0x6b, 0x56, 0x34, 0xd4, 0xfa, 0x29, 0xa8, 0xbd, 0x24, + 0xc5, 0x62, 0xa4, 0x4f, 0x15, 0xd2, 0x52, 0xa6, 0x7c, 0xa0, 0x75, 0xe1, 0xc7, 0x00, 0x62, 0x1c, + 0x69, 0x97, 0xd8, 0x40, 0xa6, 0x88, 0xd5, 0xc9, 0x11, 0x97, 0x31, 0x8e, 0x8e, 0x62, 0xed, 0x04, + 0xf2, 0xe7, 0xe0, 0x8a, 0xe4, 0x28, 0x10, 0x4f, 0x08, 0x1f, 0xc7, 0x05, 0x93, 0xe3, 0x5e, 0x4a, + 0x31, 0x46, 0xc1, 0xef, 0x82, 0x06, 0x4e, 0x12, 0xc8, 0xe1, 0xc4, 0xa5, 0x42, 0x72, 0xda, 0x1d, + 0x28, 0x5d, 0xe7, 0x09, 0x47, 0x58, 0xe7, 0x48, 0x4d, 0x27, 0x41, 0x3d, 0x95, 0xb3, 0x47, 0xc4, + 0x3e, 0x4c, 0xa4, 0xe0, 0x23, 0xf0, 0xdd, 0xae, 0xc7, 0xf0, 0x89, 0x50, 0xce, 0x39, 0x23, 0x48, + 0xda, 0xb4, 0x4f, 0x85, 0x50, 0x68, 0xf3, 0x0d, 0xa3, 0x59, 0xb2, 0xaf, 0xc6, 0xb2, 0x07, 0x84, + 0xef, 0x15, 0x24, 0x8f, 0x0a, 0x82, 0xf0, 0x06, 0x80, 0x7d, 0x2a, 0x24, 0xe3, 0x14, 0x23, 0xcf, + 0x21, 0x81, 0xe4, 0x94, 0x08, 0x73, 0x41, 0xab, 0xaf, 0xe4, 0x9c, 0x3b, 0x31, 0x03, 0xde, 0x03, + 0x57, 0xcf, 0x35, 0xea, 0xe0, 0x3e, 0x0a, 0x02, 0xe2, 0x99, 0x8b, 0xfa, 0x28, 0x5b, 0xee, 0x39, + 0x36, 0x3b, 0xb1, 0x18, 0x5c, 0x05, 0x33, 0x92, 0x85, 0xce, 0x43, 0x73, 0xa9, 0x61, 0x34, 0x17, + 0xec, 0xb2, 0x64, 0xe1, 0x43, 0xf8, 0x0e, 0x58, 0x8b, 0x90, 0x47, 0x5d, 0x24, 0x19, 0x17, 0x4e, + 0xc8, 0x9e, 0x11, 0xee, 0x60, 0x14, 0x9a, 0xcb, 0x5a, 0x06, 0xe6, 0xbc, 0x03, 0xc5, 0xea, 0xa0, + 0x10, 0xbe, 0x0d, 0x56, 0x32, 0xaa, 0x23, 0x88, 0xd4, 0xe2, 0x2b, 0x5a, 0x7c, 0x29, 0x63, 0x1c, + 0x12, 0xa9, 0x64, 0x37, 0x41, 0x15, 0x79, 0x1e, 0x7b, 0xe6, 0x51, 0x21, 0x4d, 0xd8, 0x28, 0x35, + 0xab, 0x76, 0x4e, 0x80, 0x1b, 0xa0, 0xe2, 0x92, 0x60, 0xa8, 0x99, 0xab, 0x9a, 0x99, 0xbd, 0xc3, + 0x37, 0x40, 0xd5, 0x57, 0x3d, 0x56, 0xa2, 0x13, 0x62, 0xae, 0x35, 0x8c, 0x66, 0xd9, 0xae, 0xf8, + 0x34, 0x38, 0x54, 0xef, 0xaa, 0x98, 0x7d, 0xf4, 0xdc, 0xe1, 0x28, 0x38, 0x31, 0x2f, 0x69, 0xcb, + 0x73, 0x3e, 0x7a, 0x6e, 0xa3, 0xe0, 0x04, 0xb6, 0xc0, 0xaa, 0x36, 0xe0, 0xd0, 0x40, 0x85, 0x30, + 0x22, 0x4e, 0x84, 0x3c, 0x61, 0x5e, 0x6e, 0x18, 0xcd, 0x8a, 0xbd, 0xa2, 0x59, 0xfb, 0x09, 0xe7, + 0x18, 0x79, 0xe2, 0xf6, 0xb5, 0x5f, 0x7d, 0xb6, 0x35, 0xf5, 0xe9, 0x67, 0x5b, 0x53, 0xff, 0xfc, + 0xfb, 0x8d, 0x8d, 0xa4, 0x33, 0xf5, 0x58, 0xd4, 0x4a, 0xba, 0x58, 0xab, 0xc3, 0x02, 0x49, 0x02, + 0x69, 0xfd, 0xcb, 0x00, 0x57, 0x3a, 0x59, 0xae, 0xf8, 0x2c, 0x42, 0xde, 0xb7, 0xd9, 0x93, 0x76, + 0x40, 0x55, 0xa8, 0x60, 0xe9, 0x2e, 0x50, 0x7e, 0x8d, 0x2e, 0x50, 0x51, 0x6a, 0x8a, 0x71, 0xbb, + 0xfe, 0x8a, 0x13, 0xfd, 0xb2, 0x04, 0x36, 0xd3, 0x13, 0x3d, 0x60, 0x2e, 0x7d, 0x42, 0x31, 0xfa, + 0xb6, 0x5b, 0x6d, 0x96, 0x82, 0xe5, 0x09, 0x52, 0x70, 0xe6, 0xf5, 0x52, 0x70, 0x76, 0x82, 0x14, + 0x9c, 0xbb, 0x28, 0x05, 0x2b, 0x17, 0xa5, 0x60, 0xf5, 0x82, 0x14, 0x04, 0x13, 0xa5, 0x60, 0xed, + 0x9c, 0x14, 0xb4, 0xfe, 0x60, 0x80, 0xb5, 0x3b, 0x4f, 0x07, 0x34, 0x62, 0xdf, 0x50, 0x00, 0xee, + 0x83, 0x05, 0x52, 0xc0, 0x13, 0x66, 0xa9, 0x51, 0x6a, 0xd6, 0x6e, 0xbe, 0xd5, 0x4a, 0xb2, 0x21, + 0x9b, 0xde, 0x69, 0x4a, 0x14, 0xad, 0xdb, 0xa3, 0xba, 0xb7, 0xa7, 0x4d, 0xc3, 0xfa, 0x87, 0x01, + 0x36, 0x54, 0x17, 0xe9, 0x11, 0x9b, 0x3c, 0x43, 0xdc, 0xdd, 0x23, 0x01, 0xf3, 0xc5, 0xd7, 0xf6, + 0xd3, 0x02, 0x0b, 0xae, 0x46, 0x72, 0x24, 0x73, 0x90, 0xeb, 0x6a, 0x3f, 0xb5, 0x8c, 0x22, 0x1e, + 0xb1, 0x1d, 0xd7, 0x85, 0x4d, 0xb0, 0x9c, 0xcb, 0x70, 0x55, 0x78, 0xaa, 0x1e, 0x94, 0xd8, 0x62, + 0x2a, 0xa6, 0xcb, 0xf1, 0xd5, 0xf9, 0xfe, 0x3f, 0x03, 0x2c, 0x7f, 0xe4, 0xb1, 0x2e, 0xf2, 0x0e, + 0x3d, 0x24, 0xfa, 0xaa, 0xc3, 0x0e, 0x55, 0x9d, 0x71, 0x92, 0x8c, 0x36, 0xed, 0xfe, 0xc4, 0x75, + 0xa6, 0xd4, 0xf4, 0xb0, 0xfd, 0x00, 0xac, 0x64, 0xc3, 0x26, 0xcb, 0x7b, 0x7d, 0xda, 0xdd, 0xd5, + 0x17, 0x5f, 0x6e, 0x2d, 0xa5, 0x35, 0xd6, 0xd1, 0x35, 0xb0, 0x67, 0x2f, 0xe1, 0x11, 0x82, 0x0b, + 0xeb, 0xa0, 0x46, 0xbb, 0xd8, 0x11, 0xe4, 0xa9, 0x13, 0x0c, 0x7c, 0x5d, 0x32, 0x65, 0xbb, 0x4a, + 0xbb, 0xf8, 0x90, 0x3c, 0x7d, 0x38, 0xf0, 0xe1, 0xbb, 0xe0, 0x72, 0xba, 0x82, 0xaa, 0x4c, 0x72, + 0x94, 0xbe, 0xba, 0x2e, 0xae, 0xab, 0x68, 0xde, 0x5e, 0x4d, 0xb9, 0xc7, 0xc8, 0x53, 0xc6, 0x76, + 0x5c, 0x97, 0x5b, 0x7f, 0x9d, 0x05, 0xb3, 0x07, 0x88, 0x23, 0x5f, 0xc0, 0x23, 0xb0, 0x24, 0x89, + 0x1f, 0x7a, 0x48, 0x12, 0x27, 0x5e, 0x64, 0x92, 0x93, 0x5e, 0xd7, 0x0b, 0x4e, 0x71, 0x5d, 0x6c, + 0x15, 0x16, 0xc4, 0x68, 0xbb, 0xd5, 0xd1, 0xd4, 0x43, 0x89, 0x24, 0xb1, 0x17, 0x53, 0x8c, 0x98, + 0x08, 0x6f, 0x01, 0x53, 0xf2, 0x81, 0x90, 0xf9, 0x8a, 0x91, 0xcf, 0xd6, 0x38, 0xd6, 0x97, 0x53, + 0x7e, 0x3c, 0x95, 0xb3, 0x99, 0x7a, 0xf6, 0x36, 0x51, 0xfa, 0x3a, 0xdb, 0xc4, 0x21, 0x58, 0x55, + 0xab, 0xd8, 0x38, 0x66, 0x79, 0x72, 0xcc, 0x15, 0xa5, 0x3f, 0x0a, 0xfa, 0x31, 0x80, 0x91, 0xc0, + 0xe3, 0x98, 0x33, 0xaf, 0xe1, 0x67, 0x24, 0xf0, 0x28, 0xa4, 0x0b, 0x36, 0x85, 0x4a, 0x3e, 0xc7, + 0x27, 0x52, 0xef, 0x26, 0xa1, 0x47, 0x02, 0x2a, 0xfa, 0x29, 0xf8, 0xec, 0xe4, 0xe0, 0xeb, 0x1a, + 0xe8, 0x81, 0xc2, 0xb1, 0x53, 0x98, 0xc4, 0x4a, 0x07, 0xd4, 0xcf, 0xb6, 0x92, 0x05, 0x68, 0x4e, + 0x07, 0xe8, 0x8d, 0x33, 0x20, 0xb2, 0x28, 0x09, 0x70, 0xad, 0xb0, 0x43, 0xa9, 0xaa, 0x77, 0x74, + 0xc1, 0x39, 0x9c, 0xf4, 0xd4, 0xa2, 0x81, 0xe2, 0x75, 0x8a, 0x90, 0x6c, 0x0f, 0x4c, 0x6a, 0x4f, + 0x7d, 0x04, 0x14, 0x8a, 0x8f, 0x06, 0xc9, 0xb2, 0x6c, 0xe5, 0xab, 0x56, 0xd6, 0x43, 0xec, 0x02, + 0xd6, 0x87, 0x84, 0xa8, 0x6a, 0x2f, 0xac, 0x5b, 0x24, 0x64, 0xb8, 0xaf, 0xbb, 0x6b, 0xc9, 0x5e, + 0xcc, 0x56, 0xab, 0x3b, 0x8a, 0x0a, 0xf7, 0xc1, 0x55, 0xd5, 0x7f, 0xb3, 0xc2, 0x50, 0xe0, 0x24, + 0x10, 0x03, 0xe1, 0xe4, 0xf3, 0x42, 0xb7, 0xdc, 0x92, 0x5d, 0xf7, 0xd1, 0xf3, 0x83, 0x44, 0xae, + 0x93, 0x8a, 0x1d, 0x67, 0x52, 0xf7, 0xca, 0x95, 0xca, 0x72, 0xd5, 0xfa, 0x3e, 0xa8, 0xea, 0xbe, + 0xb0, 0x83, 0x4f, 0x84, 0x1e, 0x1a, 0xae, 0xcb, 0x89, 0x10, 0x44, 0x98, 0x46, 0x32, 0x34, 0x52, + 0x82, 0x25, 0xc1, 0xfa, 0x79, 0xdf, 0x27, 0x02, 0x3e, 0x06, 0x73, 0x21, 0xd1, 0xcb, 0xb3, 0x56, + 0xac, 0xdd, 0x7c, 0xbf, 0x35, 0xc1, 0x87, 0x65, 0xeb, 0x3c, 0x40, 0x3b, 0x45, 0xb3, 0x78, 0xfe, + 0x55, 0x34, 0xb6, 0x80, 0x08, 0x78, 0x3c, 0x6e, 0xf4, 0x47, 0xaf, 0x65, 0x74, 0x0c, 0x2f, 0xb7, + 0x79, 0x1d, 0xd4, 0x76, 0xe2, 0x63, 0xff, 0x58, 0x4d, 0xc4, 0x53, 0xd7, 0x32, 0x5f, 0xbc, 0x96, + 0x7b, 0x60, 0x31, 0x59, 0x35, 0x8f, 0x98, 0xee, 0x6d, 0xf0, 0x3b, 0x00, 0x24, 0x3b, 0xaa, 0xea, + 0x89, 0xf1, 0x74, 0xa8, 0x26, 0x94, 0x7d, 0x77, 0x64, 0x51, 0x98, 0x1e, 0x59, 0x14, 0x2c, 0x1b, + 0x2c, 0x1d, 0x0b, 0xfc, 0x93, 0xf4, 0x3b, 0xe4, 0x51, 0x28, 0xe0, 0x25, 0x30, 0xab, 0xca, 0x31, + 0x01, 0x2a, 0xdb, 0x33, 0x91, 0xc0, 0xfb, 0x7a, 0x40, 0xe4, 0xdf, 0x3a, 0x2c, 0x74, 0xa8, 0x2b, + 0xcc, 0xe9, 0x46, 0xa9, 0x59, 0xb6, 0x17, 0x07, 0xb9, 0xfa, 0xbe, 0x2b, 0xac, 0x9f, 0x82, 0x5a, + 0x01, 0x10, 0x2e, 0x82, 0xe9, 0x0c, 0x6b, 0x9a, 0xba, 0xf0, 0x36, 0x58, 0xcf, 0x81, 0x46, 0x3b, + 0x7a, 0x8c, 0x58, 0xb5, 0xaf, 0x64, 0x02, 0x23, 0x4d, 0x5d, 0x58, 0x8f, 0xc0, 0xda, 0x7e, 0xde, + 0x3f, 0xb2, 0x79, 0x31, 0x72, 0x42, 0x63, 0x74, 0x15, 0xda, 0x04, 0xd5, 0xec, 0xf3, 0x5f, 0x9f, + 0xbe, 0x6c, 0xe7, 0x04, 0xcb, 0x07, 0xcb, 0xc7, 0x02, 0x1f, 0x92, 0xc0, 0xcd, 0xc1, 0xce, 0xb9, + 0x80, 0xdd, 0x71, 0xa0, 0x89, 0x3f, 0x18, 0x73, 0x73, 0x0c, 0xac, 0x1f, 0x17, 0xf7, 0x26, 0x3d, + 0xeb, 0x0f, 0x10, 0x3e, 0x21, 0x52, 0x40, 0x1b, 0x94, 0xf5, 0x7e, 0x14, 0x67, 0xd6, 0xad, 0x73, + 0x33, 0x2b, 0xda, 0x6e, 0x9d, 0x07, 0xb2, 0x87, 0x24, 0x4a, 0xda, 0x80, 0xc6, 0xb2, 0xbe, 0x07, + 0x56, 0x1f, 0x20, 0x39, 0xe0, 0xc4, 0x1d, 0x89, 0xf1, 0x32, 0x28, 0xa9, 0xf8, 0x19, 0x3a, 0x7e, + 0xea, 0xd1, 0xfa, 0x93, 0x01, 0xcc, 0x3b, 0xcf, 0x43, 0xc6, 0x25, 0x71, 0x4f, 0xdd, 0xc8, 0x05, + 0xd7, 0x7b, 0x02, 0x56, 0xd5, 0x65, 0x09, 0x12, 0xb8, 0x4e, 0x76, 0xce, 0x38, 0x8e, 0xb5, 0x9b, + 0xef, 0x4d, 0x54, 0x1d, 0xe3, 0xe6, 0x92, 0x03, 0xac, 0x44, 0x63, 0x74, 0x61, 0xfd, 0xc6, 0x00, + 0xe6, 0x7d, 0x32, 0xdc, 0x11, 0x82, 0xf6, 0x02, 0x9f, 0x04, 0x52, 0xb5, 0x53, 0x84, 0x89, 0x7a, + 0x84, 0x6f, 0x82, 0x85, 0xac, 0x4b, 0xe9, 0xa9, 0x6d, 0xe8, 0xa9, 0x3d, 0x9f, 0x12, 0x55, 0x81, + 0xc1, 0xdb, 0x00, 0x84, 0x9c, 0x44, 0x0e, 0x76, 0x4e, 0xc8, 0x30, 0x89, 0xe2, 0x66, 0x71, 0x1a, + 0xc7, 0x3f, 0xce, 0xb4, 0x0e, 0x06, 0x5d, 0x8f, 0xe2, 0xfb, 0x64, 0x68, 0x57, 0x94, 0x7c, 0xe7, + 0x3e, 0x19, 0xaa, 0xf5, 0x4b, 0x2f, 0xcd, 0x7a, 0x84, 0x96, 0xec, 0xf8, 0xc5, 0xfa, 0xbd, 0x01, + 0xae, 0x64, 0xe1, 0x48, 0xd3, 0xf5, 0x60, 0xd0, 0x55, 0x1a, 0x17, 0xdc, 0xdb, 0x29, 0x6f, 0xa7, + 0xcf, 0xf0, 0xf6, 0x03, 0x30, 0x9f, 0x15, 0x88, 0xf2, 0xb7, 0x34, 0x81, 0xbf, 0xb5, 0x54, 0xe3, + 0x3e, 0x19, 0x5a, 0xbf, 0x28, 0xf8, 0xb6, 0x3b, 0x2c, 0xf4, 0x3e, 0xfe, 0x0a, 0xdf, 0x32, 0xb3, + 0x45, 0xdf, 0x70, 0x51, 0xff, 0xd4, 0x01, 0x4a, 0xa7, 0x0f, 0x60, 0xfd, 0xd1, 0x00, 0x6b, 0x45, + 0xab, 0xe2, 0x88, 0x1d, 0xf0, 0x41, 0x40, 0x2e, 0xb2, 0x9e, 0x97, 0xdf, 0x74, 0xb1, 0xfc, 0x1e, + 0x83, 0xc5, 0x11, 0xa7, 0x44, 0x72, 0x1b, 0xef, 0x4c, 0x94, 0x63, 0x85, 0xee, 0x6a, 0x2f, 0x14, + 0xcf, 0x21, 0xac, 0xdf, 0x19, 0x00, 0x9e, 0x1e, 0x57, 0xf0, 0x07, 0x00, 0x8e, 0x0c, 0xbd, 0x62, + 0x4e, 0x2d, 0x87, 0x85, 0x31, 0xa7, 0x6f, 0x23, 0xcb, 0x8d, 0xe9, 0x42, 0x6e, 0xc0, 0x1f, 0x02, + 0x10, 0xea, 0xc0, 0x4c, 0x1c, 0xbd, 0x6a, 0x98, 0x3e, 0x5a, 0xbf, 0x36, 0xf2, 0xf1, 0x17, 0x8f, + 0x72, 0xb1, 0xe3, 0x79, 0xc9, 0xf7, 0x02, 0x0c, 0xc1, 0x5c, 0xbc, 0x2d, 0x88, 0xa4, 0x5f, 0x6c, + 0x9e, 0xb9, 0x17, 0xec, 0x11, 0xac, 0x57, 0x83, 0x5b, 0xaa, 0xa4, 0xfe, 0xf2, 0xd5, 0xd6, 0xf5, + 0x1e, 0x95, 0xfd, 0x41, 0xb7, 0x85, 0x99, 0x9f, 0xfc, 0x3e, 0x98, 0xfc, 0xbb, 0x21, 0xdc, 0x93, + 0xb6, 0x1c, 0x86, 0x44, 0xa4, 0x3a, 0xe2, 0xcf, 0xff, 0xfd, 0xdb, 0xdb, 0x86, 0x9d, 0x9a, 0xd9, + 0x7d, 0xfc, 0xf9, 0x8b, 0xba, 0xf1, 0xc5, 0x8b, 0xba, 0xf1, 0x9f, 0x17, 0x75, 0xe3, 0x93, 0x97, + 0xf5, 0xa9, 0x2f, 0x5e, 0xd6, 0xa7, 0xfe, 0xfd, 0xb2, 0x3e, 0xf5, 0xb3, 0xf7, 0x4f, 0x83, 0xe6, + 0x31, 0xb9, 0x91, 0xfd, 0x7c, 0x1b, 0xbd, 0xd7, 0x7e, 0x3e, 0xfa, 0xe3, 0xb0, 0xb6, 0xd7, 0x9d, + 0xd5, 0xdd, 0xf3, 0xdd, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff, 0x94, 0x29, 0x12, 0xdc, 0x4d, 0x16, + 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { @@ -1831,6 +1884,32 @@ func (m *ConsumerAdditionProposal) MarshalToSizedBuffer(dAtA []byte) (int, error _ = i var l int _ = l + if m.AllowInactiveVals { + i-- + if m.AllowInactiveVals { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xb0 + } + if m.MaxRank != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.MaxRank)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa8 + } + if m.MinStake != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.MinStake)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa0 + } if len(m.Denylist) > 0 { for iNdEx := len(m.Denylist) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Denylist[iNdEx]) @@ -2048,6 +2127,26 @@ func (m *ConsumerModificationProposal) MarshalToSizedBuffer(dAtA []byte) (int, e _ = i var l int _ = l + if m.AllowInactiveVals { + i-- + if m.AllowInactiveVals { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x58 + } + if m.MaxRank != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.MaxRank)) + i-- + dAtA[i] = 0x50 + } + if m.MinStake != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.MinStake)) + i-- + dAtA[i] = 0x48 + } if len(m.Denylist) > 0 { for iNdEx := len(m.Denylist) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Denylist[iNdEx]) @@ -2281,6 +2380,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxProviderConsensusValidators != 0 { + i = encodeVarintProvider(dAtA, i, uint64(m.MaxProviderConsensusValidators)) + i-- + dAtA[i] = 0x58 + } if m.BlocksPerEpoch != 0 { i = encodeVarintProvider(dAtA, i, uint64(m.BlocksPerEpoch)) i-- @@ -2995,7 +3099,7 @@ func (m *ConsumerAddrsToPrune) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ConsumerValidator) Marshal() (dAtA []byte, err error) { +func (m *ConsensusValidator) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -3005,19 +3109,19 @@ func (m *ConsumerValidator) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ConsumerValidator) MarshalTo(dAtA []byte) (int, error) { +func (m *ConsensusValidator) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ConsumerValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ConsensusValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.ConsumerPublicKey != nil { + if m.PublicKey != nil { { - size, err := m.ConsumerPublicKey.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.PublicKey.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -3161,6 +3265,15 @@ func (m *ConsumerAdditionProposal) Size() (n int) { n += 2 + l + sovProvider(uint64(l)) } } + if m.MinStake != 0 { + n += 2 + sovProvider(uint64(m.MinStake)) + } + if m.MaxRank != 0 { + n += 2 + sovProvider(uint64(m.MaxRank)) + } + if m.AllowInactiveVals { + n += 3 + } return n } @@ -3226,6 +3339,15 @@ func (m *ConsumerModificationProposal) Size() (n int) { n += 1 + l + sovProvider(uint64(l)) } } + if m.MinStake != 0 { + n += 1 + sovProvider(uint64(m.MinStake)) + } + if m.MaxRank != 0 { + n += 1 + sovProvider(uint64(m.MaxRank)) + } + if m.AllowInactiveVals { + n += 2 + } return n } @@ -3334,6 +3456,9 @@ func (m *Params) Size() (n int) { if m.BlocksPerEpoch != 0 { n += 1 + sovProvider(uint64(m.BlocksPerEpoch)) } + if m.MaxProviderConsensusValidators != 0 { + n += 1 + sovProvider(uint64(m.MaxProviderConsensusValidators)) + } return n } @@ -3613,7 +3738,7 @@ func (m *ConsumerAddrsToPrune) Size() (n int) { return n } -func (m *ConsumerValidator) Size() (n int) { +func (m *ConsensusValidator) Size() (n int) { if m == nil { return 0 } @@ -3626,8 +3751,8 @@ func (m *ConsumerValidator) Size() (n int) { if m.Power != 0 { n += 1 + sovProvider(uint64(m.Power)) } - if m.ConsumerPublicKey != nil { - l = m.ConsumerPublicKey.Size() + if m.PublicKey != nil { + l = m.PublicKey.Size() n += 1 + l + sovProvider(uint64(l)) } return n @@ -4235,6 +4360,64 @@ func (m *ConsumerAdditionProposal) Unmarshal(dAtA []byte) error { } m.Denylist = append(m.Denylist, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 20: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinStake", wireType) + } + m.MinStake = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinStake |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxRank", wireType) + } + m.MaxRank = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxRank |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 22: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowInactiveVals", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowInactiveVals = bool(v != 0) default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) @@ -4681,6 +4864,64 @@ func (m *ConsumerModificationProposal) Unmarshal(dAtA []byte) error { } m.Denylist = append(m.Denylist, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinStake", wireType) + } + m.MinStake = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinStake |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxRank", wireType) + } + m.MaxRank = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxRank |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowInactiveVals", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowInactiveVals = bool(v != 0) default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) @@ -5509,6 +5750,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxProviderConsensusValidators", wireType) + } + m.MaxProviderConsensusValidators = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProvider + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxProviderConsensusValidators |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipProvider(dAtA[iNdEx:]) @@ -7329,7 +7589,7 @@ func (m *ConsumerAddrsToPrune) Unmarshal(dAtA []byte) error { } return nil } -func (m *ConsumerValidator) Unmarshal(dAtA []byte) error { +func (m *ConsensusValidator) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -7352,10 +7612,10 @@ func (m *ConsumerValidator) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ConsumerValidator: wiretype end group for non-group") + return fmt.Errorf("proto: ConsensusValidator: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ConsumerValidator: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ConsensusValidator: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -7413,7 +7673,7 @@ func (m *ConsumerValidator) Unmarshal(dAtA []byte) error { } case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ConsumerPublicKey", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -7440,10 +7700,10 @@ func (m *ConsumerValidator) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ConsumerPublicKey == nil { - m.ConsumerPublicKey = &crypto.PublicKey{} + if m.PublicKey == nil { + m.PublicKey = &crypto.PublicKey{} } - if err := m.ConsumerPublicKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.PublicKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index a297ccbfb7..457a117b96 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -447,6 +447,12 @@ type MsgConsumerAddition struct { Denylist []string `protobuf:"bytes,17,rep,name=denylist,proto3" json:"denylist,omitempty"` // signer address Authority string `protobuf:"bytes,18,opt,name=authority,proto3" json:"authority,omitempty"` + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + MinStake uint64 `protobuf:"varint,19,opt,name=min_stake,json=minStake,proto3" json:"min_stake,omitempty"` + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + MaxRank uint32 `protobuf:"varint,20,opt,name=max_rank,json=maxRank,proto3" json:"max_rank,omitempty"` + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + AllowInactiveVals bool `protobuf:"varint,21,opt,name=allow_inactive_vals,json=allowInactiveVals,proto3" json:"allow_inactive_vals,omitempty"` } func (m *MsgConsumerAddition) Reset() { *m = MsgConsumerAddition{} } @@ -608,6 +614,27 @@ func (m *MsgConsumerAddition) GetAuthority() string { return "" } +func (m *MsgConsumerAddition) GetMinStake() uint64 { + if m != nil { + return m.MinStake + } + return 0 +} + +func (m *MsgConsumerAddition) GetMaxRank() uint32 { + if m != nil { + return m.MaxRank + } + return 0 +} + +func (m *MsgConsumerAddition) GetAllowInactiveVals() bool { + if m != nil { + return m.AllowInactiveVals + } + return false +} + // MsgConsumerAdditionResponse defines response type for MsgConsumerAddition messages type MsgConsumerAdditionResponse struct { } @@ -1131,6 +1158,12 @@ type MsgConsumerModification struct { Denylist []string `protobuf:"bytes,8,rep,name=denylist,proto3" json:"denylist,omitempty"` // signer address Authority string `protobuf:"bytes,9,opt,name=authority,proto3" json:"authority,omitempty"` + // Corresponds to the minimal amount of (provider chain) stake required to validate on the consumer chain. + MinStake uint64 `protobuf:"varint,10,opt,name=min_stake,json=minStake,proto3" json:"min_stake,omitempty"` + // Corresponds to the maximal rank in the provider chain validator set that a validator can have to validate on the consumer chain. + MaxRank uint32 `protobuf:"varint,11,opt,name=max_rank,json=maxRank,proto3" json:"max_rank,omitempty"` + // Corresponds to whether inactive validators are allowed to validate the consumer chain. + AllowInactiveVals bool `protobuf:"varint,12,opt,name=allow_inactive_vals,json=allowInactiveVals,proto3" json:"allow_inactive_vals,omitempty"` } func (m *MsgConsumerModification) Reset() { *m = MsgConsumerModification{} } @@ -1229,6 +1262,27 @@ func (m *MsgConsumerModification) GetAuthority() string { return "" } +func (m *MsgConsumerModification) GetMinStake() uint64 { + if m != nil { + return m.MinStake + } + return 0 +} + +func (m *MsgConsumerModification) GetMaxRank() uint32 { + if m != nil { + return m.MaxRank + } + return 0 +} + +func (m *MsgConsumerModification) GetAllowInactiveVals() bool { + if m != nil { + return m.AllowInactiveVals + } + return false +} + type MsgConsumerModificationResponse struct { } @@ -1295,113 +1349,118 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 1689 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0xcf, 0x6f, 0xe4, 0x48, - 0x15, 0x8e, 0xf3, 0x6b, 0xba, 0xab, 0xf3, 0xd3, 0x93, 0x90, 0x8e, 0x37, 0xdb, 0x9d, 0x34, 0xcb, - 0x6e, 0x34, 0x6c, 0xec, 0x9d, 0xc0, 0xce, 0x42, 0xb4, 0x08, 0x92, 0xf4, 0xc2, 0x64, 0x20, 0x93, - 0xe0, 0x09, 0x8b, 0x04, 0x12, 0x56, 0xb5, 0x5d, 0x71, 0x97, 0xc6, 0x76, 0x59, 0x55, 0xd5, 0x9d, - 0xed, 0x1b, 0xda, 0x13, 0x12, 0x02, 0x2d, 0x37, 0xc4, 0x69, 0x0f, 0x08, 0x81, 0x04, 0xd2, 0x1c, - 0xf6, 0xc4, 0x8d, 0xdb, 0x1c, 0x38, 0x2c, 0x2b, 0x0e, 0x88, 0xc3, 0x80, 0x66, 0x0e, 0xcb, 0x99, - 0xbf, 0x00, 0x55, 0xb9, 0xec, 0x76, 0x27, 0x9d, 0xde, 0xee, 0x0e, 0x1c, 0xb8, 0xb4, 0xda, 0xf5, - 0xbe, 0xf7, 0xd5, 0xf7, 0x3d, 0xbb, 0x5e, 0x95, 0x0d, 0x5e, 0xc7, 0x11, 0x47, 0xd4, 0x6d, 0x42, - 0x1c, 0x39, 0x0c, 0xb9, 0x2d, 0x8a, 0x79, 0xc7, 0x72, 0xdd, 0xb6, 0x15, 0x53, 0xd2, 0xc6, 0x1e, - 0xa2, 0x56, 0xfb, 0xae, 0xc5, 0xdf, 0x33, 0x63, 0x4a, 0x38, 0xd1, 0x3f, 0xdf, 0x07, 0x6d, 0xba, - 0x6e, 0xdb, 0x4c, 0xd1, 0x66, 0xfb, 0xae, 0xb1, 0x0c, 0x43, 0x1c, 0x11, 0x4b, 0xfe, 0x26, 0x79, - 0xc6, 0x86, 0x4f, 0x88, 0x1f, 0x20, 0x0b, 0xc6, 0xd8, 0x82, 0x51, 0x44, 0x38, 0xe4, 0x98, 0x44, - 0x4c, 0x45, 0xab, 0x2a, 0x2a, 0xaf, 0x1a, 0xad, 0x73, 0x8b, 0xe3, 0x10, 0x31, 0x0e, 0xc3, 0x58, - 0x01, 0x2a, 0x97, 0x01, 0x5e, 0x8b, 0x4a, 0x06, 0x15, 0x5f, 0xbf, 0x1c, 0x87, 0x51, 0x47, 0x85, - 0x56, 0x7c, 0xe2, 0x13, 0xf9, 0xd7, 0x12, 0xff, 0xd2, 0x04, 0x97, 0xb0, 0x90, 0x30, 0x27, 0x09, - 0x24, 0x17, 0x2a, 0xb4, 0x96, 0x5c, 0x59, 0x21, 0xf3, 0x85, 0xf5, 0x90, 0xf9, 0xa9, 0x4a, 0xdc, - 0x70, 0x2d, 0x97, 0x50, 0x64, 0xb9, 0x01, 0x46, 0x11, 0x17, 0xd1, 0xe4, 0x9f, 0x02, 0xec, 0x0e, - 0x53, 0xca, 0xac, 0x50, 0x49, 0x8e, 0x25, 0x48, 0x03, 0xec, 0x37, 0x79, 0x42, 0xc5, 0x2c, 0x8e, - 0x22, 0x0f, 0xd1, 0x10, 0x27, 0x13, 0x74, 0xaf, 0x52, 0x15, 0xb9, 0x38, 0xef, 0xc4, 0x88, 0x59, - 0x48, 0xf0, 0x45, 0x2e, 0x4a, 0x00, 0xb5, 0xbf, 0x6a, 0x60, 0xe5, 0x98, 0xf9, 0xfb, 0x8c, 0x61, - 0x3f, 0x3a, 0x24, 0x11, 0x6b, 0x85, 0x88, 0x7e, 0x1b, 0x75, 0xf4, 0x75, 0x50, 0x48, 0xb4, 0x61, - 0xaf, 0xac, 0x6d, 0x6a, 0xdb, 0x45, 0xfb, 0x96, 0xbc, 0x3e, 0xf2, 0xf4, 0xb7, 0xc0, 0x7c, 0xaa, - 0xcb, 0x81, 0x9e, 0x47, 0xcb, 0x93, 0x22, 0x7e, 0xa0, 0xff, 0xfb, 0x59, 0x75, 0xa1, 0x03, 0xc3, - 0x60, 0xaf, 0x26, 0x46, 0x11, 0x63, 0x35, 0x7b, 0x2e, 0x05, 0xee, 0x7b, 0x1e, 0xd5, 0xb7, 0xc0, - 0x9c, 0xab, 0xa6, 0x70, 0x1e, 0xa3, 0x4e, 0x79, 0x4a, 0xf2, 0x96, 0xdc, 0xdc, 0xb4, 0x6f, 0x80, - 0x59, 0xa1, 0x04, 0xd1, 0xf2, 0xb4, 0x24, 0x2d, 0x7f, 0xf2, 0xd1, 0xce, 0x8a, 0xaa, 0xf8, 0x7e, - 0xc2, 0xfa, 0x88, 0x53, 0x1c, 0xf9, 0xb6, 0xc2, 0xed, 0xdd, 0xfe, 0xc9, 0x87, 0xd5, 0x89, 0x7f, - 0x7d, 0x58, 0x9d, 0x78, 0xff, 0xd3, 0x27, 0x77, 0xd4, 0x60, 0xad, 0x02, 0x36, 0xfa, 0xb9, 0xb2, - 0x11, 0x8b, 0x49, 0xc4, 0x50, 0xed, 0x4f, 0x1a, 0x78, 0xf9, 0x98, 0xf9, 0x8f, 0x5a, 0x8d, 0x10, - 0xf3, 0x14, 0x70, 0x8c, 0x59, 0x03, 0x35, 0x61, 0x1b, 0x93, 0x16, 0xd5, 0xef, 0x81, 0x22, 0x93, - 0x51, 0x8e, 0x68, 0x52, 0x80, 0x01, 0x5a, 0xba, 0x50, 0xfd, 0x14, 0xcc, 0x85, 0x39, 0x1e, 0x59, - 0x9b, 0xd2, 0xee, 0xeb, 0x26, 0x6e, 0xb8, 0x66, 0xfe, 0xce, 0x99, 0xb9, 0x7b, 0xd5, 0xbe, 0x6b, - 0xe6, 0xe7, 0xb6, 0x7b, 0x18, 0xf6, 0x3e, 0x97, 0x37, 0xd8, 0x9d, 0xa9, 0xf6, 0x1a, 0xf8, 0xc2, - 0x40, 0x0b, 0x99, 0xd9, 0x27, 0x93, 0x7d, 0xcc, 0xd6, 0x49, 0xab, 0x11, 0xa0, 0x77, 0x09, 0xc7, - 0x91, 0x3f, 0xb6, 0x59, 0x07, 0xac, 0x79, 0xad, 0x38, 0xc0, 0x2e, 0xe4, 0xc8, 0x69, 0x13, 0x8e, - 0x9c, 0xf4, 0xf1, 0x52, 0xbe, 0x5f, 0xcb, 0xdb, 0x94, 0x0f, 0xa0, 0x59, 0x4f, 0x13, 0xde, 0x25, - 0x1c, 0xbd, 0xa3, 0xe0, 0xf6, 0xaa, 0xd7, 0x6f, 0x58, 0xff, 0x11, 0x58, 0xc3, 0xd1, 0x39, 0x85, - 0xae, 0x58, 0xbe, 0x4e, 0x23, 0x20, 0xee, 0x63, 0xa7, 0x89, 0xa0, 0x87, 0xa8, 0x7c, 0x78, 0x4a, - 0xbb, 0xaf, 0x7e, 0x56, 0x61, 0xef, 0x4b, 0xb4, 0xbd, 0xda, 0xa5, 0x39, 0x10, 0x2c, 0xc9, 0xf0, - 0x48, 0xb5, 0xcd, 0x57, 0x2c, 0xab, 0xed, 0xaf, 0x35, 0xb0, 0x78, 0xcc, 0xfc, 0xef, 0xc5, 0x1e, - 0xe4, 0xe8, 0x14, 0x52, 0x18, 0x32, 0x51, 0x4d, 0xd8, 0xe2, 0x4d, 0x22, 0x56, 0xf4, 0x67, 0x57, - 0x33, 0x83, 0xea, 0x47, 0x60, 0x36, 0x96, 0x0c, 0xaa, 0x78, 0x5f, 0x34, 0x87, 0xe8, 0x9f, 0x66, - 0x32, 0xe9, 0xc1, 0xf4, 0xd3, 0x67, 0xd5, 0x09, 0x5b, 0x11, 0xec, 0x2d, 0x48, 0x3f, 0x19, 0x75, - 0x6d, 0x1d, 0xac, 0x5d, 0x52, 0x99, 0x39, 0xf8, 0x59, 0x01, 0xdc, 0x3e, 0x66, 0x7e, 0xea, 0x72, - 0xdf, 0xf3, 0xb0, 0xa8, 0xd2, 0xa0, 0x06, 0xf0, 0x2d, 0xb0, 0x80, 0x23, 0xcc, 0x31, 0x0c, 0x9c, - 0x26, 0x12, 0xa5, 0x57, 0x82, 0x0d, 0x79, 0x33, 0x44, 0xd3, 0x33, 0x55, 0xab, 0x93, 0x37, 0x40, - 0x20, 0x94, 0xbe, 0x79, 0x95, 0x97, 0x0c, 0x8a, 0x86, 0xe0, 0xa3, 0x08, 0x31, 0xcc, 0x9c, 0x26, - 0x64, 0x4d, 0x79, 0x4f, 0xe7, 0xec, 0x92, 0x1a, 0xbb, 0x0f, 0x59, 0x53, 0xaf, 0x82, 0x52, 0x03, - 0x47, 0x90, 0x76, 0x12, 0xc4, 0xb4, 0x44, 0x80, 0x64, 0x48, 0x02, 0x0e, 0x01, 0x60, 0x31, 0xbc, - 0x88, 0x1c, 0xb1, 0x0d, 0x94, 0x67, 0x94, 0x90, 0xa4, 0xc5, 0x9b, 0x69, 0x8b, 0x37, 0xcf, 0xd2, - 0x3d, 0xe2, 0xa0, 0x20, 0x84, 0x7c, 0xf0, 0x8f, 0xaa, 0x66, 0x17, 0x65, 0x9e, 0x88, 0xe8, 0x0f, - 0xc1, 0x52, 0x2b, 0x6a, 0x90, 0xc8, 0xc3, 0x91, 0xef, 0xc4, 0x88, 0x62, 0xe2, 0x95, 0x67, 0x25, - 0xd5, 0xfa, 0x15, 0xaa, 0xba, 0xda, 0x4d, 0x12, 0xa6, 0x5f, 0x0a, 0xa6, 0xc5, 0x2c, 0xf9, 0x54, - 0xe6, 0xea, 0xdf, 0x05, 0xba, 0xeb, 0xb6, 0xa5, 0x24, 0xd2, 0xe2, 0x29, 0xe3, 0xad, 0xe1, 0x19, - 0x97, 0x5c, 0xb7, 0x7d, 0x96, 0x64, 0x2b, 0xca, 0x1f, 0x82, 0x35, 0x4e, 0x61, 0xc4, 0xce, 0x11, - 0xbd, 0xcc, 0x5b, 0x18, 0x9e, 0x77, 0x35, 0xe5, 0xe8, 0x25, 0xbf, 0x0f, 0x36, 0xb3, 0xce, 0x4c, - 0x91, 0x87, 0x19, 0xa7, 0xb8, 0xd1, 0x92, 0x8b, 0x2e, 0x5d, 0x36, 0xe5, 0xa2, 0x7c, 0x08, 0x2a, - 0x29, 0xce, 0xee, 0x81, 0x7d, 0x53, 0xa1, 0xf4, 0x13, 0xf0, 0x8a, 0x5c, 0xa6, 0x4c, 0x88, 0x73, - 0x7a, 0x98, 0xe4, 0xd4, 0x21, 0x66, 0x4c, 0xb0, 0x81, 0x4d, 0x6d, 0x7b, 0xca, 0xde, 0x4a, 0xb0, - 0xa7, 0x88, 0xd6, 0x73, 0xc8, 0xb3, 0x1c, 0x50, 0xdf, 0x01, 0x7a, 0x13, 0x33, 0x4e, 0x28, 0x76, - 0x61, 0xe0, 0xa0, 0x88, 0x53, 0x8c, 0x58, 0xb9, 0x24, 0xd3, 0x97, 0xbb, 0x91, 0x77, 0x92, 0x80, - 0xfe, 0x00, 0x6c, 0x5d, 0x3b, 0xa9, 0xe3, 0x36, 0x61, 0x14, 0xa1, 0xa0, 0x3c, 0x27, 0xad, 0x54, - 0xbd, 0x6b, 0xe6, 0x3c, 0x4c, 0x60, 0xfa, 0x6d, 0x30, 0xc3, 0x49, 0xec, 0x3c, 0x2c, 0xcf, 0x6f, - 0x6a, 0xdb, 0xf3, 0xf6, 0x34, 0x27, 0xf1, 0x43, 0xfd, 0x0d, 0xb0, 0xd2, 0x86, 0x01, 0xf6, 0x20, - 0x27, 0x94, 0x39, 0x31, 0xb9, 0x40, 0xd4, 0x71, 0x61, 0x5c, 0x5e, 0x90, 0x18, 0xbd, 0x1b, 0x3b, - 0x15, 0xa1, 0x43, 0x18, 0xeb, 0x77, 0xc0, 0x72, 0x36, 0xea, 0x30, 0xc4, 0x25, 0x7c, 0x51, 0xc2, - 0x17, 0xb3, 0xc0, 0x23, 0xc4, 0x05, 0x76, 0x03, 0x14, 0x61, 0x10, 0x90, 0x8b, 0x00, 0x33, 0x5e, - 0x5e, 0xda, 0x9c, 0xda, 0x2e, 0xda, 0xdd, 0x01, 0xdd, 0x00, 0x05, 0x0f, 0x45, 0x1d, 0x19, 0x5c, - 0x96, 0xc1, 0xec, 0xba, 0xb7, 0xeb, 0xe8, 0x43, 0x77, 0x9d, 0x2b, 0xad, 0xe2, 0x65, 0xf0, 0x52, - 0x9f, 0x76, 0x90, 0xb5, 0x8b, 0x3f, 0x6a, 0x40, 0xcf, 0xc5, 0x6d, 0x14, 0x92, 0x36, 0x0c, 0x06, - 0x75, 0x8b, 0x7d, 0x50, 0x64, 0xa2, 0x8c, 0x72, 0x7d, 0x4e, 0x8e, 0xb0, 0x3e, 0x0b, 0x22, 0x4d, - 0x2e, 0xcf, 0x1e, 0x6f, 0x53, 0xe3, 0x7b, 0xdb, 0x00, 0xc6, 0x55, 0xed, 0x99, 0xb5, 0x3f, 0x68, - 0x60, 0x55, 0x84, 0x9b, 0x30, 0xf2, 0x91, 0x8d, 0x2e, 0x20, 0xf5, 0xea, 0x28, 0x22, 0x21, 0xd3, - 0x6b, 0x60, 0xde, 0x93, 0xff, 0x1c, 0x4e, 0xc4, 0x91, 0xa7, 0xac, 0xc9, 0xe2, 0x97, 0x92, 0xc1, - 0x33, 0xb2, 0xef, 0x79, 0xfa, 0x36, 0x58, 0xea, 0x62, 0xa8, 0xa0, 0x16, 0x6e, 0x05, 0x6c, 0x21, - 0x85, 0xc9, 0x09, 0xff, 0x7b, 0x6e, 0xaa, 0x72, 0x5b, 0xbf, 0x2a, 0x37, 0x33, 0xf4, 0x54, 0x03, - 0x85, 0x63, 0xe6, 0x9f, 0xc4, 0xfc, 0x28, 0xfa, 0x3f, 0x3f, 0xd0, 0xe9, 0x60, 0x29, 0x75, 0x92, - 0xd9, 0xfb, 0x8d, 0x06, 0x8a, 0xc9, 0xe0, 0x49, 0x8b, 0xff, 0x4f, 0xfc, 0x75, 0xc5, 0x4f, 0xdd, - 0x44, 0xfc, 0x6d, 0xb0, 0x9c, 0xe9, 0xcc, 0xdf, 0x1c, 0x71, 0x46, 0x15, 0xeb, 0x5e, 0x95, 0xeb, - 0x90, 0x84, 0xaa, 0x01, 0xd9, 0x90, 0xa3, 0xab, 0xaa, 0xb5, 0x21, 0x55, 0xe7, 0x2b, 0x31, 0xd9, - 0x5b, 0x89, 0x07, 0x60, 0x9a, 0x42, 0x8e, 0x94, 0x9d, 0x7b, 0x62, 0xa9, 0xfd, 0xfd, 0x59, 0xf5, - 0xa5, 0xc4, 0x12, 0xf3, 0x1e, 0x9b, 0x98, 0x58, 0x21, 0xe4, 0x4d, 0xf3, 0x3b, 0xc8, 0x87, 0x6e, - 0xa7, 0x8e, 0xdc, 0x4f, 0x3e, 0xda, 0x01, 0xca, 0x71, 0x1d, 0xb9, 0xbf, 0xfd, 0xf4, 0xc9, 0x1d, - 0xcd, 0x96, 0x1c, 0x7b, 0x85, 0xd4, 0x6a, 0xed, 0x55, 0xf0, 0xca, 0x20, 0x27, 0x99, 0xe5, 0xbf, - 0x4c, 0xca, 0x63, 0x48, 0x76, 0x58, 0x25, 0x1e, 0x3e, 0x17, 0x67, 0x3e, 0xd1, 0xe6, 0x57, 0xc0, - 0x0c, 0xc7, 0x3c, 0x40, 0xea, 0xde, 0x25, 0x17, 0xfa, 0x26, 0x28, 0x79, 0x88, 0xb9, 0x14, 0xc7, - 0x72, 0x0b, 0x4a, 0xdc, 0xe4, 0x87, 0x7a, 0xcc, 0x4e, 0xf5, 0x9a, 0xcd, 0xda, 0xf7, 0xf4, 0x10, - 0xed, 0x7b, 0x66, 0xb4, 0xf6, 0x3d, 0x3b, 0x44, 0xfb, 0xbe, 0x35, 0xa8, 0x7d, 0x17, 0x06, 0xb5, - 0xef, 0xe2, 0xd0, 0x4d, 0xa1, 0xb6, 0x05, 0xaa, 0xd7, 0x94, 0x34, 0x2d, 0xfb, 0xee, 0x9f, 0x4b, - 0x60, 0xea, 0x98, 0xf9, 0xfa, 0x2f, 0x34, 0xb0, 0x7c, 0xf5, 0x45, 0xef, 0xab, 0x43, 0x9d, 0x32, - 0xfb, 0xbd, 0x4d, 0x19, 0xfb, 0x63, 0xa7, 0xa6, 0xda, 0xf4, 0xdf, 0x6b, 0xc0, 0x18, 0xf0, 0x16, - 0x76, 0x30, 0xec, 0x0c, 0xd7, 0x73, 0x18, 0x0f, 0x6e, 0xce, 0x31, 0x40, 0x6e, 0xcf, 0x7b, 0xd4, - 0x98, 0x72, 0xf3, 0x1c, 0xe3, 0xca, 0xed, 0xf7, 0x76, 0xa2, 0xff, 0x5c, 0x03, 0x4b, 0x57, 0x0e, - 0xf6, 0x5f, 0x19, 0x76, 0x82, 0xcb, 0x99, 0xc6, 0x37, 0xc6, 0xcd, 0xcc, 0x04, 0xfd, 0x54, 0x03, - 0x8b, 0x97, 0x8f, 0x0e, 0x6f, 0x8d, 0xca, 0xaa, 0x12, 0x8d, 0xaf, 0x8f, 0x99, 0x98, 0xa9, 0x79, - 0x5f, 0x03, 0x73, 0x3d, 0x6f, 0x6e, 0x5f, 0x1e, 0x96, 0x31, 0x9f, 0x65, 0xbc, 0x3d, 0x4e, 0x56, - 0x26, 0x22, 0x04, 0x33, 0xc9, 0x06, 0xbd, 0x33, 0x2c, 0x8d, 0x84, 0x1b, 0x6f, 0x8e, 0x04, 0xcf, - 0xa6, 0x8b, 0xc1, 0xac, 0xda, 0x30, 0xcd, 0x11, 0x08, 0x4e, 0x5a, 0xdc, 0xb8, 0x37, 0x1a, 0x3e, - 0x9b, 0xf1, 0x77, 0x1a, 0x58, 0xbf, 0x7e, 0x97, 0x1b, 0xba, 0x87, 0x5c, 0x4b, 0x61, 0x1c, 0xdd, - 0x98, 0x22, 0xd3, 0xfa, 0x2b, 0x0d, 0xac, 0xf4, 0xdd, 0x9e, 0xde, 0x1e, 0xf5, 0x59, 0xcb, 0x67, - 0x1b, 0xf5, 0x9b, 0x64, 0xa7, 0xe2, 0x8c, 0x99, 0x1f, 0x8b, 0xdd, 0xf7, 0xe0, 0xfb, 0x4f, 0x9f, - 0x57, 0xb4, 0x8f, 0x9f, 0x57, 0xb4, 0x7f, 0x3e, 0xaf, 0x68, 0x1f, 0xbc, 0xa8, 0x4c, 0x7c, 0xfc, - 0xa2, 0x32, 0xf1, 0xb7, 0x17, 0x95, 0x89, 0x1f, 0x7c, 0xcd, 0xc7, 0xbc, 0xd9, 0x6a, 0x98, 0x2e, - 0x09, 0xd5, 0x57, 0x4a, 0xab, 0x3b, 0xef, 0x4e, 0xf6, 0x91, 0xb1, 0xfd, 0xa6, 0xf5, 0x5e, 0xef, - 0x97, 0x46, 0xf9, 0x65, 0xa6, 0x31, 0x2b, 0x4f, 0xe3, 0x5f, 0xfa, 0x4f, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xcb, 0x72, 0xac, 0x84, 0xe5, 0x15, 0x00, 0x00, + // 1771 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0x4d, 0x8c, 0xdc, 0x48, + 0x15, 0x1e, 0x67, 0x7e, 0xd2, 0x5d, 0x3d, 0xbf, 0x9e, 0x19, 0xc6, 0xd3, 0xc9, 0x76, 0x4f, 0x9a, + 0x65, 0x77, 0x14, 0x76, 0xec, 0x4d, 0x60, 0xb3, 0x30, 0x5a, 0x04, 0xf3, 0xb3, 0x90, 0x09, 0x4c, + 0x32, 0x38, 0x21, 0x48, 0x20, 0x61, 0x55, 0xdb, 0x15, 0x77, 0x69, 0xec, 0x2a, 0xab, 0xaa, 0xba, + 0x93, 0xbe, 0xa1, 0x3d, 0x21, 0x21, 0xa1, 0xe5, 0x86, 0x38, 0xed, 0x01, 0x21, 0x90, 0x40, 0xca, + 0x61, 0x2f, 0x70, 0xe3, 0x96, 0x03, 0x87, 0xd5, 0x8a, 0x03, 0xe2, 0x10, 0x50, 0x72, 0x58, 0xce, + 0x48, 0xdc, 0x51, 0x95, 0xcb, 0x6e, 0xf7, 0xfc, 0x74, 0x3c, 0x33, 0x70, 0xe0, 0xd2, 0x6a, 0xd7, + 0xfb, 0xde, 0xf7, 0xbe, 0xf7, 0xca, 0x7e, 0xaf, 0x6c, 0xf0, 0x16, 0x26, 0x02, 0x31, 0xbf, 0x03, + 0x31, 0xf1, 0x38, 0xf2, 0xbb, 0x0c, 0x8b, 0xbe, 0xe3, 0xfb, 0x3d, 0x27, 0x61, 0xb4, 0x87, 0x03, + 0xc4, 0x9c, 0xde, 0x0d, 0x47, 0x3c, 0xb1, 0x13, 0x46, 0x05, 0x35, 0x3f, 0x7f, 0x02, 0xda, 0xf6, + 0xfd, 0x9e, 0x9d, 0xa1, 0xed, 0xde, 0x8d, 0xfa, 0x02, 0x8c, 0x31, 0xa1, 0x8e, 0xfa, 0x4d, 0xfd, + 0xea, 0x57, 0x43, 0x4a, 0xc3, 0x08, 0x39, 0x30, 0xc1, 0x0e, 0x24, 0x84, 0x0a, 0x28, 0x30, 0x25, + 0x5c, 0x5b, 0x9b, 0xda, 0xaa, 0xae, 0xda, 0xdd, 0x47, 0x8e, 0xc0, 0x31, 0xe2, 0x02, 0xc6, 0x89, + 0x06, 0x34, 0x8e, 0x02, 0x82, 0x2e, 0x53, 0x0c, 0xda, 0xbe, 0x7a, 0xd4, 0x0e, 0x49, 0x5f, 0x9b, + 0x96, 0x42, 0x1a, 0x52, 0xf5, 0xd7, 0x91, 0xff, 0x32, 0x07, 0x9f, 0xf2, 0x98, 0x72, 0x2f, 0x35, + 0xa4, 0x17, 0xda, 0xb4, 0x92, 0x5e, 0x39, 0x31, 0x0f, 0x65, 0xea, 0x31, 0x0f, 0x33, 0x95, 0xb8, + 0xed, 0x3b, 0x3e, 0x65, 0xc8, 0xf1, 0x23, 0x8c, 0x88, 0x90, 0xd6, 0xf4, 0x9f, 0x06, 0xdc, 0x2c, + 0x53, 0xca, 0xbc, 0x50, 0xa9, 0x8f, 0x23, 0x49, 0x23, 0x1c, 0x76, 0x44, 0x4a, 0xc5, 0x1d, 0x81, + 0x48, 0x80, 0x58, 0x8c, 0xd3, 0x00, 0x83, 0xab, 0x4c, 0x45, 0xc1, 0x2e, 0xfa, 0x09, 0xe2, 0x0e, + 0x92, 0x7c, 0xc4, 0x47, 0x29, 0xa0, 0xf5, 0x17, 0x03, 0x2c, 0xed, 0xf3, 0x70, 0x8b, 0x73, 0x1c, + 0x92, 0x1d, 0x4a, 0x78, 0x37, 0x46, 0xec, 0xdb, 0xa8, 0x6f, 0xae, 0x82, 0x4a, 0xaa, 0x0d, 0x07, + 0x96, 0xb1, 0x66, 0xac, 0x57, 0xdd, 0xcb, 0xea, 0x7a, 0x2f, 0x30, 0xdf, 0x05, 0x33, 0x99, 0x2e, + 0x0f, 0x06, 0x01, 0xb3, 0x2e, 0x49, 0xfb, 0xb6, 0xf9, 0xaf, 0xe7, 0xcd, 0xd9, 0x3e, 0x8c, 0xa3, + 0xcd, 0x96, 0x5c, 0x45, 0x9c, 0xb7, 0xdc, 0xe9, 0x0c, 0xb8, 0x15, 0x04, 0xcc, 0xbc, 0x06, 0xa6, + 0x7d, 0x1d, 0xc2, 0x3b, 0x44, 0x7d, 0x6b, 0x5c, 0xf1, 0xd6, 0xfc, 0x42, 0xd8, 0xb7, 0xc1, 0x94, + 0x54, 0x82, 0x98, 0x35, 0xa1, 0x48, 0xad, 0x4f, 0x3f, 0xde, 0x58, 0xd2, 0x15, 0xdf, 0x4a, 0x59, + 0xef, 0x0b, 0x86, 0x49, 0xe8, 0x6a, 0xdc, 0xe6, 0xe2, 0x4f, 0x3e, 0x6a, 0x8e, 0xfd, 0xf3, 0xa3, + 0xe6, 0xd8, 0x07, 0x9f, 0x3d, 0xbd, 0xae, 0x17, 0x5b, 0x0d, 0x70, 0xf5, 0xa4, 0xac, 0x5c, 0xc4, + 0x13, 0x4a, 0x38, 0x6a, 0xfd, 0xc9, 0x00, 0xaf, 0xed, 0xf3, 0xf0, 0x7e, 0xb7, 0x1d, 0x63, 0x91, + 0x01, 0xf6, 0x31, 0x6f, 0xa3, 0x0e, 0xec, 0x61, 0xda, 0x65, 0xe6, 0x2d, 0x50, 0xe5, 0xca, 0x2a, + 0x10, 0x4b, 0x0b, 0x30, 0x42, 0xcb, 0x00, 0x6a, 0x1e, 0x80, 0xe9, 0xb8, 0xc0, 0xa3, 0x6a, 0x53, + 0xbb, 0xf9, 0x96, 0x8d, 0xdb, 0xbe, 0x5d, 0xdc, 0x39, 0xbb, 0xb0, 0x57, 0xbd, 0x1b, 0x76, 0x31, + 0xb6, 0x3b, 0xc4, 0xb0, 0xf9, 0xb9, 0x62, 0x82, 0x83, 0x48, 0xad, 0x37, 0xc1, 0x17, 0x46, 0xa6, + 0x90, 0x27, 0xfb, 0xf4, 0xd2, 0x09, 0xc9, 0xee, 0xd2, 0x6e, 0x3b, 0x42, 0x0f, 0xa9, 0xc0, 0x24, + 0x3c, 0x77, 0xb2, 0x1e, 0x58, 0x09, 0xba, 0x49, 0x84, 0x7d, 0x28, 0x90, 0xd7, 0xa3, 0x02, 0x79, + 0xd9, 0xed, 0xa5, 0xf3, 0x7e, 0xb3, 0x98, 0xa6, 0xba, 0x01, 0xed, 0xdd, 0xcc, 0xe1, 0x21, 0x15, + 0xe8, 0x7d, 0x0d, 0x77, 0x97, 0x83, 0x93, 0x96, 0xcd, 0x1f, 0x81, 0x15, 0x4c, 0x1e, 0x31, 0xe8, + 0xcb, 0xc7, 0xd7, 0x6b, 0x47, 0xd4, 0x3f, 0xf4, 0x3a, 0x08, 0x06, 0x88, 0xa9, 0x9b, 0xa7, 0x76, + 0xf3, 0x8d, 0x57, 0x15, 0xf6, 0xb6, 0x42, 0xbb, 0xcb, 0x03, 0x9a, 0x6d, 0xc9, 0x92, 0x2e, 0x9f, + 0xa9, 0xb6, 0xc5, 0x8a, 0xe5, 0xb5, 0xfd, 0x95, 0x01, 0xe6, 0xf6, 0x79, 0xf8, 0xbd, 0x24, 0x80, + 0x02, 0x1d, 0x40, 0x06, 0x63, 0x2e, 0xab, 0x09, 0xbb, 0xa2, 0x43, 0xe5, 0x13, 0xfd, 0xea, 0x6a, + 0xe6, 0x50, 0x73, 0x0f, 0x4c, 0x25, 0x8a, 0x41, 0x17, 0xef, 0x8b, 0x76, 0x89, 0xfe, 0x69, 0xa7, + 0x41, 0xb7, 0x27, 0x9e, 0x3d, 0x6f, 0x8e, 0xb9, 0x9a, 0x60, 0x73, 0x56, 0xe5, 0x93, 0x53, 0xb7, + 0x56, 0xc1, 0xca, 0x11, 0x95, 0x79, 0x06, 0xff, 0xae, 0x80, 0xc5, 0x7d, 0x1e, 0x66, 0x59, 0x6e, + 0x05, 0x01, 0x96, 0x55, 0x1a, 0xd5, 0x00, 0xbe, 0x05, 0x66, 0x31, 0xc1, 0x02, 0xc3, 0xc8, 0xeb, + 0x20, 0x59, 0x7a, 0x2d, 0xb8, 0xae, 0x36, 0x43, 0x36, 0x3d, 0x5b, 0xb7, 0x3a, 0xb5, 0x01, 0x12, + 0xa1, 0xf5, 0xcd, 0x68, 0xbf, 0x74, 0x51, 0x36, 0x84, 0x10, 0x11, 0xc4, 0x31, 0xf7, 0x3a, 0x90, + 0x77, 0xd4, 0x9e, 0x4e, 0xbb, 0x35, 0xbd, 0x76, 0x1b, 0xf2, 0x8e, 0xd9, 0x04, 0xb5, 0x36, 0x26, + 0x90, 0xf5, 0x53, 0xc4, 0x84, 0x42, 0x80, 0x74, 0x49, 0x01, 0x76, 0x00, 0xe0, 0x09, 0x7c, 0x4c, + 0x3c, 0x39, 0x06, 0xac, 0x49, 0x2d, 0x24, 0x6d, 0xf1, 0x76, 0xd6, 0xe2, 0xed, 0x07, 0xd9, 0x8c, + 0xd8, 0xae, 0x48, 0x21, 0x1f, 0xfe, 0xbd, 0x69, 0xb8, 0x55, 0xe5, 0x27, 0x2d, 0xe6, 0x5d, 0x30, + 0xdf, 0x25, 0x6d, 0x4a, 0x02, 0x4c, 0x42, 0x2f, 0x41, 0x0c, 0xd3, 0xc0, 0x9a, 0x52, 0x54, 0xab, + 0xc7, 0xa8, 0x76, 0xf5, 0x34, 0x49, 0x99, 0x7e, 0x21, 0x99, 0xe6, 0x72, 0xe7, 0x03, 0xe5, 0x6b, + 0x7e, 0x17, 0x98, 0xbe, 0xdf, 0x53, 0x92, 0x68, 0x57, 0x64, 0x8c, 0x97, 0xcb, 0x33, 0xce, 0xfb, + 0x7e, 0xef, 0x41, 0xea, 0xad, 0x29, 0x7f, 0x08, 0x56, 0x04, 0x83, 0x84, 0x3f, 0x42, 0xec, 0x28, + 0x6f, 0xa5, 0x3c, 0xef, 0x72, 0xc6, 0x31, 0x4c, 0x7e, 0x1b, 0xac, 0xe5, 0x9d, 0x99, 0xa1, 0x00, + 0x73, 0xc1, 0x70, 0xbb, 0xab, 0x1e, 0xba, 0xec, 0xb1, 0xb1, 0xaa, 0xea, 0x26, 0x68, 0x64, 0x38, + 0x77, 0x08, 0xf6, 0x4d, 0x8d, 0x32, 0xef, 0x81, 0xd7, 0xd5, 0x63, 0xca, 0xa5, 0x38, 0x6f, 0x88, + 0x49, 0x85, 0x8e, 0x31, 0xe7, 0x92, 0x0d, 0xac, 0x19, 0xeb, 0xe3, 0xee, 0xb5, 0x14, 0x7b, 0x80, + 0xd8, 0x6e, 0x01, 0xf9, 0xa0, 0x00, 0x34, 0x37, 0x80, 0xd9, 0xc1, 0x5c, 0x50, 0x86, 0x7d, 0x18, + 0x79, 0x88, 0x08, 0x86, 0x11, 0xb7, 0x6a, 0xca, 0x7d, 0x61, 0x60, 0x79, 0x3f, 0x35, 0x98, 0x77, + 0xc0, 0xb5, 0x53, 0x83, 0x7a, 0x7e, 0x07, 0x12, 0x82, 0x22, 0x6b, 0x5a, 0xa5, 0xd2, 0x0c, 0x4e, + 0x89, 0xb9, 0x93, 0xc2, 0xcc, 0x45, 0x30, 0x29, 0x68, 0xe2, 0xdd, 0xb5, 0x66, 0xd6, 0x8c, 0xf5, + 0x19, 0x77, 0x42, 0xd0, 0xe4, 0xae, 0xf9, 0x36, 0x58, 0xea, 0xc1, 0x08, 0x07, 0x50, 0x50, 0xc6, + 0xbd, 0x84, 0x3e, 0x46, 0xcc, 0xf3, 0x61, 0x62, 0xcd, 0x2a, 0x8c, 0x39, 0xb0, 0x1d, 0x48, 0xd3, + 0x0e, 0x4c, 0xcc, 0xeb, 0x60, 0x21, 0x5f, 0xf5, 0x38, 0x12, 0x0a, 0x3e, 0xa7, 0xe0, 0x73, 0xb9, + 0xe1, 0x3e, 0x12, 0x12, 0x7b, 0x15, 0x54, 0x61, 0x14, 0xd1, 0xc7, 0x11, 0xe6, 0xc2, 0x9a, 0x5f, + 0x1b, 0x5f, 0xaf, 0xba, 0x83, 0x05, 0xb3, 0x0e, 0x2a, 0x01, 0x22, 0x7d, 0x65, 0x5c, 0x50, 0xc6, + 0xfc, 0x7a, 0xb8, 0xeb, 0x98, 0xe5, 0xbb, 0xce, 0x15, 0x50, 0x8d, 0x65, 0x7f, 0x11, 0xf0, 0x10, + 0x59, 0x8b, 0x6b, 0xc6, 0xfa, 0x84, 0x5b, 0x89, 0x31, 0xb9, 0x2f, 0xaf, 0x65, 0x13, 0x88, 0xe1, + 0x13, 0x8f, 0x41, 0x72, 0x68, 0x2d, 0x29, 0xc5, 0x97, 0x63, 0xf8, 0xc4, 0x85, 0xe4, 0xd0, 0xb4, + 0xc1, 0xa2, 0x12, 0xe6, 0x61, 0x22, 0xb7, 0xbe, 0x87, 0xbc, 0x1e, 0x8c, 0xb8, 0xb5, 0xbc, 0x66, + 0xac, 0x57, 0xdc, 0x05, 0x65, 0xda, 0xd3, 0x96, 0x87, 0x30, 0x3a, 0xde, 0x92, 0x5e, 0x03, 0x57, + 0x4e, 0x68, 0x3b, 0x79, 0x5b, 0xfa, 0xa3, 0x01, 0xcc, 0x82, 0xdd, 0x45, 0x31, 0xed, 0xc1, 0x68, + 0x54, 0x57, 0xda, 0x02, 0x55, 0x2e, 0xb7, 0x4b, 0xf5, 0x81, 0x4b, 0x67, 0xe8, 0x03, 0x15, 0xe9, + 0xa6, 0xda, 0xc0, 0x50, 0x0d, 0xc7, 0x4b, 0xd7, 0xf0, 0x58, 0x6e, 0x57, 0x41, 0xfd, 0xb8, 0xf6, + 0x3c, 0xb5, 0xdf, 0x1b, 0x60, 0x59, 0x9a, 0x3b, 0x90, 0x84, 0xc8, 0x45, 0x8f, 0x21, 0x0b, 0x76, + 0x11, 0xa1, 0x31, 0x37, 0x5b, 0x60, 0x26, 0x50, 0xff, 0x3c, 0x41, 0xe5, 0xd1, 0xca, 0x32, 0xd4, + 0x26, 0xd7, 0xd2, 0xc5, 0x07, 0x74, 0x2b, 0x08, 0xcc, 0x75, 0x30, 0x3f, 0xc0, 0x30, 0x49, 0x2d, + 0xb3, 0x95, 0xb0, 0xd9, 0x0c, 0xa6, 0x02, 0xfe, 0xf7, 0xb2, 0x69, 0xaa, 0xe3, 0xc3, 0x71, 0xb9, + 0x79, 0x42, 0xcf, 0x0c, 0x50, 0xd9, 0xe7, 0xe1, 0xbd, 0x44, 0xec, 0x91, 0xff, 0xf3, 0x83, 0xa3, + 0x09, 0xe6, 0xb3, 0x4c, 0xf2, 0xf4, 0x7e, 0x6d, 0x80, 0x6a, 0xba, 0x78, 0xaf, 0x2b, 0xfe, 0x27, + 0xf9, 0x0d, 0xc4, 0x8f, 0x5f, 0x44, 0xfc, 0x22, 0x58, 0xc8, 0x75, 0x16, 0x37, 0x47, 0x9e, 0x85, + 0x65, 0x7f, 0xd1, 0xe5, 0xda, 0xa1, 0xb1, 0x6e, 0x74, 0x2e, 0x14, 0xe8, 0xb8, 0x6a, 0xa3, 0xa4, + 0xea, 0x62, 0x25, 0x2e, 0x0d, 0x57, 0xe2, 0x0e, 0x98, 0x60, 0x50, 0x20, 0x9d, 0xce, 0x2d, 0xf9, + 0xa8, 0xfd, 0xed, 0x79, 0xf3, 0x4a, 0x9a, 0x12, 0x0f, 0x0e, 0x6d, 0x4c, 0x9d, 0x18, 0x8a, 0x8e, + 0xfd, 0x1d, 0x14, 0x42, 0xbf, 0xbf, 0x8b, 0xfc, 0x4f, 0x3f, 0xde, 0x00, 0x3a, 0xe3, 0x5d, 0xe4, + 0xff, 0xe6, 0xb3, 0xa7, 0xd7, 0x0d, 0x57, 0x71, 0x6c, 0x56, 0xb2, 0x54, 0x5b, 0x6f, 0x80, 0xd7, + 0x47, 0x65, 0x92, 0xa7, 0xfc, 0x87, 0x71, 0x75, 0xdc, 0xc9, 0x0f, 0xc5, 0x34, 0xc0, 0x8f, 0xe4, + 0xd9, 0x52, 0x8e, 0x93, 0x25, 0x30, 0x29, 0xb0, 0x88, 0x90, 0xde, 0xbb, 0xf4, 0xc2, 0x5c, 0x03, + 0xb5, 0x00, 0x71, 0x9f, 0xe1, 0x44, 0x8d, 0xba, 0x34, 0x9b, 0xe2, 0xd2, 0x50, 0xb2, 0xe3, 0xc3, + 0xc9, 0xe6, 0x63, 0x62, 0xa2, 0xc4, 0x98, 0x98, 0x3c, 0xdb, 0x98, 0x98, 0x2a, 0x31, 0x26, 0x2e, + 0x8f, 0x1a, 0x13, 0x95, 0x51, 0x63, 0xa2, 0x7a, 0xce, 0x31, 0x01, 0x46, 0x8c, 0x89, 0x5a, 0xa9, + 0x31, 0x31, 0x7d, 0xca, 0x98, 0x68, 0x5d, 0x03, 0xcd, 0x53, 0xb6, 0x2e, 0xdb, 0xde, 0x9b, 0x7f, + 0xae, 0x81, 0xf1, 0x7d, 0x1e, 0x9a, 0x3f, 0x37, 0xc0, 0xc2, 0xf1, 0x17, 0xd7, 0xaf, 0x96, 0x3a, + 0x35, 0x9f, 0xf4, 0x76, 0x58, 0xdf, 0x3a, 0xb7, 0x6b, 0xa6, 0xcd, 0xfc, 0x9d, 0x01, 0xea, 0x23, + 0xde, 0x2a, 0xb7, 0xcb, 0x46, 0x38, 0x9d, 0xa3, 0x7e, 0xe7, 0xe2, 0x1c, 0x23, 0xe4, 0x0e, 0xbd, + 0x17, 0x9e, 0x53, 0x6e, 0x91, 0xe3, 0xbc, 0x72, 0x4f, 0x7a, 0xdb, 0x32, 0x7f, 0x66, 0x80, 0xf9, + 0x63, 0x2f, 0x2a, 0x5f, 0x29, 0x1b, 0xe0, 0xa8, 0x67, 0xfd, 0x1b, 0xe7, 0xf5, 0xcc, 0x05, 0xfd, + 0xd4, 0x00, 0x73, 0x47, 0x8f, 0x28, 0xef, 0x9e, 0x95, 0x55, 0x3b, 0xd6, 0xbf, 0x7e, 0x4e, 0xc7, + 0x5c, 0xcd, 0x07, 0x06, 0x98, 0x1e, 0x7a, 0x13, 0xfd, 0x72, 0x59, 0xc6, 0xa2, 0x57, 0xfd, 0xbd, + 0xf3, 0x78, 0xe5, 0x22, 0x62, 0x30, 0x99, 0x1e, 0x04, 0x36, 0xca, 0xd2, 0x28, 0x78, 0xfd, 0x9d, + 0x33, 0xc1, 0xf3, 0x70, 0x09, 0x98, 0xd2, 0x83, 0xd9, 0x3e, 0x03, 0xc1, 0xbd, 0xae, 0xa8, 0xdf, + 0x3a, 0x1b, 0x3e, 0x8f, 0xf8, 0x5b, 0x03, 0xac, 0x9e, 0x3e, 0x4d, 0x4b, 0xf7, 0x90, 0x53, 0x29, + 0xea, 0x7b, 0x17, 0xa6, 0xc8, 0xb5, 0xfe, 0xd2, 0x00, 0x4b, 0x27, 0x8e, 0xc1, 0xf7, 0xce, 0x7a, + 0xaf, 0x15, 0xbd, 0xeb, 0xbb, 0x17, 0xf1, 0xce, 0xc4, 0xd5, 0x27, 0x7f, 0x2c, 0xa7, 0xfc, 0xf6, + 0xf7, 0x9f, 0xbd, 0x68, 0x18, 0x9f, 0xbc, 0x68, 0x18, 0xff, 0x78, 0xd1, 0x30, 0x3e, 0x7c, 0xd9, + 0x18, 0xfb, 0xe4, 0x65, 0x63, 0xec, 0xaf, 0x2f, 0x1b, 0x63, 0x3f, 0xf8, 0x5a, 0x88, 0x45, 0xa7, + 0xdb, 0xb6, 0x7d, 0x1a, 0xeb, 0xaf, 0xae, 0xce, 0x20, 0xee, 0x46, 0xfe, 0xd1, 0xb4, 0xf7, 0x8e, + 0xf3, 0x64, 0xf8, 0xcb, 0xa9, 0xfa, 0xd2, 0xd4, 0x9e, 0x52, 0xa7, 0xfe, 0x2f, 0xfd, 0x27, 0x00, + 0x00, 0xff, 0xff, 0xd1, 0xa2, 0x3a, 0x05, 0xb5, 0x16, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2107,6 +2166,32 @@ func (m *MsgConsumerAddition) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.AllowInactiveVals { + i-- + if m.AllowInactiveVals { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa8 + } + if m.MaxRank != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.MaxRank)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa0 + } + if m.MinStake != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.MinStake)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x98 + } if len(m.Authority) > 0 { i -= len(m.Authority) copy(dAtA[i:], m.Authority) @@ -2636,6 +2721,26 @@ func (m *MsgConsumerModification) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l + if m.AllowInactiveVals { + i-- + if m.AllowInactiveVals { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x60 + } + if m.MaxRank != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.MaxRank)) + i-- + dAtA[i] = 0x58 + } + if m.MinStake != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.MinStake)) + i-- + dAtA[i] = 0x50 + } if len(m.Authority) > 0 { i -= len(m.Authority) copy(dAtA[i:], m.Authority) @@ -2915,6 +3020,15 @@ func (m *MsgConsumerAddition) Size() (n int) { if l > 0 { n += 2 + l + sovTx(uint64(l)) } + if m.MinStake != 0 { + n += 2 + sovTx(uint64(m.MinStake)) + } + if m.MaxRank != 0 { + n += 2 + sovTx(uint64(m.MaxRank)) + } + if m.AllowInactiveVals { + n += 3 + } return n } @@ -3124,6 +3238,15 @@ func (m *MsgConsumerModification) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + if m.MinStake != 0 { + n += 1 + sovTx(uint64(m.MinStake)) + } + if m.MaxRank != 0 { + n += 1 + sovTx(uint64(m.MaxRank)) + } + if m.AllowInactiveVals { + n += 2 + } return n } @@ -4456,6 +4579,64 @@ func (m *MsgConsumerAddition) Unmarshal(dAtA []byte) error { } m.Authority = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 19: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinStake", wireType) + } + m.MinStake = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinStake |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 20: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxRank", wireType) + } + m.MaxRank = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxRank |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowInactiveVals", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowInactiveVals = bool(v != 0) default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) @@ -5820,6 +6001,64 @@ func (m *MsgConsumerModification) Unmarshal(dAtA []byte) error { } m.Authority = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinStake", wireType) + } + m.MinStake = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinStake |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxRank", wireType) + } + m.MaxRank = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxRank |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowInactiveVals", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowInactiveVals = bool(v != 0) default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index a25cfe467e..9d4ece168f 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -4,6 +4,8 @@ import ( context "context" "time" + addresscodec "cosmossdk.io/core/address" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" conntypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" @@ -12,12 +14,11 @@ import ( "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" - abci "github.com/cometbft/cometbft/abci/types" ) @@ -56,6 +57,18 @@ type StakingKeeper interface { GetUnbondingDelegationByUnbondingID(ctx context.Context, id uint64) (stakingtypes.UnbondingDelegation, error) GetRedelegationByUnbondingID(ctx context.Context, id uint64) (stakingtypes.Redelegation, error) GetValidatorByUnbondingID(ctx context.Context, id uint64) (stakingtypes.Validator, error) + GetBondedValidatorsByPower(ctx context.Context) ([]stakingtypes.Validator, error) + ValidatorAddressCodec() addresscodec.Codec + IterateDelegations( + ctx context.Context, delegator sdk.AccAddress, + fn func(index int64, delegation stakingtypes.DelegationI) (stop bool), + ) error + IterateBondedValidatorsByPower( + context.Context, func(index int64, validator stakingtypes.ValidatorI) (stop bool), + ) error + StakingTokenSupply(ctx context.Context) (math.Int, error) + BondedRatio(ctx context.Context) (math.LegacyDec, error) + TotalBondedTokens(ctx context.Context) (math.Int, error) } // SlashingKeeper defines the contract expected to perform ccv slashing diff --git a/x/ccv/types/utils.go b/x/ccv/types/utils.go index cb174168e8..bc2e052dcb 100644 --- a/x/ccv/types/utils.go +++ b/x/ccv/types/utils.go @@ -125,21 +125,17 @@ func GetConsAddrFromBech32(bech32str string) (sdk.ConsAddress, error) { return sdk.ConsAddress(addr), nil } -func GetLastBondedValidatorsUtil(ctx sdk.Context, stakingKeeper StakingKeeper, logger log.Logger) ([]stakingtypes.Validator, error) { - maxVals, err := stakingKeeper.MaxValidators(ctx) - if err != nil { - return nil, err - } - +// GetLastBondedValidatorsUtil iterates the last validator powers in the staking module +// and returns the first MaxValidators many validators with the largest powers. +func GetLastBondedValidatorsUtil(ctx sdk.Context, stakingKeeper StakingKeeper, logger log.Logger, maxVals uint32) ([]stakingtypes.Validator, error) { lastPowers := make([]stakingtypes.LastValidatorPower, maxVals) i := 0 - err = stakingKeeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { + err := stakingKeeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { lastPowers[i] = stakingtypes.LastValidatorPower{Address: addr.String(), Power: power} i++ return i >= int(maxVals) // stop iteration if true }) - if err != nil { return nil, err }