From aef9615fd33fba22e99b8c69b96b5f4ac99ed152 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 31 Oct 2023 22:00:11 +0000 Subject: [PATCH] [AppGate] Add the MaxDelegatedGateways parameter (#109) --- config.yml | 2 + docs/static/openapi.yml | 19 +++++ proto/pocket/application/params.proto | 3 +- testutil/keeper/application.go | 1 + .../keeper/msg_server_delegate_to_gateway.go | 7 ++ .../msg_server_delegate_to_gateway_test.go | 72 ++++++++++++++++++- x/application/types/errors.go | 18 ++--- x/application/types/genesis_test.go | 54 ++++++++++++++ x/application/types/params.go | 9 ++- 9 files changed, 173 insertions(+), 12 deletions(-) diff --git a/config.yml b/config.yml index 4ad66fbc9..609860430 100644 --- a/config.yml +++ b/config.yml @@ -79,6 +79,8 @@ genesis: - amount: "10000" denom: upokt application: + params: + maxDelegatedGateways: 7 applicationList: - address: pokt1mrqt5f7qh8uxs27cjm9t7v9e74a9vvdnq5jva4 stake: diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index 3e0e46e33..d6f879b53 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -46735,6 +46735,13 @@ paths: params: description: params holds all the parameters of this module. type: object + properties: + max_delegated_gateways: + type: integer + format: int64 + title: >- + The maximum number of gateways an application can delegate + trust to description: >- QueryParamsResponse is response type for the Query/Params RPC method. @@ -77831,6 +77838,11 @@ definitions: type: object pocket.application.Params: type: object + properties: + max_delegated_gateways: + type: integer + format: int64 + title: The maximum number of gateways an application can delegate trust to description: Params defines the parameters for the module. pocket.application.QueryAllApplicationResponse: type: object @@ -78004,6 +78016,13 @@ definitions: params: description: params holds all the parameters of this module. type: object + properties: + max_delegated_gateways: + type: integer + format: int64 + title: >- + The maximum number of gateways an application can delegate trust + to description: QueryParamsResponse is response type for the Query/Params RPC method. pocket.shared.ApplicationServiceConfig: type: object diff --git a/proto/pocket/application/params.proto b/proto/pocket/application/params.proto index 608ca124d..71d2f69ff 100644 --- a/proto/pocket/application/params.proto +++ b/proto/pocket/application/params.proto @@ -8,5 +8,6 @@ option go_package = "pocket/x/application/types"; // Params defines the parameters for the module. message Params { option (gogoproto.goproto_stringer) = false; - + + int64 max_delegated_gateways = 1 [(gogoproto.jsontag) = "max_delegated_gateways"]; // The maximum number of gateways an application can delegate trust to } diff --git a/testutil/keeper/application.go b/testutil/keeper/application.go index 71b06b446..0546b27a7 100644 --- a/testutil/keeper/application.go +++ b/testutil/keeper/application.go @@ -24,6 +24,7 @@ import ( // StakedGatewayMap is used to mock whether a gateway is staked or not for use // in the application's mocked gateway keeper. This enables the tester to // control whether a gateway is "staked" or not and whether it can be delegated to +// WARNING: Using this map may cause issues if running multiple tests in parallel var StakedGatewayMap = make(map[string]struct{}) func ApplicationKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { diff --git a/x/application/keeper/msg_server_delegate_to_gateway.go b/x/application/keeper/msg_server_delegate_to_gateway.go index 41b96e1ef..1fe9bb786 100644 --- a/x/application/keeper/msg_server_delegate_to_gateway.go +++ b/x/application/keeper/msg_server_delegate_to_gateway.go @@ -34,6 +34,13 @@ func (k msgServer) DelegateToGateway(goCtx context.Context, msg *types.MsgDelega return nil, sdkerrors.Wrapf(types.ErrAppGatewayNotFound, "gateway not found with address: %s", msg.GatewayAddress) } + // Ensure the application is not already delegated to the maximum number of gateways + maxDelegatedParam := k.GetParams(ctx).MaxDelegatedGateways + if int64(len(app.DelegateeGatewayAddresses)) >= maxDelegatedParam { + logger.Info("Application already delegated to maximum number of gateways: %d", maxDelegatedParam) + return nil, sdkerrors.Wrapf(types.ErrAppMaxDelegatedGateways, "application already delegated to %d gateways", maxDelegatedParam) + } + // Check if the application is already delegated to the gateway for _, gatewayAddr := range app.DelegateeGatewayAddresses { if gatewayAddr == msg.GatewayAddress { diff --git a/x/application/keeper/msg_server_delegate_to_gateway_test.go b/x/application/keeper/msg_server_delegate_to_gateway_test.go index 788b740b8..470c8e572 100644 --- a/x/application/keeper/msg_server_delegate_to_gateway_test.go +++ b/x/application/keeper/msg_server_delegate_to_gateway_test.go @@ -136,7 +136,7 @@ func TestMsgServer_DelegateToGateway_FailDuplicate(t *testing.T) { // Attempt to delegate the application to the gateway again _, err = srv.DelegateToGateway(wctx, delegateMsg2) - require.Error(t, err) + require.ErrorIs(t, err, types.ErrAppAlreadyDelegated) foundApp, isAppFound = k.GetApplication(ctx, appAddr) require.True(t, isAppFound) require.Equal(t, 1, len(foundApp.DelegateeGatewayAddresses)) @@ -177,8 +177,76 @@ func TestMsgServer_DelegateToGateway_FailGatewayNotStaked(t *testing.T) { // Attempt to delegate the application to the unstaked gateway _, err = srv.DelegateToGateway(wctx, delegateMsg) - require.Error(t, err) + require.ErrorIs(t, err, types.ErrAppGatewayNotFound) foundApp, isAppFound := k.GetApplication(ctx, appAddr) require.True(t, isAppFound) require.Equal(t, 0, len(foundApp.DelegateeGatewayAddresses)) } + +func TestMsgServer_DelegateToGateway_FailMaxReached(t *testing.T) { + k, ctx := keepertest.ApplicationKeeper(t) + srv := keeper.NewMsgServerImpl(*k) + wctx := sdk.WrapSDKContext(ctx) + + // Generate an address for the application and gateway + appAddr := sample.AccAddress() + gatewayAddr := sample.AccAddress() + // Mock the gateway being staked via the staked gateway map + keepertest.StakedGatewayMap[gatewayAddr] = struct{}{} + t.Cleanup(func() { + delete(keepertest.StakedGatewayMap, gatewayAddr) + }) + + // Prepare the application + stakeMsg := &types.MsgStakeApplication{ + Address: appAddr, + Stake: &sdk.Coin{Denom: "upokt", Amount: sdk.NewInt(100)}, + Services: []*sharedtypes.ApplicationServiceConfig{ + { + ServiceId: &sharedtypes.ServiceId{Id: "svc1"}, + }, + }, + } + + // Stake the application & verify that the application exists + _, err := srv.StakeApplication(wctx, stakeMsg) + require.NoError(t, err) + _, isAppFound := k.GetApplication(ctx, appAddr) + require.True(t, isAppFound) + + // Prepare the delegation message + delegateMsg := &types.MsgDelegateToGateway{ + AppAddress: appAddr, + GatewayAddress: gatewayAddr, + } + + // Delegate the application to the max number of gateways + maxDelegatedParam := k.GetParams(ctx).MaxDelegatedGateways + for i := int64(0); i < k.GetParams(ctx).MaxDelegatedGateways; i++ { + // Prepare the delegation message + gatewayAddr := sample.AccAddress() + // Mock the gateway being staked via the staked gateway map + keepertest.StakedGatewayMap[gatewayAddr] = struct{}{} + t.Cleanup(func() { + delete(keepertest.StakedGatewayMap, gatewayAddr) + }) + delegateMsg := &types.MsgDelegateToGateway{ + AppAddress: appAddr, + GatewayAddress: gatewayAddr, + } + // Delegate the application to the gateway + _, err = srv.DelegateToGateway(wctx, delegateMsg) + require.NoError(t, err) + // Check number of gateways delegated to is correct + foundApp, isAppFound := k.GetApplication(ctx, appAddr) + require.True(t, isAppFound) + require.Equal(t, int(i+1), len(foundApp.DelegateeGatewayAddresses)) + } + + // Attempt to delegate the application when the max is already reached + _, err = srv.DelegateToGateway(wctx, delegateMsg) + require.ErrorIs(t, err, types.ErrAppMaxDelegatedGateways) + foundApp, isAppFound := k.GetApplication(ctx, appAddr) + require.True(t, isAppFound) + require.Equal(t, maxDelegatedParam, int64(len(foundApp.DelegateeGatewayAddresses))) +} diff --git a/x/application/types/errors.go b/x/application/types/errors.go index 7ed7fc8e2..3445ec6f4 100644 --- a/x/application/types/errors.go +++ b/x/application/types/errors.go @@ -8,12 +8,14 @@ import ( // x/application module sentinel errors var ( - ErrAppInvalidStake = sdkerrors.Register(ModuleName, 1, "invalid application stake") - ErrAppInvalidAddress = sdkerrors.Register(ModuleName, 2, "invalid application address") - ErrAppUnauthorized = sdkerrors.Register(ModuleName, 3, "unauthorized application signer") - ErrAppNotFound = sdkerrors.Register(ModuleName, 4, "application not found") - ErrAppInvalidServiceConfigs = sdkerrors.Register(ModuleName, 6, "invalid service configs") - ErrAppGatewayNotFound = sdkerrors.Register(ModuleName, 7, "gateway not found") - ErrAppInvalidGatewayAddress = sdkerrors.Register(ModuleName, 8, "invalid gateway address") - ErrAppAlreadyDelegated = sdkerrors.Register(ModuleName, 9, "application already delegated to gateway") + ErrAppInvalidStake = sdkerrors.Register(ModuleName, 1, "invalid application stake") + ErrAppInvalidAddress = sdkerrors.Register(ModuleName, 2, "invalid application address") + ErrAppUnauthorized = sdkerrors.Register(ModuleName, 3, "unauthorized application signer") + ErrAppNotFound = sdkerrors.Register(ModuleName, 4, "application not found") + ErrAppInvalidServiceConfigs = sdkerrors.Register(ModuleName, 6, "invalid service configs") + ErrAppGatewayNotFound = sdkerrors.Register(ModuleName, 7, "gateway not found") + ErrAppInvalidGatewayAddress = sdkerrors.Register(ModuleName, 8, "invalid gateway address") + ErrAppAlreadyDelegated = sdkerrors.Register(ModuleName, 9, "application already delegated to gateway") + ErrAppMaxDelegatedGateways = sdkerrors.Register(ModuleName, 10, "maximum number of delegated gateways reached") + ErrAppInvalidMaxDelegatedGateways = sdkerrors.Register(ModuleName, 11, "invalid MaxDelegatedGateways parameter") ) diff --git a/x/application/types/genesis_test.go b/x/application/types/genesis_test.go index 90790e842..74c31c2ed 100644 --- a/x/application/types/genesis_test.go +++ b/x/application/types/genesis_test.go @@ -41,6 +41,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "valid genesis state", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -62,6 +65,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - zero app stake", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -82,6 +88,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - negative application stake", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -102,6 +111,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - wrong stake denom", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -122,6 +134,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - missing denom", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -142,6 +157,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - due to duplicated app address", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -162,6 +180,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - due to nil app stake", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -182,6 +203,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - due to missing app stake", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -202,6 +226,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - due to invalid delegatee pub key", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -222,6 +249,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - due to invalid delegatee pub keys", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -242,6 +272,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - service config not present", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -256,6 +289,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - empty service config", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -270,6 +306,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - service ID too long", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -286,6 +325,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - service name too long", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -305,6 +347,9 @@ func TestGenesisState_Validate(t *testing.T) { { desc: "invalid - service ID with invalid characters", genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 7, + }, ApplicationList: []types.Application{ { Address: addr1, @@ -318,6 +363,15 @@ func TestGenesisState_Validate(t *testing.T) { }, valid: false, }, + { + desc: "invalid - MaxDelegatedGateways less than 1", + genState: &types.GenesisState{ + Params: types.Params{ + MaxDelegatedGateways: 0, + }, + }, + valid: false, + }, // this line is used by starport scaffolding # types/genesis/testcase } diff --git a/x/application/types/params.go b/x/application/types/params.go index 357196ad6..f5ec7cd0c 100644 --- a/x/application/types/params.go +++ b/x/application/types/params.go @@ -1,10 +1,14 @@ package types import ( + sdkerrors "cosmossdk.io/errors" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) +// TODO: Revisit default param values +const DefaultMaxDelegatedGateways int64 = 7 + var _ paramtypes.ParamSet = (*Params)(nil) // ParamKeyTable the param key table for launch module @@ -14,7 +18,7 @@ func ParamKeyTable() paramtypes.KeyTable { // NewParams creates a new Params instance func NewParams() Params { - return Params{} + return Params{MaxDelegatedGateways: DefaultMaxDelegatedGateways} } // DefaultParams returns a default set of parameters @@ -29,6 +33,9 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { // Validate validates the set of params func (p Params) Validate() error { + if p.MaxDelegatedGateways < 1 { + return sdkerrors.Wrapf(ErrAppInvalidMaxDelegatedGateways, "MaxDelegatedGateways param < 1: got %d", p.MaxDelegatedGateways) + } return nil }