diff --git a/x/deployment/keeper/grpc_query.go b/x/deployment/keeper/grpc_query.go index cc68b4000..e4cb45609 100644 --- a/x/deployment/keeper/grpc_query.go +++ b/x/deployment/keeper/grpc_query.go @@ -15,6 +15,7 @@ import ( "pkg.akt.dev/go/node/deployment/v1" types "pkg.akt.dev/go/node/deployment/v1beta4" + "pkg.akt.dev/node/util/query" "pkg.akt.dev/node/x/deployment/keeper/keys" ) @@ -33,50 +34,74 @@ func (k Querier) Deployments(c context.Context, req *types.QueryDeploymentsReque if req.Pagination == nil { req.Pagination = &sdkquery.PageRequest{} + } else if req.Pagination.Offset > 0 && req.Filters.State == "" { + return nil, status.Error(codes.InvalidArgument, "invalid request parameters. if offset is set, filter.state must be provided") } if req.Pagination.Limit == 0 { req.Pagination.Limit = sdkquery.DefaultLimit } - if len(req.Pagination.Key) > 0 { - return nil, status.Error(codes.InvalidArgument, "key-based pagination is not supported") - } - ctx := sdk.UnwrapSDKContext(c) - // Determine which states to iterate - states := []v1.Deployment_State{v1.DeploymentActive, v1.DeploymentClosed} - if req.Filters.State != "" { + states := make([]byte, 0, 2) + var resumePK *keys.DeploymentPrimaryKey + + // nolint: gocritic + if len(req.Pagination.Key) > 0 { + var pkBytes []byte + var err error + states, _, pkBytes, _, err = query.DecodePaginationKey(req.Pagination.Key) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + _, pk, err := k.deployments.KeyCodec().Decode(pkBytes) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + resumePK = &pk + } else if req.Filters.State != "" { stateVal := v1.Deployment_State(v1.Deployment_State_value[req.Filters.State]) + if stateVal == v1.DeploymentStateInvalid { return nil, status.Error(codes.InvalidArgument, "invalid state value") } - states = []v1.Deployment_State{stateVal} + + states = append(states, byte(stateVal)) + } else { + states = append(states, byte(v1.DeploymentActive), byte(v1.DeploymentClosed)) } - if req.Pagination.Reverse { + if len(req.Pagination.Key) == 0 && req.Pagination.Reverse { for i, j := 0, len(states)-1; i < j; i, j = i+1, j-1 { states[i], states[j] = states[j], states[i] } } var deployments types.DeploymentResponses - limit := req.Pagination.Limit + var nextKey []byte + total := uint64(0) offset := req.Pagination.Offset - skipped := uint64(0) - countTotal := req.Pagination.CountTotal - var total uint64 - var acctErr error + var scanErr error - for _, state := range states { - if limit == 0 && !countTotal { + for idx := range states { + if req.Pagination.Limit == 0 && len(nextKey) > 0 { break } + state := v1.Deployment_State(states[idx]) + var iter indexes.MultiIterator[int32, keys.DeploymentPrimaryKey] var err error - if req.Pagination.Reverse { + + if idx == 0 && resumePK != nil { + r := collections.NewPrefixedPairRange[int32, keys.DeploymentPrimaryKey](int32(state)).StartInclusive(*resumePK) + if req.Pagination.Reverse { + r = collections.NewPrefixedPairRange[int32, keys.DeploymentPrimaryKey](int32(state)).EndInclusive(*resumePK).Descending() + } + iter, err = k.deployments.Indexes.State.Iterate(ctx, r) + } else if req.Pagination.Reverse { iter, err = k.deployments.Indexes.State.Iterate(ctx, collections.NewPrefixedPairRange[int32, keys.DeploymentPrimaryKey](int32(state)).Descending()) } else { @@ -86,33 +111,43 @@ func (k Querier) Deployments(c context.Context, req *types.QueryDeploymentsReque return nil, status.Error(codes.Internal, err.Error()) } + count := uint64(0) + err = indexes.ScanValues(ctx, k.deployments, iter, func(deployment v1.Deployment) bool { if !req.Filters.Accept(deployment, state) { return false } - if countTotal { - total++ - } - - if limit == 0 { - return !countTotal + if offset > 0 { + offset-- + return false } - if skipped < offset { - skipped++ - return false + if req.Pagination.Limit == 0 { + // Page is full — encode this item's PK as NextKey + pk := keys.DeploymentIDToKey(deployment.ID) + pkBuf := make([]byte, k.deployments.KeyCodec().Size(pk)) + if _, encErr := k.deployments.KeyCodec().Encode(pkBuf, pk); encErr != nil { + scanErr = encErr + return true + } + var encErr error + nextKey, encErr = query.EncodePaginationKey(states[idx:], []byte{states[idx]}, pkBuf, nil) + if encErr != nil { + scanErr = encErr + } + return true } - account, acctE := k.ekeeper.GetAccount(ctx, deployment.ID.ToEscrowAccountID()) - if acctE != nil { - acctErr = fmt.Errorf("%w: fetching escrow account for DeploymentID=%s", acctE, deployment.ID) + account, acctErr := k.ekeeper.GetAccount(ctx, deployment.ID.ToEscrowAccountID()) + if acctErr != nil { + scanErr = fmt.Errorf("%w: fetching escrow account for DeploymentID=%s", acctErr, deployment.ID) return true } groups, grpErr := k.GetGroups(ctx, deployment.ID) if grpErr != nil { - acctErr = fmt.Errorf("%w: fetching groups for DeploymentID=%s", grpErr, deployment.ID) + scanErr = fmt.Errorf("%w: fetching groups for DeploymentID=%s", grpErr, deployment.ID) return true } @@ -121,28 +156,28 @@ func (k Querier) Deployments(c context.Context, req *types.QueryDeploymentsReque Groups: groups, EscrowAccount: account, }) - limit-- + req.Pagination.Limit-- + count++ return false }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - if acctErr != nil { - return nil, status.Error(codes.Internal, acctErr.Error()) + if scanErr != nil { + return nil, status.Error(codes.Internal, scanErr.Error()) } - } - resp := &types.QueryDeploymentsResponse{ - Deployments: deployments, - Pagination: &sdkquery.PageResponse{}, + total += count } - if countTotal { - resp.Pagination.Total = total - } - - return resp, nil + return &types.QueryDeploymentsResponse{ + Deployments: deployments, + Pagination: &sdkquery.PageResponse{ + Total: total, + NextKey: nextKey, + }, + }, nil } // Deployment returns deployment details based on DeploymentID diff --git a/x/deployment/keeper/grpc_query_test.go b/x/deployment/keeper/grpc_query_test.go index 15fd1b489..62abd8f8f 100644 --- a/x/deployment/keeper/grpc_query_test.go +++ b/x/deployment/keeper/grpc_query_test.go @@ -270,6 +270,93 @@ func TestGRPCQueryDeployments(t *testing.T) { page1.Deployments[0].Deployment.ID, "offset pagination must return different deployments") }) + + // Validate offset without state filter is rejected + t.Run("offset without state filter returns error", func(t *testing.T) { + _, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Pagination: &sdkquery.PageRequest{Offset: 1, Limit: 1}, + }) + require.Error(t, err) + }) + + // Validate NextKey is set when there are more results + t.Run("NextKey set when more results exist", func(t *testing.T) { + res, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Pagination: &sdkquery.PageRequest{Limit: 1}, + }) + require.NoError(t, err) + require.Len(t, res.Deployments, 1) + require.NotNil(t, res.Pagination.NextKey, "NextKey must be set when more results exist") + assert.Equal(t, uint64(1), res.Pagination.Total, "Total should equal count of returned items") + }) + + // Validate NextKey is nil when all results fit + t.Run("NextKey nil when all results returned", func(t *testing.T) { + res, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Pagination: &sdkquery.PageRequest{Limit: 100}, + }) + require.NoError(t, err) + require.Len(t, res.Deployments, 3) + require.Nil(t, res.Pagination.NextKey, "NextKey must be nil when all results returned") + assert.Equal(t, uint64(3), res.Pagination.Total) + }) + + // Validate key-based pagination returns correct next page + t.Run("key-based pagination returns next page", func(t *testing.T) { + // Get first page + page1, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Pagination: &sdkquery.PageRequest{Limit: 1}, + }) + require.NoError(t, err) + require.Len(t, page1.Deployments, 1) + require.NotNil(t, page1.Pagination.NextKey) + + // Get second page using NextKey + page2, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Pagination: &sdkquery.PageRequest{Key: page1.Pagination.NextKey, Limit: 1}, + }) + require.NoError(t, err) + require.Len(t, page2.Deployments, 1) + require.NotNil(t, page2.Pagination.NextKey, "second page should have NextKey (3 items total)") + + // Get third page using NextKey + page3, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Pagination: &sdkquery.PageRequest{Key: page2.Pagination.NextKey, Limit: 1}, + }) + require.NoError(t, err) + require.Len(t, page3.Deployments, 1) + require.Nil(t, page3.Pagination.NextKey, "last page should not have NextKey") + + // All three pages should have different deployments + ids := map[string]bool{ + page1.Deployments[0].Deployment.ID.String(): true, + page2.Deployments[0].Deployment.ID.String(): true, + page3.Deployments[0].Deployment.ID.String(): true, + } + assert.Len(t, ids, 3, "all pages should return distinct deployments") + }) + + // Validate key-based pagination with state filter + t.Run("key-based pagination with state filter", func(t *testing.T) { + page1, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Filters: v1beta4.DeploymentFilters{State: v1.DeploymentActive.String()}, + Pagination: &sdkquery.PageRequest{Limit: 1}, + }) + require.NoError(t, err) + require.Len(t, page1.Deployments, 1) + require.NotNil(t, page1.Pagination.NextKey, "should have next key for active deployments") + + page2, err := suite.queryClient.Deployments(suite.ctx, &v1beta4.QueryDeploymentsRequest{ + Pagination: &sdkquery.PageRequest{Key: page1.Pagination.NextKey, Limit: 10}, + }) + require.NoError(t, err) + require.Len(t, page2.Deployments, 1, "should return remaining active deployment") + require.Nil(t, page2.Pagination.NextKey, "no more active deployments") + + require.NotEqual(t, page1.Deployments[0].Deployment.ID, + page2.Deployments[0].Deployment.ID, + "pages must return different deployments") + }) } type deploymentFilterModifier struct { diff --git a/x/market/keeper/grpc_query.go b/x/market/keeper/grpc_query.go index b35fbea2d..790eb9775 100644 --- a/x/market/keeper/grpc_query.go +++ b/x/market/keeper/grpc_query.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -14,6 +15,7 @@ import ( "pkg.akt.dev/go/node/market/v1" types "pkg.akt.dev/go/node/market/v1beta5" + "pkg.akt.dev/node/util/query" "pkg.akt.dev/node/x/market/keeper/keys" ) @@ -32,49 +34,74 @@ func (k Querier) Orders(c context.Context, req *types.QueryOrdersRequest) (*type if req.Pagination == nil { req.Pagination = &sdkquery.PageRequest{} + } else if req.Pagination.Offset > 0 && req.Filters.State == "" { + return nil, status.Error(codes.InvalidArgument, "invalid request parameters. if offset is set, filter.state must be provided") } if req.Pagination.Limit == 0 { req.Pagination.Limit = sdkquery.DefaultLimit } - if len(req.Pagination.Key) > 0 { - return nil, status.Error(codes.InvalidArgument, "key-based pagination is not supported") - } - ctx := sdk.UnwrapSDKContext(c) - // Determine which states to iterate - states := []types.Order_State{types.OrderOpen, types.OrderActive, types.OrderClosed} - if req.Filters.State != "" { + states := make([]byte, 0, 3) + var resumePK *keys.OrderPrimaryKey + + // nolint: gocritic + if len(req.Pagination.Key) > 0 { + var pkBytes []byte + var err error + states, _, pkBytes, _, err = query.DecodePaginationKey(req.Pagination.Key) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + _, pk, err := k.orders.KeyCodec().Decode(pkBytes) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + resumePK = &pk + } else if req.Filters.State != "" { stateVal := types.Order_State(types.Order_State_value[req.Filters.State]) + if stateVal == types.OrderStateInvalid { return nil, status.Error(codes.InvalidArgument, "invalid state value") } - states = []types.Order_State{stateVal} + + states = append(states, byte(stateVal)) + } else { + states = append(states, byte(types.OrderOpen), byte(types.OrderActive), byte(types.OrderClosed)) } - if req.Pagination.Reverse { + if len(req.Pagination.Key) == 0 && req.Pagination.Reverse { for i, j := 0, len(states)-1; i < j; i, j = i+1, j-1 { states[i], states[j] = states[j], states[i] } } var orders types.Orders - limit := req.Pagination.Limit + var nextKey []byte + total := uint64(0) offset := req.Pagination.Offset - skipped := uint64(0) - countTotal := req.Pagination.CountTotal - var total uint64 + var scanErr error - for _, state := range states { - if limit == 0 && !countTotal { + for idx := range states { + if req.Pagination.Limit == 0 && len(nextKey) > 0 { break } + state := types.Order_State(states[idx]) + var iter indexes.MultiIterator[int32, keys.OrderPrimaryKey] var err error - if req.Pagination.Reverse { + + if idx == 0 && resumePK != nil { + r := collections.NewPrefixedPairRange[int32, keys.OrderPrimaryKey](int32(state)).StartInclusive(*resumePK) + if req.Pagination.Reverse { + r = collections.NewPrefixedPairRange[int32, keys.OrderPrimaryKey](int32(state)).EndInclusive(*resumePK).Descending() + } + iter, err = k.orders.Indexes.State.Iterate(ctx, r) + } else if req.Pagination.Reverse { iter, err = k.orders.Indexes.State.Iterate(ctx, collections.NewPrefixedPairRange[int32, keys.OrderPrimaryKey](int32(state)).Descending()) } else { @@ -84,44 +111,56 @@ func (k Querier) Orders(c context.Context, req *types.QueryOrdersRequest) (*type return nil, status.Error(codes.Internal, err.Error()) } + count := uint64(0) + err = indexes.ScanValues(ctx, k.orders, iter, func(order types.Order) bool { if !req.Filters.Accept(order, state) { return false } - if countTotal { - total++ - } - - if limit == 0 { - return !countTotal + if offset > 0 { + offset-- + return false } - if skipped < offset { - skipped++ - return false + if req.Pagination.Limit == 0 { + pk := keys.OrderIDToKey(order.ID) + pkBuf := make([]byte, k.orders.KeyCodec().Size(pk)) + if _, encErr := k.orders.KeyCodec().Encode(pkBuf, pk); encErr != nil { + scanErr = encErr + return true + } + var encErr error + nextKey, encErr = query.EncodePaginationKey(states[idx:], []byte{states[idx]}, pkBuf, nil) + if encErr != nil { + scanErr = encErr + } + return true } orders = append(orders, order) - limit-- + req.Pagination.Limit-- + count++ return false }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - } - - resp := &types.QueryOrdersResponse{ - Orders: orders, - Pagination: &sdkquery.PageResponse{}, - } + if scanErr != nil { + return nil, status.Error(codes.Internal, scanErr.Error()) + } - if countTotal { - resp.Pagination.Total = total + total += count } - return resp, nil + return &types.QueryOrdersResponse{ + Orders: orders, + Pagination: &sdkquery.PageResponse{ + Total: total, + NextKey: nextKey, + }, + }, nil } // Bids returns bids based on filters @@ -132,54 +171,103 @@ func (k Querier) Bids(c context.Context, req *types.QueryBidsRequest) (*types.Qu if req.Pagination == nil { req.Pagination = &sdkquery.PageRequest{} + } else if req.Pagination.Offset > 0 && req.Filters.State == "" { + return nil, status.Error(codes.InvalidArgument, "invalid request parameters. if offset is set, filter.state must be provided") } if req.Pagination.Limit == 0 { req.Pagination.Limit = sdkquery.DefaultLimit } + ctx := sdk.UnwrapSDKContext(c) + + reverseSearch := false + states := make([]byte, 0, 4) + var resumePK *keys.BidPrimaryKey + + // nolint: gocritic if len(req.Pagination.Key) > 0 { - return nil, status.Error(codes.InvalidArgument, "key-based pagination is not supported") - } + var pkBytes []byte + var unsolicited []byte + var err error + states, _, pkBytes, unsolicited, err = query.DecodePaginationKey(req.Pagination.Key) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } - ctx := sdk.UnwrapSDKContext(c) + if len(unsolicited) != 1 { + return nil, status.Error(codes.InvalidArgument, "invalid pagination key") + } + + if unsolicited[0] == 1 { + reverseSearch = true + } + + _, pk, err := k.bids.KeyCodec().Decode(pkBytes) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + resumePK = &pk + } else if req.Filters.State != "" { + reverseSearch = (req.Filters.Owner == "") && (req.Filters.Provider != "") - // Determine which states to iterate - states := []types.Bid_State{types.BidOpen, types.BidActive, types.BidLost, types.BidClosed} - if req.Filters.State != "" { stateVal := types.Bid_State(types.Bid_State_value[req.Filters.State]) + if stateVal == types.BidStateInvalid { return nil, status.Error(codes.InvalidArgument, "invalid state value") } - states = []types.Bid_State{stateVal} + + states = append(states, byte(stateVal)) + } else { + states = append(states, byte(types.BidOpen), byte(types.BidActive), byte(types.BidLost), byte(types.BidClosed)) } - if req.Pagination.Reverse { + if len(req.Pagination.Key) == 0 && req.Pagination.Reverse { for i, j := 0, len(states)-1; i < j; i, j = i+1, j-1 { states[i], states[j] = states[j], states[i] } } var bids []types.QueryBidResponse - limit := req.Pagination.Limit + var nextKey []byte + total := uint64(0) offset := req.Pagination.Offset - skipped := uint64(0) - countTotal := req.Pagination.CountTotal - var total uint64 - - // Use Provider index when filtering by provider without owner - providerSearch := req.Filters.Owner == "" && req.Filters.Provider != "" - var acctErr error + var scanErr error + + encodeBidNextKey := func(bid types.Bid, idx int) { + pk := keys.BidIDToKey(bid.ID) + pkBuf := make([]byte, k.bids.KeyCodec().Size(pk)) + if _, encErr := k.bids.KeyCodec().Encode(pkBuf, pk); encErr != nil { + scanErr = encErr + return + } + unsolicited := []byte{0} + if reverseSearch { + unsolicited[0] = 1 + } + var encErr error + nextKey, encErr = query.EncodePaginationKey(states[idx:], []byte{states[idx]}, pkBuf, unsolicited) + if encErr != nil { + scanErr = encErr + } + } - if providerSearch { + if reverseSearch { stateSet := make(map[types.Bid_State]bool) for _, s := range states { - stateSet[s] = true + stateSet[types.Bid_State(s)] = true } var iter indexes.MultiIterator[string, keys.BidPrimaryKey] var err error - if req.Pagination.Reverse { + + if resumePK != nil { + r := collections.NewPrefixedPairRange[string, keys.BidPrimaryKey](req.Filters.Provider).StartInclusive(*resumePK) + if req.Pagination.Reverse { + r = collections.NewPrefixedPairRange[string, keys.BidPrimaryKey](req.Filters.Provider).EndInclusive(*resumePK).Descending() + } + iter, err = k.bids.Indexes.Provider.Iterate(ctx, r) + } else if req.Pagination.Reverse { iter, err = k.bids.Indexes.Provider.Iterate(ctx, collections.NewPrefixedPairRange[string, keys.BidPrimaryKey](req.Filters.Provider).Descending()) } else { @@ -198,22 +286,19 @@ func (k Querier) Bids(c context.Context, req *types.QueryBidsRequest) (*types.Qu return false } - if countTotal { - total++ - } - - if limit == 0 { - return !countTotal + if offset > 0 { + offset-- + return false } - if skipped < offset { - skipped++ - return false + if req.Pagination.Limit == 0 { + encodeBidNextKey(bid, 0) + return true } - acct, acctE := k.ekeeper.GetAccount(ctx, bid.ID.ToEscrowAccountID()) - if acctE != nil { - acctErr = acctE + acct, acctErr := k.ekeeper.GetAccount(ctx, bid.ID.ToEscrowAccountID()) + if acctErr != nil { + scanErr = acctErr return true } @@ -221,25 +306,35 @@ func (k Querier) Bids(c context.Context, req *types.QueryBidsRequest) (*types.Qu Bid: bid, EscrowAccount: acct, }) - limit-- + req.Pagination.Limit-- + total++ return false }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - if acctErr != nil { - return nil, status.Error(codes.Internal, acctErr.Error()) + if scanErr != nil { + return nil, status.Error(codes.Internal, scanErr.Error()) } } else { - for _, state := range states { - if limit == 0 && !countTotal { + for idx := range states { + if req.Pagination.Limit == 0 && len(nextKey) > 0 { break } + state := types.Bid_State(states[idx]) + var iter indexes.MultiIterator[int32, keys.BidPrimaryKey] var err error - if req.Pagination.Reverse { + + if idx == 0 && resumePK != nil { + r := collections.NewPrefixedPairRange[int32, keys.BidPrimaryKey](int32(state)).StartInclusive(*resumePK) + if req.Pagination.Reverse { + r = collections.NewPrefixedPairRange[int32, keys.BidPrimaryKey](int32(state)).EndInclusive(*resumePK).Descending() + } + iter, err = k.bids.Indexes.State.Iterate(ctx, r) + } else if req.Pagination.Reverse { iter, err = k.bids.Indexes.State.Iterate(ctx, collections.NewPrefixedPairRange[int32, keys.BidPrimaryKey](int32(state)).Descending()) } else { @@ -249,27 +344,26 @@ func (k Querier) Bids(c context.Context, req *types.QueryBidsRequest) (*types.Qu return nil, status.Error(codes.Internal, err.Error()) } + count := uint64(0) + err = indexes.ScanValues(ctx, k.bids, iter, func(bid types.Bid) bool { if !req.Filters.Accept(bid, state) { return false } - if countTotal { - total++ - } - - if limit == 0 { - return !countTotal + if offset > 0 { + offset-- + return false } - if skipped < offset { - skipped++ - return false + if req.Pagination.Limit == 0 { + encodeBidNextKey(bid, idx) + return true } - acct, acctE := k.ekeeper.GetAccount(ctx, bid.ID.ToEscrowAccountID()) - if acctE != nil { - acctErr = acctE + acct, acctErr := k.ekeeper.GetAccount(ctx, bid.ID.ToEscrowAccountID()) + if acctErr != nil { + scanErr = fmt.Errorf("%w: fetching escrow account for BidID=%s", acctErr, bid.ID) return true } @@ -277,29 +371,29 @@ func (k Querier) Bids(c context.Context, req *types.QueryBidsRequest) (*types.Qu Bid: bid, EscrowAccount: acct, }) - limit-- + req.Pagination.Limit-- + count++ return false }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - if acctErr != nil { - return nil, status.Error(codes.Internal, acctErr.Error()) + if scanErr != nil { + return nil, status.Error(codes.Internal, scanErr.Error()) } - } - } - - resp := &types.QueryBidsResponse{ - Bids: bids, - Pagination: &sdkquery.PageResponse{}, - } - if countTotal { - resp.Pagination.Total = total + total += count + } } - return resp, nil + return &types.QueryBidsResponse{ + Bids: bids, + Pagination: &sdkquery.PageResponse{ + Total: total, + NextKey: nextKey, + }, + }, nil } // Leases returns leases based on filters @@ -310,54 +404,103 @@ func (k Querier) Leases(c context.Context, req *types.QueryLeasesRequest) (*type if req.Pagination == nil { req.Pagination = &sdkquery.PageRequest{} + } else if req.Pagination.Offset > 0 && req.Filters.State == "" { + return nil, status.Error(codes.InvalidArgument, "invalid request parameters. if offset is set, filter.state must be provided") } if req.Pagination.Limit == 0 { req.Pagination.Limit = sdkquery.DefaultLimit } + ctx := sdk.UnwrapSDKContext(c) + + reverseSearch := false + states := make([]byte, 0, 3) + var resumePK *keys.LeasePrimaryKey + + // nolint: gocritic if len(req.Pagination.Key) > 0 { - return nil, status.Error(codes.InvalidArgument, "key-based pagination is not supported") - } + var pkBytes []byte + var unsolicited []byte + var err error + states, _, pkBytes, unsolicited, err = query.DecodePaginationKey(req.Pagination.Key) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } - ctx := sdk.UnwrapSDKContext(c) + if len(unsolicited) != 1 { + return nil, status.Error(codes.InvalidArgument, "invalid pagination key") + } + + if unsolicited[0] == 1 { + reverseSearch = true + } + + _, pk, err := k.leases.KeyCodec().Decode(pkBytes) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + resumePK = &pk + } else if req.Filters.State != "" { + reverseSearch = (req.Filters.Owner == "") && (req.Filters.Provider != "") - // Determine which states to iterate - states := []v1.Lease_State{v1.LeaseActive, v1.LeaseInsufficientFunds, v1.LeaseClosed} - if req.Filters.State != "" { stateVal := v1.Lease_State(v1.Lease_State_value[req.Filters.State]) + if stateVal == v1.LeaseStateInvalid { return nil, status.Error(codes.InvalidArgument, "invalid state value") } - states = []v1.Lease_State{stateVal} + + states = append(states, byte(stateVal)) + } else { + states = append(states, byte(v1.LeaseActive), byte(v1.LeaseInsufficientFunds), byte(v1.LeaseClosed)) } - if req.Pagination.Reverse { + if len(req.Pagination.Key) == 0 && req.Pagination.Reverse { for i, j := 0, len(states)-1; i < j; i, j = i+1, j-1 { states[i], states[j] = states[j], states[i] } } var leases []types.QueryLeaseResponse - limit := req.Pagination.Limit + var nextKey []byte + total := uint64(0) offset := req.Pagination.Offset - skipped := uint64(0) - countTotal := req.Pagination.CountTotal - var total uint64 - - // Use Provider index when filtering by provider without owner - providerSearch := req.Filters.Owner == "" && req.Filters.Provider != "" - var pmntErr error + var scanErr error + + encodeLeaseNextKey := func(lease v1.Lease, idx int) { + pk := keys.LeaseIDToKey(lease.ID) + pkBuf := make([]byte, k.leases.KeyCodec().Size(pk)) + if _, encErr := k.leases.KeyCodec().Encode(pkBuf, pk); encErr != nil { + scanErr = encErr + return + } + unsolicited := []byte{0} + if reverseSearch { + unsolicited[0] = 1 + } + var encErr error + nextKey, encErr = query.EncodePaginationKey(states[idx:], []byte{states[idx]}, pkBuf, unsolicited) + if encErr != nil { + scanErr = encErr + } + } - if providerSearch { + if reverseSearch { stateSet := make(map[v1.Lease_State]bool) for _, s := range states { - stateSet[s] = true + stateSet[v1.Lease_State(s)] = true } var iter indexes.MultiIterator[string, keys.LeasePrimaryKey] var err error - if req.Pagination.Reverse { + + if resumePK != nil { + r := collections.NewPrefixedPairRange[string, keys.LeasePrimaryKey](req.Filters.Provider).StartInclusive(*resumePK) + if req.Pagination.Reverse { + r = collections.NewPrefixedPairRange[string, keys.LeasePrimaryKey](req.Filters.Provider).EndInclusive(*resumePK).Descending() + } + iter, err = k.leases.Indexes.Provider.Iterate(ctx, r) + } else if req.Pagination.Reverse { iter, err = k.leases.Indexes.Provider.Iterate(ctx, collections.NewPrefixedPairRange[string, keys.LeasePrimaryKey](req.Filters.Provider).Descending()) } else { @@ -376,22 +519,19 @@ func (k Querier) Leases(c context.Context, req *types.QueryLeasesRequest) (*type return false } - if countTotal { - total++ - } - - if limit == 0 { - return !countTotal + if offset > 0 { + offset-- + return false } - if skipped < offset { - skipped++ - return false + if req.Pagination.Limit == 0 { + encodeLeaseNextKey(lease, 0) + return true } - payment, pmntE := k.ekeeper.GetPayment(ctx, lease.ID.ToEscrowPaymentID()) - if pmntE != nil { - pmntErr = pmntE + payment, pmntErr := k.ekeeper.GetPayment(ctx, lease.ID.ToEscrowPaymentID()) + if pmntErr != nil { + scanErr = pmntErr return true } @@ -399,25 +539,35 @@ func (k Querier) Leases(c context.Context, req *types.QueryLeasesRequest) (*type Lease: lease, EscrowPayment: payment, }) - limit-- + req.Pagination.Limit-- + total++ return false }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - if pmntErr != nil { - return nil, status.Error(codes.Internal, pmntErr.Error()) + if scanErr != nil { + return nil, status.Error(codes.Internal, scanErr.Error()) } } else { - for _, state := range states { - if limit == 0 && !countTotal { + for idx := range states { + if req.Pagination.Limit == 0 && len(nextKey) > 0 { break } + state := v1.Lease_State(states[idx]) + var iter indexes.MultiIterator[int32, keys.LeasePrimaryKey] var err error - if req.Pagination.Reverse { + + if idx == 0 && resumePK != nil { + r := collections.NewPrefixedPairRange[int32, keys.LeasePrimaryKey](int32(state)).StartInclusive(*resumePK) + if req.Pagination.Reverse { + r = collections.NewPrefixedPairRange[int32, keys.LeasePrimaryKey](int32(state)).EndInclusive(*resumePK).Descending() + } + iter, err = k.leases.Indexes.State.Iterate(ctx, r) + } else if req.Pagination.Reverse { iter, err = k.leases.Indexes.State.Iterate(ctx, collections.NewPrefixedPairRange[int32, keys.LeasePrimaryKey](int32(state)).Descending()) } else { @@ -427,27 +577,26 @@ func (k Querier) Leases(c context.Context, req *types.QueryLeasesRequest) (*type return nil, status.Error(codes.Internal, err.Error()) } + count := uint64(0) + err = indexes.ScanValues(ctx, k.leases, iter, func(lease v1.Lease) bool { if !req.Filters.Accept(lease, state) { return false } - if countTotal { - total++ - } - - if limit == 0 { - return !countTotal + if offset > 0 { + offset-- + return false } - if skipped < offset { - skipped++ - return false + if req.Pagination.Limit == 0 { + encodeLeaseNextKey(lease, idx) + return true } - payment, pmntE := k.ekeeper.GetPayment(ctx, lease.ID.ToEscrowPaymentID()) - if pmntE != nil { - pmntErr = pmntE + payment, pmntErr := k.ekeeper.GetPayment(ctx, lease.ID.ToEscrowPaymentID()) + if pmntErr != nil { + scanErr = pmntErr return true } @@ -455,29 +604,29 @@ func (k Querier) Leases(c context.Context, req *types.QueryLeasesRequest) (*type Lease: lease, EscrowPayment: payment, }) - limit-- + req.Pagination.Limit-- + count++ return false }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - if pmntErr != nil { - return nil, status.Error(codes.Internal, pmntErr.Error()) + if scanErr != nil { + return nil, status.Error(codes.Internal, scanErr.Error()) } - } - } - resp := &types.QueryLeasesResponse{ - Leases: leases, - Pagination: &sdkquery.PageResponse{}, - } - - if countTotal { - resp.Pagination.Total = total + total += count + } } - return resp, nil + return &types.QueryLeasesResponse{ + Leases: leases, + Pagination: &sdkquery.PageResponse{ + Total: total, + NextKey: nextKey, + }, + }, nil } // Order returns order details based on OrderID