From 6e2f0546cc4ceeb604fd6b83ec072b4a42f2d160 Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Wed, 19 Apr 2023 12:30:44 -0600 Subject: [PATCH] add stargate query handler to wasmkeeper (#1482) (#1490) Co-authored-by: kwt <4344285+kwtalley@users.noreply.github.com> --- CHANGELOG.md | 1 + app/app.go | 2 +- go.mod | 2 +- internal/provwasm/query_plugins.go | 60 ++++++++- internal/provwasm/stargate_whitelist.go | 154 ++++++++++++++++++++++++ 5 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 internal/provwasm/stargate_whitelist.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb4bb7a3b..531eead253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * Metadata party rollup and optional parties [#1438](https://github.com/provenance-io/provenance/issues/1438). * Repeated roles in a spec require multiple different parties [#1437](https://github.com/provenance-io/provenance/issues/1437). * The `PROVENANCE` role can only be used by smart contract addresses, and vice versa [#1381](https://github.com/provenance-io/provenance/issues/1381). +* Add stargate query from wasm support [#1481](https://github.com/provenance-io/provenance/issues/1481). ### Improvements diff --git a/app/app.go b/app/app.go index 677f6b0763..1e2e4ef16d 100644 --- a/app/app.go +++ b/app/app.go @@ -536,7 +536,7 @@ func New( wasmConfig, supportedFeatures, authtypes.NewModuleAddress(govtypes.ModuleName).String(), - wasmkeeper.WithQueryPlugins(provwasm.QueryPlugins(querierRegistry)), + wasmkeeper.WithQueryPlugins(provwasm.QueryPlugins(querierRegistry, *app.GRPCQueryRouter(), appCodec)), wasmkeeper.WithMessageEncoders(provwasm.MessageEncoders(encoderRegistry, logger)), ) diff --git a/go.mod b/go.mod index 41d3ad8f63..6498435b36 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0 github.com/CosmWasm/wasmd v0.29.0 + github.com/CosmWasm/wasmvm v1.1.2 github.com/armon/go-metrics v0.4.1 github.com/cosmos/cosmos-proto v1.0.0-beta.1 github.com/cosmos/cosmos-sdk v0.46.7 @@ -47,7 +48,6 @@ require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect - github.com/CosmWasm/wasmvm v1.1.2 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/aws/aws-sdk-go v1.44.122 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/internal/provwasm/query_plugins.go b/internal/provwasm/query_plugins.go index 1f6e08b8b9..878a1b8222 100644 --- a/internal/provwasm/query_plugins.go +++ b/internal/provwasm/query_plugins.go @@ -6,7 +6,12 @@ import ( "fmt" "github.com/CosmWasm/wasmd/x/wasm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -38,9 +43,10 @@ func (qr *QuerierRegistry) RegisterQuerier(route string, querier Querier) { } // QueryPlugins provides provenance query support for smart contracts. -func QueryPlugins(registry *QuerierRegistry) *wasm.QueryPlugins { +func QueryPlugins(registry *QuerierRegistry, queryRouter baseapp.GRPCQueryRouter, codec codec.Codec) *wasm.QueryPlugins { return &wasm.QueryPlugins{ - Custom: customPlugins(registry), + Custom: customPlugins(registry), + Stargate: StargateQuerier(queryRouter, codec), } } @@ -74,3 +80,53 @@ func customPlugins(registry *QuerierRegistry) wasm.CustomQuerier { return bz, nil } } + +// StargateQuerier dispatches whitelisted stargate queries +func StargateQuerier(queryRouter baseapp.GRPCQueryRouter, cdc codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + protoResponseType, err := GetWhitelistedQuery(request.Path) + if err != nil { + return nil, err + } + + route := queryRouter.Route(request.Path) + if route == nil { + return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", request.Path)} + } + + res, err := route(ctx, abci.RequestQuery{ + Data: request.Data, + Path: request.Path, + }) + if err != nil { + return nil, err + } + + bz, err := ConvertProtoToJSONMarshal(protoResponseType, res.Value, cdc) + if err != nil { + return nil, err + } + + return bz, nil + } +} + +// ConvertProtoToJsonMarshal unmarshals the given bytes into a proto message and then marshals it to json. +// This is done so that clients calling stargate queries do not need to define their own proto unmarshalers, +// being able to use response directly by json marshaling, which is supported in cosmwasm. +func ConvertProtoToJSONMarshal(protoResponseType codec.ProtoMarshaler, bz []byte, cdc codec.Codec) ([]byte, error) { + // unmarshal binary into stargate response data structure + err := cdc.Unmarshal(bz, protoResponseType) + if err != nil { + return nil, wasmvmtypes.Unknown{} + } + + bz, err = cdc.MarshalJSON(protoResponseType) + if err != nil { + return nil, wasmvmtypes.Unknown{} + } + + protoResponseType.Reset() + + return bz, nil +} diff --git a/internal/provwasm/stargate_whitelist.go b/internal/provwasm/stargate_whitelist.go new file mode 100644 index 0000000000..aac4aa850b --- /dev/null +++ b/internal/provwasm/stargate_whitelist.go @@ -0,0 +1,154 @@ +package provwasm + +import ( + "fmt" + "sync" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + "github.com/cosmos/cosmos-sdk/codec" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctransfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + + attributetypes "github.com/provenance-io/provenance/x/attribute/types" + markertypes "github.com/provenance-io/provenance/x/marker/types" + metadatatypes "github.com/provenance-io/provenance/x/metadata/types" + msgfeestypes "github.com/provenance-io/provenance/x/msgfees/types" + nametypes "github.com/provenance-io/provenance/x/name/types" + rewardtypes "github.com/provenance-io/provenance/x/reward/types" +) + +// stargateWhitelist keeps whitelist and its deterministic +// response binding for stargate queries. +// +// The query can be multi-thread, so we have to use +// thread safe sync.Map. +var stargateWhitelist sync.Map + +// Note: When adding a migration here, we should also add it to the Async ICQ params in the upgrade. +// In the future we may want to find a better way to keep these in sync + +func init() { + // ibc queries + setWhitelistedQuery("/ibc.applications.transfer.v1.Query/DenomTrace", &ibctransfertypes.QueryDenomTraceResponse{}) + + // ========================================================== + // cosmos-sdk queries + // ========================================================== + + // auth + setWhitelistedQuery("/cosmos.auth.v1beta1.Query/Account", &authtypes.QueryAccountResponse{}) + setWhitelistedQuery("/cosmos.auth.v1beta1.Query/Params", &authtypes.QueryParamsResponse{}) + + // bank + setWhitelistedQuery("/cosmos.bank.v1beta1.Query/Balance", &banktypes.QueryBalanceResponse{}) + setWhitelistedQuery("/cosmos.bank.v1beta1.Query/DenomMetadata", &banktypes.QueryDenomsMetadataResponse{}) + setWhitelistedQuery("/cosmos.bank.v1beta1.Query/Params", &banktypes.QueryParamsResponse{}) + setWhitelistedQuery("/cosmos.bank.v1beta1.Query/SupplyOf", &banktypes.QuerySupplyOfResponse{}) + + // distribution + setWhitelistedQuery("/cosmos.distribution.v1beta1.Query/Params", &distributiontypes.QueryParamsResponse{}) + setWhitelistedQuery("/cosmos.distribution.v1beta1.Query/DelegatorWithdrawAddress", &distributiontypes.QueryDelegatorWithdrawAddressResponse{}) + setWhitelistedQuery("/cosmos.distribution.v1beta1.Query/ValidatorCommission", &distributiontypes.QueryValidatorCommissionResponse{}) + + // gov + setWhitelistedQuery("/cosmos.gov.v1beta1.Query/Deposit", &govtypes.QueryDepositResponse{}) + setWhitelistedQuery("/cosmos.gov.v1beta1.Query/Params", &govtypes.QueryParamsResponse{}) + setWhitelistedQuery("/cosmos.gov.v1beta1.Query/Vote", &govtypes.QueryVoteResponse{}) + + // slashing + setWhitelistedQuery("/cosmos.slashing.v1beta1.Query/Params", &slashingtypes.QueryParamsResponse{}) + setWhitelistedQuery("/cosmos.slashing.v1beta1.Query/SigningInfo", &slashingtypes.QuerySigningInfoResponse{}) + + // staking + setWhitelistedQuery("/cosmos.staking.v1beta1.Query/Delegation", &stakingtypes.QueryDelegationResponse{}) + setWhitelistedQuery("/cosmos.staking.v1beta1.Query/Params", &stakingtypes.QueryParamsResponse{}) + setWhitelistedQuery("/cosmos.staking.v1beta1.Query/Validator", &stakingtypes.QueryValidatorResponse{}) + + // ========================================================== + // provenance queries + // ========================================================== + + // attribute + setWhitelistedQuery("/provenance.attribute.v1.Query/Params", &attributetypes.QueryParamsResponse{}) + setWhitelistedQuery("/provenance.attribute.v1.Query/Attribute", &attributetypes.QueryAttributeResponse{}) + setWhitelistedQuery("/provenance.attribute.v1.Query/Attributes", &attributetypes.QueryAttributesResponse{}) + setWhitelistedQuery("/provenance.attribute.v1.Query/Scan", &attributetypes.QueryScanResponse{}) + + // marker + setWhitelistedQuery("/provenance.marker.v1.Query/Params", &markertypes.QueryParamsResponse{}) + setWhitelistedQuery("/provenance.marker.v1.Query/Marker", &markertypes.QueryMarkerResponse{}) + setWhitelistedQuery("/provenance.marker.v1.Query/Holding", &markertypes.QueryHoldingResponse{}) + setWhitelistedQuery("/provenance.marker.v1.Query/Supply", &markertypes.QuerySupplyResponse{}) + setWhitelistedQuery("/provenance.marker.v1.Query/Escrow", &markertypes.QueryEscrowResponse{}) + setWhitelistedQuery("/provenance.marker.v1.Query/Access", &markertypes.QueryAccessResponse{}) + setWhitelistedQuery("/provenance.marker.v1.Query/DenomMetadata", &markertypes.QueryDenomMetadataResponse{}) + + // metadata + setWhitelistedQuery("/provenance.metadata.v1.QueryParams", &metadatatypes.QueryParamsResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/Scope", &metadatatypes.ScopeResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/Sessions", &metadatatypes.SessionsResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/Records", &metadatatypes.RecordsResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/Ownership", &metadatatypes.OwnershipResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/ValueOwnership", &metadatatypes.ValueOwnershipResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/ScopeSpecification", &metadatatypes.ScopeSpecificationResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/ContractSpecification", &metadatatypes.ContractSpecificationResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/RecordSpecificationsForContractSpecification", &metadatatypes.RecordSpecificationsForContractSpecificationResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/RecordSpecification", &metadatatypes.RecordSpecificationResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/OSLocatorParams", &metadatatypes.OSLocatorParamsResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/OSLocator", &metadatatypes.OSLocatorResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/OSLocatorsByURI", &metadatatypes.OSLocatorsByURIResponse{}) + setWhitelistedQuery("/provenance.metadata.v1.Query/OSLocatorsByScope", &metadatatypes.OSLocatorsByScopeResponse{}) + + // msg fee + setWhitelistedQuery("/provenance.msgfees.v1.Query/Params", &msgfeestypes.QueryParamsResponse{}) + + // name + setWhitelistedQuery("/provenance.name.v1.Query/Params", &nametypes.QueryParamsResponse{}) + setWhitelistedQuery("/provenance.name.v1.Query/Resolve", &nametypes.QueryResolveResponse{}) + setWhitelistedQuery("/provenance.name.v1.Query/ReverseLookup", &nametypes.QueryReverseLookupResponse{}) + + // reward + setWhitelistedQuery("/provenance.reward.v1.Query/RewardProgramByID", &rewardtypes.QueryRewardProgramByIDResponse{}) + setWhitelistedQuery("/provenance.reward.v1.Query/RewardPrograms", &rewardtypes.QueryRewardProgramsResponse{}) + setWhitelistedQuery("/provenance.reward.v1.Query/ClaimPeriodRewardDistributions", &rewardtypes.QueryClaimPeriodRewardDistributionsResponse{}) + setWhitelistedQuery("/provenance.reward.v1.Query/ClaimPeriodRewardDistributionsByID", &rewardtypes.QueryClaimPeriodRewardDistributionsByIDResponse{}) + setWhitelistedQuery("/provenance.reward.v1.Query/RewardDistributionsByAddress", &rewardtypes.QueryRewardDistributionsByAddressResponse{}) +} + +// GetWhitelistedQuery returns the whitelisted query at the provided path. +// If the query does not exist, or it was setup wrong by the chain, this returns an error. +func GetWhitelistedQuery(queryPath string) (codec.ProtoMarshaler, error) { + protoResponseAny, isWhitelisted := stargateWhitelist.Load(queryPath) + if !isWhitelisted { + return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", queryPath)} + } + protoResponseType, ok := protoResponseAny.(codec.ProtoMarshaler) + if !ok { + return nil, wasmvmtypes.Unknown{} + } + return protoResponseType, nil +} + +func setWhitelistedQuery(queryPath string, protoType codec.ProtoMarshaler) { + stargateWhitelist.Store(queryPath, protoType) +} + +func GetStargateWhitelistedPaths() (keys []string) { + // Iterate over the map and collect the keys + stargateWhitelist.Range(func(key, value interface{}) bool { + keyStr, ok := key.(string) + if !ok { + panic("key is not a string") + } + keys = append(keys, keyStr) + return true + }) + + return keys +}