Skip to content

Commit

Permalink
[Application] Support filtering apps by delegatee gateway address (#957)
Browse files Browse the repository at this point in the history
## Summary

Support querying applications based on the delegated gateway address:
- Add a new field to the `QueryAllApplicationsRequest` type.
- Update the application keeper to process the query request based on
the updated struct.
- Add unit tests to cover the new functionality.

Note: #888 adds a similar functionality by allowing access to delegating
apps directly from a gateway struct. This PR allows querying apps
directly for a specific gateway delegation. Likely both PRs are needed
in the long term.

## Issue

This is needed ot support integration with PATH. More specifically, it
allows PATH to get the list of all applications which delegate to a
specific gateway address:


https://github.com/buildwithgrove/path/blob/8533ee8b6dae4dcc5d4615bdd1b58d6229ef85a7/protocol/shannon/gateway_mode.go#L25-L26


## Type of change

Select one or more from the following:

- [x] New feature, functionality or library
- [ ] Consensus breaking; add the `consensus-breaking` label if so. See
#791 for details
- [ ] Bug fix
- [ ] Code health or cleanup
- [ ] Documentation
- [ ] Other (specify)

## Testing

- [x] **Unit Tests**: `make go_develop_and_test`
- [ ] **LocalNet E2E Tests**: `make test_e2e`
- [ ] **DevNet E2E Tests**: Add the `devnet-test-e2e` label to the PR.

## Sanity Checklist

- [x] I have tested my changes using the available tooling
- [x] I have commented my code
- [x] I have performed a self-review of my own code; both comments &
source code
- [ ] I create and reference any new tickets, if applicable
- [ ] I have left TODOs throughout the codebase, if applicable

---------

Co-authored-by: Redouane Lakrache <r3d0ne@gmail.com>
  • Loading branch information
adshmh and red-0ne authored Dec 3, 2024
1 parent d404489 commit ff369fe
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 117 deletions.
203 changes: 139 additions & 64 deletions api/poktroll/application/query.pulsar.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions proto/poktroll/application/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ message QueryGetApplicationResponse {

message QueryAllApplicationsRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
// TODO_MAINNET(@adshmh): rename this field to `gateway_address_delegated_to`
// delegatee_gateway_address, if specified, filters the application list to only include those with delegation to the specified gateway address.
string delegatee_gateway_address = 2;
}

message QueryAllApplicationsResponse {
Expand Down
20 changes: 19 additions & 1 deletion x/application/keeper/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper_test

import (
"context"
"slices"
"strconv"
"testing"

Expand All @@ -18,18 +19,35 @@ import (
// Prevent strconv unused error
var _ = strconv.IntSize

func createNApplications(keeper keeper.Keeper, ctx context.Context, n int) []types.Application {
// testAppModifier represents any function that can be used to modify an application being instantiated for testing purposes.
type testAppModifier func(app *types.Application)

func createNApplications(keeper keeper.Keeper, ctx context.Context, n int, testAppModifiers ...testAppModifier) []types.Application {
apps := make([]types.Application, n)
for i := range apps {
apps[i].Address = strconv.Itoa(i)
// Setting pending undelegations since nullify.Fill() does not seem to do it.
apps[i].PendingUndelegations = make(map[uint64]types.UndelegatingGatewayList)

for _, modifier := range testAppModifiers {
modifier(&apps[i])
}

keeper.SetApplication(ctx, apps[i])
}
return apps
}

// testAppModifierDelegateeAddr adds the supplied gateway address to the application's delegatee list if the application's address matches
// the supplied address list.
func withAppDelegateeGatewayAddr(delegateeGatewayAddr string, appsWithDelegationAddr []string) testAppModifier {
return func(app *types.Application) {
if slices.Contains(appsWithDelegationAddr, app.Address) {
app.DelegateeGatewayAddresses = append(app.DelegateeGatewayAddresses, delegateeGatewayAddr)
}
}
}

func init() {
cmd.InitSDKConfig()
}
Expand Down
11 changes: 11 additions & 0 deletions x/application/keeper/query_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
"context"
"fmt"
"slices"

"cosmossdk.io/store/prefix"
"github.com/cosmos/cosmos-sdk/runtime"
Expand All @@ -20,6 +21,10 @@ func (k Keeper) AllApplications(ctx context.Context, req *types.QueryAllApplicat
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if err := req.ValidateBasic(); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

var apps []types.Application

store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
Expand All @@ -32,6 +37,12 @@ func (k Keeper) AllApplications(ctx context.Context, req *types.QueryAllApplicat
return status.Error(codes.Internal, err.Error())
}

// Filter out the application if the request specifies a delegatee gateway address as a contraint and the application
// does not delegate to the specifies gateway address.
if req.DelegateeGatewayAddress != "" && !slices.Contains(application.DelegateeGatewayAddresses, req.DelegateeGatewayAddress) {
return nil
}

// Ensure that the PendingUndelegations is an empty map and not nil when
// unmarshalling an app that has no pending undelegations.
if application.PendingUndelegations == nil {
Expand Down
48 changes: 48 additions & 0 deletions x/application/keeper/query_application_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"slices"
"strconv"
"testing"

Expand All @@ -11,6 +12,7 @@ import (

keepertest "github.com/pokt-network/poktroll/testutil/keeper"
"github.com/pokt-network/poktroll/testutil/nullify"
"github.com/pokt-network/poktroll/testutil/sample"
"github.com/pokt-network/poktroll/x/application/types"
)

Expand Down Expand Up @@ -122,3 +124,49 @@ func TestApplicationQueryPaginated(t *testing.T) {
require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "invalid request"))
})
}

func TestAllApplicationsQuery_WithDelegateeGatewayAddressConstraint(t *testing.T) {
keeper, ctx := keepertest.ApplicationKeeper(t)
gatewayAddr1 := sample.AccAddress()
appsWithDelegationAddr := []string{"1", "2"}
apps := createNApplications(keeper, ctx, 5, withAppDelegateeGatewayAddr(gatewayAddr1, appsWithDelegationAddr))

requestBuilder := func(gatewayAddr string) *types.QueryAllApplicationsRequest {
return &types.QueryAllApplicationsRequest{
DelegateeGatewayAddress: gatewayAddr,
}
}

t.Run("QueryAppsWithDelegatee", func(t *testing.T) {
resp, err := keeper.AllApplications(ctx, requestBuilder(gatewayAddr1))
require.NoError(t, err)

var expectedApps []types.Application
for _, app := range apps {
if slices.Contains(appsWithDelegationAddr, app.Address) {
expectedApps = append(expectedApps, app)
}
}

require.ElementsMatch(t,
nullify.Fill(expectedApps),
nullify.Fill(resp.Applications),
)
})

t.Run("QueryAppsWithNoDelegationConstraint", func(t *testing.T) {
resp, err := keeper.AllApplications(ctx, &types.QueryAllApplicationsRequest{})
require.NoError(t, err)

require.ElementsMatch(t,
nullify.Fill(apps),
nullify.Fill(resp.Applications),
)
})

t.Run("QueryAppsWithInvalidGatewayAddr", func(t *testing.T) {
addrInvalid := "invalid-address"
_, err := keeper.AllApplications(ctx, requestBuilder(addrInvalid))
require.ErrorContains(t, err, types.ErrQueryAppsInvalidGatewayAddress.Error())
})
}
33 changes: 17 additions & 16 deletions x/application/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import sdkerrors "cosmossdk.io/errors"

// x/application module sentinel errors
var (
ErrAppInvalidSigner = sdkerrors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
ErrAppInvalidStake = sdkerrors.Register(ModuleName, 1101, "invalid application stake")
ErrAppInvalidAddress = sdkerrors.Register(ModuleName, 1102, "invalid application address")
ErrAppUnauthorized = sdkerrors.Register(ModuleName, 1103, "unauthorized application signer")
ErrAppNotFound = sdkerrors.Register(ModuleName, 1104, "application not found")
ErrAppInvalidServiceConfigs = sdkerrors.Register(ModuleName, 1106, "invalid service configs")
ErrAppGatewayNotFound = sdkerrors.Register(ModuleName, 1107, "gateway not found")
ErrAppInvalidGatewayAddress = sdkerrors.Register(ModuleName, 1108, "invalid gateway address")
ErrAppAlreadyDelegated = sdkerrors.Register(ModuleName, 1109, "application already delegated to gateway")
ErrAppMaxDelegatedGateways = sdkerrors.Register(ModuleName, 1110, "maximum number of delegated gateways reached")
ErrAppNotDelegated = sdkerrors.Register(ModuleName, 1111, "application not delegated to gateway")
ErrAppIsUnstaking = sdkerrors.Register(ModuleName, 1112, "application is in unbonding period")
ErrAppDuplicateAddress = sdkerrors.Register(ModuleName, 1113, "duplicate application address")
ErrAppHasPendingTransfer = sdkerrors.Register(ModuleName, 1114, "application is in transfer period")
ErrAppParamInvalid = sdkerrors.Register(ModuleName, 1115, "the provided param is invalid")
ErrAppEmitEvent = sdkerrors.Register(ModuleName, 1116, "unable to emit on-chain event")
ErrAppInvalidSigner = sdkerrors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
ErrAppInvalidStake = sdkerrors.Register(ModuleName, 1101, "invalid application stake")
ErrAppInvalidAddress = sdkerrors.Register(ModuleName, 1102, "invalid application address")
ErrAppUnauthorized = sdkerrors.Register(ModuleName, 1103, "unauthorized application signer")
ErrAppNotFound = sdkerrors.Register(ModuleName, 1104, "application not found")
ErrAppInvalidServiceConfigs = sdkerrors.Register(ModuleName, 1106, "invalid service configs")
ErrAppGatewayNotFound = sdkerrors.Register(ModuleName, 1107, "gateway not found")
ErrAppInvalidGatewayAddress = sdkerrors.Register(ModuleName, 1108, "invalid gateway address")
ErrAppAlreadyDelegated = sdkerrors.Register(ModuleName, 1109, "application already delegated to gateway")
ErrAppMaxDelegatedGateways = sdkerrors.Register(ModuleName, 1110, "maximum number of delegated gateways reached")
ErrAppNotDelegated = sdkerrors.Register(ModuleName, 1111, "application not delegated to gateway")
ErrAppIsUnstaking = sdkerrors.Register(ModuleName, 1112, "application is in unbonding period")
ErrAppDuplicateAddress = sdkerrors.Register(ModuleName, 1113, "duplicate application address")
ErrAppHasPendingTransfer = sdkerrors.Register(ModuleName, 1114, "application is in transfer period")
ErrAppParamInvalid = sdkerrors.Register(ModuleName, 1115, "the provided param is invalid")
ErrAppEmitEvent = sdkerrors.Register(ModuleName, 1116, "unable to emit on-chain event")
ErrQueryAppsInvalidGatewayAddress = sdkerrors.Register(ModuleName, 1117, "invalid gateway address querying for apps with delegatee gateway address")
)
125 changes: 89 additions & 36 deletions x/application/types/query.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions x/application/types/query_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package types

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// ValidateBasic performs basic (non-state-dependant) validation on a QueryAllApplicationsRequest.
func (query *QueryAllApplicationsRequest) ValidateBasic() error {
delegateeGatewayAddr := query.GetDelegateeGatewayAddress()
if delegateeGatewayAddr == "" {
return nil
}

// Validate the delegation gateway address if the request specifies it as a constraint.
if _, err := sdk.AccAddressFromBech32(delegateeGatewayAddr); err != nil {
return ErrQueryAppsInvalidGatewayAddress.Wrapf("%q; (%v)", delegateeGatewayAddr, err)
}

return nil
}

0 comments on commit ff369fe

Please sign in to comment.