diff --git a/.changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md b/.changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md new file mode 100644 index 0000000000..9f274f7bb4 --- /dev/null +++ b/.changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md @@ -0,0 +1,3 @@ +- Update how consumer-assigned keys are checked when a validator is + created on the provider. + ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1339-check-key-assignment-in-use.md b/.changelog/unreleased/state-breaking/provider/1339-check-key-assignment-in-use.md new file mode 100644 index 0000000000..2890582ba8 --- /dev/null +++ b/.changelog/unreleased/state-breaking/provider/1339-check-key-assignment-in-use.md @@ -0,0 +1,3 @@ +- Change the states by adding a consumer key for each chain that is + not yet registered meaning for which the gov proposal has not passed. + ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) \ No newline at end of file diff --git a/app/provider/app.go b/app/provider/app.go index 497158449a..f0f5e9e61e 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -203,7 +203,7 @@ type App struct { // nolint: golint // different fee-pool from the consumer chain ConsumerKeeper DistrKeeper distrkeeper.Keeper - GovKeeper govkeeper.Keeper + GovKeeper *govkeeper.Keeper // Gov Keeper must be a pointer in the app, so we can SetRouter on it correctly CrisisKeeper crisiskeeper.Keeper UpgradeKeeper upgradekeeper.Keeper ParamsKeeper paramskeeper.Keeper @@ -403,6 +403,25 @@ func New( scopedIBCKeeper, ) + // create evidence keeper with router + app.EvidenceKeeper = *evidencekeeper.NewKeeper( + appCodec, + keys[evidencetypes.StoreKey], + app.StakingKeeper, + app.SlashingKeeper, + ) + + app.GovKeeper = govkeeper.NewKeeper( + appCodec, + keys[govtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + app.MsgServiceRouter(), + govtypes.DefaultConfig(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + app.ProviderKeeper = ibcproviderkeeper.NewKeeper( appCodec, keys[providertypes.StoreKey], @@ -417,6 +436,7 @@ func New( app.AccountKeeper, app.DistrKeeper, app.BankKeeper, + app.GovKeeper, authtypes.FeeCollectorName, ) @@ -431,22 +451,14 @@ func New( AddRoute(ibchost.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)). AddRoute(providertypes.RouterKey, ibcprovider.NewProviderProposalHandler(app.ProviderKeeper)). AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) - govConfig := govtypes.DefaultConfig() - - app.GovKeeper = *govkeeper.NewKeeper( - appCodec, - keys[govtypes.StoreKey], - app.AccountKeeper, - app.BankKeeper, - app.StakingKeeper, - app.MsgServiceRouter(), - govConfig, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) // Set legacy router for backwards compatibility with gov v1beta1 app.GovKeeper.SetLegacyRouter(govRouter) + app.GovKeeper = app.GovKeeper.SetHooks( + govtypes.NewMultiGovHooks(app.ProviderKeeper.Hooks()), + ) + app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], @@ -493,7 +505,7 @@ func New( bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), - gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), 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)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), @@ -590,7 +602,7 @@ func New( auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), - gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), diff --git a/go.sum b/go.sum index d4bce198d6..9619db64ae 100644 --- a/go.sum +++ b/go.sum @@ -1056,6 +1056,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1350,6 +1351,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/proto/interchain_security/ccv/provider/v1/query.proto b/proto/interchain_security/ccv/provider/v1/query.proto index e648e2be35..05071a354c 100644 --- a/proto/interchain_security/ccv/provider/v1/query.proto +++ b/proto/interchain_security/ccv/provider/v1/query.proto @@ -73,6 +73,15 @@ service Query { option (google.api.http).get = "/interchain_security/ccv/provider/registered_consumer_reward_denoms"; } + + // QueryProposedConsumerChainIDs returns the chain IDs of the proposed consumer chain addition proposals + // that are still in the voting period + rpc QueryProposedConsumerChainIDs( + QueryProposedChainIDsRequest) + returns (QueryProposedChainIDsResponse) { + option (google.api.http).get = + "/interchain_security/ccv/provider/proposed_consumer_chains"; + } } message QueryConsumerGenesisRequest { string chain_id = 1; } @@ -150,3 +159,15 @@ message QueryRegisteredConsumerRewardDenomsRequest {} message QueryRegisteredConsumerRewardDenomsResponse { repeated string denoms = 1; } + +message QueryProposedChainIDsRequest {} + +message QueryProposedChainIDsResponse { + repeated ProposedChain proposedChains = 1 + [ (gogoproto.nullable) = false ]; +} + +message ProposedChain { + string chainID = 1; + uint64 proposalID = 2; +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 6eacdfe3c9..311ee67c89 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -505,7 +505,7 @@ func (s *TestConfig) SetRelayerConfig(useRly bool) { } // validateStringLiterals enforces that configs follow the constraints -// necessary to to execute the tests +// necessary to execute the tests // // Note: Network interfaces (name of virtual ethernet interfaces for ip link) // within the container will be named as "$CHAIN_ID-$VAL_ID-out" etc. @@ -513,7 +513,6 @@ func (s *TestConfig) SetRelayerConfig(useRly bool) { // used as a validatorID or chainID needs to be 5 char or less. func (s *TestConfig) validateStringLiterals() { for valID, valConfig := range s.validatorConfigs { - if len(valID) > 5 { panic("validator id string literal must be 5 char or less") } diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 1671236104..563ce1ff30 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -20,6 +20,7 @@ type State map[ChainID]ChainState type ChainState struct { ValBalances *map[ValidatorID]uint Proposals *map[uint]Proposal + ProposedConsumerChains *[]string ValPowers *map[ValidatorID]uint StakedTokens *map[ValidatorID]uint Params *[]Param @@ -123,6 +124,11 @@ func (tr TestConfig) getChainState(chain ChainID, modelState ChainState) ChainSt chainState.Proposals = &proposals } + if modelState.ProposedConsumerChains != nil { + proposedConsumerChains := tr.getProposedConsumerChains(chain) + chainState.ProposedConsumerChains = &proposedConsumerChains + } + if modelState.ValPowers != nil { tr.waitBlocks(chain, 1, 10*time.Second) powers := tr.getValPowers(chain, *modelState.ValPowers) @@ -836,6 +842,28 @@ func (tc TestConfig) getTrustedHeight( return clienttypes.Height{RevisionHeight: uint64(revHeight), RevisionNumber: uint64(revNumber)} } +func (tr TestConfig) getProposedConsumerChains(chain ChainID) []string { + tr.waitBlocks(chain, 1, 10*time.Second) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, + "query", "provider", "list-proposed-consumer-chains", + `--node`, tr.getQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + arr := gjson.Get(string(bz), "proposedChains").Array() + chains := []string{} + for _, c := range arr { + cid := c.Get("chainID").String() + chains = append(chains, cid) + } + + return chains +} + func uintPtr(i uint) *uint { return &i } diff --git a/tests/e2e/steps_start_chains.go b/tests/e2e/steps_start_chains.go index ace3d6c255..58a5f75162 100644 --- a/tests/e2e/steps_start_chains.go +++ b/tests/e2e/steps_start_chains.go @@ -54,6 +54,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Status: "PROPOSAL_STATUS_VOTING_PERIOD", }, }, + ProposedConsumerChains: &[]string{consumerName}, }, }, }, @@ -163,6 +164,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint ValidatorID("bob"): 9500000000, ValidatorID("carol"): 9500000000, }, + ProposedConsumerChains: &[]string{}, }, ChainID(consumerName): ChainState{ ValBalances: &map[ValidatorID]uint{ diff --git a/tests/integration/setup.go b/tests/integration/setup.go index eee8fb1a22..4628ca3111 100644 --- a/tests/integration/setup.go +++ b/tests/integration/setup.go @@ -24,6 +24,7 @@ import ( testutil "github.com/cosmos/interchain-security/v3/testutil/integration" "github.com/cosmos/interchain-security/v3/testutil/simibc" consumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v3/x/ccv/types" ) @@ -129,6 +130,9 @@ func (suite *CCVTestSuite) SetupTest() { providerKeeper := suite.providerApp.GetProviderKeeper() // re-assign all validator keys for the first consumer chain + providerKeeper.SetPendingConsumerAdditionProp(suite.providerCtx(), &types.ConsumerAdditionProposal{ + ChainId: icstestingutils.FirstConsumerChainID, + }) preProposalKeyAssignment(suite, icstestingutils.FirstConsumerChainID) // start consumer chains diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index b00678a50a..02b4749e43 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -14,6 +14,7 @@ import ( types0 "github.com/cosmos/cosmos-sdk/types" types1 "github.com/cosmos/cosmos-sdk/x/auth/types" types2 "github.com/cosmos/cosmos-sdk/x/capability/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" types3 "github.com/cosmos/cosmos-sdk/x/slashing/types" types4 "github.com/cosmos/cosmos-sdk/x/staking/types" types5 "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" @@ -1166,3 +1167,41 @@ func (mr *MockScopedKeeperMockRecorder) GetCapability(ctx, name interface{}) *go mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCapability", reflect.TypeOf((*MockScopedKeeper)(nil).GetCapability), ctx, name) } + +// MockGovKeeper is a mock of GovKeeper interface. +type MockGovKeeper struct { + ctrl *gomock.Controller + recorder *MockGovKeeperMockRecorder +} + +// MockGovKeeperMockRecorder is the mock recorder for MockGovKeeper. +type MockGovKeeperMockRecorder struct { + mock *MockGovKeeper +} + +// NewMockGovKeeper creates a new mock instance. +func NewMockGovKeeper(ctrl *gomock.Controller) *MockGovKeeper { + mock := &MockGovKeeper{ctrl: ctrl} + mock.recorder = &MockGovKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGovKeeper) EXPECT() *MockGovKeeperMockRecorder { + return m.recorder +} + +// GetProposal mocks base method. +func (m *MockGovKeeper) GetProposal(ctx types0.Context, proposalID uint64) (v1.Proposal, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProposal", ctx, proposalID) + ret0, _ := ret[0].(v1.Proposal) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetProposal indicates an expected call of GetProposal. +func (mr *MockGovKeeperMockRecorder) GetProposal(ctx, proposalID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProposal", reflect.TypeOf((*MockGovKeeper)(nil).GetProposal), ctx, proposalID) +} diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index 557be11123..d114a57651 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -88,6 +88,7 @@ type MockedKeepers struct { *MockIBCTransferKeeper *MockIBCCoreKeeper *MockDistributionKeeper + *MockGovKeeper } // NewMockedKeepers instantiates a struct with pointers to properly instantiated mocked keepers. @@ -105,6 +106,7 @@ func NewMockedKeepers(ctrl *gomock.Controller) MockedKeepers { MockIBCTransferKeeper: NewMockIBCTransferKeeper(ctrl), MockIBCCoreKeeper: NewMockIBCCoreKeeper(ctrl), MockDistributionKeeper: NewMockDistributionKeeper(ctrl), + MockGovKeeper: NewMockGovKeeper(ctrl), } } @@ -124,6 +126,7 @@ func NewInMemProviderKeeper(params InMemKeeperParams, mocks MockedKeepers) provi mocks.MockAccountKeeper, mocks.MockDistributionKeeper, mocks.MockBankKeeper, + mocks.MockGovKeeper, authtypes.FeeCollectorName, ) } diff --git a/x/ccv/provider/client/cli/query.go b/x/ccv/provider/client/cli/query.go index 80746ff5d2..2b1d0e1dee 100644 --- a/x/ccv/provider/client/cli/query.go +++ b/x/ccv/provider/client/cli/query.go @@ -32,6 +32,7 @@ func NewQueryCmd() *cobra.Command { cmd.AddCommand(CmdProviderValidatorKey()) cmd.AddCommand(CmdThrottleState()) cmd.AddCommand(CmdRegisteredConsumerRewardDenoms()) + cmd.AddCommand(CmdProposedConsumerChains()) return cmd } @@ -92,6 +93,33 @@ func CmdConsumerChains() *cobra.Command { return cmd } +func CmdProposedConsumerChains() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-proposed-consumer-chains", + Short: "Query chainIDs in consumer addition proposal before voting finishes", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryProposedChainIDsRequest{} + res, err := queryClient.QueryProposedConsumerChainIDs(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + func CmdConsumerStartProposals() *cobra.Command { cmd := &cobra.Command{ Use: "list-start-proposals", diff --git a/x/ccv/provider/handler_test.go b/x/ccv/provider/handler_test.go index e4835cf14a..3ff2d5a26a 100644 --- a/x/ccv/provider/handler_test.go +++ b/x/ccv/provider/handler_test.go @@ -51,6 +51,9 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { setup: func(ctx sdk.Context, k keeper.Keeper, mocks testkeeper.MockedKeepers, ) { + k.SetPendingConsumerAdditionProp(ctx, &providertypes.ConsumerAdditionProposal{ + ChainId: "chainid", + }) gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidator( ctx, providerCryptoId.SDKValOpAddress(), @@ -64,11 +67,29 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { expError: false, chainID: "chainid", }, + { + name: "fail: chain ID not registered", + setup: func(ctx sdk.Context, + k keeper.Keeper, mocks testkeeper.MockedKeepers, + ) { + gomock.InOrder( + mocks.MockStakingKeeper.EXPECT().GetValidator( + ctx, providerCryptoId.SDKValOpAddress(), + // Return a valid validator, found! + ).Return(providerCryptoId.SDKStakingValidator(), true).Times(1), + ) + }, + expError: true, + chainID: "chainid", + }, { name: "fail: missing validator", setup: func(ctx sdk.Context, k keeper.Keeper, mocks testkeeper.MockedKeepers, ) { + k.SetPendingConsumerAdditionProp(ctx, &providertypes.ConsumerAdditionProposal{ + ChainId: "chainid", + }) gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidator( ctx, providerCryptoId.SDKValOpAddress(), @@ -84,6 +105,9 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { setup: func(ctx sdk.Context, k keeper.Keeper, mocks testkeeper.MockedKeepers, ) { + k.SetPendingConsumerAdditionProp(ctx, &providertypes.ConsumerAdditionProposal{ + ChainId: "chainid", + }) // Use the consumer key already k.SetValidatorByConsumerAddr(ctx, "chainid", consumerConsAddr, providerConsAddr) diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index 881a6b1a98..d1f111e38e 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -168,3 +168,17 @@ func (k Keeper) QueryRegisteredConsumerRewardDenoms(goCtx context.Context, req * Denoms: denoms, }, nil } + +func (k Keeper) QueryProposedConsumerChainIDs(goCtx context.Context, req *types.QueryProposedChainIDsRequest) (*types.QueryProposedChainIDsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + chains := k.GetAllProposedConsumerChainIDs(ctx) + + return &types.QueryProposedChainIDsResponse{ + ProposedChains: chains, + }, nil +} diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index da2daa7512..be7e5947a1 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -1,7 +1,11 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkgov "github.com/cosmos/cosmos-sdk/x/gov/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" providertypes "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" @@ -13,13 +17,20 @@ type Hooks struct { k *Keeper } -var _ stakingtypes.StakingHooks = Hooks{} +var ( + _ stakingtypes.StakingHooks = Hooks{} + _ sdkgov.GovHooks = Hooks{} +) // Returns new provider hooks func (k *Keeper) Hooks() Hooks { return Hooks{k} } +// +// staking hooks +// + // This stores a record of each unbonding op from staking, allowing us to track which consumer chains have unbonded func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { var consumerChainIDS []string @@ -80,38 +91,8 @@ func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { return nil } -// ValidatorConsensusKeyInUse is called when a new validator is created -// in the x/staking module of cosmos-sdk. In case it panics, the TX aborts -// and thus, the validator is not created. See AfterValidatorCreated hook. -func ValidatorConsensusKeyInUse(k *Keeper, ctx sdk.Context, valAddr sdk.ValAddress) bool { - // Get the validator being added in the staking module. - val, found := k.stakingKeeper.GetValidator(ctx, valAddr) - if !found { - // Abort TX, do NOT allow validator to be created - panic("did not find newly created validator in staking module") - } - - // Get the consensus address of the validator being added - consensusAddr, err := val.GetConsAddr() - if err != nil { - // Abort TX, do NOT allow validator to be created - panic("could not get validator cons addr ") - } - - inUse := false - - for _, validatorConsumerAddrs := range k.GetAllValidatorsByConsumerAddr(ctx, nil) { - if sdk.ConsAddress(validatorConsumerAddrs.ConsumerAddr).Equals(consensusAddr) { - inUse = true - break - } - } - - return inUse -} - func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) error { - if ValidatorConsensusKeyInUse(h.k, ctx, valAddr) { + if h.k.ValidatorConsensusKeyInUse(ctx, valAddr) { // Abort TX, do NOT allow validator to be created panic("cannot create a validator with a consensus key that is already in use or was recently in use as an assigned consumer chain key") } @@ -168,6 +149,71 @@ func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.Va return nil } +// +// gov hooks +// + +// AfterProposalSubmission - call hook if registered +// After a consumerAddition proposal submission, a record is created +// that maps the proposal ID to the consumer chain ID. +func (h Hooks) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) { + if p, ok := h.GetConsumerAdditionLegacyPropFromProp(ctx, proposalID); ok { + h.k.SetProposedConsumerChain(ctx, p.ChainId, proposalID) + } +} + +// AfterProposalVotingPeriodEnded - call hook if registered +// After proposal voting ends, the consumer chainID in store is deleted. +// When a consumerAddition proposal passes, the consumer chainID is available in providerKeeper.GetAllPendingConsumerAdditionProps +// or providerKeeper.GetAllConsumerChains(ctx). +func (h Hooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) { + if _, ok := h.GetConsumerAdditionLegacyPropFromProp(ctx, proposalID); ok { + h.k.DeleteProposedConsumerChainInStore(ctx, proposalID) + } +} + +func (h Hooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) { +} + +func (h Hooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { +} + +func (h Hooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) { +} + +// GetConsumerAdditionLegacyPropFromProp extracts a consumer addition legacy proposal from +// the proposal with the given ID +func (h Hooks) GetConsumerAdditionLegacyPropFromProp( + ctx sdk.Context, + proposalID uint64, +) (providertypes.ConsumerAdditionProposal, bool) { + p, ok := h.k.govKeeper.GetProposal(ctx, proposalID) + if !ok { + panic(fmt.Errorf("failed to get proposal %d from store", proposalID)) + } + + // Iterate over the messages in the proposal + // Note that it's assumed that at most ONE message can contain a consumer addition proposal + for _, msg := range p.GetMessages() { + sdkMsg, isLegacyProposal := msg.GetCachedValue().(*v1.MsgExecLegacyContent) + if !isLegacyProposal { + continue + } + + content, err := v1.LegacyContentFromMessage(sdkMsg) + if err != nil { + panic(fmt.Errorf("failed to get legacy proposal %d from prop message", proposalID)) + } + + // returns if legacy prop is of ConsumerAddition proposal type + prop, ok := content.(*providertypes.ConsumerAdditionProposal) + if ok { + return *prop, true + } + } + return providertypes.ConsumerAdditionProposal{}, false +} + func (h Hooks) BeforeTokenizeShareRecordRemoved(_ sdk.Context, _ uint64) error { return nil } diff --git a/x/ccv/provider/keeper/hooks_test.go b/x/ccv/provider/keeper/hooks_test.go index 83dbfe9622..47175eb907 100644 --- a/x/ccv/provider/keeper/hooks_test.go +++ b/x/ccv/provider/keeper/hooks_test.go @@ -2,10 +2,18 @@ package keeper_test import ( "testing" + "time" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" cryptotestutil "github.com/cosmos/interchain-security/v3/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" @@ -38,6 +46,7 @@ func TestValidatorConsensusKeyInUse(t *testing.T) { newValidator.ConsumerConsAddress(), anotherValidator0.ProviderConsAddress(), ) + k.SetConsumerClientId(ctx, "chainid", "clientID") }, expect: true, }, @@ -50,16 +59,20 @@ func TestValidatorConsensusKeyInUse(t *testing.T) { newValidator.ConsumerConsAddress(), anotherValidator0.ProviderConsAddress(), ) + k.SetConsumerClientId(ctx, "chainid0", "clientID0") + k.SetValidatorByConsumerAddr(ctx, "chainid1", anotherValidator1.ConsumerConsAddress(), anotherValidator1.ProviderConsAddress(), ) + k.SetConsumerClientId(ctx, "chainid1", "clientID1") }, expect: true, }, } for _, tt := range tests { - k, ctx, _, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, @@ -70,9 +83,143 @@ func TestValidatorConsensusKeyInUse(t *testing.T) { tt.setup(ctx, k) t.Run(tt.name, func(t *testing.T) { - if actual := providerkeeper.ValidatorConsensusKeyInUse(&k, ctx, newValidator.SDKStakingValidator().GetOperator()); actual != tt.expect { + if actual := k.ValidatorConsensusKeyInUse(ctx, newValidator.SDKStakingValidator().GetOperator()); actual != tt.expect { t.Errorf("validatorConsensusKeyInUse() = %v, want %v", actual, tt.expect) } }) } } + +func TestAfterPropSubmissionAndVotingPeriodEnded(t *testing.T) { + acct := cryptotestutil.NewCryptoIdentityFromIntSeed(0) + + propMsg, err := v1.NewLegacyContent( + testkeeper.GetTestConsumerAdditionProp(), + authtypes.NewModuleAddress("gov").String(), + ) + require.NoError(t, err) + + prop, err := v1.NewProposal( + []sdk.Msg{propMsg}, + 0, + time.Now(), + time.Now(), + "", + "", + "", + sdk.AccAddress(acct.SDKValOpAddress()), + ) + require.NoError(t, err) + + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // pass the prop to the mocked gov keeper, + // which is called by both the AfterProposalVotingPeriodEnded and + // AfterProposalSubmission gov hooks + gomock.InOrder( + mocks.MockGovKeeper.EXPECT().GetProposal(ctx, prop.Id).Return(prop, true).Times(2), + ) + + k.Hooks().AfterProposalSubmission(ctx, prop.Id) + // verify that the proposal ID is created + require.NotEmpty(t, k.GetProposedConsumerChain(ctx, prop.Id)) + + k.Hooks().AfterProposalVotingPeriodEnded(ctx, prop.Id) + // verify that the proposal ID is deleted + require.Empty(t, k.GetProposedConsumerChain(ctx, prop.Id)) +} + +func TestGetConsumerAdditionLegacyPropFromProp(t *testing.T) { + acct := cryptotestutil.NewCryptoIdentityFromIntSeed(0) + anotherAcct := cryptotestutil.NewCryptoIdentityFromIntSeed(1) + + // create a dummy bank send message + dummyMsg := &banktypes.MsgSend{ + FromAddress: sdk.AccAddress(acct.SDKValOpAddress()).String(), + ToAddress: sdk.AccAddress(anotherAcct.SDKValOpAddress()).String(), + Amount: sdk.NewCoins(sdk.NewCoin("stake", math.OneInt())), + } + + textProp, err := v1.NewLegacyContent( + v1beta1.NewTextProposal("a title", "a legacy text prop"), + authtypes.NewModuleAddress("gov").String(), + ) + require.NoError(t, err) + + consuProp, err := v1.NewLegacyContent( + testkeeper.GetTestConsumerAdditionProp(), + authtypes.NewModuleAddress("gov").String(), + ) + require.NoError(t, err) + + testCases := map[string]struct { + propMsg sdk.Msg + // setup func(sdk.Context, k providerkeeper, proposalID uint64) + expPanic bool + expConsuAddProp bool + }{ + "prop not found": { + propMsg: nil, + expPanic: true, + }, + "msgs in prop contain no legacy props": { + propMsg: dummyMsg, + }, + "msgs contain a legacy prop but not of ConsumerAdditionProposal type": { + propMsg: textProp, + }, + "msgs contain an invalid legacy prop": { + propMsg: &v1.MsgExecLegacyContent{}, + expPanic: true, + }, + "msg contains a prop of ConsumerAdditionProposal type - hook should create a new proposed chain": { + propMsg: consuProp, + expConsuAddProp: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + var ( + prop v1.Proposal + propFound bool + ) + + if tc.propMsg != nil { + propFound = true + prop, err = v1.NewProposal( + []sdk.Msg{tc.propMsg}, + 0, + time.Now(), + time.Now(), + "", + "", + "", + sdk.AccAddress(acct.SDKValOpAddress()), + ) + require.NoError(t, err) + } + + gomock.InOrder( + mocks.MockGovKeeper.EXPECT().GetProposal(ctx, prop.Id).Return(prop, propFound), + ) + + if tc.expPanic { + defer func() { + // fail test if not panic was recovered + if r := recover(); r == nil { + require.Fail(t, r.(string)) + } + }() + } + + // retrieve consumer addition proposal + _, ok := k.Hooks().GetConsumerAdditionLegacyPropFromProp(ctx, prop.Id) + require.Equal(t, tc.expConsuAddProp, ok) + }) + } +} diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 0148354b93..cf2ba795d9 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -43,6 +43,7 @@ type Keeper struct { slashingKeeper ccv.SlashingKeeper distributionKeeper ccv.DistributionKeeper bankKeeper ccv.BankKeeper + govKeeper ccv.GovKeeper feeCollectorName string } @@ -52,9 +53,9 @@ func NewKeeper( channelKeeper ccv.ChannelKeeper, portKeeper ccv.PortKeeper, connectionKeeper ccv.ConnectionKeeper, clientKeeper ccv.ClientKeeper, stakingKeeper ccv.StakingKeeper, slashingKeeper ccv.SlashingKeeper, - accountKeeper ccv.AccountKeeper, distributionKeeper ccv.DistributionKeeper, - bankKeeper ccv.BankKeeper, - feeCollectorName string, + accountKeeper ccv.AccountKeeper, + distributionKeeper ccv.DistributionKeeper, bankKeeper ccv.BankKeeper, + govKeeper ccv.GovKeeper, feeCollectorName string, ) Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { @@ -75,6 +76,7 @@ func NewKeeper( accountKeeper: accountKeeper, distributionKeeper: distributionKeeper, bankKeeper: bankKeeper, + govKeeper: govKeeper, feeCollectorName: feeCollectorName, } @@ -92,8 +94,8 @@ func (k *Keeper) SetParamSpace(ctx sdk.Context, ps paramtypes.Subspace) { // non-nil values for all its fields. Otherwise this method will panic. func (k Keeper) mustValidateFields() { // Ensures no fields are missed in this validation - if reflect.ValueOf(k).NumField() != 14 { - panic("number of fields in provider keeper is not 14") + if reflect.ValueOf(k).NumField() != 15 { + panic("number of fields in provider keeper is not 15") } ccv.PanicIfZeroOrNil(k.cdc, "cdc") // 1 @@ -109,7 +111,8 @@ func (k Keeper) mustValidateFields() { ccv.PanicIfZeroOrNil(k.slashingKeeper, "slashingKeeper") // 11 ccv.PanicIfZeroOrNil(k.distributionKeeper, "distributionKeeper") // 12 ccv.PanicIfZeroOrNil(k.bankKeeper, "bankKeeper") // 13 - ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 14 + ccv.PanicIfZeroOrNil(k.govKeeper, "govKeeper") // 14 + ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 15 } // Logger returns a module-specific logger. @@ -175,6 +178,60 @@ func (k Keeper) DeleteChainToChannel(ctx sdk.Context, chainID string) { store.Delete(types.ChainToChannelKey(chainID)) } +// SetProposedConsumerChain stores a consumer chainId corresponding to a submitted consumer addition proposal +// This consumer chainId is deleted once the voting period for the proposal ends. +func (k Keeper) SetProposedConsumerChain(ctx sdk.Context, chainID string, proposalID uint64) { + store := ctx.KVStore(k.storeKey) + store.Set(types.ProposedConsumerChainKey(proposalID), []byte(chainID)) +} + +// GetProposedConsumerChain returns the proposed chainID for the given consumerAddition proposal ID. +func (k Keeper) GetProposedConsumerChain(ctx sdk.Context, proposalID uint64) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.ProposedConsumerChainKey(proposalID))) +} + +// DeleteProposedConsumerChainInStore deletes the consumer chainID from store +// which is in gov consumerAddition proposal +func (k Keeper) DeleteProposedConsumerChainInStore(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ProposedConsumerChainKey(proposalID)) +} + +// GetAllProposedConsumerChainIDs returns the proposed chainID of all gov consumerAddition proposals that are still in the voting period. +func (k Keeper) GetAllProposedConsumerChainIDs(ctx sdk.Context) []types.ProposedChain { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte{types.ProposedConsumerChainByteKey}) + defer iterator.Close() + + proposedChains := []types.ProposedChain{} + for ; iterator.Valid(); iterator.Next() { + proposalID, err := types.ParseProposedConsumerChainKey(types.ProposedConsumerChainByteKey, iterator.Key()) + if err != nil { + panic(fmt.Errorf("proposed chains cannot be parsed: %w", err)) + } + + proposedChains = append(proposedChains, types.ProposedChain{ + ChainID: string(iterator.Value()), + ProposalID: proposalID, + }) + + } + + return proposedChains +} + +// GetAllPendingConsumerChainIDs gets pending consumer chains have not reach spawn time +func (k Keeper) GetAllPendingConsumerChainIDs(ctx sdk.Context) []string { + chainIDs := []string{} + props := k.GetAllPendingConsumerAdditionProps(ctx) + for _, prop := range props { + chainIDs = append(chainIDs, prop.ChainId) + } + + return chainIDs +} + // GetAllConsumerChains gets all of the consumer chains, for which the provider module // created IBC clients. Consumer chains with created clients are also referred to as registered. // @@ -1063,3 +1120,19 @@ func (k Keeper) GetSlashLog( func (k Keeper) BondDenom(ctx sdk.Context) string { return k.stakingKeeper.BondDenom(ctx) } + +func (k Keeper) GetAllRegisteredAndProposedChainIDs(ctx sdk.Context) []string { + allConsumerChains := []string{} + consumerChains := k.GetAllConsumerChains(ctx) + for _, consumerChain := range consumerChains { + allConsumerChains = append(allConsumerChains, consumerChain.ChainId) + } + proposedChains := k.GetAllProposedConsumerChainIDs(ctx) + for _, proposedChain := range proposedChains { + allConsumerChains = append(allConsumerChains, proposedChain.ChainID) + } + pendingChainIDs := k.GetAllPendingConsumerChainIDs(ctx) + allConsumerChains = append(allConsumerChains, pendingChainIDs...) + + return allConsumerChains +} diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index be3ef4001c..14c86fe775 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -533,3 +533,98 @@ func TestSetSlashLog(t *testing.T) { require.True(t, providerKeeper.GetSlashLog(ctx, addrWithDoubleSigns)) require.False(t, providerKeeper.GetSlashLog(ctx, addrWithoutDoubleSigns)) } + +func TestSetProposedConsumerChains(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + tests := []struct { + chainID string + proposalID uint64 + }{ + {chainID: "1", proposalID: 1}, + {chainID: "some other ID", proposalID: 12}, + {chainID: "some other other chain ID", proposalID: 123}, + {chainID: "", proposalID: 1234}, + } + + for _, test := range tests { + providerKeeper.SetProposedConsumerChain(ctx, test.chainID, test.proposalID) + cID := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) + require.Equal(t, cID, test.chainID) + } +} + +func TestDeleteProposedConsumerChainInStore(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + tests := []struct { + chainID string + proposalID uint64 + deleteProposalID uint64 + ok bool + }{ + {chainID: "1", proposalID: 1, deleteProposalID: 1, ok: true}, + {chainID: "", proposalID: 12, deleteProposalID: 12, ok: true}, + {chainID: "1", proposalID: 0, deleteProposalID: 1, ok: false}, + } + for _, test := range tests { + providerKeeper.SetProposedConsumerChain(ctx, test.chainID, test.proposalID) + providerKeeper.DeleteProposedConsumerChainInStore(ctx, test.deleteProposalID) + cID := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) + if test.ok { + require.Equal(t, cID, "") + } else { + require.Equal(t, cID, test.chainID) + } + } +} + +func TestGetAllProposedConsumerChainIDs(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + tests := [][]types.ProposedChain{ + {}, + { + { + ChainID: "1", + ProposalID: 1, + }, + }, + { + { + ChainID: "1", + ProposalID: 1, + }, + { + ChainID: "2", + ProposalID: 2, + }, + { + ChainID: "", + ProposalID: 3, + }, + }, + } + + for _, test := range tests { + for _, tc := range test { + providerKeeper.SetProposedConsumerChain(ctx, tc.ChainID, tc.ProposalID) + } + + chains := providerKeeper.GetAllProposedConsumerChainIDs(ctx) + + sort.Slice(chains, func(i, j int) bool { + return chains[i].ProposalID < chains[j].ProposalID + }) + sort.Slice(test, func(i, j int) bool { + return test[i].ProposalID < test[j].ProposalID + }) + require.Equal(t, chains, test) + + for _, tc := range test { + providerKeeper.DeleteProposedConsumerChainInStore(ctx, tc.ProposalID) + } + } +} diff --git a/x/ccv/provider/keeper/key_assignment.go b/x/ccv/provider/keeper/key_assignment.go index d440848bbf..46447790df 100644 --- a/x/ccv/provider/keeper/key_assignment.go +++ b/x/ccv/provider/keeper/key_assignment.go @@ -370,13 +370,22 @@ func (k Keeper) DeleteConsumerAddrsToPrune(ctx sdk.Context, chainID string, vscI } // AssignConsumerKey assigns the consumerKey to the validator with providerAddr -// on the consumer chain with ID chainID +// on the consumer chain with ID chainID, if it is either registered or currently +// voted on in a ConsumerAddition governance proposal func (k Keeper) AssignConsumerKey( ctx sdk.Context, chainID string, validator stakingtypes.Validator, consumerKey tmprotocrypto.PublicKey, ) error { + // check that the consumer chain is either registered or that + // a ConsumerAdditionProposal was submitted. + if !k.IsConsumerProposedOrRegistered(ctx, chainID) { + return errorsmod.Wrapf( + types.ErrUnknownConsumerChainId, chainID, + ) + } + consAddrTmp, err := ccvtypes.TMCryptoPublicKeyToConsAddr(consumerKey) if err != nil { return err @@ -390,15 +399,15 @@ func (k Keeper) AssignConsumerKey( providerAddr := types.NewProviderConsAddress(consAddrTmp) if existingVal, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consumerAddr.ToSdkConsAddr()); found { - // If there is a validator using the consumer key to validate on the provider - // we prevent assigning the consumer key, unless the validator is assigning validator. - // This ensures that a validator joining the active set who has not explicitly assigned - // a consumer key, will be able to use their provider key as consumer key (as per default). + // If there is already a different validator using the consumer key to validate on the provider + // we prevent assigning the consumer key. if existingVal.OperatorAddress != validator.OperatorAddress { return errorsmod.Wrapf( types.ErrConsumerKeyInUse, "a different validator already uses the consumer key", ) } + // We prevent a validator from assigning the default provider key as a consumer key + // if it has not already assigned a different consumer key _, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) if !found { return errorsmod.Wrapf( @@ -629,3 +638,47 @@ func (k Keeper) DeleteKeyAssignments(ctx sdk.Context, chainID string) { k.DeleteConsumerAddrsToPrune(ctx, chainID, consumerAddrsToPrune.VscId) } } + +// IsConsumerProposedOrRegistered checks if a consumer chain is either registered, meaning either already running +// or will run soon, or proposed its ConsumerAdditionProposal was submitted but the chain was not yet added to ICS yet. +func (k Keeper) IsConsumerProposedOrRegistered(ctx sdk.Context, chainID string) bool { + allConsumerChains := k.GetAllRegisteredAndProposedChainIDs(ctx) + for _, c := range allConsumerChains { + if c == chainID { + return true + } + } + + return false +} + +// ValidatorConsensusKeyInUse checks if the given consensus key is already +// used by validator in a consumer chain. +// Note that this method is called when a new validator is created in the x/staking module of cosmos-sdk. +// In case it panics, the TX aborts and thus, the validator is not created. See AfterValidatorCreated hook. +func (k Keeper) ValidatorConsensusKeyInUse(ctx sdk.Context, valAddr sdk.ValAddress) bool { + // Get the validator being added in the staking module. + val, found := k.stakingKeeper.GetValidator(ctx, valAddr) + if !found { + // Abort TX, do NOT allow validator to be created + panic("did not find newly created validator in staking module") + } + + // Get the consensus address of the validator being added + consensusAddr, err := val.GetConsAddr() + if err != nil { + // Abort TX, do NOT allow validator to be created + panic("could not get validator cons addr ") + } + + allConsumerChains := k.GetAllRegisteredAndProposedChainIDs(ctx) + for _, c := range allConsumerChains { + if _, exist := k.GetValidatorByConsumerAddr(ctx, + c, + types.NewConsumerConsAddress(consensusAddr), + ); exist { + return true + } + } + return false +} diff --git a/x/ccv/provider/keeper/key_assignment_test.go b/x/ccv/provider/keeper/key_assignment_test.go index e9cb1dd646..0009680864 100644 --- a/x/ccv/provider/keeper/key_assignment_test.go +++ b/x/ccv/provider/keeper/key_assignment_test.go @@ -389,17 +389,32 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { doActions func(sdk.Context, providerkeeper.Keeper) }{ /* - 0. Consumer registered: Assign PK0->CK0 and retrieve PK0->CK0 - 1. Consumer registered: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 - 2. Consumer registered: Assign PK0->CK0, PK1->CK0 and error - 3. Consumer registered: Assign PK1->PK0 and error - 4. Consumer not registered: Assign PK0->CK0 and retrieve PK0->CK0 - 5. Consumer not registered: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 - 6. Consumer not registered: Assign PK0->CK0, PK1->CK0 and error - 7. Consumer not registered: Assign PK1->PK0 and error + 0. Consumer not registered: Assign PK0->CK0 and error + 1. Consumer registered: Assign PK0->CK0 and retrieve PK0->CK0 + 2. Consumer registered: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 + 3. Consumer registered: Assign PK0->CK0, PK1->CK0 and error + 4. Consumer registered: Assign PK1->PK0 and error + 5. Consumer proposed: Assign Assign PK0->CK0 and retrieve PK0->CK0 + 6. Consumer proposed: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 + 7. Consumer proposed: Assign PK0->CK0, PK1->CK0 and error + 8. Consumer proposed: Assign PK1->PK0 and error */ { - name: "0", + name: "0", + mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) {}, + doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + err := k.AssignConsumerKey(ctx, chainID, + providerIdentities[0].SDKStakingValidator(), + consumerIdentities[0].TMProtoCryptoPublicKey(), + ) + require.Error(t, err) + _, found := k.GetValidatorByConsumerAddr(ctx, chainID, + consumerIdentities[0].ConsumerConsAddress()) + require.False(t, found) + }, + }, + { + name: "1", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -424,7 +439,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "1", + name: "2", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -460,7 +475,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "2", + name: "3", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -493,7 +508,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "3", + name: "4", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -511,7 +526,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "4", + name: "5", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -520,6 +535,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[0].SDKStakingValidator(), consumerIdentities[0].TMProtoCryptoPublicKey(), @@ -532,7 +548,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "5", + name: "6", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -544,6 +560,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[0].SDKStakingValidator(), consumerIdentities[0].TMProtoCryptoPublicKey(), @@ -561,7 +578,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "6", + name: "7", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -573,6 +590,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[0].SDKStakingValidator(), consumerIdentities[0].TMProtoCryptoPublicKey(), @@ -590,7 +608,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "7", + name: "8", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -599,6 +617,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[1].SDKStakingValidator(), providerIdentities[0].TMProtoCryptoPublicKey(), @@ -634,6 +653,10 @@ func TestCannotReassignDefaultKeyAssignment(t *testing.T) { providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() + providerKeeper.SetPendingConsumerAdditionProp(ctx, &types.ConsumerAdditionProposal{ + ChainId: "chain", + }) + // Mock that the validator is validating with the single key, as confirmed by provider's staking keeper gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 03b98403bc..cd9b2c2c07 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -29,15 +29,11 @@ func NewMsgServerImpl(keeper *Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} +// AssignConsumerKey defines a method to assign a consensus key on a consumer chain +// for a given validator on the provider func (k msgServer) AssignConsumerKey(goCtx context.Context, msg *types.MsgAssignConsumerKey) (*types.MsgAssignConsumerKeyResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // It is possible to assign keys for consumer chains that are not yet approved. - // TODO: In future, a mechanism will be added to limit assigning keys to chains - // which are approved or pending approval, only. - // Note that current attack potential is restricted because validators must sign - // the transaction, and the chainID size is limited. - providerValidatorAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { return nil, err diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 95b5b4b820..d2845d094a 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -142,6 +142,9 @@ const ( // to the minimum height of a valid consumer equivocation evidence EquivocationEvidenceMinHeightBytePrefix + // ProposedConsumerChainByteKey is the byte prefix storing the consumer chainId in consumerAddition gov proposal submitted before voting finishes + ProposedConsumerChainByteKey + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -494,6 +497,26 @@ func VSCMaturedHandledThisBlockKey() []byte { return []byte{VSCMaturedHandledThisBlockBytePrefix} } +// ProposedConsumerChainKey returns the key of proposed consumer chainId in consumerAddition gov proposal before voting finishes, the stored key format is prefix|proposalID, value is chainID +func ProposedConsumerChainKey(proposalID uint64) []byte { + return ccvtypes.AppendMany( + []byte{ProposedConsumerChainByteKey}, + sdk.Uint64ToBigEndian(proposalID), + ) +} + +// ParseProposedConsumerChainKey get the proposalID in the key +func ParseProposedConsumerChainKey(prefix byte, bz []byte) (uint64, error) { + expectedPrefix := []byte{prefix} + prefixL := len(expectedPrefix) + if prefix := bz[:prefixL]; !bytes.Equal(prefix, expectedPrefix) { + return 0, fmt.Errorf("invalid prefix; expected: %X, got: %X", expectedPrefix, prefix) + } + proposalID := sdk.BigEndianToUint64(bz[prefixL:]) + + return proposalID, nil +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 6b263a4fdb..c1c9394764 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -55,6 +55,7 @@ func getAllKeyPrefixes() []byte { providertypes.SlashLogBytePrefix, providertypes.VSCMaturedHandledThisBlockBytePrefix, providertypes.EquivocationEvidenceMinHeightBytePrefix, + providertypes.ProposedConsumerChainByteKey, } } @@ -311,3 +312,22 @@ func TestKeysWithUint64Payload(t *testing.T) { } } } + +func TestParseProposedConsumerChainKey(t *testing.T) { + tests := []struct { + chainID string + proposalID uint64 + }{ + {chainID: "1", proposalID: 1}, + {chainID: "some other ID", proposalID: 12}, + {chainID: "some other other chain ID", proposalID: 123}, + } + + for _, test := range tests { + key := providertypes.ProposedConsumerChainKey(test.proposalID) + pID, err := providertypes.ParseProposedConsumerChainKey( + providertypes.ProposedConsumerChainByteKey, key) + require.NoError(t, err) + require.Equal(t, pID, test.proposalID) + } +} diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index c2551f9ceb..f7ee11325c 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -77,8 +77,7 @@ func (msg MsgAssignConsumerKey) ValidateBasic() error { // It is possible to assign keys for consumer chains that are not yet approved. // This can only be done by a signing validator, but it is still sensible // to limit the chainID size to prevent abuse. - // TODO: In future, a mechanism will be added to limit assigning keys to chains - // which are approved or pending approval, only. + if 128 < len(msg.ChainId) { return ErrBlankConsumerChainID } diff --git a/x/ccv/provider/types/query.pb.go b/x/ccv/provider/types/query.pb.go index 37ef05d20f..f5f9852846 100644 --- a/x/ccv/provider/types/query.pb.go +++ b/x/ccv/provider/types/query.pb.go @@ -781,6 +781,138 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) GetDenoms() []string { return nil } +type QueryProposedChainIDsRequest struct { +} + +func (m *QueryProposedChainIDsRequest) Reset() { *m = QueryProposedChainIDsRequest{} } +func (m *QueryProposedChainIDsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryProposedChainIDsRequest) ProtoMessage() {} +func (*QueryProposedChainIDsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_422512d7b7586cd7, []int{17} +} +func (m *QueryProposedChainIDsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryProposedChainIDsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryProposedChainIDsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryProposedChainIDsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryProposedChainIDsRequest.Merge(m, src) +} +func (m *QueryProposedChainIDsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryProposedChainIDsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryProposedChainIDsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryProposedChainIDsRequest proto.InternalMessageInfo + +type QueryProposedChainIDsResponse struct { + ProposedChains []ProposedChain `protobuf:"bytes,1,rep,name=proposedChains,proto3" json:"proposedChains"` +} + +func (m *QueryProposedChainIDsResponse) Reset() { *m = QueryProposedChainIDsResponse{} } +func (m *QueryProposedChainIDsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryProposedChainIDsResponse) ProtoMessage() {} +func (*QueryProposedChainIDsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_422512d7b7586cd7, []int{18} +} +func (m *QueryProposedChainIDsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryProposedChainIDsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryProposedChainIDsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryProposedChainIDsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryProposedChainIDsResponse.Merge(m, src) +} +func (m *QueryProposedChainIDsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryProposedChainIDsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryProposedChainIDsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryProposedChainIDsResponse proto.InternalMessageInfo + +func (m *QueryProposedChainIDsResponse) GetProposedChains() []ProposedChain { + if m != nil { + return m.ProposedChains + } + return nil +} + +type ProposedChain struct { + ChainID string `protobuf:"bytes,1,opt,name=chainID,proto3" json:"chainID,omitempty"` + ProposalID uint64 `protobuf:"varint,2,opt,name=proposalID,proto3" json:"proposalID,omitempty"` +} + +func (m *ProposedChain) Reset() { *m = ProposedChain{} } +func (m *ProposedChain) String() string { return proto.CompactTextString(m) } +func (*ProposedChain) ProtoMessage() {} +func (*ProposedChain) Descriptor() ([]byte, []int) { + return fileDescriptor_422512d7b7586cd7, []int{19} +} +func (m *ProposedChain) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ProposedChain) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ProposedChain.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ProposedChain) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProposedChain.Merge(m, src) +} +func (m *ProposedChain) XXX_Size() int { + return m.Size() +} +func (m *ProposedChain) XXX_DiscardUnknown() { + xxx_messageInfo_ProposedChain.DiscardUnknown(m) +} + +var xxx_messageInfo_ProposedChain proto.InternalMessageInfo + +func (m *ProposedChain) GetChainID() string { + if m != nil { + return m.ChainID + } + return "" +} + +func (m *ProposedChain) GetProposalID() uint64 { + if m != nil { + return m.ProposalID + } + return 0 +} + func init() { proto.RegisterType((*QueryConsumerGenesisRequest)(nil), "interchain_security.ccv.provider.v1.QueryConsumerGenesisRequest") proto.RegisterType((*QueryConsumerGenesisResponse)(nil), "interchain_security.ccv.provider.v1.QueryConsumerGenesisResponse") @@ -799,6 +931,9 @@ func init() { proto.RegisterType((*QueryThrottleStateResponse)(nil), "interchain_security.ccv.provider.v1.QueryThrottleStateResponse") proto.RegisterType((*QueryRegisteredConsumerRewardDenomsRequest)(nil), "interchain_security.ccv.provider.v1.QueryRegisteredConsumerRewardDenomsRequest") proto.RegisterType((*QueryRegisteredConsumerRewardDenomsResponse)(nil), "interchain_security.ccv.provider.v1.QueryRegisteredConsumerRewardDenomsResponse") + proto.RegisterType((*QueryProposedChainIDsRequest)(nil), "interchain_security.ccv.provider.v1.QueryProposedChainIDsRequest") + proto.RegisterType((*QueryProposedChainIDsResponse)(nil), "interchain_security.ccv.provider.v1.QueryProposedChainIDsResponse") + proto.RegisterType((*ProposedChain)(nil), "interchain_security.ccv.provider.v1.ProposedChain") } func init() { @@ -806,74 +941,81 @@ func init() { } var fileDescriptor_422512d7b7586cd7 = []byte{ - // 1060 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0xdc, 0x44, - 0x14, 0x5e, 0x27, 0x34, 0x24, 0x13, 0x20, 0xd5, 0xb4, 0x94, 0xad, 0x13, 0xed, 0x16, 0x57, 0x40, - 0x5a, 0xc0, 0xce, 0x6e, 0x2e, 0x6d, 0x51, 0xba, 0xd9, 0x0d, 0x21, 0x54, 0x6d, 0xd5, 0xe0, 0x56, - 0x20, 0x01, 0xc2, 0x9a, 0xd8, 0xc3, 0xae, 0x25, 0xaf, 0xc7, 0x9d, 0x99, 0x75, 0x1a, 0x55, 0x1c, - 0xe0, 0x00, 0x3d, 0x56, 0x42, 0x70, 0xee, 0x9f, 0xd3, 0x1b, 0x45, 0xbd, 0x70, 0x2a, 0x28, 0xe1, - 0xc0, 0x11, 0x71, 0x47, 0xaa, 0x3c, 0x1e, 0x7b, 0x7f, 0x39, 0xbb, 0xce, 0x26, 0x37, 0x7b, 0xe6, - 0xbd, 0xef, 0x7d, 0xdf, 0xdb, 0x37, 0xf3, 0x79, 0x81, 0xe1, 0xfa, 0x1c, 0x53, 0xbb, 0x85, 0x5c, - 0xdf, 0x62, 0xd8, 0xee, 0x50, 0x97, 0xef, 0x19, 0xb6, 0x1d, 0x1a, 0x01, 0x25, 0xa1, 0xeb, 0x60, - 0x6a, 0x84, 0x15, 0xe3, 0x7e, 0x07, 0xd3, 0x3d, 0x3d, 0xa0, 0x84, 0x13, 0x78, 0x31, 0x23, 0x41, - 0xb7, 0xed, 0x50, 0x4f, 0x12, 0xf4, 0xb0, 0xa2, 0x2e, 0x35, 0x09, 0x69, 0x7a, 0xd8, 0x40, 0x81, - 0x6b, 0x20, 0xdf, 0x27, 0x1c, 0x71, 0x97, 0xf8, 0x2c, 0x86, 0x50, 0xcf, 0x36, 0x49, 0x93, 0x88, - 0x47, 0x23, 0x7a, 0x92, 0xab, 0x65, 0x99, 0x23, 0xde, 0x76, 0x3a, 0xdf, 0x1a, 0xdc, 0x6d, 0x63, - 0xc6, 0x51, 0x3b, 0x90, 0x01, 0xd5, 0x3c, 0x54, 0x53, 0x16, 0x71, 0xce, 0xca, 0x61, 0x39, 0x61, - 0xc5, 0x60, 0x2d, 0x44, 0xb1, 0x63, 0xd9, 0xc4, 0x67, 0x9d, 0x76, 0x9a, 0xf1, 0xce, 0x88, 0x8c, - 0x5d, 0x97, 0xe2, 0x38, 0x4c, 0xbb, 0x02, 0x16, 0x3f, 0x8b, 0xba, 0xb2, 0x21, 0xb3, 0xb7, 0xb0, - 0x8f, 0x99, 0xcb, 0x4c, 0x7c, 0xbf, 0x83, 0x19, 0x87, 0xe7, 0xc1, 0x6c, 0x0c, 0xe1, 0x3a, 0x45, - 0xe5, 0x82, 0xb2, 0x3c, 0x67, 0xbe, 0x2a, 0xde, 0x6f, 0x38, 0xda, 0x43, 0xb0, 0x94, 0x9d, 0xc9, - 0x02, 0xe2, 0x33, 0x0c, 0xbf, 0x02, 0xaf, 0x37, 0xe3, 0x25, 0x8b, 0x71, 0xc4, 0xb1, 0xc8, 0x9f, - 0xaf, 0xae, 0xe8, 0x87, 0x35, 0x3e, 0xac, 0xe8, 0x03, 0x58, 0x77, 0xa3, 0xbc, 0xc6, 0x2b, 0x4f, - 0x5f, 0x94, 0x0b, 0xe6, 0x6b, 0xcd, 0x9e, 0x35, 0x6d, 0x09, 0xa8, 0x7d, 0xc5, 0x37, 0x22, 0xb8, - 0x84, 0xb5, 0x86, 0x06, 0x44, 0x25, 0xbb, 0x92, 0x59, 0x03, 0xcc, 0x88, 0xf2, 0xac, 0xa8, 0x5c, - 0x98, 0x5e, 0x9e, 0xaf, 0x5e, 0xd6, 0x73, 0xcc, 0x82, 0x2e, 0x40, 0x4c, 0x99, 0xa9, 0x5d, 0x02, - 0xef, 0x0d, 0x97, 0xb8, 0xcb, 0x11, 0xe5, 0xdb, 0x94, 0x04, 0x84, 0x21, 0x2f, 0x65, 0xf3, 0x48, - 0x01, 0xcb, 0xe3, 0x63, 0x25, 0xb7, 0xaf, 0xc1, 0x5c, 0x90, 0x2c, 0xca, 0x8e, 0x5d, 0xcf, 0x47, - 0x4f, 0x82, 0xd7, 0x1d, 0xc7, 0x8d, 0x86, 0xb4, 0x0b, 0xdd, 0x05, 0xd4, 0x96, 0xc1, 0xbb, 0x59, - 0x4c, 0x48, 0x30, 0x44, 0xfa, 0x47, 0x25, 0x5b, 0x60, 0x5f, 0x68, 0xfa, 0x4b, 0x0f, 0x71, 0x5e, - 0x3b, 0x12, 0x67, 0x13, 0xb7, 0x49, 0x88, 0xbc, 0x4c, 0xca, 0x35, 0x70, 0x4a, 0x94, 0x1e, 0x31, - 0x8a, 0x70, 0x11, 0xcc, 0xd9, 0x9e, 0x8b, 0x7d, 0x1e, 0xed, 0x4d, 0x89, 0xbd, 0xd9, 0x78, 0xe1, - 0x86, 0xa3, 0xfd, 0xa4, 0x80, 0xb7, 0x85, 0x92, 0xcf, 0x91, 0xe7, 0x3a, 0x88, 0x13, 0xda, 0xd3, - 0x2a, 0x3a, 0x7e, 0xd0, 0xe1, 0x1a, 0x38, 0x9d, 0x90, 0xb6, 0x90, 0xe3, 0x50, 0xcc, 0x58, 0x5c, - 0xa4, 0x01, 0xff, 0x7b, 0x51, 0x7e, 0x63, 0x0f, 0xb5, 0xbd, 0x6b, 0x9a, 0xdc, 0xd0, 0xcc, 0x85, - 0x24, 0xb6, 0x1e, 0xaf, 0x5c, 0x9b, 0x7d, 0xf4, 0xa4, 0x5c, 0xf8, 0xe7, 0x49, 0xb9, 0xa0, 0xdd, - 0x01, 0xda, 0x28, 0x22, 0xb2, 0x9b, 0x97, 0xc0, 0xe9, 0xe4, 0x28, 0xa7, 0xe5, 0x62, 0x46, 0x0b, - 0x76, 0x4f, 0x7c, 0x54, 0x6c, 0x58, 0xda, 0x76, 0x4f, 0xf1, 0x7c, 0xd2, 0x86, 0x6a, 0x8d, 0x90, - 0x36, 0x50, 0x7f, 0x94, 0xb4, 0x7e, 0x22, 0x5d, 0x69, 0x43, 0x9d, 0x94, 0xd2, 0x06, 0xba, 0xa6, - 0x2d, 0x82, 0xf3, 0x02, 0xf0, 0x5e, 0x8b, 0x12, 0xce, 0x3d, 0x2c, 0x8e, 0x7d, 0x32, 0x9c, 0xbf, - 0x2b, 0xf2, 0xf8, 0x0f, 0xec, 0xca, 0x32, 0x65, 0x30, 0xcf, 0x3c, 0xc4, 0x5a, 0x56, 0x1b, 0x73, - 0x4c, 0x45, 0x85, 0x69, 0x13, 0x88, 0xa5, 0xdb, 0xd1, 0x0a, 0xac, 0x82, 0x37, 0x7b, 0x02, 0x2c, - 0xe4, 0x79, 0x64, 0x17, 0xf9, 0x36, 0x16, 0xda, 0xa7, 0xcd, 0x33, 0xdd, 0xd0, 0x7a, 0xb2, 0x05, - 0xbf, 0x01, 0x45, 0x1f, 0x3f, 0xe0, 0x16, 0xc5, 0x81, 0x87, 0x7d, 0x97, 0xb5, 0x2c, 0x1b, 0xf9, - 0x4e, 0x24, 0x16, 0x17, 0xa7, 0xc5, 0xcc, 0xab, 0x7a, 0x7c, 0xf3, 0xeb, 0xc9, 0xcd, 0xaf, 0xdf, - 0x4b, 0x6e, 0xfe, 0xc6, 0x6c, 0x74, 0x87, 0x3d, 0xfe, 0xb3, 0xac, 0x98, 0xe7, 0x22, 0x14, 0x33, - 0x01, 0xd9, 0x48, 0x30, 0xb4, 0x0f, 0xc0, 0x65, 0x21, 0xc9, 0xc4, 0x4d, 0x97, 0x71, 0x4c, 0xb1, - 0xd3, 0x3d, 0x1d, 0xbb, 0x88, 0x3a, 0x1f, 0x63, 0x9f, 0xb4, 0xd3, 0xe3, 0xb9, 0x09, 0xde, 0xcf, - 0x15, 0x2d, 0x3b, 0x72, 0x0e, 0xcc, 0x38, 0x62, 0x45, 0xdc, 0x78, 0x73, 0xa6, 0x7c, 0xab, 0xfe, - 0xba, 0x00, 0x4e, 0x09, 0x1c, 0xb8, 0xaf, 0x80, 0xb3, 0x59, 0xd7, 0x39, 0x5c, 0xcf, 0x75, 0x92, - 0x47, 0x78, 0x88, 0x5a, 0x3f, 0x06, 0x42, 0xcc, 0x5f, 0xdb, 0xfc, 0xe1, 0xf9, 0xdf, 0x3f, 0x4f, - 0xd5, 0xe0, 0xda, 0x78, 0x9b, 0x4f, 0xe7, 0x59, 0xfa, 0x85, 0xf1, 0x30, 0x19, 0xfe, 0xef, 0xe0, - 0x73, 0x05, 0x9c, 0xc9, 0x30, 0x06, 0x58, 0x3b, 0x3a, 0xc3, 0x3e, 0xc3, 0x51, 0xd7, 0x27, 0x07, - 0x90, 0x0a, 0xaf, 0x0a, 0x85, 0xab, 0xb0, 0x72, 0x04, 0x85, 0xb1, 0x15, 0xc1, 0xef, 0xa7, 0x40, - 0xf1, 0x10, 0x7f, 0x61, 0xf0, 0xd6, 0x84, 0xcc, 0x32, 0xad, 0x4c, 0xbd, 0x7d, 0x42, 0x68, 0x52, - 0xf4, 0xa7, 0x42, 0x74, 0x03, 0xae, 0x1f, 0x55, 0x74, 0xf4, 0x45, 0x41, 0xb9, 0x95, 0xba, 0x04, - 0xfc, 0x5f, 0x01, 0x6f, 0x65, 0xdb, 0x15, 0x83, 0x37, 0x27, 0x26, 0x3d, 0xec, 0x8b, 0xea, 0xad, - 0x93, 0x01, 0x93, 0x0d, 0xd8, 0x12, 0x0d, 0xa8, 0xc3, 0xda, 0x04, 0x0d, 0x20, 0x41, 0x8f, 0xfe, - 0x7f, 0x93, 0x1b, 0x31, 0xd3, 0x5b, 0xe0, 0x27, 0xf9, 0x59, 0x8f, 0x72, 0x49, 0x75, 0xeb, 0xd8, - 0x38, 0x52, 0x78, 0x5d, 0x08, 0xff, 0x08, 0x5e, 0xcd, 0xf1, 0xdd, 0x9e, 0x00, 0x59, 0x7d, 0x56, - 0x95, 0x21, 0xb9, 0xd7, 0x73, 0x26, 0x92, 0x9c, 0xe1, 0x9e, 0x13, 0x49, 0xce, 0x32, 0xbf, 0xc9, - 0x24, 0xf7, 0xd9, 0x25, 0xfc, 0x4d, 0x01, 0x70, 0xd8, 0xf7, 0xe0, 0xf5, 0xfc, 0x14, 0xb3, 0xec, - 0x54, 0xad, 0x4d, 0x9c, 0x2f, 0xa5, 0x5d, 0x11, 0xd2, 0xaa, 0x70, 0x65, 0xbc, 0x34, 0x2e, 0x01, - 0xe2, 0xff, 0x04, 0xf0, 0x97, 0x29, 0x70, 0x31, 0x87, 0x91, 0xc1, 0x3b, 0xf9, 0x29, 0xe6, 0x32, - 0x50, 0x75, 0xfb, 0xe4, 0x00, 0x65, 0x13, 0x6e, 0x8a, 0x26, 0x6c, 0xc2, 0x8d, 0xf1, 0x4d, 0xa0, - 0x29, 0x62, 0x77, 0xa6, 0xa9, 0xc0, 0xb4, 0x62, 0x63, 0x6e, 0x7c, 0xf1, 0x74, 0xbf, 0xa4, 0x3c, - 0xdb, 0x2f, 0x29, 0x7f, 0xed, 0x97, 0x94, 0xc7, 0x07, 0xa5, 0xc2, 0xb3, 0x83, 0x52, 0xe1, 0x8f, - 0x83, 0x52, 0xe1, 0xcb, 0xb5, 0xa6, 0xcb, 0x5b, 0x9d, 0x1d, 0xdd, 0x26, 0x6d, 0xc3, 0x26, 0xac, - 0x4d, 0x58, 0x4f, 0xbd, 0x0f, 0xd3, 0x7a, 0xe1, 0xaa, 0xf1, 0x60, 0xa0, 0xf3, 0x7b, 0x01, 0x66, - 0x3b, 0x33, 0xe2, 0xe3, 0x64, 0xf5, 0x65, 0x00, 0x00, 0x00, 0xff, 0xff, 0xad, 0x57, 0x98, 0xb7, - 0x30, 0x0f, 0x00, 0x00, + // 1170 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0x1b, 0xc5, + 0x17, 0xf7, 0x26, 0x6d, 0xbe, 0xc9, 0xe4, 0xdb, 0x1f, 0x4c, 0x4b, 0x71, 0x37, 0xc1, 0x2e, 0x5b, + 0x01, 0x69, 0x81, 0xdd, 0xc4, 0xb9, 0xb4, 0x81, 0x34, 0xb1, 0xe3, 0x10, 0xac, 0xb6, 0x6a, 0xd8, + 0x56, 0x20, 0x01, 0x62, 0xd9, 0xec, 0x0e, 0xf6, 0x4a, 0xeb, 0x9d, 0xed, 0xcc, 0xd8, 0x69, 0x54, + 0x71, 0x28, 0x48, 0xd0, 0x63, 0x25, 0xc4, 0xbd, 0x7f, 0x4e, 0x6f, 0x14, 0xf5, 0xc2, 0xa9, 0xa0, + 0x84, 0x03, 0xe2, 0x84, 0xb8, 0x23, 0xa1, 0x9d, 0x9d, 0x5d, 0xaf, 0xed, 0x8d, 0xbd, 0x76, 0x72, + 0xb3, 0x67, 0xde, 0xfb, 0xbc, 0xcf, 0xe7, 0xf9, 0xcd, 0x7b, 0xcf, 0x40, 0x73, 0x3c, 0x86, 0x88, + 0xd5, 0x30, 0x1d, 0xcf, 0xa0, 0xc8, 0x6a, 0x11, 0x87, 0xed, 0x69, 0x96, 0xd5, 0xd6, 0x7c, 0x82, + 0xdb, 0x8e, 0x8d, 0x88, 0xd6, 0x5e, 0xd2, 0xee, 0xb7, 0x10, 0xd9, 0x53, 0x7d, 0x82, 0x19, 0x86, + 0x97, 0x53, 0x1c, 0x54, 0xcb, 0x6a, 0xab, 0x91, 0x83, 0xda, 0x5e, 0x92, 0xe7, 0xeb, 0x18, 0xd7, + 0x5d, 0xa4, 0x99, 0xbe, 0xa3, 0x99, 0x9e, 0x87, 0x99, 0xc9, 0x1c, 0xec, 0xd1, 0x10, 0x42, 0x3e, + 0x5f, 0xc7, 0x75, 0xcc, 0x3f, 0x6a, 0xc1, 0x27, 0x71, 0x5a, 0x14, 0x3e, 0xfc, 0xdb, 0x4e, 0xeb, + 0x6b, 0x8d, 0x39, 0x4d, 0x44, 0x99, 0xd9, 0xf4, 0x85, 0x41, 0x29, 0x0b, 0xd5, 0x98, 0x45, 0xe8, + 0xb3, 0x78, 0x98, 0x4f, 0x7b, 0x49, 0xa3, 0x0d, 0x93, 0x20, 0xdb, 0xb0, 0xb0, 0x47, 0x5b, 0xcd, + 0xd8, 0xe3, 0xcd, 0x01, 0x1e, 0xbb, 0x0e, 0x41, 0xa1, 0x99, 0x72, 0x0d, 0xcc, 0x7d, 0x1c, 0x64, + 0x65, 0x43, 0x78, 0x6f, 0x21, 0x0f, 0x51, 0x87, 0xea, 0xe8, 0x7e, 0x0b, 0x51, 0x06, 0x2f, 0x82, + 0xe9, 0x10, 0xc2, 0xb1, 0xf3, 0xd2, 0x25, 0x69, 0x61, 0x46, 0xff, 0x1f, 0xff, 0x5e, 0xb3, 0x95, + 0x87, 0x60, 0x3e, 0xdd, 0x93, 0xfa, 0xd8, 0xa3, 0x08, 0x7e, 0x0e, 0x4e, 0xd5, 0xc3, 0x23, 0x83, + 0x32, 0x93, 0x21, 0xee, 0x3f, 0x5b, 0x5a, 0x54, 0x0f, 0x4b, 0x7c, 0x7b, 0x49, 0xed, 0xc1, 0xba, + 0x1b, 0xf8, 0x55, 0x4e, 0x3c, 0x7b, 0x59, 0xcc, 0xe9, 0xff, 0xaf, 0x27, 0xce, 0x94, 0x79, 0x20, + 0x77, 0x05, 0xdf, 0x08, 0xe0, 0x22, 0xd6, 0x8a, 0xd9, 0x23, 0x2a, 0xba, 0x15, 0xcc, 0x2a, 0x60, + 0x8a, 0x87, 0xa7, 0x79, 0xe9, 0xd2, 0xe4, 0xc2, 0x6c, 0xe9, 0xaa, 0x9a, 0xa1, 0x16, 0x54, 0x0e, + 0xa2, 0x0b, 0x4f, 0xe5, 0x0a, 0x78, 0xbb, 0x3f, 0xc4, 0x5d, 0x66, 0x12, 0xb6, 0x4d, 0xb0, 0x8f, + 0xa9, 0xe9, 0xc6, 0x6c, 0x1e, 0x4b, 0x60, 0x61, 0xb8, 0xad, 0xe0, 0xf6, 0x05, 0x98, 0xf1, 0xa3, + 0x43, 0x91, 0xb1, 0x1b, 0xd9, 0xe8, 0x09, 0xf0, 0xb2, 0x6d, 0x3b, 0x41, 0x91, 0x76, 0xa0, 0x3b, + 0x80, 0xca, 0x02, 0x78, 0x2b, 0x8d, 0x09, 0xf6, 0xfb, 0x48, 0x7f, 0x2f, 0xa5, 0x0b, 0xec, 0x32, + 0x8d, 0x7f, 0xe9, 0x3e, 0xce, 0xab, 0x23, 0x71, 0xd6, 0x51, 0x13, 0xb7, 0x4d, 0x37, 0x95, 0xf2, + 0x1a, 0x38, 0xc9, 0x43, 0x0f, 0x28, 0x45, 0x38, 0x07, 0x66, 0x2c, 0xd7, 0x41, 0x1e, 0x0b, 0xee, + 0x26, 0xf8, 0xdd, 0x74, 0x78, 0x50, 0xb3, 0x95, 0x1f, 0x24, 0xf0, 0x06, 0x57, 0xf2, 0x89, 0xe9, + 0x3a, 0xb6, 0xc9, 0x30, 0x49, 0xa4, 0x8a, 0x0c, 0x2f, 0x74, 0xb8, 0x0a, 0xce, 0x46, 0xa4, 0x0d, + 0xd3, 0xb6, 0x09, 0xa2, 0x34, 0x0c, 0x52, 0x81, 0xff, 0xbc, 0x2c, 0x9e, 0xde, 0x33, 0x9b, 0xee, + 0x8a, 0x22, 0x2e, 0x14, 0xfd, 0x4c, 0x64, 0x5b, 0x0e, 0x4f, 0x56, 0xa6, 0x1f, 0x3f, 0x2d, 0xe6, + 0xfe, 0x7c, 0x5a, 0xcc, 0x29, 0x77, 0x80, 0x32, 0x88, 0x88, 0xc8, 0xe6, 0x15, 0x70, 0x36, 0x7a, + 0xca, 0x71, 0xb8, 0x90, 0xd1, 0x19, 0x2b, 0x61, 0x1f, 0x04, 0xeb, 0x97, 0xb6, 0x9d, 0x08, 0x9e, + 0x4d, 0x5a, 0x5f, 0xac, 0x01, 0xd2, 0x7a, 0xe2, 0x0f, 0x92, 0xd6, 0x4d, 0xa4, 0x23, 0xad, 0x2f, + 0x93, 0x42, 0x5a, 0x4f, 0xd6, 0x94, 0x39, 0x70, 0x91, 0x03, 0xde, 0x6b, 0x10, 0xcc, 0x98, 0x8b, + 0xf8, 0xb3, 0x8f, 0x8a, 0xf3, 0x17, 0x49, 0x3c, 0xff, 0x9e, 0x5b, 0x11, 0xa6, 0x08, 0x66, 0xa9, + 0x6b, 0xd2, 0x86, 0xd1, 0x44, 0x0c, 0x11, 0x1e, 0x61, 0x52, 0x07, 0xfc, 0xe8, 0x76, 0x70, 0x02, + 0x4b, 0xe0, 0xd5, 0x84, 0x81, 0x61, 0xba, 0x2e, 0xde, 0x35, 0x3d, 0x0b, 0x71, 0xed, 0x93, 0xfa, + 0xb9, 0x8e, 0x69, 0x39, 0xba, 0x82, 0x5f, 0x82, 0xbc, 0x87, 0x1e, 0x30, 0x83, 0x20, 0xdf, 0x45, + 0x9e, 0x43, 0x1b, 0x86, 0x65, 0x7a, 0x76, 0x20, 0x16, 0xe5, 0x27, 0x79, 0xcd, 0xcb, 0x6a, 0xd8, + 0xf9, 0xd5, 0xa8, 0xf3, 0xab, 0xf7, 0xa2, 0xce, 0x5f, 0x99, 0x0e, 0x7a, 0xd8, 0x93, 0xdf, 0x8a, + 0x92, 0x7e, 0x21, 0x40, 0xd1, 0x23, 0x90, 0x8d, 0x08, 0x43, 0x79, 0x17, 0x5c, 0xe5, 0x92, 0x74, + 0x54, 0x77, 0x28, 0x43, 0x04, 0xd9, 0x9d, 0xd7, 0xb1, 0x6b, 0x12, 0xbb, 0x8a, 0x3c, 0xdc, 0x8c, + 0x9f, 0xe7, 0x26, 0x78, 0x27, 0x93, 0xb5, 0xc8, 0xc8, 0x05, 0x30, 0x65, 0xf3, 0x13, 0xde, 0xf1, + 0x66, 0x74, 0xf1, 0x4d, 0x29, 0x88, 0x1e, 0x1e, 0xbe, 0x3c, 0x64, 0xf3, 0x97, 0x56, 0xab, 0xc6, + 0x61, 0x1e, 0x49, 0xe0, 0xf5, 0x43, 0x0c, 0x04, 0xf2, 0x57, 0xe0, 0xb4, 0x9f, 0xbc, 0x8b, 0x7a, + 0x6a, 0x29, 0x53, 0x03, 0xe8, 0x82, 0x15, 0x8d, 0xbe, 0x07, 0x4f, 0xa9, 0x81, 0x53, 0x5d, 0x66, + 0x30, 0x0f, 0x44, 0xfd, 0x56, 0xbb, 0xcb, 0xb9, 0x0a, 0x0b, 0x00, 0x44, 0x8d, 0xa3, 0x56, 0xe5, + 0x3f, 0xe6, 0x09, 0x3d, 0x71, 0x52, 0xfa, 0xee, 0x15, 0x70, 0x92, 0xcb, 0x81, 0xfb, 0x12, 0x38, + 0x9f, 0x36, 0xbd, 0xe0, 0x7a, 0x26, 0xde, 0x03, 0x46, 0xa6, 0x5c, 0x3e, 0x02, 0x42, 0x98, 0x54, + 0x65, 0xf3, 0xdb, 0x17, 0x7f, 0xfc, 0x38, 0xb1, 0x06, 0x57, 0x87, 0x6f, 0x35, 0xf1, 0xf3, 0x15, + 0xe3, 0x51, 0x7b, 0x18, 0xbd, 0xf5, 0x6f, 0xe0, 0x0b, 0x09, 0x9c, 0x4b, 0x99, 0x83, 0x70, 0x6d, + 0x74, 0x86, 0x5d, 0xf3, 0x55, 0x5e, 0x1f, 0x1f, 0x40, 0x28, 0xbc, 0xce, 0x15, 0x2e, 0xc3, 0xa5, + 0x11, 0x14, 0x86, 0x93, 0x17, 0x3e, 0x9a, 0x00, 0xf9, 0x43, 0xc6, 0x29, 0x85, 0xb7, 0xc6, 0x64, + 0x96, 0x3a, 0xb9, 0xe5, 0xdb, 0xc7, 0x84, 0x26, 0x44, 0x7f, 0xc4, 0x45, 0x57, 0xe0, 0xfa, 0xa8, + 0xa2, 0x83, 0x05, 0x8a, 0x30, 0x23, 0x1e, 0x8a, 0xf0, 0x5f, 0x09, 0xbc, 0x96, 0x3e, 0x9d, 0x29, + 0xbc, 0x39, 0x36, 0xe9, 0xfe, 0x35, 0x40, 0xbe, 0x75, 0x3c, 0x60, 0x22, 0x01, 0x5b, 0x3c, 0x01, + 0x65, 0xb8, 0x36, 0x46, 0x02, 0xb0, 0x9f, 0xd0, 0xff, 0x77, 0x34, 0x00, 0x52, 0x47, 0x29, 0xfc, + 0x30, 0x3b, 0xeb, 0x41, 0x4b, 0x81, 0xbc, 0x75, 0x64, 0x1c, 0x21, 0xbc, 0xcc, 0x85, 0xbf, 0x0f, + 0xaf, 0x67, 0xf8, 0x9b, 0x12, 0x01, 0x19, 0x5d, 0x93, 0x39, 0x45, 0x72, 0x72, 0xc4, 0x8e, 0x25, + 0x39, 0x65, 0x59, 0x18, 0x4b, 0x72, 0xda, 0xac, 0x1f, 0x4f, 0x72, 0xd7, 0x76, 0x00, 0x7f, 0x96, + 0x00, 0xec, 0x1f, 0xf3, 0xf0, 0x46, 0x76, 0x8a, 0x69, 0xdb, 0x83, 0xbc, 0x36, 0xb6, 0xbf, 0x90, + 0x76, 0x8d, 0x4b, 0x2b, 0xc1, 0xc5, 0xe1, 0xd2, 0x98, 0x00, 0x08, 0xff, 0x02, 0xc1, 0x9f, 0x26, + 0xc0, 0xe5, 0x0c, 0x73, 0x1b, 0xde, 0xc9, 0x4e, 0x31, 0xd3, 0xbe, 0x20, 0x6f, 0x1f, 0x1f, 0xa0, + 0x48, 0xc2, 0x4d, 0x9e, 0x84, 0x4d, 0xb8, 0x31, 0x3c, 0x09, 0x24, 0x46, 0xec, 0xd4, 0x34, 0xe1, + 0x98, 0x46, 0xb8, 0x87, 0xc0, 0xbf, 0xfa, 0xf6, 0x8c, 0x64, 0x33, 0xa9, 0x55, 0x29, 0x1c, 0x61, + 0xaa, 0x1e, 0xb2, 0xcc, 0xc8, 0x95, 0xa3, 0x40, 0x08, 0xd5, 0x15, 0xae, 0xfa, 0x03, 0xb8, 0x32, + 0x5c, 0x75, 0xb4, 0xc6, 0x18, 0x3d, 0x03, 0xac, 0xf2, 0xe9, 0xb3, 0xfd, 0x82, 0xf4, 0x7c, 0xbf, + 0x20, 0xfd, 0xbe, 0x5f, 0x90, 0x9e, 0x1c, 0x14, 0x72, 0xcf, 0x0f, 0x0a, 0xb9, 0x5f, 0x0f, 0x0a, + 0xb9, 0xcf, 0x56, 0xeb, 0x0e, 0x6b, 0xb4, 0x76, 0x54, 0x0b, 0x37, 0x35, 0x0b, 0xd3, 0x26, 0xa6, + 0x89, 0x30, 0xef, 0xc5, 0x61, 0xda, 0xcb, 0xda, 0x83, 0x9e, 0x32, 0xdb, 0xf3, 0x11, 0xdd, 0x99, + 0xe2, 0x8b, 0xe7, 0xf2, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xc2, 0x02, 0x37, 0x01, 0x0c, 0x11, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -910,6 +1052,9 @@ type QueryClient interface { // QueryRegisteredConsumerRewardDenoms returns a list of consumer reward // denoms that are registered QueryRegisteredConsumerRewardDenoms(ctx context.Context, in *QueryRegisteredConsumerRewardDenomsRequest, opts ...grpc.CallOption) (*QueryRegisteredConsumerRewardDenomsResponse, error) + // QueryProposedConsumerChainIDs returns the chain IDs of the proposed consumer chain addition proposals + // that are still in the voting period + QueryProposedConsumerChainIDs(ctx context.Context, in *QueryProposedChainIDsRequest, opts ...grpc.CallOption) (*QueryProposedChainIDsResponse, error) } type queryClient struct { @@ -992,6 +1137,15 @@ func (c *queryClient) QueryRegisteredConsumerRewardDenoms(ctx context.Context, i return out, nil } +func (c *queryClient) QueryProposedConsumerChainIDs(ctx context.Context, in *QueryProposedChainIDsRequest, opts ...grpc.CallOption) (*QueryProposedChainIDsResponse, error) { + out := new(QueryProposedChainIDsResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Query/QueryProposedConsumerChainIDs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // ConsumerGenesis queries the genesis state needed to start a consumer chain @@ -1016,6 +1170,9 @@ type QueryServer interface { // QueryRegisteredConsumerRewardDenoms returns a list of consumer reward // denoms that are registered QueryRegisteredConsumerRewardDenoms(context.Context, *QueryRegisteredConsumerRewardDenomsRequest) (*QueryRegisteredConsumerRewardDenomsResponse, error) + // QueryProposedConsumerChainIDs returns the chain IDs of the proposed consumer chain addition proposals + // that are still in the voting period + QueryProposedConsumerChainIDs(context.Context, *QueryProposedChainIDsRequest) (*QueryProposedChainIDsResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -1046,6 +1203,9 @@ func (*UnimplementedQueryServer) QueryThrottleState(ctx context.Context, req *Qu func (*UnimplementedQueryServer) QueryRegisteredConsumerRewardDenoms(ctx context.Context, req *QueryRegisteredConsumerRewardDenomsRequest) (*QueryRegisteredConsumerRewardDenomsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method QueryRegisteredConsumerRewardDenoms not implemented") } +func (*UnimplementedQueryServer) QueryProposedConsumerChainIDs(ctx context.Context, req *QueryProposedChainIDsRequest) (*QueryProposedChainIDsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method QueryProposedConsumerChainIDs not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -1195,6 +1355,24 @@ func _Query_QueryRegisteredConsumerRewardDenoms_Handler(srv interface{}, ctx con return interceptor(ctx, in, info, handler) } +func _Query_QueryProposedConsumerChainIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryProposedChainIDsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).QueryProposedConsumerChainIDs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Query/QueryProposedConsumerChainIDs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).QueryProposedConsumerChainIDs(ctx, req.(*QueryProposedChainIDsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Query", HandlerType: (*QueryServer)(nil), @@ -1231,6 +1409,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "QueryRegisteredConsumerRewardDenoms", Handler: _Query_QueryRegisteredConsumerRewardDenoms_Handler, }, + { + MethodName: "QueryProposedConsumerChainIDs", + Handler: _Query_QueryProposedConsumerChainIDs_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/query.proto", @@ -1765,6 +1947,101 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) MarshalToSizedBuffer(dAtA return len(dAtA) - i, nil } +func (m *QueryProposedChainIDsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryProposedChainIDsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryProposedChainIDsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryProposedChainIDsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryProposedChainIDsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryProposedChainIDsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ProposedChains) > 0 { + for iNdEx := len(m.ProposedChains) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ProposedChains[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ProposedChain) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProposedChain) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ProposedChain) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ProposalID != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.ProposalID)) + i-- + dAtA[i] = 0x10 + } + if len(m.ChainID) > 0 { + i -= len(m.ChainID) + copy(dAtA[i:], m.ChainID) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChainID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1995,6 +2272,46 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) Size() (n int) { return n } +func (m *QueryProposedChainIDsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryProposedChainIDsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ProposedChains) > 0 { + for _, e := range m.ProposedChains { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *ProposedChain) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChainID) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.ProposalID != 0 { + n += 1 + sovQuery(uint64(m.ProposalID)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3381,6 +3698,241 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) Unmarshal(dAtA []byte) err } return nil } +func (m *QueryProposedChainIDsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryProposedChainIDsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryProposedChainIDsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryProposedChainIDsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryProposedChainIDsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryProposedChainIDsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposedChains", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProposedChains = append(m.ProposedChains, ProposedChain{}) + if err := m.ProposedChains[len(m.ProposedChains)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProposedChain) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProposedChain: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProposedChain: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChainID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposalID", wireType) + } + m.ProposalID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ProposalID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/provider/types/query.pb.gw.go b/x/ccv/provider/types/query.pb.gw.go index 1a4b418158..f16dc6181d 100644 --- a/x/ccv/provider/types/query.pb.gw.go +++ b/x/ccv/provider/types/query.pb.gw.go @@ -249,6 +249,24 @@ func local_request_Query_QueryRegisteredConsumerRewardDenoms_0(ctx context.Conte } +func request_Query_QueryProposedConsumerChainIDs_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryProposedChainIDsRequest + var metadata runtime.ServerMetadata + + msg, err := client.QueryProposedConsumerChainIDs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_QueryProposedConsumerChainIDs_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryProposedChainIDsRequest + var metadata runtime.ServerMetadata + + msg, err := server.QueryProposedConsumerChainIDs(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -439,6 +457,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_QueryProposedConsumerChainIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_QueryProposedConsumerChainIDs_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_QueryProposedConsumerChainIDs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -640,6 +681,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_QueryProposedConsumerChainIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_QueryProposedConsumerChainIDs_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_QueryProposedConsumerChainIDs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -659,6 +720,8 @@ var ( pattern_Query_QueryThrottleState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"interchain_security", "ccv", "provider", "throttle_state"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_QueryRegisteredConsumerRewardDenoms_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"interchain_security", "ccv", "provider", "registered_consumer_reward_denoms"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_QueryProposedConsumerChainIDs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"interchain_security", "ccv", "provider", "proposed_consumer_chains"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -677,4 +740,6 @@ var ( forward_Query_QueryThrottleState_0 = runtime.ForwardResponseMessage forward_Query_QueryRegisteredConsumerRewardDenoms_0 = runtime.ForwardResponseMessage + + forward_Query_QueryProposedConsumerChainIDs_0 = runtime.ForwardResponseMessage ) diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 5b2f3f7a53..10546dff21 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -15,6 +15,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -146,3 +147,7 @@ type ScopedKeeper interface { AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error } + +type GovKeeper interface { + GetProposal(ctx sdk.Context, proposalID uint64) (v1.Proposal, bool) +}