这是indexloc提供的服务,不要输入任何密码
Skip to content

fix: query all proposals #642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ This v0.50.x branch was created at the [eb1a8e88a4ddf77bc2fe235fc07c57016b7386f0
* [#640](https://github.com/osmosis-labs/cosmos-sdk/pull/640) fix: bump IAVL to avoid pruning issues
* [#641](https://github.com/osmosis-labs/cosmos-sdk/pull/641) fix: bump IAVL to skip broken legacy state

* Gov proposals fix
* [#642](https://github.com/osmosis-labs/cosmos-sdk/pull/642) fix: gov proposal fix for missing codec value

* Supply offset fix
* [#629](https://github.com/osmosis-labs/cosmos-sdk/pull/629) fix: add old supply offset functions

Expand Down
333 changes: 333 additions & 0 deletions x/gov/keeper/collections_pagination_gov.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
package keeper

import (
"context"
errorsGo "errors"
"fmt"
"strings"

"cosmossdk.io/collections"
storetypes "cosmossdk.io/store/types"

"github.com/cosmos/cosmos-sdk/types/query"
)

// CollectionFilteredPaginate works in the same way as CollectionPaginate but allows to filter
// results using a predicateFunc.
// A nil predicateFunc means no filtering is applied and results are collected as is.
// TransformFunc is applied only to results which are in range of the pagination and allow
// to convert the result to a different type.
// NOTE: do not collect results using the values/keys passed to predicateFunc as they are not
// guaranteed to be in the pagination range requested.
func CollectionFilteredPaginate[K, V any, C query.Collection[K, V], T any](
ctx context.Context,
coll C,
pageReq *query.PageRequest,
predicateFunc func(key K, value V) (include bool, err error),
transformFunc func(key K, value V) (T, error),
opts ...func(opt *query.CollectionsPaginateOptions[K]),
) (results []T, pageRes *query.PageResponse, err error) {
pageReq = initPageRequestDefaults(pageReq)

offset := pageReq.Offset
key := pageReq.Key
limit := pageReq.Limit
countTotal := pageReq.CountTotal
reverse := pageReq.Reverse

if offset > 0 && key != nil {
return nil, nil, fmt.Errorf("invalid request, either offset or key is expected, got both")
}

opt := new(query.CollectionsPaginateOptions[K])
for _, o := range opts {
o(opt)
}

var prefix []byte
if opt.Prefix != nil {
prefix, err = encodeCollKey[K, V](coll, *opt.Prefix)
if err != nil {
return nil, nil, err
}
}

if len(key) != 0 {
results, pageRes, err = collFilteredPaginateByKey(ctx, coll, prefix, key, reverse, limit, predicateFunc, transformFunc)
} else {
results, pageRes, err = collFilteredPaginateNoKey(ctx, coll, prefix, reverse, offset, limit, countTotal, predicateFunc, transformFunc)
}
// invalid iter error is ignored to retain Paginate behavior
if errorsGo.Is(err, collections.ErrInvalidIterator) {
return results, new(query.PageResponse), nil
}
// strip the prefix from next key
if len(pageRes.NextKey) != 0 && prefix != nil {
pageRes.NextKey = pageRes.NextKey[len(prefix):]
}
return results, pageRes, err
}

// initPageRequestDefaults initializes a PageRequest's defaults when those are not set.
func initPageRequestDefaults(pageRequest *query.PageRequest) *query.PageRequest {
// if the PageRequest is nil, use default PageRequest
if pageRequest == nil {
pageRequest = &query.PageRequest{}
}

pageRequestCopy := *pageRequest
if len(pageRequestCopy.Key) == 0 {
pageRequestCopy.Key = nil
}

if pageRequestCopy.Limit == 0 {
pageRequestCopy.Limit = query.DefaultLimit

// count total results when the limit is zero/not supplied
pageRequestCopy.CountTotal = true
}

return &pageRequestCopy
}

// todo maybe move to collections?
func encodeCollKey[K, V any, C query.Collection[K, V]](coll C, key K) ([]byte, error) {
buffer := make([]byte, coll.KeyCodec().Size(key))
_, err := coll.KeyCodec().Encode(buffer, key)
return buffer, err
}

func getCollIter[K, V any, C query.Collection[K, V]](ctx context.Context, coll C, prefix, start []byte, reverse bool) (collections.Iterator[K, V], error) {
// TODO: maybe can be simplified
if reverse {
// if we are in reverse mode, we need to increase the start key
// to include the start key in the iteration.
start = storetypes.PrefixEndBytes(append(prefix, start...))
end := prefix

return coll.IterateRaw(ctx, end, start, collections.OrderDescending)
}
var end []byte
if prefix != nil {
start = append(prefix, start...)
end = storetypes.PrefixEndBytes(prefix)
}
return coll.IterateRaw(ctx, start, end, collections.OrderAscending)
}

func advanceIter[I interface {
Next()
Valid() bool
}](iter I, offset uint64,
) bool {
for i := uint64(0); i < offset; i++ {
if !iter.Valid() {
return false
}
iter.Next()
}
return true
}

// collFilteredPaginateNoKey applies the provided pagination on the collection when the starting key is not set.
// If predicateFunc is nil no filtering is applied.
func collFilteredPaginateNoKey[K, V any, C query.Collection[K, V], T any](
ctx context.Context,
coll C,
prefix []byte,
reverse bool,
offset uint64,
limit uint64,
countTotal bool,
predicateFunc func(K, V) (bool, error),
transformFunc func(K, V) (T, error),
) ([]T, *query.PageResponse, error) {
iterator, err := getCollIter[K, V](ctx, coll, prefix, nil, reverse)
if err != nil {
return nil, nil, err
}
defer iterator.Close()
// we advance the iter equal to the provided offset
if !advanceIter(iterator, offset) {
return nil, nil, collections.ErrInvalidIterator
}

var (
count uint64
nextKey []byte
results []T
)

for ; iterator.Valid(); iterator.Next() {
switch {
// first case, we still haven't found all the results up to the limit
case count < limit:
kv, err := iterator.KeyValue()
if err != nil {
if strings.Contains(err.Error(), "no concrete type registered for type URL") {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the fix

// URL /osmosis.concentratedliquidity.v1beta1.CreateConcentratedLiquidityPoolsProposal
continue
}
return nil, nil, err
}
// if no predicate function is specified then we just include the result
if predicateFunc == nil {
transformed, err := transformFunc(kv.Key, kv.Value)
if err != nil {
return nil, nil, err
}
results = append(results, transformed)
count++

// if predicate function is defined we check if the result matches the filtering criteria
} else {
include, err := predicateFunc(kv.Key, kv.Value)
if err != nil {
return nil, nil, err
}
if include {
transformed, err := transformFunc(kv.Key, kv.Value)
if err != nil {
return nil, nil, err
}
results = append(results, transformed)
count++
}
}
// second case, we found all the objects specified within the limit
case count == limit:
key, err := iterator.Key()
if err != nil {
if strings.Contains(err.Error(), "no concrete type registered for type URL") {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added here as well just incase

// URL /osmosis.concentratedliquidity.v1beta1.CreateConcentratedLiquidityPoolsProposal
continue
}
return nil, nil, err
}
nextKey, err = encodeCollKey[K, V](coll, key)
if err != nil {
return nil, nil, err
}
// if count total was not specified, we return the next key only
if !countTotal {
return results, &query.PageResponse{
NextKey: nextKey,
}, nil
}
// otherwise we fallthrough the third case
fallthrough
// this is the case in which we found all the required results
// but we need to count how many possible results exist in total.
// so we keep increasing the count until the iterator is fully consumed.
case count > limit:
if predicateFunc == nil {
count++

// if predicate function is defined we check if the result matches the filtering criteria
} else {
kv, err := iterator.KeyValue()
if err != nil {
if strings.Contains(err.Error(), "no concrete type registered for type URL") {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added here as well just incase

// URL /osmosis.concentratedliquidity.v1beta1.CreateConcentratedLiquidityPoolsProposal
continue
}
return nil, nil, err
}

include, err := predicateFunc(kv.Key, kv.Value)
if err != nil {
return nil, nil, err
}
if include {
count++
}
}
}
}

resp := &query.PageResponse{
NextKey: nextKey,
}

if countTotal {
resp.Total = count + offset
}
return results, resp, nil
}

// collFilteredPaginateByKey paginates a collection when a starting key
// is provided in the PageRequest. Predicate is applied only if not nil.
func collFilteredPaginateByKey[K, V any, C query.Collection[K, V], T any](
ctx context.Context,
coll C,
prefix []byte,
key []byte,
reverse bool,
limit uint64,
predicateFunc func(key K, value V) (bool, error),
transformFunc func(key K, value V) (transformed T, err error),
) (results []T, pageRes *query.PageResponse, err error) {
iterator, err := getCollIter[K, V](ctx, coll, prefix, key, reverse)
if err != nil {
return nil, nil, err
}
defer iterator.Close()

var (
count uint64
nextKey []byte
)

for ; iterator.Valid(); iterator.Next() {
// if we reached the specified limit
// then we get the next key, and we exit the iteration.
if count == limit {
concreteKey, err := iterator.Key()
if err != nil {
if strings.Contains(err.Error(), "no concrete type registered for type URL") {
// URL /osmosis.concentratedliquidity.v1beta1.CreateConcentratedLiquidityPoolsProposal
continue
}
return nil, nil, err
}

nextKey, err = encodeCollKey[K, V](coll, concreteKey)
if err != nil {
return nil, nil, err
}
break
}

kv, err := iterator.KeyValue()
if err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we missing the fix here as well? This is called when the key param is provided for pagination afaik

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good shout added

return nil, nil, err
}
// if no predicate is specified then we just append the result
if predicateFunc == nil {
transformed, err := transformFunc(kv.Key, kv.Value)
if err != nil {
return nil, nil, err
}
results = append(results, transformed)
// if predicate is applied we execute the predicate function
// and append only if predicateFunc yields true.
} else {
include, err := predicateFunc(kv.Key, kv.Value)
if err != nil {
return nil, nil, err
}
if include {
transformed, err := transformFunc(kv.Key, kv.Value)
if err != nil {
return nil, nil, err
}
results = append(results, transformed)
}
}
count++
}

return results, &query.PageResponse{
NextKey: nextKey,
}, nil
}
2 changes: 1 addition & 1 deletion x/gov/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (q queryServer) Proposal(ctx context.Context, req *v1.QueryProposalRequest)

// Proposals implements the Query/Proposals gRPC method
func (q queryServer) Proposals(ctx context.Context, req *v1.QueryProposalsRequest) (*v1.QueryProposalsResponse, error) {
filteredProposals, pageRes, err := query.CollectionFilteredPaginate(ctx, q.k.Proposals, req.Pagination, func(key uint64, p v1.Proposal) (include bool, err error) {
filteredProposals, pageRes, err := CollectionFilteredPaginate(ctx, q.k.Proposals, req.Pagination, func(key uint64, p v1.Proposal) (include bool, err error) {
matchVoter, matchDepositor, matchStatus := true, true, true

// match status (if supplied/valid)
Expand Down
Loading