From 5eaac52a7852fb8c9835065b57cb2c6f3d4e3f6e Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Wed, 10 Mar 2021 18:04:54 +0900 Subject: [PATCH 01/12] feat: add legacy_querier --- x/wasm/internal/keeper/legacy_querier.go | 59 ++++++++++++++++++++++++ x/wasm/internal/keeper/querier.go | 15 ------ 2 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 x/wasm/internal/keeper/legacy_querier.go diff --git a/x/wasm/internal/keeper/legacy_querier.go b/x/wasm/internal/keeper/legacy_querier.go new file mode 100644 index 0000000000..51e9be4184 --- /dev/null +++ b/x/wasm/internal/keeper/legacy_querier.go @@ -0,0 +1,59 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/line/lbm-sdk/x/wasm/internal/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +const ( + QueryListContractByCode = "list-contracts-by-code" + QueryGetContract = "contract-info" + QueryGetContractState = "contract-state" + QueryGetCode = "code" + QueryListCode = "list-code" + QueryContractHistory = "contract-history" +) + +const ( + QueryMethodContractStateSmart = "smart" + QueryMethodContractStateAll = "all" + QueryMethodContractStateRaw = "raw" +) + +// NewLegacyQuerier creates a new querier +func NewLegacyQuerier(keeper Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { + switch path[0] { + case QueryContractHistory: + return queryContractHistory(ctx, path[1], keeper) + default: + return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") + } + } +} + +func queryContractHistory(ctx sdk.Context, bech string, keeper Keeper) ([]byte, error) { + contractAddr, err := sdk.AccAddressFromBech32(bech) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) + } + entries := keeper.GetContractHistory(ctx, contractAddr) + if entries == nil { + // nil, nil leads to 404 in rest handler + return nil, nil + } + + histories := make([]types.ContractHistoryResponse, len(entries)) + for i, entry := range entries { + histories[i] = types.NewContractHistoryResponse(entry) + } + + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, histories) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return bz, nil +} diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index e980044cec..18d739a49e 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -12,21 +12,6 @@ import ( "github.com/line/lbm-sdk/x/wasm/internal/types" ) -const ( - QueryListContractByCode = "list-contracts-by-code" - QueryGetContract = "contract-info" - QueryGetContractState = "contract-state" - QueryGetCode = "code" - QueryListCode = "list-code" - QueryContractHistory = "contract-history" -) - -const ( - QueryMethodContractStateSmart = "smart" - QueryMethodContractStateAll = "all" - QueryMethodContractStateRaw = "raw" -) - // NewQuerier creates a new querier func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { From 0421c56d0981104165a1b9fac4320b440889a9f3 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Wed, 3 Mar 2021 07:05:23 +0900 Subject: [PATCH 02/12] feat: add pagination query for contract history --- x/wasm/client/cli/query.go | 38 ++++++- x/wasm/client/utils/utils.go | 82 ++++++++++++++ x/wasm/internal/keeper/keeper.go | 134 +++++++++++++++++++++-- x/wasm/internal/keeper/legacy_querier.go | 6 +- x/wasm/internal/keeper/querier.go | 48 ++++++-- x/wasm/internal/types/keys.go | 29 ++++- x/wasm/internal/types/querier.go | 22 ++++ 7 files changed, 328 insertions(+), 31 deletions(-) diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index 0716421b99..579d0b3007 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -19,6 +19,7 @@ import ( "github.com/line/lbm-sdk/codec" sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/wasm/client/utils" "github.com/line/lbm-sdk/x/wasm/internal/keeper" "github.com/line/lbm-sdk/x/wasm/internal/types" ) @@ -269,7 +270,7 @@ func GetCmdGetContractStateSmart(cdc *codec.Codec) *cobra.Command { // GetCmdGetContractHistory prints the code history for a given contract func GetCmdGetContractHistory(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "contract-history [bech32_address]", Short: "Prints out the code history for a contract given its address", Long: "Prints out the code history for a contract given its address", @@ -282,8 +283,21 @@ func GetCmdGetContractHistory(cdc *codec.Codec) *cobra.Command { return err } + pageReq, err := utils.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) + if err != nil { + return err + } + data := &types.QueryContractHistoryRequest{ + Address: addr, + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + return err + } + route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryContractHistory, addr.String()) - res, _, err := cliCtx.Query(route) + res, _, err := cliCtx.QueryWithData(route, bs) if err != nil { return err } @@ -291,6 +305,9 @@ func GetCmdGetContractHistory(cdc *codec.Codec) *cobra.Command { return nil }, } + + utils.AddPaginationFlagsToCmd(cmd, "contract history") + return cmd } type argumentDecoder struct { @@ -335,3 +352,20 @@ func (a *argumentDecoder) DecodeString(s string) ([]byte, error) { func asciiDecodeString(s string) ([]byte, error) { return []byte(s), nil } + +// sdk ReadPageRequest expects binary but we encoded to base64 in our marshaller +func withPageKeyDecoded(flagSet *flag.FlagSet) *flag.FlagSet { + encoded, err := flagSet.GetString(utils.FlagPageKey) + if err != nil { + panic(err.Error()) + } + raw, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + panic(err.Error()) + } + err = flagSet.Set(utils.FlagPageKey, string(raw)) + if err != nil { + panic(err.Error()) + } + return flagSet +} diff --git a/x/wasm/client/utils/utils.go b/x/wasm/client/utils/utils.go index bbe9adc7f9..c8bf46b00f 100644 --- a/x/wasm/client/utils/utils.go +++ b/x/wasm/client/utils/utils.go @@ -3,6 +3,14 @@ package utils import ( "bytes" "compress/gzip" + "encoding/binary" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + sdkerrors "github.com/line/lbm-sdk/types/errors" + "github.com/line/lbm-sdk/x/wasm/internal/types" ) var ( @@ -10,6 +18,15 @@ var ( wasmIdent = []byte("\x00\x61\x73\x6D") ) +// List of CLI flags +const ( + FlagPage = "page" + FlagLimit = "limit" + FlagPageKey = "page-key" + FlagOffset = "offset" + FlagCountTotal = "count-total" +) + // IsGzip returns checks if the file contents are gzip compressed func IsGzip(input []byte) bool { return bytes.Equal(input[:3], gzipIdent) @@ -36,3 +53,68 @@ func GzipIt(input []byte) ([]byte, error) { return b.Bytes(), nil } + +// AddPaginationFlagsToCmd adds common pagination flags to cmd +func AddPaginationFlagsToCmd(cmd *cobra.Command, query string) { + cmd.Flags().Uint64(FlagPage, 1, fmt.Sprintf("pagination page of %s to query for. This sets offset to a multiple of limit", query)) + cmd.Flags().String(FlagPageKey, "", fmt.Sprintf("pagination page-key of %s to query for", query)) + cmd.Flags().Uint64(FlagOffset, 0, fmt.Sprintf("pagination offset of %s to query for", query)) + cmd.Flags().Uint64(FlagLimit, 100, fmt.Sprintf("pagination limit of %s to query for", query)) + cmd.Flags().Bool( + FlagCountTotal, false, fmt.Sprintf("count total number of records in %s to query for", query)) +} + +// BigEndianToUint64 returns an uint64 from big endian encoded bytes. If encoding +// is empty, zero is returned. +// This function is included in cosmos-sdk v0.40.0 +// Once cosmos-sdk is updated, use the sdk functions. +func BigEndianToUint64(bz []byte) uint64 { + if len(bz) == 0 { + return 0 + } + + return binary.BigEndian.Uint64(bz) +} + +// ReadPageRequest reads and builds the necessary page request flags for pagination. +func ReadPageRequest(flagSet *pflag.FlagSet) (*types.PageRequest, error) { + pageKey, err := flagSet.GetString(FlagPageKey) + if err != nil { + return nil, err + } + offset, err := flagSet.GetUint64(FlagOffset) + if err != nil { + return nil, err + } + limit, err := flagSet.GetUint64(FlagLimit) + if err != nil { + return nil, err + } + countTotal, err := flagSet.GetBool(FlagCountTotal) + if err != nil { + return nil, err + } + page, err := flagSet.GetUint64(FlagPage) + if err != nil { + return nil, err + } + + return NewPageRequest(pageKey, offset, limit, page, countTotal) +} + +func NewPageRequest(pageKey string, offset, limit, page uint64, countTotal bool) (*types.PageRequest, error) { + if page > 1 && offset > 0 { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "page and offset cannot be used together") + } + + if page > 1 { + offset = (page - 1) * limit + } + + return &types.PageRequest{ + Key: []byte(pageKey), + Offset: offset, + Limit: limit, + CountTotal: countTotal, + }, nil +} diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 5e2d04c1a1..86d986b4e3 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -3,6 +3,7 @@ package keeper import ( "bytes" "encoding/binary" + "fmt" "path/filepath" wasm "github.com/CosmWasm/wasmvm" @@ -19,6 +20,7 @@ import ( "github.com/pkg/errors" "github.com/tendermint/tendermint/crypto" + "github.com/line/lbm-sdk/x/wasm/client/utils" "github.com/line/lbm-sdk/x/wasm/internal/types" ) @@ -42,6 +44,8 @@ const InstanceCost uint64 = 40_000 // CompileCost is how much SDK gas we charge *per byte* for compiling WASM code. const CompileCost uint64 = 2 +const DefaultLimit = 100 + // Keeper will have a reference to Wasmer with it's own data directory. type Keeper struct { storeKey sdk.StoreKey @@ -389,19 +393,31 @@ func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAd } func (k Keeper) appendToContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress, newEntries ...types.ContractCodeHistoryEntry) { - entries := append(k.GetContractHistory(ctx, contractAddr), newEntries...) - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.ContractHistoryStorePrefix) - prefixStore.Set(contractAddr, k.cdc.MustMarshalBinaryBare(&entries)) + store := ctx.KVStore(k.storeKey) + // find last element position + var pos uint64 + prefixStore := prefix.NewStore(store, types.GetContractCodeHistoryElementPrefix(contractAddr)) + if iter := prefixStore.ReverseIterator(nil, nil); iter.Valid() { + pos = utils.BigEndianToUint64(iter.Value()) + } + // then store with incrementing position + for i := range newEntries { + pos++ + key := types.GetContractCodeHistoryElementKey(contractAddr, pos) + store.Set(key, k.cdc.MustMarshalBinaryBare(&newEntries[i])) + } } func (k Keeper) GetContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress) []types.ContractCodeHistoryEntry { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.ContractHistoryStorePrefix) - var entries []types.ContractCodeHistoryEntry - bz := prefixStore.Get(contractAddr) - if bz != nil { - k.cdc.MustUnmarshalBinaryBare(bz, &entries) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr)) + r := make([]types.ContractCodeHistoryEntry, 0) + iter := prefixStore.Iterator(nil, nil) + for ; iter.Valid(); iter.Next() { + var e types.ContractCodeHistoryEntry + k.cdc.MustUnmarshalBinaryBare(iter.Value(), &e) + r = append(r, e) } - return entries + return r } // QuerySmart queries the smart contract itself. @@ -676,3 +692,103 @@ func gasMeter(ctx sdk.Context) MultipiedGasMeter { originalMeter: ctx.GasMeter(), } } + +// NOTE: This function is implemented in cosmos-sdk v0.40.0. +// If you want to update cosmos-sdk to 0.40.0 or later, you can use the sdk function. +func FilteredPaginate( + prefixStore storeTypes.KVStore, + pageRequest *types.PageRequest, + onResult func(key []byte, value []byte, accumulate bool) (bool, error), +) (*types.PageResponse, error) { + // if the PageRequest is nil, use default PageRequest + if pageRequest == nil { + pageRequest = &types.PageRequest{} + } + + offset := pageRequest.Offset + key := pageRequest.Key + limit := pageRequest.Limit + countTotal := pageRequest.CountTotal + + if offset > 0 && key != nil { + return nil, fmt.Errorf("invalid request, either offset or key is expected, got both") + } + + if limit == 0 { + limit = DefaultLimit + + // count total results when the limit is zero/not supplied + countTotal = true + } + + if len(key) != 0 { + iterator := prefixStore.Iterator(key, nil) + defer iterator.Close() + + var numHits uint64 + var nextKey []byte + + for ; iterator.Valid(); iterator.Next() { + if numHits == limit { + nextKey = iterator.Key() + break + } + + if iterator.Error() != nil { + return nil, iterator.Error() + } + + hit, err := onResult(iterator.Key(), iterator.Value(), true) + if err != nil { + return nil, err + } + + if hit { + numHits++ + } + } + + return &types.PageResponse{ + NextKey: nextKey, + }, nil + } + + iterator := prefixStore.Iterator(nil, nil) + defer iterator.Close() + + end := offset + limit + + var numHits uint64 + var nextKey []byte + + for ; iterator.Valid(); iterator.Next() { + if iterator.Error() != nil { + return nil, iterator.Error() + } + + accumulate := numHits >= offset && numHits < end + hit, err := onResult(iterator.Key(), iterator.Value(), accumulate) + if err != nil { + return nil, err + } + + if hit { + numHits++ + } + + if numHits == end+1 { + nextKey = iterator.Key() + + if !countTotal { + break + } + } + } + + res := &types.PageResponse{NextKey: nextKey} + if countTotal { + res.Total = numHits + } + + return res, nil +} diff --git a/x/wasm/internal/keeper/legacy_querier.go b/x/wasm/internal/keeper/legacy_querier.go index 51e9be4184..ef8c9259f5 100644 --- a/x/wasm/internal/keeper/legacy_querier.go +++ b/x/wasm/internal/keeper/legacy_querier.go @@ -1,9 +1,9 @@ package keeper import ( - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/line/lbm-sdk/codec" + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" "github.com/line/lbm-sdk/x/wasm/internal/types" abci "github.com/tendermint/tendermint/abci/types" ) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 18d739a49e..d3893099ff 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/line/lbm-sdk/codec" + "github.com/line/lbm-sdk/store/prefix" sdk "github.com/line/lbm-sdk/types" sdkerrors "github.com/line/lbm-sdk/types/errors" abci "github.com/tendermint/tendermint/abci/types" @@ -30,7 +31,7 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryListCode: return queryCodeList(ctx, keeper) case QueryContractHistory: - return queryContractHistory(ctx, path[1], keeper) + return queryContractHistory2(ctx, path[1], req, keeper) default: return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") } @@ -163,25 +164,48 @@ func queryCodeList(ctx sdk.Context, keeper Keeper) ([]byte, error) { return bz, nil } -func queryContractHistory(ctx sdk.Context, bech string, keeper Keeper) ([]byte, error) { - contractAddr, err := sdk.AccAddressFromBech32(bech) +func queryContractHistory2(ctx sdk.Context, _ string, req abci.RequestQuery, keeper Keeper) ([]byte, error) { + var params types.QueryContractHistoryRequest + + if err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + res, err := keeper.ContractHistory(ctx, ¶ms) if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) + return nil, err } - entries := keeper.GetContractHistory(ctx, contractAddr) - if entries == nil { + if res.Entries == nil { // nil, nil leads to 404 in rest handler return nil, nil } - histories := make([]types.ContractHistoryResponse, len(entries)) - for i, entry := range entries { - histories[i] = types.NewContractHistoryResponse(entry) - } - - bz, err := codec.MarshalJSONIndent(types.ModuleCdc, histories) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, res) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } + +func (k Keeper) ContractHistory(ctx sdk.Context, req *types.QueryContractHistoryRequest) (*types.QueryContractHistoryResponse, error) { + r := make([]types.ContractCodeHistoryEntry, 0) + + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(req.Address)) + pageRes, err := FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { + if accumulate { + var e types.ContractCodeHistoryEntry + if err := k.cdc.UnmarshalBinaryBare(value, &e); err != nil { + return false, err + } + e.Updated = nil // redact + r = append(r, e) + } + return true, nil + }) + if err != nil { + return nil, err + } + return &types.QueryContractHistoryResponse{ + Entries: r, + Pagination: pageRes, + }, nil +} diff --git a/x/wasm/internal/types/keys.go b/x/wasm/internal/types/keys.go index 721f2475d3..b990c4b226 100644 --- a/x/wasm/internal/types/keys.go +++ b/x/wasm/internal/types/keys.go @@ -31,11 +31,11 @@ const ( // event attributes // nolint var ( - CodeKeyPrefix = []byte{0x01} - ContractKeyPrefix = []byte{0x02} - ContractStorePrefix = []byte{0x03} - SequenceKeyPrefix = []byte{0x04} - ContractHistoryStorePrefix = []byte{0x05} + CodeKeyPrefix = []byte{0x01} + ContractKeyPrefix = []byte{0x02} + ContractStorePrefix = []byte{0x03} + SequenceKeyPrefix = []byte{0x04} + ContractCodeHistoryElementPrefix = []byte{0x05} KeyLastCodeID = append(SequenceKeyPrefix, []byte("lastCodeId")...) KeyLastInstanceID = append(SequenceKeyPrefix, []byte("lastContractId")...) @@ -61,3 +61,22 @@ func GetContractAddressKey(addr sdk.AccAddress) []byte { func GetContractStorePrefixKey(addr sdk.AccAddress) []byte { return append(ContractStorePrefix, addr...) } + +// GetContractCodeHistoryElementKey returns the key a contract code history entry: `` +func GetContractCodeHistoryElementKey(contractAddr sdk.AccAddress, pos uint64) []byte { + prefix := GetContractCodeHistoryElementPrefix(contractAddr) + prefixLen := len(prefix) + r := make([]byte, prefixLen+8) + copy(r[0:], prefix) + copy(r[prefixLen:], sdk.Uint64ToBigEndian(pos)) + return r +} + +// GetContractCodeHistoryElementPrefix returns the key prefix for a contract code history entry: `` +func GetContractCodeHistoryElementPrefix(contractAddr sdk.AccAddress) []byte { + prefixLen := len(ContractCodeHistoryElementPrefix) + r := make([]byte, prefixLen+sdk.AddrLen) + copy(r[0:], ContractCodeHistoryElementPrefix) + copy(r[prefixLen:], contractAddr) + return r +} diff --git a/x/wasm/internal/types/querier.go b/x/wasm/internal/types/querier.go index 2b6a0d2a20..41da732084 100644 --- a/x/wasm/internal/types/querier.go +++ b/x/wasm/internal/types/querier.go @@ -149,3 +149,25 @@ func (r contractHistory) GetCodeID() uint64 { func (r contractHistory) GetMsg() json.RawMessage { return r.Msg } + +type PageRequest struct { + Key []byte + Offset uint64 + Limit uint64 + CountTotal bool +} + +type PageResponse struct { + NextKey []byte + Total uint64 +} + +type QueryContractHistoryRequest struct { + Address sdk.AccAddress + Pagination *PageRequest +} + +type QueryContractHistoryResponse struct { + Entries []ContractCodeHistoryEntry + Pagination *PageResponse +} From fda259e9936819a63709b43542a580d5c3463a71 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Wed, 3 Mar 2021 07:43:13 +0900 Subject: [PATCH 03/12] feat: add pagination for rest api --- x/wasm/client/rest/query.go | 25 ++++++++++++++++- x/wasm/client/utils/utils.go | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/x/wasm/client/rest/query.go b/x/wasm/client/rest/query.go index eae8b49f71..c6e2e76816 100644 --- a/x/wasm/client/rest/query.go +++ b/x/wasm/client/rest/query.go @@ -14,6 +14,7 @@ import ( sdk "github.com/line/lbm-sdk/types" "github.com/line/lbm-sdk/types/rest" + "github.com/line/lbm-sdk/x/wasm/client/utils" "github.com/line/lbm-sdk/x/wasm/internal/keeper" "github.com/line/lbm-sdk/x/wasm/internal/types" ) @@ -238,7 +239,29 @@ func queryContractHistoryFn(cliCtx context.CLIContext) http.HandlerFunc { } route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryContractHistory, addr.String()) - res, height, err := cliCtx.Query(route) + pageKey, offset, limit, page, countTotal, err := utils.ParseHTTPArgs(r) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + pageReq, err := utils.NewPageRequest(pageKey, offset, limit, page, countTotal) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + data := &types.QueryContractHistoryRequest{ + Address: addr, + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + res, height, err := cliCtx.QueryWithData(route, bs) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/wasm/client/utils/utils.go b/x/wasm/client/utils/utils.go index c8bf46b00f..3d3e5415bb 100644 --- a/x/wasm/client/utils/utils.go +++ b/x/wasm/client/utils/utils.go @@ -4,12 +4,16 @@ import ( "bytes" "compress/gzip" "encoding/binary" + "errors" "fmt" + "net/http" + "strconv" "github.com/spf13/cobra" "github.com/spf13/pflag" sdkerrors "github.com/line/lbm-sdk/types/errors" + "github.com/line/lbm-sdk/types/rest" "github.com/line/lbm-sdk/x/wasm/internal/types" ) @@ -118,3 +122,52 @@ func NewPageRequest(pageKey string, offset, limit, page uint64, countTotal bool) CountTotal: countTotal, }, nil } + +func ParseHTTPArgs(r *http.Request) (pageKey string, offset, limit, page uint64, countTotal bool, err error) { + pageKey = r.FormValue("page-key") + + offsetStr := r.FormValue("offset") + if offsetStr != "" { + offset, err = strconv.ParseUint(offsetStr, 10, 64) + if err != nil { + return pageKey, offset, limit, page, countTotal, err + } + if offset <= 0 { + return pageKey, offset, limit, page, countTotal, errors.New("offset must greater than 0") + } + } + + pageStr := r.FormValue("page") + if pageStr == "" { + page = rest.DefaultPage + } else { + page, err = strconv.ParseUint(pageStr, 10, 64) + if err != nil { + return pageKey, offset, limit, page, countTotal, err + } else if page <= 0 { + return pageKey, offset, limit, page, countTotal, errors.New("page must greater than 0") + } + } + + limitStr := r.FormValue("limit") + if limitStr == "" { + limit = rest.DefaultLimit + } else { + limit, err = strconv.ParseUint(limitStr, 10, 64) + if err != nil { + return pageKey, offset, limit, page, countTotal, err + } else if limit <= 0 { + return pageKey, offset, limit, page, countTotal, errors.New("limit must greater than 0") + } + } + + countTotalStr := r.FormValue("count-total") + if countTotalStr != "" { + countTotal, err = strconv.ParseBool(countTotalStr) + if err != nil { + return pageKey, offset, limit, page, countTotal, err + } + } + + return pageKey, offset, limit, page, countTotal, nil +} From 1f0c296944eb116a08416bfded813e1223e367bb Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Wed, 3 Mar 2021 07:43:32 +0900 Subject: [PATCH 04/12] fix: modify unit test --- x/wasm/internal/keeper/genesis_test.go | 20 ++++-- x/wasm/internal/keeper/keeper_test.go | 2 +- x/wasm/internal/keeper/querier_test.go | 93 ++++++++++++++++++-------- 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index 4ccab6b4ea..c5f955428a 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -103,13 +103,21 @@ func TestGenesisExportImport(t *testing.T) { dstIT := dstCtx.KVStore(dstStoreKeys[j]).Iterator(nil, nil) for i := 0; srcIT.Valid(); i++ { - require.True(t, dstIT.Valid(), "[%s] destination DB has less elements than source. Missing: %s", srcStoreKeys[j].Name(), srcIT.Key()) - require.Equal(t, srcIT.Key(), dstIT.Key(), i) - - isContractHistory := srcStoreKeys[j].Name() == types.StoreKey && bytes.HasPrefix(srcIT.Key(), types.ContractHistoryStorePrefix) - if !isContractHistory { // only skip history entries because we know they are different - require.Equal(t, srcIT.Value(), dstIT.Value(), "[%s] element (%d): %X", srcStoreKeys[j].Name(), i, srcIT.Key()) + isContractHistory := srcStoreKeys[j].Name() == types.StoreKey && bytes.HasPrefix(srcIT.Key(), types.ContractCodeHistoryElementPrefix) + if isContractHistory { + // only skip history entries because we know they are different + // from genesis they are merged into 1 single entry + srcIT.Next() + if dstIT.Valid() { + if bytes.HasPrefix(dstIT.Key(), types.ContractCodeHistoryElementPrefix) { + dstIT.Next() + } + } + continue } + require.True(t, dstIT.Valid(), "[%s] destination DB has less elements than source. Missing: %x", srcStoreKeys[j].Name(), srcIT.Key()) + require.Equal(t, srcIT.Key(), dstIT.Key(), i) + require.Equal(t, srcIT.Value(), dstIT.Value(), "[%s] element (%d): %X", srcStoreKeys[j].Name(), i, srcIT.Key()) srcIT.Next() dstIT.Next() } diff --git a/x/wasm/internal/keeper/keeper_test.go b/x/wasm/internal/keeper/keeper_test.go index 0b368f0ff4..50a7d53150 100644 --- a/x/wasm/internal/keeper/keeper_test.go +++ b/x/wasm/internal/keeper/keeper_test.go @@ -309,7 +309,7 @@ func TestInstantiate(t *testing.T) { require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String()) gasAfter := ctx.GasMeter().GasConsumed() - require.Equal(t, uint64(0x10c50), gasAfter-gasBefore) + require.Equal(t, uint64(0x1080e), gasAfter-gasBefore) // ensure it is stored properly info := keeper.GetContractInfo(ctx, contractAddr) diff --git a/x/wasm/internal/keeper/querier_test.go b/x/wasm/internal/keeper/querier_test.go index 4884335c96..052b3d9dca 100644 --- a/x/wasm/internal/keeper/querier_test.go +++ b/x/wasm/internal/keeper/querier_test.go @@ -2,7 +2,6 @@ package keeper import ( - "bytes" "fmt" "io/ioutil" "os" @@ -234,13 +233,14 @@ func TestQueryContractHistory(t *testing.T) { keeper := keepers.WasmKeeper var ( - otherAddr sdk.AccAddress = bytes.Repeat([]byte{0x2}, sdk.AddrLen) + _, _, myContractAddr = keyPubAddr() + _, _, otherAddr = keyPubAddr() ) specs := map[string]struct { - srcQueryAddr sdk.AccAddress - srcHistory []types.ContractCodeHistoryEntry - expContent []types.ContractCodeHistoryEntry + srcHistory []types.ContractCodeHistoryEntry + req types.QueryContractHistoryRequest + expContent []types.ContractCodeHistoryEntry }{ "response with internal fields cleared": { srcHistory: []types.ContractCodeHistoryEntry{{ @@ -249,6 +249,7 @@ func TestQueryContractHistory(t *testing.T) { Updated: types.NewAbsoluteTxPosition(ctx), Msg: []byte(`"init message"`), }}, + req: types.QueryContractHistoryRequest{Address: myContractAddr}, expContent: []types.ContractCodeHistoryEntry{{ Operation: types.GenesisContractCodeHistoryType, CodeID: firstCodeID, @@ -272,6 +273,7 @@ func TestQueryContractHistory(t *testing.T) { Updated: types.NewAbsoluteTxPosition(ctx), Msg: []byte(`"migrate message 2"`), }}, + req: types.QueryContractHistoryRequest{Address: myContractAddr}, expContent: []types.ContractCodeHistoryEntry{{ Operation: types.InitContractCodeHistoryType, CodeID: firstCodeID, @@ -286,8 +288,56 @@ func TestQueryContractHistory(t *testing.T) { Msg: []byte(`"migrate message 2"`), }}, }, + "with pagination offset": { + srcHistory: []types.ContractCodeHistoryEntry{{ + Operation: types.InitContractCodeHistoryType, + CodeID: firstCodeID, + Updated: types.NewAbsoluteTxPosition(ctx), + Msg: []byte(`"init message"`), + }, { + Operation: types.MigrateContractCodeHistoryType, + CodeID: 2, + Updated: types.NewAbsoluteTxPosition(ctx), + Msg: []byte(`"migrate message 1"`), + }}, + req: types.QueryContractHistoryRequest{ + Address: myContractAddr, + Pagination: &types.PageRequest{ + Offset: 1, + }, + }, + expContent: []types.ContractCodeHistoryEntry{{ + Operation: types.MigrateContractCodeHistoryType, + CodeID: 2, + Msg: []byte(`"migrate message 1"`), + }}, + }, + "with pagination limit": { + srcHistory: []types.ContractCodeHistoryEntry{{ + Operation: types.InitContractCodeHistoryType, + CodeID: firstCodeID, + Updated: types.NewAbsoluteTxPosition(ctx), + Msg: []byte(`"init message"`), + }, { + Operation: types.MigrateContractCodeHistoryType, + CodeID: 2, + Updated: types.NewAbsoluteTxPosition(ctx), + Msg: []byte(`"migrate message 1"`), + }}, + req: types.QueryContractHistoryRequest{ + Address: myContractAddr, + Pagination: &types.PageRequest{ + Limit: 1, + }, + }, + expContent: []types.ContractCodeHistoryEntry{{ + Operation: types.InitContractCodeHistoryType, + CodeID: firstCodeID, + Msg: []byte(`"init message"`), + }}, + }, "unknown contract address": { - srcQueryAddr: otherAddr, + req: types.QueryContractHistoryRequest{Address: otherAddr}, srcHistory: []types.ContractCodeHistoryEntry{{ Operation: types.GenesisContractCodeHistoryType, CodeID: firstCodeID, @@ -299,43 +349,32 @@ func TestQueryContractHistory(t *testing.T) { } for msg, spec := range specs { t.Run(msg, func(t *testing.T) { - _, _, myContractAddr := keyPubAddr() - keeper.appendToContractHistory(ctx, myContractAddr, spec.srcHistory...) + xCtx, _ := ctx.CacheContext() + keeper.appendToContractHistory(xCtx, myContractAddr, spec.srcHistory...) q := NewQuerier(keeper) - queryContractAddr := spec.srcQueryAddr - if queryContractAddr == nil { - queryContractAddr = myContractAddr - } // when - query := []string{QueryContractHistory, queryContractAddr.String()} - data := abci.RequestQuery{} - resData, err := q(ctx, query, data) + query := []string{QueryContractHistory, myContractAddr.String()} + bs, err := types.ModuleCdc.MarshalJSON(spec.req) + require.NoError(t, err) + data := abci.RequestQuery{Data: bs} + resData, err := q(xCtx, query, data) // then require.NoError(t, err) if spec.expContent == nil { - require.Nil(t, resData) + require.Error(t, types.ErrEmpty) return } - var got []types.ContractHistoryResponse + var got types.QueryContractHistoryResponse err = types.ModuleCdc.UnmarshalJSON(resData, &got) require.NoError(t, err) - assertContractHistory(t, spec.expContent, got) + assert.Equal(t, spec.expContent, got.Entries) }) } } -func assertContractHistory(t *testing.T, expected []types.ContractCodeHistoryEntry, actual []types.ContractHistoryResponse) { - assert.Equal(t, len(expected), len(actual)) - - for i, entry := range expected { - expectedResponse := types.NewContractHistoryResponse(entry) - assert.Equal(t, expectedResponse, actual[i]) - } -} - func TestQueryCodeList(t *testing.T) { wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) From 87b8e1b8275ca54bf8779b2b2a08f9bb0993b326 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Fri, 5 Mar 2021 12:51:53 +0900 Subject: [PATCH 05/12] fix: apply review --- x/wasm/internal/keeper/keeper.go | 1 + x/wasm/internal/keeper/querier.go | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 86d986b4e3..57b6af446a 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -20,6 +20,7 @@ import ( "github.com/pkg/errors" "github.com/tendermint/tendermint/crypto" + storeTypes "github.com/line/lbm-sdk/store/types" "github.com/line/lbm-sdk/x/wasm/client/utils" "github.com/line/lbm-sdk/x/wasm/internal/types" ) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index d3893099ff..48da1b3e44 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -13,6 +13,21 @@ import ( "github.com/line/lbm-sdk/x/wasm/internal/types" ) +const ( + QueryListContractByCode = "list-contracts-by-code" + QueryGetContract = "contract-info" + QueryGetContractState = "contract-state" + QueryGetCode = "code" + QueryListCode = "list-code" + QueryContractHistory = "contract-history" +) + +const ( + QueryMethodContractStateSmart = "smart" + QueryMethodContractStateAll = "all" + QueryMethodContractStateRaw = "raw" +) + // NewQuerier creates a new querier func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { @@ -31,7 +46,7 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryListCode: return queryCodeList(ctx, keeper) case QueryContractHistory: - return queryContractHistory2(ctx, path[1], req, keeper) + return queryContractHistory(ctx, path[1], req, keeper) default: return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") } @@ -164,13 +179,13 @@ func queryCodeList(ctx sdk.Context, keeper Keeper) ([]byte, error) { return bz, nil } -func queryContractHistory2(ctx sdk.Context, _ string, req abci.RequestQuery, keeper Keeper) ([]byte, error) { +func queryContractHistory(ctx sdk.Context, _ string, req abci.RequestQuery, keeper Keeper) ([]byte, error) { var params types.QueryContractHistoryRequest if err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } - res, err := keeper.ContractHistory(ctx, ¶ms) + res, err := keeper.contractHistory(ctx, ¶ms) if err != nil { return nil, err } @@ -186,7 +201,7 @@ func queryContractHistory2(ctx sdk.Context, _ string, req abci.RequestQuery, kee return bz, nil } -func (k Keeper) ContractHistory(ctx sdk.Context, req *types.QueryContractHistoryRequest) (*types.QueryContractHistoryResponse, error) { +func (k Keeper) contractHistory(ctx sdk.Context, req *types.QueryContractHistoryRequest) (*types.QueryContractHistoryResponse, error) { r := make([]types.ContractCodeHistoryEntry, 0) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(req.Address)) From 9bf9b68c65570ac3cf537afce199c292fcdf68a1 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Fri, 5 Mar 2021 17:55:22 +0900 Subject: [PATCH 06/12] feat: add pagination for queryContractListByCode, queryCodeList --- x/wasm/client/cli/query.go | 39 ++++++++-- x/wasm/client/rest/query.go | 49 +++++++++++- x/wasm/internal/keeper/genesis_test.go | 1 + x/wasm/internal/keeper/keeper.go | 9 ++- x/wasm/internal/keeper/keeper_test.go | 2 +- x/wasm/internal/keeper/querier.go | 93 +++++++++++++++------- x/wasm/internal/keeper/querier_test.go | 102 ++++++++++++++++++------- x/wasm/internal/keeper/test_fuzz.go | 2 +- x/wasm/internal/types/keys.go | 33 ++++++-- x/wasm/internal/types/querier.go | 19 +++++ x/wasm/internal/types/types.go | 18 ++++- x/wasm/module_test.go | 27 ++++--- 12 files changed, 311 insertions(+), 83 deletions(-) diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index 579d0b3007..60ae632724 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -45,7 +45,7 @@ func GetQueryCmd(cdc *codec.Codec) *cobra.Command { // GetCmdListCode lists all wasm code uploaded func GetCmdListCode(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "list-code", Short: "List all wasm bytecode on the chain", Long: "List all wasm bytecode on the chain", @@ -53,8 +53,20 @@ func GetCmdListCode(cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) + pageReq, err := utils.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) + if err != nil { + return err + } + data := &types.QueryCodesRequest{ + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + return err + } + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListCode) - res, _, err := cliCtx.Query(route) + res, _, err := cliCtx.QueryWithData(route, bs) if err != nil { return err } @@ -62,11 +74,14 @@ func GetCmdListCode(cdc *codec.Codec) *cobra.Command { return nil }, } + + utils.AddPaginationFlagsToCmd(cmd, "list codes") + return cmd } // GetCmdListContractByCode lists all wasm code uploaded for given code id func GetCmdListContractByCode(cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "list-contract-by-code [code_id]", Short: "List wasm all bytecode on the chain for given code id", Long: "List wasm all bytecode on the chain for given code id", @@ -79,8 +94,20 @@ func GetCmdListContractByCode(cdc *codec.Codec) *cobra.Command { return err } - route := fmt.Sprintf("custom/%s/%s/%d", types.QuerierRoute, keeper.QueryListContractByCode, codeID) - res, _, err := cliCtx.Query(route) + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListContractByCode) + pageReq, err := utils.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) + if err != nil { + return err + } + data := &types.QueryContractsByCodeRequest{ + CodeID: codeID, + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + return err + } + res, _, err := cliCtx.QueryWithData(route, bs) if err != nil { return err } @@ -88,6 +115,8 @@ func GetCmdListContractByCode(cdc *codec.Codec) *cobra.Command { return nil }, } + utils.AddPaginationFlagsToCmd(cmd, "list contracts by code") + return cmd } // GetCmdQueryCode returns the bytecode for a given contract diff --git a/x/wasm/client/rest/query.go b/x/wasm/client/rest/query.go index c6e2e76816..3a57ca2adb 100644 --- a/x/wasm/client/rest/query.go +++ b/x/wasm/client/rest/query.go @@ -38,7 +38,28 @@ func listCodesHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListCode) - res, height, err := cliCtx.Query(route) + pageKey, offset, limit, page, countTotal, err := utils.ParseHTTPArgs(r) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + pageReq, err := utils.NewPageRequest(pageKey, offset, limit, page, countTotal) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + data := &types.QueryCodesRequest{ + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + res, height, err := cliCtx.QueryWithData(route, bs) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -89,8 +110,30 @@ func listContractsByCodeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - route := fmt.Sprintf("custom/%s/%s/%d", types.QuerierRoute, keeper.QueryListContractByCode, codeID) - res, height, err := cliCtx.Query(route) + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListContractByCode) + pageKey, offset, limit, page, countTotal, err := utils.ParseHTTPArgs(r) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + pageReq, err := utils.NewPageRequest(pageKey, offset, limit, page, countTotal) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + data := &types.QueryContractsByCodeRequest{ + CodeID: codeID, + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + res, height, err := cliCtx.QueryWithData(route, bs) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index c5f955428a..0c32733d03 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -84,6 +84,7 @@ func TestGenesisExportImport(t *testing.T) { // reset contract history in source DB for comparison with dest DB srcKeeper.IterateContractInfo(srcCtx, func(address sdk.AccAddress, info types.ContractInfo) bool { + srcKeeper.deleteContractSecondIndex(srcCtx, address, &info) info.ResetFromGenesis(srcCtx) srcKeeper.setContractInfo(srcCtx, address, &info) return false diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 57b6af446a..2e2ba2e105 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -257,7 +257,7 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A // persist instance createdAt := types.NewAbsoluteTxPosition(ctx) instance := types.NewContractInfo(codeID, creator, admin, label, createdAt) - store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(instance)) + k.setContractInfo(ctx, contractAddress, &instance) k.appendToContractHistory(ctx, contractAddress, instance.InitialHistory(initMsg)) return contractAddress, nil } @@ -357,6 +357,8 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller events := types.ParseEvents(res.Attributes, contractAddress) ctx.EventManager().EmitEvents(events) + // delete old secondary index entry + k.deleteContractSecondIndex(ctx, contractAddress, contractInfo) historyEntry := contractInfo.AddMigration(ctx, newCodeID, msg) k.appendToContractHistory(ctx, contractAddress, historyEntry) k.setContractInfo(ctx, contractAddress, contractInfo) @@ -370,6 +372,10 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller }, nil } +func (k Keeper) deleteContractSecondIndex(ctx sdk.Context, contractAddress sdk.AccAddress, contractInfo *types.ContractInfo) { + ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contractInfo)) +} + // UpdateContractAdmin sets the admin value on the ContractInfo. It must be a valid address (use ClearContractAdmin to remove it) func (k Keeper) UpdateContractAdmin(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newAdmin sdk.AccAddress) error { return k.setContractAdmin(ctx, contractAddress, caller, newAdmin, k.authZPolicy) @@ -494,6 +500,7 @@ func (k Keeper) containsContractInfo(ctx sdk.Context, contractAddress sdk.AccAdd func (k Keeper) setContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress, contract *types.ContractInfo) { store := ctx.KVStore(k.storeKey) store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(contract)) + store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contract), []byte{}) } func (k Keeper) IterateContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) { diff --git a/x/wasm/internal/keeper/keeper_test.go b/x/wasm/internal/keeper/keeper_test.go index 50a7d53150..8037708254 100644 --- a/x/wasm/internal/keeper/keeper_test.go +++ b/x/wasm/internal/keeper/keeper_test.go @@ -309,7 +309,7 @@ func TestInstantiate(t *testing.T) { require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String()) gasAfter := ctx.GasMeter().GasConsumed() - require.Equal(t, uint64(0x1080e), gasAfter-gasBefore) + require.Equal(t, uint64(0x10fde), gasAfter-gasBefore) // ensure it is stored properly info := keeper.GetContractInfo(ctx, contractAddr) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index 48da1b3e44..b8393c3ed4 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -1,7 +1,7 @@ package keeper import ( - "sort" + "encoding/binary" "strconv" "github.com/line/lbm-sdk/codec" @@ -35,7 +35,7 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryGetContract: return queryContractInfo(ctx, path[1], keeper) case QueryListContractByCode: - return queryContractListByCode(ctx, path[1], keeper) + return queryContractListByCode(ctx, req, keeper) case QueryGetContractState: if len(path) < 3 { return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") @@ -44,7 +44,7 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryGetCode: return queryCode(ctx, path[1], keeper) case QueryListCode: - return queryCodeList(ctx, keeper) + return queryCodeList(ctx, req, keeper) case QueryContractHistory: return queryContractHistory(ctx, path[1], req, keeper) default: @@ -76,28 +76,18 @@ func redact(info *types.ContractInfo) { info.Created = nil } -func queryContractListByCode(ctx sdk.Context, codeIDstr string, keeper Keeper) ([]byte, error) { - codeID, err := strconv.ParseUint(codeIDstr, 10, 64) +func queryContractListByCode(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { + var params types.QueryContractsByCodeRequest + + if err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + res, err := keeper.contractsByCode(ctx, ¶ms) if err != nil { return nil, err } - var contracts []types.ContractInfoResponse - keeper.IterateContractInfo(ctx, func(addr sdk.AccAddress, info types.ContractInfo) bool { - if info.CodeID == codeID { - // and add the address - infoWithAddress := types.NewContractInfoResponse(info, addr) - contracts = append(contracts, infoWithAddress) - } - return false - }) - - // now we sort them by AbsoluteTxPosition - sort.Slice(contracts, func(i, j int) bool { - return contracts[i].LessThan(contracts[j]) - }) - - bz, err := codec.MarshalJSONIndent(types.ModuleCdc, contracts) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, res) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } @@ -165,14 +155,18 @@ func queryCode(ctx sdk.Context, codeIDstr string, keeper Keeper) ([]byte, error) return bz, nil } -func queryCodeList(ctx sdk.Context, keeper Keeper) ([]byte, error) { - var info []types.CodeInfoResponse - keeper.IterateCodeInfos(ctx, func(i uint64, res types.CodeInfo) bool { - info = append(info, types.NewCodeInfoResponse(i, res, nil)) - return false - }) +func queryCodeList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { + var params types.QueryCodesRequest + if err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } - bz, err := codec.MarshalJSONIndent(types.ModuleCdc, info) + res, err := keeper.codes(ctx, ¶ms) + if err != nil { + return nil, err + } + + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, res) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } @@ -201,6 +195,49 @@ func queryContractHistory(ctx sdk.Context, _ string, req abci.RequestQuery, keep return bz, nil } +func (k Keeper) codes(ctx sdk.Context, req *types.QueryCodesRequest) (*types.QueryCodesResponse, error) { + r := make([]types.CodeInfoResponse, 0) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.CodeKeyPrefix) + pageRes, err := FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { + if accumulate { + var c types.CodeInfo + if err := k.cdc.UnmarshalBinaryBare(value, &c); err != nil { + return false, err + } + r = append(r, types.NewCodeInfoResponse(binary.BigEndian.Uint64(key), c, nil)) + } + return true, nil + }) + if err != nil { + return nil, err + } + return &types.QueryCodesResponse{CodeInfos: r, Pagination: pageRes}, nil +} + +func (k Keeper) contractsByCode(ctx sdk.Context, req *types.QueryContractsByCodeRequest) (*types.QueryContractsByCodeResponse, error) { + r := make([]types.ContractInfoResponse, 0) + prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractByCodeIDSecondaryIndexPrefix(req.CodeID)) + pageRes, err := FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { + var contractAddr sdk.AccAddress = key[types.AbsoluteTxPositionLen:] + c := k.GetContractInfo(ctx, contractAddr) + if c == nil { + return false, types.ErrNotFound + } + c.Created = nil // redact + if accumulate { + r = append(r, types.NewContractInfoResponse(*c, contractAddr)) + } + return true, nil + }) + if err != nil { + return nil, err + } + return &types.QueryContractsByCodeResponse{ + ContractInfos: r, + Pagination: pageRes, + }, nil +} + func (k Keeper) contractHistory(ctx sdk.Context, req *types.QueryContractHistoryRequest) (*types.QueryContractHistoryResponse, error) { r := make([]types.ContractCodeHistoryEntry, 0) diff --git a/x/wasm/internal/keeper/querier_test.go b/x/wasm/internal/keeper/querier_test.go index 052b3d9dca..472766812e 100644 --- a/x/wasm/internal/keeper/querier_test.go +++ b/x/wasm/internal/keeper/querier_test.go @@ -2,6 +2,7 @@ package keeper import ( + "encoding/base64" "fmt" "io/ioutil" "os" @@ -207,22 +208,24 @@ func TestListContractByCodeOrdering(t *testing.T) { // query and check the results are properly sorted q := NewQuerier(keeper) query := []string{QueryListContractByCode, fmt.Sprintf("%d", codeID)} - data := abci.RequestQuery{} - res, err := q(ctx, query, data) + + req := types.QueryContractsByCodeRequest{CodeID: codeID} + bs, err := types.ModuleCdc.MarshalJSON(req) + require.NoError(t, err) + data := abci.RequestQuery{Data: bs} + resData, err := q(ctx, query, data) require.NoError(t, err) - var contracts []types.ContractInfoResponse - err = types.ModuleCdc.UnmarshalJSON(res, &contracts) + var res types.QueryContractsByCodeResponse + err = types.ModuleCdc.UnmarshalJSON(resData, &res) require.NoError(t, err) - require.Equal(t, 10, len(contracts)) + require.Equal(t, 10, len(res.ContractInfos)) - for i, contract := range contracts { + for i, contract := range res.ContractInfos { assert.Equal(t, fmt.Sprintf("contract %d", i), contract.GetLabel()) assert.NotEmpty(t, contract.GetAddress()) } - assert.NotContains(t, string(res), "create") - assert.NotContains(t, string(res), "Create") } func TestQueryContractHistory(t *testing.T) { @@ -379,48 +382,91 @@ func TestQueryCodeList(t *testing.T) { wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + ctx, keepers := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil) + keeper := keepers.WasmKeeper + specs := map[string]struct { - codeIDs []uint64 + storedCodeIDs []uint64 + req types.QueryCodesRequest + expCodeIDs []uint64 }{ "none": {}, "no gaps": { - codeIDs: []uint64{1, 2, 3}, + storedCodeIDs: []uint64{1, 2, 3}, + expCodeIDs: []uint64{1, 2, 3}, }, "with gaps": { - codeIDs: []uint64{2, 4, 6}, + storedCodeIDs: []uint64{2, 4, 6}, + expCodeIDs: []uint64{2, 4, 6}, + }, + "with pagination offset": { + storedCodeIDs: []uint64{1, 2, 3}, + req: types.QueryCodesRequest{ + Pagination: &types.PageRequest{ + Offset: 1, + }, + }, + expCodeIDs: []uint64{2, 3}, + }, + "with pagination limit": { + storedCodeIDs: []uint64{1, 2, 3}, + req: types.QueryCodesRequest{ + Pagination: &types.PageRequest{ + Limit: 2, + }, + }, + expCodeIDs: []uint64{1, 2}, + }, + "with pagination next key": { + storedCodeIDs: []uint64{1, 2, 3}, + req: types.QueryCodesRequest{ + Pagination: &types.PageRequest{ + Key: fromBase64("AAAAAAAAAAI="), + }, + }, + expCodeIDs: []uint64{2, 3}, }, } - for msg, spec := range specs { t.Run(msg, func(t *testing.T) { - tempDir, err := ioutil.TempDir("", "wasm") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - ctx, keepers := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil) - keeper := keepers.WasmKeeper + xCtx, _ := ctx.CacheContext() - for _, codeID := range spec.codeIDs { - require.NoError(t, keeper.importCode(ctx, codeID, + for _, codeID := range spec.storedCodeIDs { + require.NoError(t, keeper.importCode(xCtx, codeID, types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)), wasmCode), ) } - q := NewQuerier(keeper) // when + q := NewQuerier(keeper) query := []string{QueryListCode} - data := abci.RequestQuery{} - resData, err := q(ctx, query, data) - - // then + bs, err := types.ModuleCdc.MarshalJSON(spec.req) + require.NoError(t, err) + data := abci.RequestQuery{Data: bs} + resData, err := q(xCtx, query, data) require.NoError(t, err) - var got []types.CodeInfoResponse + // then + var got types.QueryCodesResponse err = types.ModuleCdc.UnmarshalJSON(resData, &got) + require.NoError(t, err) - require.Len(t, got, len(spec.codeIDs)) - for i, exp := range spec.codeIDs { - assert.EqualValues(t, exp, got[i].GetID()) + require.Len(t, got.CodeInfos, len(spec.expCodeIDs)) + for i, exp := range spec.expCodeIDs { + assert.EqualValues(t, exp, got.CodeInfos[i].GetID()) } }) } } + +func fromBase64(s string) []byte { + r, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic(err) + } + return r +} diff --git a/x/wasm/internal/keeper/test_fuzz.go b/x/wasm/internal/keeper/test_fuzz.go index 698f37a8ce..51451b342c 100644 --- a/x/wasm/internal/keeper/test_fuzz.go +++ b/x/wasm/internal/keeper/test_fuzz.go @@ -17,7 +17,7 @@ func FuzzAddr(m *sdk.AccAddress, c fuzz.Continue) { } func FuzzAbsoluteTxPosition(m *types.AbsoluteTxPosition, c fuzz.Continue) { - m.BlockHeight = int64(c.RandUint64()) // can't be negative + m.BlockHeight = c.RandUint64() // can't be negative m.TxIndex = c.RandUint64() } diff --git a/x/wasm/internal/types/keys.go b/x/wasm/internal/types/keys.go index b990c4b226..cbff982149 100644 --- a/x/wasm/internal/types/keys.go +++ b/x/wasm/internal/types/keys.go @@ -31,11 +31,12 @@ const ( // event attributes // nolint var ( - CodeKeyPrefix = []byte{0x01} - ContractKeyPrefix = []byte{0x02} - ContractStorePrefix = []byte{0x03} - SequenceKeyPrefix = []byte{0x04} - ContractCodeHistoryElementPrefix = []byte{0x05} + CodeKeyPrefix = []byte{0x01} + ContractKeyPrefix = []byte{0x02} + ContractStorePrefix = []byte{0x03} + SequenceKeyPrefix = []byte{0x04} + ContractCodeHistoryElementPrefix = []byte{0x05} + ContractByCodeIDAndCreatedSecondaryIndexPrefix = []byte{0x06} KeyLastCodeID = append(SequenceKeyPrefix, []byte("lastCodeId")...) KeyLastInstanceID = append(SequenceKeyPrefix, []byte("lastContractId")...) @@ -62,6 +63,28 @@ func GetContractStorePrefixKey(addr sdk.AccAddress) []byte { return append(ContractStorePrefix, addr...) } +// GetContractByCreatedSecondaryIndexKey returns the key for the secondary index: +// `` +func GetContractByCreatedSecondaryIndexKey(contractAddr sdk.AccAddress, c *ContractInfo) []byte { + prefix := GetContractByCodeIDSecondaryIndexPrefix(c.CodeID) + prefixLen := len(prefix) + r := make([]byte, prefixLen+AbsoluteTxPositionLen+sdk.AddrLen) + copy(r[0:], prefix) + copy(r[prefixLen:], c.Created.Bytes()) + copy(r[prefixLen+AbsoluteTxPositionLen:], contractAddr) + return r +} + +// GetContractByCodeIDSecondaryIndexPrefix returns the prefix for the second index: `` +func GetContractByCodeIDSecondaryIndexPrefix(codeID uint64) []byte { + prefixLen := len(ContractByCodeIDAndCreatedSecondaryIndexPrefix) + const codeIDLen = 8 + r := make([]byte, prefixLen+codeIDLen) + copy(r[0:], ContractByCodeIDAndCreatedSecondaryIndexPrefix) + copy(r[prefixLen:], sdk.Uint64ToBigEndian(codeID)) + return r +} + // GetContractCodeHistoryElementKey returns the key a contract code history entry: `` func GetContractCodeHistoryElementKey(contractAddr sdk.AccAddress, pos uint64) []byte { prefix := GetContractCodeHistoryElementPrefix(contractAddr) diff --git a/x/wasm/internal/types/querier.go b/x/wasm/internal/types/querier.go index 41da732084..7811fed8e7 100644 --- a/x/wasm/internal/types/querier.go +++ b/x/wasm/internal/types/querier.go @@ -162,11 +162,30 @@ type PageResponse struct { Total uint64 } +type QueryContractsByCodeRequest struct { + CodeID uint64 + Pagination *PageRequest +} + +type QueryCodesRequest struct { + Pagination *PageRequest +} + type QueryContractHistoryRequest struct { Address sdk.AccAddress Pagination *PageRequest } +type QueryCodesResponse struct { + CodeInfos []CodeInfoResponse + Pagination *PageResponse +} + +type QueryContractsByCodeResponse struct { + ContractInfos []ContractInfoResponse + Pagination *PageResponse +} + type QueryContractHistoryResponse struct { Entries []ContractCodeHistoryEntry Pagination *PageResponse diff --git a/x/wasm/internal/types/types.go b/x/wasm/internal/types/types.go index b9de875c17..0536e088e8 100644 --- a/x/wasm/internal/types/types.go +++ b/x/wasm/internal/types/types.go @@ -158,7 +158,7 @@ func (c *ContractInfo) ResetFromGenesis(ctx sdk.Context) ContractCodeHistoryEntr // AbsoluteTxPosition can be used to sort contracts type AbsoluteTxPosition struct { // BlockHeight is the block the contract was created at - BlockHeight int64 + BlockHeight uint64 // TxIndex is a monotonic counter within the block (actual transaction index, or gas consumed) TxIndex uint64 } @@ -174,6 +174,20 @@ func (a *AbsoluteTxPosition) LessThan(b *AbsoluteTxPosition) bool { return a.BlockHeight < b.BlockHeight || (a.BlockHeight == b.BlockHeight && a.TxIndex < b.TxIndex) } +// AbsoluteTxPositionLen number of elements in byte representation +const AbsoluteTxPositionLen = 16 + +// Bytes encodes the object into a 16 byte representation with big endian block height and tx index. +func (a *AbsoluteTxPosition) Bytes() []byte { + if a == nil { + panic("object must not be nil") + } + r := make([]byte, AbsoluteTxPositionLen) + copy(r[0:], sdk.Uint64ToBigEndian(a.BlockHeight)) + copy(r[8:], sdk.Uint64ToBigEndian(a.TxIndex)) + return r +} + // NewAbsoluteTxPosition gets a timestamp from the context func NewAbsoluteTxPosition(ctx sdk.Context) *AbsoluteTxPosition { // we must safely handle nil gas meters @@ -183,7 +197,7 @@ func NewAbsoluteTxPosition(ctx sdk.Context) *AbsoluteTxPosition { index = meter.GasConsumed() } return &AbsoluteTxPosition{ - BlockHeight: ctx.BlockHeight(), + BlockHeight: uint64(ctx.BlockHeight()), TxIndex: index, } } diff --git a/x/wasm/module_test.go b/x/wasm/module_test.go index b9ff0a8442..a6938e6a03 100644 --- a/x/wasm/module_test.go +++ b/x/wasm/module_test.go @@ -416,7 +416,11 @@ func assertAttribute(t *testing.T, key string, value string, attr kv.Pair) { } func assertCodeList(t *testing.T, q sdk.Querier, ctx sdk.Context, expectedNum int) { - bz, sdkerr := q(ctx, []string{QueryListCode}, abci.RequestQuery{}) + req := types.QueryCodesRequest{} + bs, err := types.ModuleCdc.MarshalJSON(req) + require.NoError(t, err) + data := abci.RequestQuery{Data: bs} + bz, sdkerr := q(ctx, []string{QueryListCode}, data) require.NoError(t, sdkerr) if len(bz) == 0 { @@ -424,11 +428,11 @@ func assertCodeList(t *testing.T, q sdk.Querier, ctx sdk.Context, expectedNum in return } - var res []CodeInfo - err := types.ModuleCdc.UnmarshalJSON(bz, &res) + var res types.QueryCodesResponse + err = types.ModuleCdc.UnmarshalJSON(bz, &res) require.NoError(t, err) - assert.Equal(t, expectedNum, len(res)) + assert.Equal(t, expectedNum, len(res.CodeInfos)) } func assertCodeBytes(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, expectedBytes []byte) { @@ -450,7 +454,12 @@ func assertCodeBytes(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64 } func assertContractList(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, addrs []string) { - bz, sdkerr := q(ctx, []string{QueryListContractByCode, fmt.Sprintf("%d", codeID)}, abci.RequestQuery{}) + req := types.QueryContractsByCodeRequest{CodeID: codeID} + bs, err := types.ModuleCdc.MarshalJSON(req) + require.NoError(t, err) + data := abci.RequestQuery{Data: bs} + + bz, sdkerr := q(ctx, []string{QueryListContractByCode, fmt.Sprintf("%d", codeID)}, data) require.NoError(t, sdkerr) if len(bz) == 0 { @@ -458,12 +467,12 @@ func assertContractList(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uin return } - var res []ContractInfoResponse - err := types.ModuleCdc.UnmarshalJSON(bz, &res) + var res types.QueryContractsByCodeResponse + err = types.ModuleCdc.UnmarshalJSON(bz, &res) require.NoError(t, err) - var hasAddrs = make([]string, len(res)) - for i, r := range res { + var hasAddrs = make([]string, len(res.ContractInfos)) + for i, r := range res.ContractInfos { hasAddrs[i] = r.GetAddress().String() } From 37abfebf2e86d556fbbb37754e304b828db3b0cc Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Wed, 10 Mar 2021 20:29:11 +0900 Subject: [PATCH 07/12] fix: remove legacy_querier --- x/wasm/internal/keeper/legacy_querier.go | 59 ------------------------ 1 file changed, 59 deletions(-) delete mode 100644 x/wasm/internal/keeper/legacy_querier.go diff --git a/x/wasm/internal/keeper/legacy_querier.go b/x/wasm/internal/keeper/legacy_querier.go deleted file mode 100644 index ef8c9259f5..0000000000 --- a/x/wasm/internal/keeper/legacy_querier.go +++ /dev/null @@ -1,59 +0,0 @@ -package keeper - -import ( - "github.com/line/lbm-sdk/codec" - sdk "github.com/line/lbm-sdk/types" - sdkerrors "github.com/line/lbm-sdk/types/errors" - "github.com/line/lbm-sdk/x/wasm/internal/types" - abci "github.com/tendermint/tendermint/abci/types" -) - -const ( - QueryListContractByCode = "list-contracts-by-code" - QueryGetContract = "contract-info" - QueryGetContractState = "contract-state" - QueryGetCode = "code" - QueryListCode = "list-code" - QueryContractHistory = "contract-history" -) - -const ( - QueryMethodContractStateSmart = "smart" - QueryMethodContractStateAll = "all" - QueryMethodContractStateRaw = "raw" -) - -// NewLegacyQuerier creates a new querier -func NewLegacyQuerier(keeper Keeper) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { - switch path[0] { - case QueryContractHistory: - return queryContractHistory(ctx, path[1], keeper) - default: - return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") - } - } -} - -func queryContractHistory(ctx sdk.Context, bech string, keeper Keeper) ([]byte, error) { - contractAddr, err := sdk.AccAddressFromBech32(bech) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) - } - entries := keeper.GetContractHistory(ctx, contractAddr) - if entries == nil { - // nil, nil leads to 404 in rest handler - return nil, nil - } - - histories := make([]types.ContractHistoryResponse, len(entries)) - for i, entry := range entries { - histories[i] = types.NewContractHistoryResponse(entry) - } - - bz, err := codec.MarshalJSONIndent(types.ModuleCdc, histories) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) - } - return bz, nil -} From d270cf68960de40e65978373c03b5dfeb127dfc5 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Fri, 5 Mar 2021 21:20:22 +0900 Subject: [PATCH 08/12] fix: fix integration test error --- x/wasm/linkwasmd/cli_test/cli_test.go | 16 ++++++++-------- x/wasm/linkwasmd/cli_test/test_helpers.go | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/x/wasm/linkwasmd/cli_test/cli_test.go b/x/wasm/linkwasmd/cli_test/cli_test.go index 702cdabf32..fee77f2fc9 100644 --- a/x/wasm/linkwasmd/cli_test/cli_test.go +++ b/x/wasm/linkwasmd/cli_test/cli_test.go @@ -57,7 +57,7 @@ func TestLinkCLIWasmEscrow(t *testing.T) { // validate that there are no code in the chain { - listCode := f.QueryListCodeWasm() + listCode := f.QueryListCodeWasm().CodeInfos require.Empty(t, listCode) } @@ -69,7 +69,7 @@ func TestLinkCLIWasmEscrow(t *testing.T) { // validate the code is stored { - listCode := f.QueryListCodeWasm() + listCode := f.QueryListCodeWasm().CodeInfos require.Len(t, listCode, 1) //validate the hash is the same @@ -97,7 +97,7 @@ func TestLinkCLIWasmEscrow(t *testing.T) { // validate that there are no contract using the code (id=1) { - listContract := f.QueryListContractByCodeWasm(codeId) + listContract := f.QueryListContractByCodeWasm(codeId).ContractInfos require.Empty(t, listContract) } @@ -118,7 +118,7 @@ func TestLinkCLIWasmEscrow(t *testing.T) { // validate there is only one contract using codeId=1 and get contractAddress { - listContract := f.QueryListContractByCodeWasm(codeId) + listContract := f.QueryListContractByCodeWasm(codeId).ContractInfos require.Len(t, listContract, 1) contractAddress = listContract[0].GetAddress() } @@ -207,7 +207,7 @@ func TestLinkCLIWasmTokenTester(t *testing.T) { // validate there is only one contract using codeId=1 and get contractAddress { - listContract := f.QueryListContractByCodeWasm(codeId) + listContract := f.QueryListContractByCodeWasm(codeId).ContractInfos require.Len(t, listContract, 1) contractAddress = listContract[0].GetAddress() } @@ -626,7 +626,7 @@ func TestLinkCLIWasmTokenTesterProxy(t *testing.T) { // validate there is only one contract using codeId=1 and get contractAddress { - listContract := f.QueryListContractByCodeWasm(codeId) + listContract := f.QueryListContractByCodeWasm(codeId).ContractInfos require.Len(t, listContract, 1) contractAddress = listContract[0].GetAddress() } @@ -784,7 +784,7 @@ func TestLinkCLIWasmCollectionTester(t *testing.T) { // validate there is only one contract using codeId=1 and get contractAddress { - listContract := f.QueryListContractByCodeWasm(codeId) + listContract := f.QueryListContractByCodeWasm(codeId).ContractInfos require.Len(t, listContract, 1) contractAddress = listContract[0].GetAddress() } @@ -1550,7 +1550,7 @@ func TestLinkCLIWasmCollectionTesterProxy(t *testing.T) { // validate there is only one contract using codeId=1 and get contractAddress { - listContract := f.QueryListContractByCodeWasm(codeId) + listContract := f.QueryListContractByCodeWasm(codeId).ContractInfos require.Len(t, listContract, 1) contractAddress = listContract[0].GetAddress() } diff --git a/x/wasm/linkwasmd/cli_test/test_helpers.go b/x/wasm/linkwasmd/cli_test/test_helpers.go index 12d4a6acb8..425585e870 100644 --- a/x/wasm/linkwasmd/cli_test/test_helpers.go +++ b/x/wasm/linkwasmd/cli_test/test_helpers.go @@ -1326,23 +1326,23 @@ func (f *Fixtures) TxClearContractAdminWasm(contractAddress sdk.AccAddress, flag return executeWriteRetStdStreams(f.T, addFlags(cmd, flags)) } -func (f *Fixtures) QueryListCodeWasm() []wasmtypes.CodeInfoResponse { +func (f *Fixtures) QueryListCodeWasm() wasmtypes.QueryCodesResponse { cmd := fmt.Sprintf("%s query wasm list-code %s", f.LinkcliBinary, f.Flags()) res, errStr := tests.ExecuteT(f.T, cmd, "") require.Empty(f.T, errStr) cdc := app.MakeCodec() - var listCode []wasmtypes.CodeInfoResponse + var listCode wasmtypes.QueryCodesResponse err := cdc.UnmarshalJSON([]byte(res), &listCode) require.NoError(f.T, err) return listCode } -func (f *Fixtures) QueryListContractByCodeWasm(codeId uint64) []wasmtypes.ContractInfoResponse { +func (f *Fixtures) QueryListContractByCodeWasm(codeId uint64) wasmtypes.QueryContractsByCodeResponse { cmd := fmt.Sprintf("%s query wasm list-contract-by-code %d %s", f.LinkcliBinary, codeId, f.Flags()) res, errStr := tests.ExecuteT(f.T, cmd, "") require.Empty(f.T, errStr) cdc := app.MakeCodec() - var listContract []wasmtypes.ContractInfoResponse + var listContract wasmtypes.QueryContractsByCodeResponse err := cdc.UnmarshalJSON([]byte(res), &listContract) require.NoError(f.T, err) return listContract From b3f69cf6c8b016b5cb8a64a1c6053b91d7274963 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Thu, 11 Mar 2021 21:14:26 +0900 Subject: [PATCH 09/12] fix: apply review --- x/wasm/client/cli/query.go | 33 ++----------- x/wasm/client/rest/query.go | 38 ++------------- x/wasm/client/utils/utils.go | 80 +++++++++++++++++++++---------- x/wasm/internal/keeper/querier.go | 4 +- 4 files changed, 63 insertions(+), 92 deletions(-) diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index 60ae632724..8362c12460 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -57,16 +57,8 @@ func GetCmdListCode(cdc *codec.Codec) *cobra.Command { if err != nil { return err } - data := &types.QueryCodesRequest{ - Pagination: pageReq, - } - bs, err := cliCtx.Codec.MarshalJSON(data) - if err != nil { - return err - } - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListCode) - res, _, err := cliCtx.QueryWithData(route, bs) + res, _, err := utils.QueryCodeList(cliCtx, keeper.QueryListCode, pageReq) if err != nil { return err } @@ -94,20 +86,12 @@ func GetCmdListContractByCode(cdc *codec.Codec) *cobra.Command { return err } - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListContractByCode) pageReq, err := utils.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) if err != nil { return err } - data := &types.QueryContractsByCodeRequest{ - CodeID: codeID, - Pagination: pageReq, - } - bs, err := cliCtx.Codec.MarshalJSON(data) - if err != nil { - return err - } - res, _, err := cliCtx.QueryWithData(route, bs) + + res, _, err := utils.QueryContractsByCode(cliCtx, keeper.QueryListContractByCode, codeID, pageReq) if err != nil { return err } @@ -316,17 +300,8 @@ func GetCmdGetContractHistory(cdc *codec.Codec) *cobra.Command { if err != nil { return err } - data := &types.QueryContractHistoryRequest{ - Address: addr, - Pagination: pageReq, - } - bs, err := cliCtx.Codec.MarshalJSON(data) - if err != nil { - return err - } - route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryContractHistory, addr.String()) - res, _, err := cliCtx.QueryWithData(route, bs) + res, _, err := utils.QueryContractHistory(cliCtx, keeper.QueryContractHistory, addr, pageReq) if err != nil { return err } diff --git a/x/wasm/client/rest/query.go b/x/wasm/client/rest/query.go index 3a57ca2adb..609049f72a 100644 --- a/x/wasm/client/rest/query.go +++ b/x/wasm/client/rest/query.go @@ -37,7 +37,6 @@ func listCodesHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListCode) pageKey, offset, limit, page, countTotal, err := utils.ParseHTTPArgs(r) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -50,16 +49,7 @@ func listCodesHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - data := &types.QueryCodesRequest{ - Pagination: pageReq, - } - bs, err := cliCtx.Codec.MarshalJSON(data) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - res, height, err := cliCtx.QueryWithData(route, bs) + res, height, err := utils.QueryCodeList(cliCtx, keeper.QueryListCode, pageReq) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -110,7 +100,6 @@ func listContractsByCodeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListContractByCode) pageKey, offset, limit, page, countTotal, err := utils.ParseHTTPArgs(r) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -123,17 +112,7 @@ func listContractsByCodeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - data := &types.QueryContractsByCodeRequest{ - CodeID: codeID, - Pagination: pageReq, - } - bs, err := cliCtx.Codec.MarshalJSON(data) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - res, height, err := cliCtx.QueryWithData(route, bs) + res, height, err := utils.QueryContractsByCode(cliCtx, keeper.QueryListContractByCode, codeID, pageReq) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -281,7 +260,6 @@ func queryContractHistoryFn(cliCtx context.CLIContext) http.HandlerFunc { return } - route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryContractHistory, addr.String()) pageKey, offset, limit, page, countTotal, err := utils.ParseHTTPArgs(r) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -294,17 +272,7 @@ func queryContractHistoryFn(cliCtx context.CLIContext) http.HandlerFunc { return } - data := &types.QueryContractHistoryRequest{ - Address: addr, - Pagination: pageReq, - } - bs, err := cliCtx.Codec.MarshalJSON(data) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - res, height, err := cliCtx.QueryWithData(route, bs) + res, height, err := utils.QueryContractHistory(cliCtx, keeper.QueryContractHistory, addr, pageReq) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/x/wasm/client/utils/utils.go b/x/wasm/client/utils/utils.go index 3d3e5415bb..f8bb2f3ae1 100644 --- a/x/wasm/client/utils/utils.go +++ b/x/wasm/client/utils/utils.go @@ -12,6 +12,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + sdk "github.com/line/lbm-sdk/types" + + "github.com/line/lbm-sdk/client/context" sdkerrors "github.com/line/lbm-sdk/types/errors" "github.com/line/lbm-sdk/types/rest" "github.com/line/lbm-sdk/x/wasm/internal/types" @@ -123,7 +126,57 @@ func NewPageRequest(pageKey string, offset, limit, page uint64, countTotal bool) }, nil } +func QueryCodeList(cliCtx context.CLIContext, path string, pageReq *types.PageRequest) ([]byte, int64, error) { + data := &types.QueryCodesRequest{ + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + return nil, 0, err + } + + return Query(cliCtx, bs, path) +} + +func QueryContractsByCode(cliCtx context.CLIContext, path string, codeID uint64, pageReq *types.PageRequest) ([]byte, int64, error) { + data := &types.QueryContractsByCodeRequest{ + CodeID: codeID, + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + return nil, 0, err + } + + return Query(cliCtx, bs, path) +} + +func QueryContractHistory(cliCtx context.CLIContext, path string, addr sdk.AccAddress, pageReq *types.PageRequest) ([]byte, int64, error) { + data := &types.QueryContractHistoryRequest{ + Address: addr, + Pagination: pageReq, + } + bs, err := cliCtx.Codec.MarshalJSON(data) + if err != nil { + return nil, 0, err + } + + return Query(cliCtx, bs, path) +} + +func Query(cliCtx context.CLIContext, data []byte, queryPath string) ([]byte, int64, error) { + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, queryPath) + return cliCtx.QueryWithData(route, data) +} + func ParseHTTPArgs(r *http.Request) (pageKey string, offset, limit, page uint64, countTotal bool, err error) { + _, p, l, err := rest.ParseHTTPArgs(r) + if err != nil { + return pageKey, offset, limit, page, countTotal, err + } + page = uint64(p) + limit = uint64(l) + pageKey = r.FormValue("page-key") offsetStr := r.FormValue("offset") @@ -131,36 +184,11 @@ func ParseHTTPArgs(r *http.Request) (pageKey string, offset, limit, page uint64, offset, err = strconv.ParseUint(offsetStr, 10, 64) if err != nil { return pageKey, offset, limit, page, countTotal, err - } - if offset <= 0 { + } else if offset <= 0 { return pageKey, offset, limit, page, countTotal, errors.New("offset must greater than 0") } } - pageStr := r.FormValue("page") - if pageStr == "" { - page = rest.DefaultPage - } else { - page, err = strconv.ParseUint(pageStr, 10, 64) - if err != nil { - return pageKey, offset, limit, page, countTotal, err - } else if page <= 0 { - return pageKey, offset, limit, page, countTotal, errors.New("page must greater than 0") - } - } - - limitStr := r.FormValue("limit") - if limitStr == "" { - limit = rest.DefaultLimit - } else { - limit, err = strconv.ParseUint(limitStr, 10, 64) - if err != nil { - return pageKey, offset, limit, page, countTotal, err - } else if limit <= 0 { - return pageKey, offset, limit, page, countTotal, errors.New("limit must greater than 0") - } - } - countTotalStr := r.FormValue("count-total") if countTotalStr != "" { countTotal, err = strconv.ParseBool(countTotalStr) diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index b8393c3ed4..b659557c0c 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -46,7 +46,7 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryListCode: return queryCodeList(ctx, req, keeper) case QueryContractHistory: - return queryContractHistory(ctx, path[1], req, keeper) + return queryContractHistory(ctx, req, keeper) default: return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown data query endpoint") } @@ -173,7 +173,7 @@ func queryCodeList(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byt return bz, nil } -func queryContractHistory(ctx sdk.Context, _ string, req abci.RequestQuery, keeper Keeper) ([]byte, error) { +func queryContractHistory(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { var params types.QueryContractHistoryRequest if err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { From d037d184d5d2ba2a929d0b81bb33488493e997e5 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Fri, 12 Mar 2021 15:04:27 +0900 Subject: [PATCH 10/12] refactor: move filteredPaginate to keeper/querier.go --- x/wasm/internal/keeper/keeper.go | 102 ---------------------------- x/wasm/internal/keeper/querier.go | 109 +++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 105 deletions(-) diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 2e2ba2e105..aa63ac67c8 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -3,7 +3,6 @@ package keeper import ( "bytes" "encoding/binary" - "fmt" "path/filepath" wasm "github.com/CosmWasm/wasmvm" @@ -20,7 +19,6 @@ import ( "github.com/pkg/errors" "github.com/tendermint/tendermint/crypto" - storeTypes "github.com/line/lbm-sdk/store/types" "github.com/line/lbm-sdk/x/wasm/client/utils" "github.com/line/lbm-sdk/x/wasm/internal/types" ) @@ -700,103 +698,3 @@ func gasMeter(ctx sdk.Context) MultipiedGasMeter { originalMeter: ctx.GasMeter(), } } - -// NOTE: This function is implemented in cosmos-sdk v0.40.0. -// If you want to update cosmos-sdk to 0.40.0 or later, you can use the sdk function. -func FilteredPaginate( - prefixStore storeTypes.KVStore, - pageRequest *types.PageRequest, - onResult func(key []byte, value []byte, accumulate bool) (bool, error), -) (*types.PageResponse, error) { - // if the PageRequest is nil, use default PageRequest - if pageRequest == nil { - pageRequest = &types.PageRequest{} - } - - offset := pageRequest.Offset - key := pageRequest.Key - limit := pageRequest.Limit - countTotal := pageRequest.CountTotal - - if offset > 0 && key != nil { - return nil, fmt.Errorf("invalid request, either offset or key is expected, got both") - } - - if limit == 0 { - limit = DefaultLimit - - // count total results when the limit is zero/not supplied - countTotal = true - } - - if len(key) != 0 { - iterator := prefixStore.Iterator(key, nil) - defer iterator.Close() - - var numHits uint64 - var nextKey []byte - - for ; iterator.Valid(); iterator.Next() { - if numHits == limit { - nextKey = iterator.Key() - break - } - - if iterator.Error() != nil { - return nil, iterator.Error() - } - - hit, err := onResult(iterator.Key(), iterator.Value(), true) - if err != nil { - return nil, err - } - - if hit { - numHits++ - } - } - - return &types.PageResponse{ - NextKey: nextKey, - }, nil - } - - iterator := prefixStore.Iterator(nil, nil) - defer iterator.Close() - - end := offset + limit - - var numHits uint64 - var nextKey []byte - - for ; iterator.Valid(); iterator.Next() { - if iterator.Error() != nil { - return nil, iterator.Error() - } - - accumulate := numHits >= offset && numHits < end - hit, err := onResult(iterator.Key(), iterator.Value(), accumulate) - if err != nil { - return nil, err - } - - if hit { - numHits++ - } - - if numHits == end+1 { - nextKey = iterator.Key() - - if !countTotal { - break - } - } - } - - res := &types.PageResponse{NextKey: nextKey} - if countTotal { - res.Total = numHits - } - - return res, nil -} diff --git a/x/wasm/internal/keeper/querier.go b/x/wasm/internal/keeper/querier.go index b659557c0c..b0f496ca40 100644 --- a/x/wasm/internal/keeper/querier.go +++ b/x/wasm/internal/keeper/querier.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/binary" + "fmt" "strconv" "github.com/line/lbm-sdk/codec" @@ -10,6 +11,8 @@ import ( sdkerrors "github.com/line/lbm-sdk/types/errors" abci "github.com/tendermint/tendermint/abci/types" + storeTypes "github.com/line/lbm-sdk/store/types" + "github.com/line/lbm-sdk/x/wasm/internal/types" ) @@ -198,7 +201,7 @@ func queryContractHistory(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) func (k Keeper) codes(ctx sdk.Context, req *types.QueryCodesRequest) (*types.QueryCodesResponse, error) { r := make([]types.CodeInfoResponse, 0) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.CodeKeyPrefix) - pageRes, err := FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { + pageRes, err := filteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { if accumulate { var c types.CodeInfo if err := k.cdc.UnmarshalBinaryBare(value, &c); err != nil { @@ -217,7 +220,7 @@ func (k Keeper) codes(ctx sdk.Context, req *types.QueryCodesRequest) (*types.Que func (k Keeper) contractsByCode(ctx sdk.Context, req *types.QueryContractsByCodeRequest) (*types.QueryContractsByCodeResponse, error) { r := make([]types.ContractInfoResponse, 0) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractByCodeIDSecondaryIndexPrefix(req.CodeID)) - pageRes, err := FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { + pageRes, err := filteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { var contractAddr sdk.AccAddress = key[types.AbsoluteTxPositionLen:] c := k.GetContractInfo(ctx, contractAddr) if c == nil { @@ -242,7 +245,7 @@ func (k Keeper) contractHistory(ctx sdk.Context, req *types.QueryContractHistory r := make([]types.ContractCodeHistoryEntry, 0) prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(req.Address)) - pageRes, err := FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { + pageRes, err := filteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { if accumulate { var e types.ContractCodeHistoryEntry if err := k.cdc.UnmarshalBinaryBare(value, &e); err != nil { @@ -261,3 +264,103 @@ func (k Keeper) contractHistory(ctx sdk.Context, req *types.QueryContractHistory Pagination: pageRes, }, nil } + +// NOTE: This function is implemented in cosmos-sdk v0.40.0. +// If you want to update cosmos-sdk to 0.40.0 or later, you can use the sdk function. +func filteredPaginate( + prefixStore storeTypes.KVStore, + pageRequest *types.PageRequest, + onResult func(key []byte, value []byte, accumulate bool) (bool, error), +) (*types.PageResponse, error) { + // if the PageRequest is nil, use default PageRequest + if pageRequest == nil { + pageRequest = &types.PageRequest{} + } + + offset := pageRequest.Offset + key := pageRequest.Key + limit := pageRequest.Limit + countTotal := pageRequest.CountTotal + + if offset > 0 && key != nil { + return nil, fmt.Errorf("invalid request, either offset or key is expected, got both") + } + + if limit == 0 { + limit = DefaultLimit + + // count total results when the limit is zero/not supplied + countTotal = true + } + + if len(key) != 0 { + iterator := prefixStore.Iterator(key, nil) + defer iterator.Close() + + var numHits uint64 + var nextKey []byte + + for ; iterator.Valid(); iterator.Next() { + if numHits == limit { + nextKey = iterator.Key() + break + } + + if iterator.Error() != nil { + return nil, iterator.Error() + } + + hit, err := onResult(iterator.Key(), iterator.Value(), true) + if err != nil { + return nil, err + } + + if hit { + numHits++ + } + } + + return &types.PageResponse{ + NextKey: nextKey, + }, nil + } + + iterator := prefixStore.Iterator(nil, nil) + defer iterator.Close() + + end := offset + limit + + var numHits uint64 + var nextKey []byte + + for ; iterator.Valid(); iterator.Next() { + if iterator.Error() != nil { + return nil, iterator.Error() + } + + accumulate := numHits >= offset && numHits < end + hit, err := onResult(iterator.Key(), iterator.Value(), accumulate) + if err != nil { + return nil, err + } + + if hit { + numHits++ + } + + if numHits == end+1 { + nextKey = iterator.Key() + + if !countTotal { + break + } + } + } + + res := &types.PageResponse{NextKey: nextKey} + if countTotal { + res.Total = numHits + } + + return res, nil +} From c35995a1af42fa09f3a66d446c2a1bdb0f9814b3 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Fri, 12 Mar 2021 16:47:24 +0900 Subject: [PATCH 11/12] fix: fix offset condition --- x/wasm/client/utils/utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/wasm/client/utils/utils.go b/x/wasm/client/utils/utils.go index f8bb2f3ae1..453df6d66d 100644 --- a/x/wasm/client/utils/utils.go +++ b/x/wasm/client/utils/utils.go @@ -184,7 +184,8 @@ func ParseHTTPArgs(r *http.Request) (pageKey string, offset, limit, page uint64, offset, err = strconv.ParseUint(offsetStr, 10, 64) if err != nil { return pageKey, offset, limit, page, countTotal, err - } else if offset <= 0 { + } + if offset < 0 { return pageKey, offset, limit, page, countTotal, errors.New("offset must greater than 0") } } From 0d363469b3dd71457f8f8ea2ef7da982a6d26307 Mon Sep 17 00:00:00 2001 From: "shiki.takahashi" Date: Fri, 12 Mar 2021 17:06:00 +0900 Subject: [PATCH 12/12] fix: fix lint error --- x/wasm/client/utils/utils.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x/wasm/client/utils/utils.go b/x/wasm/client/utils/utils.go index 453df6d66d..7763faa559 100644 --- a/x/wasm/client/utils/utils.go +++ b/x/wasm/client/utils/utils.go @@ -4,7 +4,6 @@ import ( "bytes" "compress/gzip" "encoding/binary" - "errors" "fmt" "net/http" "strconv" @@ -185,9 +184,6 @@ func ParseHTTPArgs(r *http.Request) (pageKey string, offset, limit, page uint64, if err != nil { return pageKey, offset, limit, page, countTotal, err } - if offset < 0 { - return pageKey, offset, limit, page, countTotal, errors.New("offset must greater than 0") - } } countTotalStr := r.FormValue("count-total")