feat!: remove legacy REST (#9594)
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description ref: #7517 * [x] Remove the x/{module}/client/rest folder * [x] Remove all glue code between simapp/modules and the REST server <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - see #9615 - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [x] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [x] reviewed API design and naming - [ ] reviewed documentation is accurate - see #9615 - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
2c6b866551
commit
cd221680c0
|
@ -53,6 +53,14 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||||
* [\#9246](https://github.com/cosmos/cosmos-sdk/pull/9246) The `New` method for the network package now returns an error.
|
* [\#9246](https://github.com/cosmos/cosmos-sdk/pull/9246) The `New` method for the network package now returns an error.
|
||||||
* (codec) [\#9521](https://github.com/cosmos/cosmos-sdk/pull/9521) Removed deprecated `clientCtx.JSONCodec` from `client.Context`.
|
* (codec) [\#9521](https://github.com/cosmos/cosmos-sdk/pull/9521) Removed deprecated `clientCtx.JSONCodec` from `client.Context`.
|
||||||
* (codec) [\#9521](https://github.com/cosmos/cosmos-sdk/pull/9521) Rename `EncodingConfig.Marshaler` to `Codec`.
|
* (codec) [\#9521](https://github.com/cosmos/cosmos-sdk/pull/9521) Rename `EncodingConfig.Marshaler` to `Codec`.
|
||||||
|
* [\#9594](https://github.com/cosmos/cosmos-sdk/pull/9594) `RESTHandlerFn` argument is removed from the `gov/NewProposalHandler`.
|
||||||
|
* [\#9594](https://github.com/cosmos/cosmos-sdk/pull/9594) `types/rest` package moved to `testutil/rest`.
|
||||||
|
|
||||||
|
|
||||||
|
### Client Breaking Changes
|
||||||
|
|
||||||
|
* [\#9594](https://github.com/cosmos/cosmos-sdk/pull/9594) Remove legacy REST API. Please see the [REST Endpoints Migration guide](https://docs.cosmos.network/master/migrations/rest.html) to migrate to the new REST endpoints.
|
||||||
|
|
||||||
|
|
||||||
### CLI Breaking Changes
|
### CLI Breaking Changes
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
{
|
{
|
||||||
"swagger": "2.0",
|
"swagger": "2.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Cosmos SDK - Legacy REST and gRPC Gateway docs",
|
"title": "Cosmos SDK - gRPC Gateway docs",
|
||||||
"description": "A REST interface for state queries, legacy transactions",
|
"description": "A REST interface for state queries",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
},
|
},
|
||||||
"apis": [
|
"apis": [
|
||||||
{
|
|
||||||
"url": "./client/docs/swagger_legacy.yaml",
|
|
||||||
"dereference": {
|
|
||||||
"circular": "ignore"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"url": "./tmp-swagger-gen/cosmos/auth/v1beta1/query.swagger.json",
|
"url": "./tmp-swagger-gen/cosmos/auth/v1beta1/query.swagger.json",
|
||||||
"operationIds": {
|
"operationIds": {
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,7 @@ import (
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||||
qtypes "github.com/cosmos/cosmos-sdk/types/query"
|
qtypes "github.com/cosmos/cosmos-sdk/types/query"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
"github.com/cosmos/cosmos-sdk/testutil/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/version"
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeprecationURL is the URL for migrating deprecated REST endpoints to newer ones.
|
|
||||||
// TODO Switch to `/` (not `/master`) once v0.40 docs are deployed.
|
|
||||||
// https://github.com/cosmos/cosmos-sdk/issues/8019
|
|
||||||
const DeprecationURL = "https://docs.cosmos.network/master/migrations/rest.html"
|
|
||||||
|
|
||||||
// addHTTPDeprecationHeaders is a mux middleware function for adding HTTP
|
|
||||||
// Deprecation headers to a http handler
|
|
||||||
func addHTTPDeprecationHeaders(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Deprecation", "true")
|
|
||||||
w.Header().Set("Link", "<"+DeprecationURL+">; rel=\"deprecation\"")
|
|
||||||
w.Header().Set("Warning", "199 - \"this endpoint is deprecated and may not work as before, see deprecation link for more info\"")
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHTTPDeprecationHeaders returns a new *mux.Router, identical to its input
|
|
||||||
// but with the addition of HTTP Deprecation headers. This is used to mark legacy
|
|
||||||
// amino REST endpoints as deprecated in the REST API.
|
|
||||||
// nolint: gocritic
|
|
||||||
func WithHTTPDeprecationHeaders(r *mux.Router) *mux.Router {
|
|
||||||
subRouter := r.NewRoute().Subrouter()
|
|
||||||
subRouter.Use(addHTTPDeprecationHeaders)
|
|
||||||
return subRouter
|
|
||||||
}
|
|
|
@ -3,16 +3,13 @@ package rpc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BlockCommand returns the verified block data for a given heights
|
// BlockCommand returns the verified block data for a given heights
|
||||||
|
@ -88,47 +85,3 @@ func GetChainHeight(clientCtx client.Context) (int64, error) {
|
||||||
height := status.SyncInfo.LatestBlockHeight
|
height := status.SyncInfo.LatestBlockHeight
|
||||||
return height, nil
|
return height, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// REST handler to get a block
|
|
||||||
func BlockRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
|
|
||||||
height, err := strconv.ParseInt(vars["height"], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest,
|
|
||||||
"couldn't parse block height. Assumed format is '/block/{height}'.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chainHeight, err := GetChainHeight(clientCtx)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, "failed to parse chain height")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if height > chainHeight {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusNotFound, "requested block height is bigger then the chain length")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := getBlock(clientCtx, &height)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// REST handler to get the latest block
|
|
||||||
func LatestBlockRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
output, err := getBlock(clientCtx, nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register REST endpoints.
|
|
||||||
func RegisterRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc("/node_info", NodeInfoRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/syncing", NodeSyncingRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/blocks/latest", LatestBlockRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/blocks/{height}", BlockRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/validatorsets/latest", LatestValidatorSetRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/validatorsets/{height}", ValidatorSetRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
}
|
|
|
@ -5,13 +5,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||||
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
|
||||||
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
type IntegrationTestSuite struct {
|
||||||
|
@ -46,17 +43,6 @@ func (s *IntegrationTestSuite) TestStatusCommand() {
|
||||||
s.Require().Contains(out.String(), fmt.Sprintf("\"moniker\":\"%s\"", val0.Moniker))
|
s.Require().Contains(out.String(), fmt.Sprintf("\"moniker\":\"%s\"", val0.Moniker))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestLatestBlocks() {
|
|
||||||
val0 := s.network.Validators[0]
|
|
||||||
|
|
||||||
res, err := rest.GetRequest(fmt.Sprintf("%s/blocks/latest", val0.APIAddress))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var result ctypes.ResultBlock
|
|
||||||
err = legacy.Cdc.UnmarshalJSON(res, &result)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
func TestIntegrationTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
suite.Run(t, new(IntegrationTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
@ -14,8 +13,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidatorInfo is info about the node's validator, same as Tendermint,
|
// ValidatorInfo is info about the node's validator, same as Tendermint,
|
||||||
|
@ -88,45 +85,3 @@ func getNodeStatus(clientCtx client.Context) (*ctypes.ResultStatus, error) {
|
||||||
|
|
||||||
return node.Status(context.Background())
|
return node.Status(context.Background())
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfoResponse defines a response type that contains node status and version
|
|
||||||
// information.
|
|
||||||
type NodeInfoResponse struct {
|
|
||||||
p2p.DefaultNodeInfo `json:"node_info"`
|
|
||||||
|
|
||||||
ApplicationVersion version.Info `json:"application_version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// REST handler for node info
|
|
||||||
func NodeInfoRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
status, err := getNodeStatus(clientCtx)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := NodeInfoResponse{
|
|
||||||
DefaultNodeInfo: status.NodeInfo,
|
|
||||||
ApplicationVersion: version.NewInfo(),
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, resp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncingResponse defines a response type that contains node syncing information.
|
|
||||||
type SyncingResponse struct {
|
|
||||||
Syncing bool `json:"syncing"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// REST handler for node syncing
|
|
||||||
func NodeSyncingRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
status, err := getNodeStatus(clientCtx)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, SyncingResponse{Syncing: status.SyncInfo.CatchingUp})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,11 +3,9 @@ package rpc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
@ -16,7 +14,7 @@ import (
|
||||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO these next two functions feel kinda hacky based on their placement
|
// TODO these next two functions feel kinda hacky based on their placement
|
||||||
|
@ -60,7 +58,7 @@ func ValidatorCommand() *cobra.Command {
|
||||||
|
|
||||||
cmd.Flags().StringP(flags.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
cmd.Flags().StringP(flags.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||||
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
|
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
|
||||||
cmd.Flags().Int(flags.FlagPage, rest.DefaultPage, "Query a specific page of paginated results")
|
cmd.Flags().Int(flags.FlagPage, query.DefaultPage, "Query a specific page of paginated results")
|
||||||
cmd.Flags().Int(flags.FlagLimit, 100, "Query number of results returned per page")
|
cmd.Flags().Int(flags.FlagLimit, 100, "Query number of results returned per page")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
@ -148,57 +146,3 @@ func GetValidators(ctx context.Context, clientCtx client.Context, height *int64,
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// REST
|
|
||||||
|
|
||||||
// Validator Set at a height REST handler
|
|
||||||
func ValidatorSetRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 100)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse pagination parameters")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
height, err := strconv.ParseInt(vars["height"], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse block height")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chainHeight, err := GetChainHeight(clientCtx)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, "failed to parse chain height")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if height > chainHeight {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusNotFound, "requested block height is bigger then the chain length")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := GetValidators(r.Context(), clientCtx, &height, &page, &limit)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rest.PostProcessResponse(w, clientCtx, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Latest Validator Set REST handler
|
|
||||||
func LatestValidatorSetRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 100)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse pagination parameters")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := GetValidators(r.Context(), clientCtx, nil, &page, &limit)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,19 +5,16 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
gogogrpc "github.com/gogo/protobuf/grpc"
|
gogogrpc "github.com/gogo/protobuf/grpc"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/input"
|
"github.com/cosmos/cosmos-sdk/client/input"
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||||
|
@ -115,75 +112,6 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
|
||||||
return clientCtx.PrintProto(res)
|
return clientCtx.PrintProto(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteGeneratedTxResponse writes a generated unsigned transaction to the
|
|
||||||
// provided http.ResponseWriter. It will simulate gas costs if requested by the
|
|
||||||
// BaseReq. Upon any error, the error will be written to the http.ResponseWriter.
|
|
||||||
// Note that this function returns the legacy StdTx Amino JSON format for compatibility
|
|
||||||
// with legacy clients.
|
|
||||||
// Deprecated: We are removing Amino soon.
|
|
||||||
func WriteGeneratedTxResponse(
|
|
||||||
clientCtx client.Context, w http.ResponseWriter, br rest.BaseReq, msgs ...sdk.Msg,
|
|
||||||
) {
|
|
||||||
gasAdj, ok := rest.ParseFloat64OrReturnBadRequest(w, br.GasAdjustment, flags.DefaultGasAdjustment)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gasSetting, err := flags.ParseGasSetting(br.Gas)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txf := Factory{fees: br.Fees, gasPrices: br.GasPrices}.
|
|
||||||
WithAccountNumber(br.AccountNumber).
|
|
||||||
WithSequence(br.Sequence).
|
|
||||||
WithGas(gasSetting.Gas).
|
|
||||||
WithGasAdjustment(gasAdj).
|
|
||||||
WithMemo(br.Memo).
|
|
||||||
WithChainID(br.ChainID).
|
|
||||||
WithSimulateAndExecute(br.Simulate).
|
|
||||||
WithTxConfig(clientCtx.TxConfig).
|
|
||||||
WithTimeoutHeight(br.TimeoutHeight)
|
|
||||||
|
|
||||||
if br.Simulate || gasSetting.Simulate {
|
|
||||||
if gasAdj < 0 {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, sdkerrors.ErrorInvalidGasAdjustment.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, adjusted, err := CalculateGas(clientCtx, txf, msgs...)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txf = txf.WithGas(adjusted)
|
|
||||||
|
|
||||||
if br.Simulate {
|
|
||||||
rest.WriteSimulationResponse(w, clientCtx.LegacyAmino, txf.Gas())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := txf.BuildUnsignedTx(msgs...)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stdTx, err := ConvertTxToStdTx(clientCtx.LegacyAmino, tx.GetTx())
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := clientCtx.LegacyAmino.MarshalJSON(stdTx)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = w.Write(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalculateGas simulates the execution of a transaction and returns the
|
// CalculateGas simulates the execution of a transaction and returns the
|
||||||
// simulation response obtained by the query and the adjusted gas amount.
|
// simulation response obtained by the query and the adjusted gas amount.
|
||||||
func CalculateGas(
|
func CalculateGas(
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
"github.com/cosmos/cosmos-sdk/simapp"
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -130,12 +129,4 @@ func TestAminoCodecFullDecodeAndEncode(t *testing.T) {
|
||||||
marshaledTx, err := legacyCdc.MarshalJSON(tx)
|
marshaledTx, err := legacyCdc.MarshalJSON(tx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, string(marshaledTx), txSigned)
|
require.Equal(t, string(marshaledTx), txSigned)
|
||||||
|
|
||||||
// Marshalling/unmarshalling the tx wrapped in a struct should work.
|
|
||||||
txRequest := &rest.BroadcastReq{
|
|
||||||
Mode: "block",
|
|
||||||
Tx: tx,
|
|
||||||
}
|
|
||||||
_, err = legacyCdc.MarshalJSON(txRequest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -874,6 +874,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -1091,6 +1092,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -15,10 +15,10 @@ import (
|
||||||
tmrpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
|
tmrpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
||||||
"github.com/cosmos/cosmos-sdk/server/config"
|
"github.com/cosmos/cosmos-sdk/server/config"
|
||||||
"github.com/cosmos/cosmos-sdk/telemetry"
|
"github.com/cosmos/cosmos-sdk/telemetry"
|
||||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
|
|
||||||
// unnamed import of statik for swagger UI support
|
// unnamed import of statik for swagger UI support
|
||||||
_ "github.com/cosmos/cosmos-sdk/client/docs/statik"
|
_ "github.com/cosmos/cosmos-sdk/client/docs/statik"
|
||||||
|
@ -133,7 +133,7 @@ func (s *Server) registerMetrics() {
|
||||||
|
|
||||||
gr, err := s.metrics.Gather(format)
|
gr, err := s.metrics.Gather(format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err))
|
writeErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,3 +143,22 @@ func (s *Server) registerMetrics() {
|
||||||
|
|
||||||
s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET")
|
s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errorResponse defines the attributes of a JSON error response.
|
||||||
|
type errorResponse struct {
|
||||||
|
Code int `json:"code,omitempty"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newErrorResponse creates a new errorResponse instance.
|
||||||
|
func newErrorResponse(code int, err string) errorResponse {
|
||||||
|
return errorResponse{Code: code, Error: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeErrorResponse prepares and writes a HTTP error
|
||||||
|
// given a status code and an error message.
|
||||||
|
func writeErrorResponse(w http.ResponseWriter, status int, err string) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
_, _ = w.Write(legacy.Cdc.MustMarshalJSON(newErrorResponse(0, err)))
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
|
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
|
||||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
"github.com/cosmos/cosmos-sdk/server/api"
|
"github.com/cosmos/cosmos-sdk/server/api"
|
||||||
|
@ -32,7 +31,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/version"
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||||
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||||
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
||||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||||
|
@ -513,9 +511,6 @@ func (app *SimApp) SimulationManager() *module.SimulationManager {
|
||||||
// API server.
|
// API server.
|
||||||
func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) {
|
func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) {
|
||||||
clientCtx := apiSvr.ClientCtx
|
clientCtx := apiSvr.ClientCtx
|
||||||
rpc.RegisterRoutes(clientCtx, apiSvr.Router)
|
|
||||||
// Register legacy tx routes.
|
|
||||||
authrest.RegisterTxRoutes(clientCtx, apiSvr.Router)
|
|
||||||
// Register new tx routes from grpc-gateway.
|
// Register new tx routes from grpc-gateway.
|
||||||
authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
|
authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
|
||||||
// Register new tendermint queries routes from grpc-gateway.
|
// Register new tendermint queries routes from grpc-gateway.
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Package rest provides HTTP types and primitives for REST
|
||||||
|
// requests validation and responses handling.
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetRequest defines a wrapper around an HTTP GET request with a provided URL.
|
||||||
|
// An error is returned if the request or reading the body fails.
|
||||||
|
func GetRequest(url string) ([]byte, error) {
|
||||||
|
res, err := http.Get(url) // nolint:gosec
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostRequest defines a wrapper around an HTTP POST request with a provided URL and data.
|
||||||
|
// An error is returned if the request or reading the body fails.
|
||||||
|
func PostRequest(url string, contentType string, data []byte) ([]byte, error) {
|
||||||
|
res, err := http.Post(url, contentType, bytes.NewBuffer(data)) // nolint:gosec
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error while sending post request: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
bz, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bz, nil
|
||||||
|
}
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
@ -38,9 +38,9 @@ func TestBasicManager(t *testing.T) {
|
||||||
mockAppModuleBasic1.EXPECT().Name().AnyTimes().Return("mockAppModuleBasic1")
|
mockAppModuleBasic1.EXPECT().Name().AnyTimes().Return("mockAppModuleBasic1")
|
||||||
mockAppModuleBasic1.EXPECT().DefaultGenesis(gomock.Eq(cdc)).Times(1).Return(json.RawMessage(``))
|
mockAppModuleBasic1.EXPECT().DefaultGenesis(gomock.Eq(cdc)).Times(1).Return(json.RawMessage(``))
|
||||||
mockAppModuleBasic1.EXPECT().ValidateGenesis(gomock.Eq(cdc), gomock.Eq(nil), gomock.Eq(wantDefaultGenesis["mockAppModuleBasic1"])).Times(1).Return(errFoo)
|
mockAppModuleBasic1.EXPECT().ValidateGenesis(gomock.Eq(cdc), gomock.Eq(nil), gomock.Eq(wantDefaultGenesis["mockAppModuleBasic1"])).Times(1).Return(errFoo)
|
||||||
mockAppModuleBasic1.EXPECT().RegisterRESTRoutes(gomock.Eq(client.Context{}), gomock.Eq(&mux.Router{})).Times(1)
|
|
||||||
mockAppModuleBasic1.EXPECT().RegisterLegacyAminoCodec(gomock.Eq(legacyAmino)).Times(1)
|
mockAppModuleBasic1.EXPECT().RegisterLegacyAminoCodec(gomock.Eq(legacyAmino)).Times(1)
|
||||||
mockAppModuleBasic1.EXPECT().RegisterInterfaces(gomock.Eq(interfaceRegistry)).Times(1)
|
mockAppModuleBasic1.EXPECT().RegisterInterfaces(gomock.Eq(interfaceRegistry)).Times(1)
|
||||||
|
mockAppModuleBasic1.EXPECT().RegisterRESTRoutes(gomock.Eq(client.Context{}), gomock.Eq(&mux.Router{})).Times(1)
|
||||||
mockAppModuleBasic1.EXPECT().GetTxCmd().Times(1).Return(nil)
|
mockAppModuleBasic1.EXPECT().GetTxCmd().Times(1).Return(nil)
|
||||||
mockAppModuleBasic1.EXPECT().GetQueryCmd().Times(1).Return(nil)
|
mockAppModuleBasic1.EXPECT().GetQueryCmd().Times(1).Return(nil)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,10 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/store/types"
|
"github.com/cosmos/cosmos-sdk/store/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultPage is the default `page` number for queries.
|
||||||
|
// If the `page` number is not supplied, `DefaultPage` will be used.
|
||||||
|
const DefaultPage = 1
|
||||||
|
|
||||||
// DefaultLimit is the default `limit` for queries
|
// DefaultLimit is the default `limit` for queries
|
||||||
// if the `limit` is not supplied, paginate will use `DefaultLimit`
|
// if the `limit` is not supplied, paginate will use `DefaultLimit`
|
||||||
const DefaultLimit = 100
|
const DefaultLimit = 100
|
||||||
|
|
|
@ -1,449 +0,0 @@
|
||||||
// Package rest provides HTTP types and primitives for REST
|
|
||||||
// requests validation and responses handling.
|
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultPage = 1
|
|
||||||
DefaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
|
||||||
TxMinHeightKey = "tx.minheight" // Inclusive minimum height filter
|
|
||||||
TxMaxHeightKey = "tx.maxheight" // Inclusive maximum height filter
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponseWithHeight defines a response object type that wraps an original
|
|
||||||
// response with a height.
|
|
||||||
type ResponseWithHeight struct {
|
|
||||||
Height int64 `json:"height"`
|
|
||||||
Result json.RawMessage `json:"result"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResponseWithHeight creates a new ResponseWithHeight instance
|
|
||||||
func NewResponseWithHeight(height int64, result json.RawMessage) ResponseWithHeight {
|
|
||||||
return ResponseWithHeight{
|
|
||||||
Height: height,
|
|
||||||
Result: result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseResponseWithHeight returns the raw result from a JSON-encoded
|
|
||||||
// ResponseWithHeight object.
|
|
||||||
func ParseResponseWithHeight(cdc *codec.LegacyAmino, bz []byte) ([]byte, error) {
|
|
||||||
r := ResponseWithHeight{}
|
|
||||||
if err := cdc.UnmarshalJSON(bz, &r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GasEstimateResponse defines a response definition for tx gas estimation.
|
|
||||||
type GasEstimateResponse struct {
|
|
||||||
GasEstimate uint64 `json:"gas_estimate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseReq defines a structure that can be embedded in other request structures
|
|
||||||
// that all share common "base" fields.
|
|
||||||
type BaseReq struct {
|
|
||||||
From string `json:"from"`
|
|
||||||
Memo string `json:"memo"`
|
|
||||||
ChainID string `json:"chain_id"`
|
|
||||||
AccountNumber uint64 `json:"account_number"`
|
|
||||||
Sequence uint64 `json:"sequence"`
|
|
||||||
TimeoutHeight uint64 `json:"timeout_height"`
|
|
||||||
Fees sdk.Coins `json:"fees"`
|
|
||||||
GasPrices sdk.DecCoins `json:"gas_prices"`
|
|
||||||
Gas string `json:"gas"`
|
|
||||||
GasAdjustment string `json:"gas_adjustment"`
|
|
||||||
Simulate bool `json:"simulate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBaseReq creates a new basic request instance and sanitizes its values
|
|
||||||
func NewBaseReq(
|
|
||||||
from, memo, chainID string, gas, gasAdjustment string, accNumber, seq uint64,
|
|
||||||
fees sdk.Coins, gasPrices sdk.DecCoins, simulate bool,
|
|
||||||
) BaseReq {
|
|
||||||
return BaseReq{
|
|
||||||
From: strings.TrimSpace(from),
|
|
||||||
Memo: strings.TrimSpace(memo),
|
|
||||||
ChainID: strings.TrimSpace(chainID),
|
|
||||||
Fees: fees,
|
|
||||||
GasPrices: gasPrices,
|
|
||||||
Gas: strings.TrimSpace(gas),
|
|
||||||
GasAdjustment: strings.TrimSpace(gasAdjustment),
|
|
||||||
AccountNumber: accNumber,
|
|
||||||
Sequence: seq,
|
|
||||||
Simulate: simulate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize performs basic sanitization on a BaseReq object.
|
|
||||||
func (br BaseReq) Sanitize() BaseReq {
|
|
||||||
return NewBaseReq(
|
|
||||||
br.From, br.Memo, br.ChainID, br.Gas, br.GasAdjustment,
|
|
||||||
br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.Simulate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateBasic performs basic validation of a BaseReq. If custom validation
|
|
||||||
// logic is needed, the implementing request handler should perform those
|
|
||||||
// checks manually.
|
|
||||||
func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
|
|
||||||
if !br.Simulate {
|
|
||||||
switch {
|
|
||||||
case len(br.ChainID) == 0:
|
|
||||||
WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified")
|
|
||||||
return false
|
|
||||||
|
|
||||||
case !br.Fees.IsZero() && !br.GasPrices.IsZero():
|
|
||||||
// both fees and gas prices were provided
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices")
|
|
||||||
return false
|
|
||||||
|
|
||||||
case !br.Fees.IsValid() && !br.GasPrices.IsValid():
|
|
||||||
// neither fees or gas prices were provided
|
|
||||||
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sdk.AccAddressFromBech32(br.From); err != nil || len(br.From) == 0 {
|
|
||||||
WriteErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("invalid from address: %s", br.From))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadRESTReq reads and unmarshals a Request's body to the the BaseReq struct.
|
|
||||||
// Writes an error response to ResponseWriter and returns false if errors occurred.
|
|
||||||
func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.LegacyAmino, req interface{}) bool {
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if CheckBadRequestError(w, err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cdc.UnmarshalJSON(body, req)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorResponse defines the attributes of a JSON error response.
|
|
||||||
type ErrorResponse struct {
|
|
||||||
Code int `json:"code,omitempty"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewErrorResponse creates a new ErrorResponse instance.
|
|
||||||
func NewErrorResponse(code int, err string) ErrorResponse {
|
|
||||||
return ErrorResponse{Code: code, Error: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckError takes care of writing an error response if err is not nil.
|
|
||||||
// Returns false when err is nil; it returns true otherwise.
|
|
||||||
func CheckError(w http.ResponseWriter, status int, err error) bool {
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, status, err.Error())
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckBadRequestError attaches an error message to an HTTP 400 BAD REQUEST response.
|
|
||||||
// Returns false when err is nil; it returns true otherwise.
|
|
||||||
func CheckBadRequestError(w http.ResponseWriter, err error) bool {
|
|
||||||
return CheckError(w, http.StatusBadRequest, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckInternalServerError attaches an error message to an HTTP 500 INTERNAL SERVER ERROR response.
|
|
||||||
// Returns false when err is nil; it returns true otherwise.
|
|
||||||
func CheckInternalServerError(w http.ResponseWriter, err error) bool {
|
|
||||||
return CheckError(w, http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckNotFoundError attaches an error message to an HTTP 404 NOT FOUND response.
|
|
||||||
// Returns false when err is nil; it returns true otherwise.
|
|
||||||
func CheckNotFoundError(w http.ResponseWriter, err error) bool {
|
|
||||||
return CheckError(w, http.StatusNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteErrorResponse prepares and writes a HTTP error
|
|
||||||
// given a status code and an error message.
|
|
||||||
func WriteErrorResponse(w http.ResponseWriter, status int, err string) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(status)
|
|
||||||
_, _ = w.Write(legacy.Cdc.MustMarshalJSON(NewErrorResponse(0, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteSimulationResponse prepares and writes an HTTP
|
|
||||||
// response for transactions simulations.
|
|
||||||
func WriteSimulationResponse(w http.ResponseWriter, cdc *codec.LegacyAmino, gas uint64) {
|
|
||||||
gasEst := GasEstimateResponse{GasEstimate: gas}
|
|
||||||
|
|
||||||
resp, err := cdc.MarshalJSON(gasEst)
|
|
||||||
if CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = w.Write(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseUint64OrReturnBadRequest converts s to a uint64 value.
|
|
||||||
func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
n, err = strconv.ParseUint(s, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("'%s' is not a valid uint64", s))
|
|
||||||
|
|
||||||
return n, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a
|
|
||||||
// default value, defaultIfEmpty, if the string is empty.
|
|
||||||
func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return defaultIfEmpty, true
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := strconv.ParseFloat(s, 64)
|
|
||||||
if CheckBadRequestError(w, err) {
|
|
||||||
return n, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseQueryHeightOrReturnBadRequest sets the height to execute a query if set by the http request.
|
|
||||||
// It returns false if there was an error parsing the height.
|
|
||||||
func ParseQueryHeightOrReturnBadRequest(w http.ResponseWriter, clientCtx client.Context, r *http.Request) (client.Context, bool) {
|
|
||||||
heightStr := r.FormValue("height")
|
|
||||||
if heightStr != "" {
|
|
||||||
height, err := strconv.ParseInt(heightStr, 10, 64)
|
|
||||||
if CheckBadRequestError(w, err) {
|
|
||||||
return clientCtx, false
|
|
||||||
}
|
|
||||||
|
|
||||||
if height < 0 {
|
|
||||||
WriteErrorResponse(w, http.StatusBadRequest, "height must be equal or greater than zero")
|
|
||||||
return clientCtx, false
|
|
||||||
}
|
|
||||||
|
|
||||||
if height > 0 {
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clientCtx = clientCtx.WithHeight(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientCtx, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostProcessResponseBare post processes a body similar to PostProcessResponse
|
|
||||||
// except it does not wrap the body and inject the height.
|
|
||||||
func PostProcessResponseBare(w http.ResponseWriter, ctx client.Context, body interface{}) {
|
|
||||||
var (
|
|
||||||
resp []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
switch b := body.(type) {
|
|
||||||
case []byte:
|
|
||||||
resp = b
|
|
||||||
|
|
||||||
default:
|
|
||||||
resp, err = ctx.LegacyAmino.MarshalJSON(body)
|
|
||||||
if CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, _ = w.Write(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostProcessResponse performs post processing for a REST response. The result
|
|
||||||
// returned to clients will contain two fields, the height at which the resource
|
|
||||||
// was queried at and the original result.
|
|
||||||
func PostProcessResponse(w http.ResponseWriter, ctx client.Context, resp interface{}) {
|
|
||||||
var (
|
|
||||||
result []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if ctx.Height < 0 {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("negative height in response").Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// LegacyAmino used intentionally for REST
|
|
||||||
marshaler := ctx.LegacyAmino
|
|
||||||
|
|
||||||
switch res := resp.(type) {
|
|
||||||
case []byte:
|
|
||||||
result = res
|
|
||||||
|
|
||||||
default:
|
|
||||||
result, err = marshaler.MarshalJSON(resp)
|
|
||||||
if CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappedResp := NewResponseWithHeight(ctx.Height, result)
|
|
||||||
|
|
||||||
output, err := marshaler.MarshalJSON(wrappedResp)
|
|
||||||
if CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, _ = w.Write(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTPArgsWithLimit parses the request's URL and returns a slice containing
|
|
||||||
// all arguments pairs. It separates page and limit used for pagination where a
|
|
||||||
// default limit can be provided.
|
|
||||||
func ParseHTTPArgsWithLimit(r *http.Request, defaultLimit int) (tags []string, page, limit int, err error) {
|
|
||||||
tags = make([]string, 0, len(r.Form))
|
|
||||||
|
|
||||||
for key, values := range r.Form {
|
|
||||||
if key == "page" || key == "limit" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var value string
|
|
||||||
value, err = url.QueryUnescape(values[0])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return tags, page, limit, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tag string
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case types.TxHeightKey:
|
|
||||||
tag = fmt.Sprintf("%s=%s", key, value)
|
|
||||||
|
|
||||||
case TxMinHeightKey:
|
|
||||||
tag = fmt.Sprintf("%s>=%s", types.TxHeightKey, value)
|
|
||||||
|
|
||||||
case TxMaxHeightKey:
|
|
||||||
tag = fmt.Sprintf("%s<=%s", types.TxHeightKey, value)
|
|
||||||
|
|
||||||
default:
|
|
||||||
tag = fmt.Sprintf("%s='%s'", key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = append(tags, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
pageStr := r.FormValue("page")
|
|
||||||
if pageStr == "" {
|
|
||||||
page = DefaultPage
|
|
||||||
} else {
|
|
||||||
page, err = strconv.Atoi(pageStr)
|
|
||||||
if err != nil {
|
|
||||||
return tags, page, limit, err
|
|
||||||
} else if page <= 0 {
|
|
||||||
return tags, page, limit, errors.New("page must greater than 0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
limitStr := r.FormValue("limit")
|
|
||||||
if limitStr == "" {
|
|
||||||
limit = defaultLimit
|
|
||||||
} else {
|
|
||||||
limit, err = strconv.Atoi(limitStr)
|
|
||||||
if err != nil {
|
|
||||||
return tags, page, limit, err
|
|
||||||
} else if limit <= 0 {
|
|
||||||
return tags, page, limit, errors.New("limit must greater than 0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags, page, limit, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseHTTPArgs parses the request's URL and returns a slice containing all
|
|
||||||
// arguments pairs. It separates page and limit used for pagination.
|
|
||||||
func ParseHTTPArgs(r *http.Request) (tags []string, page, limit int, err error) {
|
|
||||||
return ParseHTTPArgsWithLimit(r, DefaultLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseQueryParamBool parses the given param to a boolean. It returns false by
|
|
||||||
// default if the string is not parseable to bool.
|
|
||||||
func ParseQueryParamBool(r *http.Request, param string) bool {
|
|
||||||
if value, err := strconv.ParseBool(r.FormValue(param)); err == nil {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRequest defines a wrapper around an HTTP GET request with a provided URL.
|
|
||||||
// An error is returned if the request or reading the body fails.
|
|
||||||
func GetRequest(url string) ([]byte, error) {
|
|
||||||
res, err := http.Get(url) // nolint:gosec
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = res.Body.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostRequest defines a wrapper around an HTTP POST request with a provided URL and data.
|
|
||||||
// An error is returned if the request or reading the body fails.
|
|
||||||
func PostRequest(url string, contentType string, data []byte) ([]byte, error) {
|
|
||||||
res, err := http.Post(url, contentType, bytes.NewBuffer(data)) // nolint:gosec
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error while sending post request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = res.Body.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bz, nil
|
|
||||||
}
|
|
|
@ -1,460 +0,0 @@
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
||||||
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBaseReq_Sanitize(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
sanitized := rest.BaseReq{ChainID: " test",
|
|
||||||
Memo: "memo ",
|
|
||||||
From: " cosmos1cq0sxam6x4l0sv9yz3a2vlqhdhvt2k6jtgcse0 ",
|
|
||||||
Gas: " ",
|
|
||||||
GasAdjustment: " 0.3",
|
|
||||||
}.Sanitize()
|
|
||||||
require.Equal(t, rest.BaseReq{ChainID: "test",
|
|
||||||
Memo: "memo",
|
|
||||||
From: "cosmos1cq0sxam6x4l0sv9yz3a2vlqhdhvt2k6jtgcse0",
|
|
||||||
Gas: "",
|
|
||||||
GasAdjustment: "0.3",
|
|
||||||
}, sanitized)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseReq_ValidateBasic(t *testing.T) {
|
|
||||||
fromAddr := "cosmos1cq0sxam6x4l0sv9yz3a2vlqhdhvt2k6jtgcse0"
|
|
||||||
tenstakes, err := types.ParseCoinsNormalized("10stake")
|
|
||||||
require.NoError(t, err)
|
|
||||||
onestake, err := types.ParseDecCoins("1.0stake")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req1 := rest.NewBaseReq(
|
|
||||||
fromAddr, "", "nonempty", "", "", 0, 0, tenstakes, nil, false,
|
|
||||||
)
|
|
||||||
req2 := rest.NewBaseReq(
|
|
||||||
"", "", "nonempty", "", "", 0, 0, tenstakes, nil, false,
|
|
||||||
)
|
|
||||||
req3 := rest.NewBaseReq(
|
|
||||||
fromAddr, "", "", "", "", 0, 0, tenstakes, nil, false,
|
|
||||||
)
|
|
||||||
req4 := rest.NewBaseReq(
|
|
||||||
fromAddr, "", "nonempty", "", "", 0, 0, tenstakes, onestake, false,
|
|
||||||
)
|
|
||||||
req5 := rest.NewBaseReq(
|
|
||||||
fromAddr, "", "nonempty", "", "", 0, 0, types.Coins{}, types.DecCoins{}, false,
|
|
||||||
)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
req rest.BaseReq
|
|
||||||
w http.ResponseWriter
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{"ok", req1, httptest.NewRecorder(), true},
|
|
||||||
{"neither fees nor gasprices provided", req5, httptest.NewRecorder(), true},
|
|
||||||
{"empty from", req2, httptest.NewRecorder(), false},
|
|
||||||
{"empty chain-id", req3, httptest.NewRecorder(), false},
|
|
||||||
{"fees and gasprices provided", req4, httptest.NewRecorder(), false},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equal(t, tt.want, tt.req.ValidateBasic(tt.w))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseHTTPArgs(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
req0 := mustNewRequest(t, "", "/", nil)
|
|
||||||
req1 := mustNewRequest(t, "", "/?limit=5", nil)
|
|
||||||
req2 := mustNewRequest(t, "", "/?page=5", nil)
|
|
||||||
req3 := mustNewRequest(t, "", "/?page=5&limit=5", nil)
|
|
||||||
|
|
||||||
reqE1 := mustNewRequest(t, "", "/?page=-1", nil)
|
|
||||||
reqE2 := mustNewRequest(t, "", "/?limit=-1", nil)
|
|
||||||
req4 := mustNewRequest(t, "", "/?foo=faa", nil)
|
|
||||||
|
|
||||||
reqTxH := mustNewRequest(t, "", "/?tx.minheight=12&tx.maxheight=14", nil)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
req *http.Request
|
|
||||||
w http.ResponseWriter
|
|
||||||
tags []string
|
|
||||||
page int
|
|
||||||
limit int
|
|
||||||
err bool
|
|
||||||
}{
|
|
||||||
{"no params", req0, httptest.NewRecorder(), []string{}, rest.DefaultPage, rest.DefaultLimit, false},
|
|
||||||
{"Limit", req1, httptest.NewRecorder(), []string{}, rest.DefaultPage, 5, false},
|
|
||||||
{"Page", req2, httptest.NewRecorder(), []string{}, 5, rest.DefaultLimit, false},
|
|
||||||
{"Page and limit", req3, httptest.NewRecorder(), []string{}, 5, 5, false},
|
|
||||||
|
|
||||||
{"error page 0", reqE1, httptest.NewRecorder(), []string{}, rest.DefaultPage, rest.DefaultLimit, true},
|
|
||||||
{"error limit 0", reqE2, httptest.NewRecorder(), []string{}, rest.DefaultPage, rest.DefaultLimit, true},
|
|
||||||
|
|
||||||
{"tags", req4, httptest.NewRecorder(), []string{"foo='faa'"}, rest.DefaultPage, rest.DefaultLimit, false},
|
|
||||||
{"tags", reqTxH, httptest.NewRecorder(), []string{"tx.height<=14", "tx.height>=12"}, rest.DefaultPage, rest.DefaultLimit, false},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
tags, page, limit, err := rest.ParseHTTPArgs(tt.req)
|
|
||||||
|
|
||||||
sort.Strings(tags)
|
|
||||||
|
|
||||||
if tt.err {
|
|
||||||
require.NotNil(t, err)
|
|
||||||
} else {
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, tt.tags, tags)
|
|
||||||
require.Equal(t, tt.page, page)
|
|
||||||
require.Equal(t, tt.limit, limit)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseQueryHeight(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
var emptyHeight int64
|
|
||||||
height := int64(1256756)
|
|
||||||
|
|
||||||
req0 := mustNewRequest(t, "", "/", nil)
|
|
||||||
req1 := mustNewRequest(t, "", "/?height=1256756", nil)
|
|
||||||
req2 := mustNewRequest(t, "", "/?height=456yui4567", nil)
|
|
||||||
req3 := mustNewRequest(t, "", "/?height=-1", nil)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
req *http.Request
|
|
||||||
w http.ResponseWriter
|
|
||||||
clientCtx client.Context
|
|
||||||
expectedHeight int64
|
|
||||||
expectedOk bool
|
|
||||||
}{
|
|
||||||
{"no height", req0, httptest.NewRecorder(), client.Context{}, emptyHeight, true},
|
|
||||||
{"height", req1, httptest.NewRecorder(), client.Context{}, height, true},
|
|
||||||
{"invalid height", req2, httptest.NewRecorder(), client.Context{}, emptyHeight, false},
|
|
||||||
{"negative height", req3, httptest.NewRecorder(), client.Context{}, emptyHeight, false},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(tt.w, tt.clientCtx, tt.req)
|
|
||||||
if tt.expectedOk {
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, tt.expectedHeight, clientCtx.Height)
|
|
||||||
} else {
|
|
||||||
require.False(t, ok)
|
|
||||||
require.Empty(t, tt.expectedHeight, clientCtx.Height)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProcessPostResponse(t *testing.T) {
|
|
||||||
// mock account
|
|
||||||
// PubKey field ensures amino encoding is used first since standard
|
|
||||||
// JSON encoding will panic on cryptotypes.PubKey
|
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
type mockAccount struct {
|
|
||||||
Address types.AccAddress `json:"address"`
|
|
||||||
Coins types.Coins `json:"coins"`
|
|
||||||
PubKey cryptotypes.PubKey `json:"public_key"`
|
|
||||||
AccountNumber uint64 `json:"account_number"`
|
|
||||||
Sequence uint64 `json:"sequence"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup
|
|
||||||
viper.Set(flags.FlagOffline, true)
|
|
||||||
ctx := client.Context{}
|
|
||||||
height := int64(194423)
|
|
||||||
|
|
||||||
privKey := secp256k1.GenPrivKey()
|
|
||||||
pubKey := privKey.PubKey()
|
|
||||||
addr := types.AccAddress(pubKey.Address())
|
|
||||||
coins := types.NewCoins(types.NewCoin("atom", types.NewInt(100)), types.NewCoin("tree", types.NewInt(125)))
|
|
||||||
accNumber := uint64(104)
|
|
||||||
sequence := uint64(32)
|
|
||||||
|
|
||||||
acc := mockAccount{addr, coins, pubKey, accNumber, sequence}
|
|
||||||
cdc := codec.NewLegacyAmino()
|
|
||||||
cryptocodec.RegisterCrypto(cdc)
|
|
||||||
cdc.RegisterConcrete(&mockAccount{}, "cosmos-sdk/mockAccount", nil)
|
|
||||||
ctx = ctx.WithLegacyAmino(cdc)
|
|
||||||
|
|
||||||
// setup expected results
|
|
||||||
jsonNoIndent, err := ctx.LegacyAmino.MarshalJSON(acc)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
respNoIndent := rest.NewResponseWithHeight(height, jsonNoIndent)
|
|
||||||
expectedNoIndent, err := ctx.LegacyAmino.MarshalJSON(respNoIndent)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// check that negative height writes an error
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
ctx = ctx.WithHeight(-1)
|
|
||||||
rest.PostProcessResponse(w, ctx, acc)
|
|
||||||
require.Equal(t, http.StatusInternalServerError, w.Code)
|
|
||||||
|
|
||||||
// check that height returns expected response
|
|
||||||
ctx = ctx.WithHeight(height)
|
|
||||||
runPostProcessResponse(t, ctx, acc, expectedNoIndent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadRESTReq(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
reqBody := ioutil.NopCloser(strings.NewReader(`{"chain_id":"alessio","memo":"text"}`))
|
|
||||||
req := &http.Request{Body: reqBody}
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
var br rest.BaseReq
|
|
||||||
|
|
||||||
// test OK
|
|
||||||
rest.ReadRESTReq(w, req, codec.NewLegacyAmino(), &br)
|
|
||||||
res := w.Result() //nolint:bodyclose
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, rest.BaseReq{ChainID: "alessio", Memo: "text"}, br)
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
||||||
|
|
||||||
// test non valid JSON
|
|
||||||
reqBody = ioutil.NopCloser(strings.NewReader(`MALFORMED`))
|
|
||||||
req = &http.Request{Body: reqBody}
|
|
||||||
br = rest.BaseReq{}
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
rest.ReadRESTReq(w, req, codec.NewLegacyAmino(), &br)
|
|
||||||
require.Equal(t, br, br)
|
|
||||||
res = w.Result() //nolint:bodyclose
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteSimulationResponse(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
rest.WriteSimulationResponse(w, codec.NewLegacyAmino(), 10)
|
|
||||||
res := w.Result() //nolint:bodyclose
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
||||||
bs, err := ioutil.ReadAll(res.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, `{"gas_estimate":"10"}`, string(bs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseUint64OrReturnBadRequest(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
_, ok := rest.ParseUint64OrReturnBadRequest(w, "100")
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose
|
|
||||||
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
_, ok = rest.ParseUint64OrReturnBadRequest(w, "-100")
|
|
||||||
require.False(t, ok)
|
|
||||||
require.Equal(t, http.StatusBadRequest, w.Result().StatusCode) //nolint:bodyclose
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseFloat64OrReturnBadRequest(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
_, ok := rest.ParseFloat64OrReturnBadRequest(w, "100", 0)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose
|
|
||||||
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
_, ok = rest.ParseFloat64OrReturnBadRequest(w, "bad request", 0)
|
|
||||||
require.False(t, ok)
|
|
||||||
require.Equal(t, http.StatusBadRequest, w.Result().StatusCode) //nolint:bodyclose
|
|
||||||
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
ret, ok := rest.ParseFloat64OrReturnBadRequest(w, "", 9.0)
|
|
||||||
require.Equal(t, float64(9), ret)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseQueryParamBool(t *testing.T) {
|
|
||||||
req := httptest.NewRequest("GET", "/target?boolean=true", nil)
|
|
||||||
require.True(t, rest.ParseQueryParamBool(req, "boolean"))
|
|
||||||
require.False(t, rest.ParseQueryParamBool(req, "nokey"))
|
|
||||||
req = httptest.NewRequest("GET", "/target?boolean=false", nil)
|
|
||||||
require.False(t, rest.ParseQueryParamBool(req, "boolean"))
|
|
||||||
require.False(t, rest.ParseQueryParamBool(req, ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPostProcessResponseBare(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
encodingConfig := simappparams.MakeTestEncodingConfig()
|
|
||||||
clientCtx := client.Context{}.
|
|
||||||
WithTxConfig(encodingConfig.TxConfig).
|
|
||||||
WithLegacyAmino(encodingConfig.Amino) // amino used intentionally here
|
|
||||||
// write bytes
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
bs := []byte("text string")
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, bs)
|
|
||||||
|
|
||||||
res := w.Result() //nolint:bodyclose
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
||||||
|
|
||||||
got, err := ioutil.ReadAll(res.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, "text string", string(got))
|
|
||||||
|
|
||||||
// write struct and indent response
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
data := struct {
|
|
||||||
X int `json:"x"`
|
|
||||||
S string `json:"s"`
|
|
||||||
}{X: 10, S: "test"}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, data)
|
|
||||||
|
|
||||||
res = w.Result() //nolint:bodyclose
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
||||||
|
|
||||||
got, err = ioutil.ReadAll(res.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, "{\"x\":\"10\",\"s\":\"test\"}", string(got))
|
|
||||||
|
|
||||||
// write struct, don't indent response
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
data = struct {
|
|
||||||
X int `json:"x"`
|
|
||||||
S string `json:"s"`
|
|
||||||
}{X: 10, S: "test"}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, data)
|
|
||||||
|
|
||||||
res = w.Result() //nolint:bodyclose
|
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
||||||
|
|
||||||
got, err = ioutil.ReadAll(res.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, `{"x":"10","s":"test"}`, string(got))
|
|
||||||
|
|
||||||
// test marshalling failure
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
data2 := badJSONMarshaller{}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, data2)
|
|
||||||
|
|
||||||
res = w.Result() //nolint:bodyclose
|
|
||||||
require.Equal(t, http.StatusInternalServerError, res.StatusCode)
|
|
||||||
|
|
||||||
got, err = ioutil.ReadAll(res.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() { res.Body.Close() })
|
|
||||||
require.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
|
|
||||||
require.Equal(t, `{"error":"couldn't marshal"}`, string(got))
|
|
||||||
}
|
|
||||||
|
|
||||||
type badJSONMarshaller struct{}
|
|
||||||
|
|
||||||
func (badJSONMarshaller) MarshalJSON() ([]byte, error) {
|
|
||||||
return nil, errors.New("couldn't marshal")
|
|
||||||
}
|
|
||||||
|
|
||||||
// asserts that ResponseRecorder returns the expected code and body
|
|
||||||
// runs PostProcessResponse on the objects regular interface and on
|
|
||||||
// the marshalled struct.
|
|
||||||
func runPostProcessResponse(t *testing.T, ctx client.Context, obj interface{}, expectedBody []byte) {
|
|
||||||
// test using regular struct
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, ctx, obj)
|
|
||||||
require.Equal(t, http.StatusOK, w.Code, w.Body)
|
|
||||||
|
|
||||||
resp := w.Result() //nolint:bodyclose
|
|
||||||
t.Cleanup(func() { resp.Body.Close() })
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, expectedBody, body)
|
|
||||||
|
|
||||||
marshalled, err := ctx.LegacyAmino.MarshalJSON(obj)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// test using marshalled struct
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
rest.PostProcessResponse(w, ctx, marshalled)
|
|
||||||
|
|
||||||
require.Equal(t, http.StatusOK, w.Code, w.Body)
|
|
||||||
resp = w.Result() //nolint:bodyclose
|
|
||||||
|
|
||||||
t.Cleanup(func() { resp.Body.Close() })
|
|
||||||
body, err = ioutil.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, string(expectedBody), string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustNewRequest(t *testing.T, method, url string, body io.Reader) *http.Request {
|
|
||||||
req, err := http.NewRequest(method, url, body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = req.ParseForm()
|
|
||||||
require.NoError(t, err)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckErrors(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
err := errors.New("ERROR")
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
checkerFn func(w http.ResponseWriter, err error) bool
|
|
||||||
error error
|
|
||||||
wantErr bool
|
|
||||||
wantString string
|
|
||||||
wantStatus int
|
|
||||||
}{
|
|
||||||
{"500", rest.CheckInternalServerError, err, true, `{"error":"ERROR"}`, http.StatusInternalServerError},
|
|
||||||
{"500 (no error)", rest.CheckInternalServerError, nil, false, ``, http.StatusInternalServerError},
|
|
||||||
{"400", rest.CheckBadRequestError, err, true, `{"error":"ERROR"}`, http.StatusBadRequest},
|
|
||||||
{"400 (no error)", rest.CheckBadRequestError, nil, false, ``, http.StatusBadRequest},
|
|
||||||
{"404", rest.CheckNotFoundError, err, true, `{"error":"ERROR"}`, http.StatusNotFound},
|
|
||||||
{"404 (no error)", rest.CheckNotFoundError, nil, false, ``, http.StatusNotFound},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
require.Equal(t, tt.wantErr, tt.checkerFn(w, tt.error))
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Equal(t, w.Body.String(), tt.wantString)
|
|
||||||
require.Equal(t, w.Code, tt.wantStatus)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
"github.com/cosmos/cosmos-sdk/version"
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
@ -199,8 +199,8 @@ $ %s query txs --%s 'message.sender=cosmos1...&message.action=withdraw_delegator
|
||||||
}
|
}
|
||||||
|
|
||||||
flags.AddQueryFlagsToCmd(cmd)
|
flags.AddQueryFlagsToCmd(cmd)
|
||||||
cmd.Flags().Int(flags.FlagPage, rest.DefaultPage, "Query a specific page of paginated results")
|
cmd.Flags().Int(flags.FlagPage, query.DefaultPage, "Query a specific page of paginated results")
|
||||||
cmd.Flags().Int(flags.FlagLimit, rest.DefaultLimit, "Query number of transactions results per page returned")
|
cmd.Flags().Int(flags.FlagLimit, query.DefaultLimit, "Query number of transactions results per page returned")
|
||||||
cmd.Flags().String(flagEvents, "", fmt.Sprintf("list of transaction events in the form of %s", eventFormat))
|
cmd.Flags().String(flagEvents, "", fmt.Sprintf("list of transaction events in the form of %s", eventFormat))
|
||||||
cmd.MarkFlagRequired(flagEvents)
|
cmd.MarkFlagRequired(flagEvents)
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,16 @@ import (
|
||||||
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
|
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
"github.com/cosmos/cosmos-sdk/version"
|
"github.com/cosmos/cosmos-sdk/version"
|
||||||
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BroadcastReq defines a tx broadcasting request.
|
||||||
|
type BroadcastReq struct {
|
||||||
|
Tx legacytx.StdTx `json:"tx" yaml:"tx"`
|
||||||
|
Mode string `json:"mode" yaml:"mode"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetSignCommand returns the sign command
|
// GetSignCommand returns the sign command
|
||||||
func GetMultiSignCommand() *cobra.Command {
|
func GetMultiSignCommand() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -155,7 +161,7 @@ func makeMultiSignCmd() func(cmd *cobra.Command, args []string) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := rest.BroadcastReq{
|
req := BroadcastReq{
|
||||||
Tx: stdTx,
|
Tx: stdTx,
|
||||||
Mode: "block|sync|async",
|
Mode: "block|sync|async",
|
||||||
}
|
}
|
||||||
|
@ -338,7 +344,7 @@ func makeBatchMultisignCmd() func(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := rest.BroadcastReq{
|
req := BroadcastReq{
|
||||||
Tx: stdTx,
|
Tx: stdTx,
|
||||||
Mode: "block|sync|async",
|
Mode: "block|sync|async",
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||||
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -263,7 +262,7 @@ func makeSignCmd() func(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req := rest.BroadcastReq{
|
req := BroadcastReq{
|
||||||
Tx: stdTx,
|
Tx: stdTx,
|
||||||
Mode: "block|sync|async",
|
Mode: "block|sync|async",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BroadcastReq defines a tx broadcasting request.
|
|
||||||
type BroadcastReq struct {
|
|
||||||
Tx legacytx.StdTx `json:"tx" yaml:"tx"`
|
|
||||||
Mode string `json:"mode" yaml:"mode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ codectypes.UnpackInterfacesMessage = BroadcastReq{}
|
|
||||||
|
|
||||||
// UnpackInterfaces implements the UnpackInterfacesMessage interface.
|
|
||||||
func (m BroadcastReq) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
|
|
||||||
return m.Tx.UnpackInterfaces(unpacker)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BroadcastTxRequest implements a tx broadcasting handler that is responsible
|
|
||||||
// for broadcasting a valid and signed tx to a full node. The tx can be
|
|
||||||
// broadcasted via a sync|async|block mechanism.
|
|
||||||
func BroadcastTxRequest(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req BroadcastReq
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: amino is used intentionally here, don't migrate it!
|
|
||||||
err = clientCtx.LegacyAmino.UnmarshalJSON(body, &req)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("this transaction cannot be broadcasted via legacy REST endpoints, because it does not support"+
|
|
||||||
" Amino serialization. Please either use CLI, gRPC, gRPC-gateway, or directly query the Tendermint RPC"+
|
|
||||||
" endpoint to broadcast this transaction. The new REST endpoint (via gRPC-gateway) is POST /cosmos/tx/v1beta1/txs."+
|
|
||||||
" Please also see the REST endpoints migration guide at %s for more info", clientrest.DeprecationURL)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
txBytes, err := tx.ConvertAndEncodeStdTx(clientCtx.TxConfig, req.Tx)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithBroadcastMode(req.Mode)
|
|
||||||
|
|
||||||
res, err := clientCtx.BroadcastTx(txBytes)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// DecodeReq defines a tx decoding request.
|
|
||||||
DecodeReq struct {
|
|
||||||
Tx string `json:"tx"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeResp defines a tx decoding response.
|
|
||||||
DecodeResp legacytx.StdTx
|
|
||||||
)
|
|
||||||
|
|
||||||
// DecodeTxRequestHandlerFn returns the decode tx REST handler. In particular,
|
|
||||||
// it takes base64-decoded bytes, decodes it from the Amino wire protocol,
|
|
||||||
// and responds with a json-formatted transaction.
|
|
||||||
func DecodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req DecodeReq
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: amino is used intentionally here, don't migrate it
|
|
||||||
err = clientCtx.LegacyAmino.UnmarshalJSON(body, &req)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txBytes, err := base64.StdEncoding.DecodeString(req.Tx)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stdTx, err := convertToStdTx(w, clientCtx, txBytes)
|
|
||||||
if err != nil {
|
|
||||||
// Error is already returned by convertToStdTx.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response := DecodeResp(stdTx)
|
|
||||||
|
|
||||||
err = checkAminoMarshalError(clientCtx, response, "/cosmos/tx/v1beta1/txs/decode")
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertToStdTx converts tx proto binary bytes retrieved from Tendermint into
|
|
||||||
// a StdTx. Returns the StdTx, as well as a flag denoting if the function
|
|
||||||
// successfully converted or not.
|
|
||||||
func convertToStdTx(w http.ResponseWriter, clientCtx client.Context, txBytes []byte) (legacytx.StdTx, error) {
|
|
||||||
txI, err := clientCtx.TxConfig.TxDecoder()(txBytes)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return legacytx.StdTx{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, ok := txI.(signing.Tx)
|
|
||||||
if !ok {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("%+v is not backwards compatible with %T", tx, legacytx.StdTx{}))
|
|
||||||
return legacytx.StdTx{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expected %T, got %T", (signing.Tx)(nil), txI)
|
|
||||||
}
|
|
||||||
|
|
||||||
stdTx, err := clienttx.ConvertTxToStdTx(clientCtx.LegacyAmino, tx)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return legacytx.StdTx{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdTx, nil
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EncodeResp defines a tx encoding response.
|
|
||||||
type EncodeResp struct {
|
|
||||||
Tx string `json:"tx" yaml:"tx"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrEncodeDecode is the error to show when encoding/decoding txs that are not
|
|
||||||
// amino-serializable (e.g. IBC txs).
|
|
||||||
var ErrEncodeDecode error = fmt.Errorf("this endpoint does not support txs that are not serializable"+
|
|
||||||
" via Amino, such as txs that contain IBC `Msg`s. For more info, please refer to our"+
|
|
||||||
" REST migration guide at %s", clientrest.DeprecationURL)
|
|
||||||
|
|
||||||
// EncodeTxRequestHandlerFn returns the encode tx REST handler. In particular,
|
|
||||||
// it takes a json-formatted transaction, encodes it to the Amino wire protocol,
|
|
||||||
// and responds with base64-encoded bytes.
|
|
||||||
func EncodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req legacytx.StdTx
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: amino is used intentionally here, don't migrate it
|
|
||||||
err = clientCtx.LegacyAmino.UnmarshalJSON(body, &req)
|
|
||||||
// If there's an unmarshalling error, we assume that it's because we're
|
|
||||||
// using amino to unmarshal a non-amino tx.
|
|
||||||
if err != nil {
|
|
||||||
if rest.CheckBadRequestError(w, ErrEncodeDecode) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-encode it in the chain's native binary format
|
|
||||||
txBytes, err := tx.ConvertAndEncodeStdTx(clientCtx.TxConfig, req)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// base64 encode the encoded tx bytes
|
|
||||||
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)
|
|
||||||
|
|
||||||
response := EncodeResp{Tx: txBytesBase64}
|
|
||||||
|
|
||||||
// NOTE: amino is set intentionally here, don't migrate it
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, response)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,220 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
||||||
genutilrest "github.com/cosmos/cosmos-sdk/x/genutil/client/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryAccountRequestHandlerFn is the query accountREST Handler.
|
|
||||||
func QueryAccountRequestHandlerFn(storeName string, clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bech32addr := vars["address"]
|
|
||||||
|
|
||||||
addr, err := sdk.AccAddressFromBech32(bech32addr)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accGetter := types.AccountRetriever{}
|
|
||||||
|
|
||||||
account, height, err := accGetter.GetAccountWithHeight(clientCtx, addr)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: Handle more appropriately based on the error type.
|
|
||||||
// Ref: https://github.com/cosmos/cosmos-sdk/issues/4923
|
|
||||||
if err := accGetter.EnsureExists(clientCtx, addr); err != nil {
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, types.BaseAccount{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryTxsRequestHandlerFn implements a REST handler that searches for transactions.
|
|
||||||
// Genesis transactions are returned if the height parameter is set to zero,
|
|
||||||
// otherwise the transactions are searched for by events.
|
|
||||||
func QueryTxsRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(
|
|
||||||
w, http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("failed to parse query parameters: %s", err),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the height query param is set to zero, query for genesis transactions
|
|
||||||
heightStr := r.FormValue("height")
|
|
||||||
if heightStr != "" {
|
|
||||||
if height, err := strconv.ParseInt(heightStr, 10, 64); err == nil && height == 0 {
|
|
||||||
genutilrest.QueryGenesisTxs(clientCtx, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
events []string
|
|
||||||
txs []sdk.TxResponse
|
|
||||||
page, limit int
|
|
||||||
)
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.Form) == 0 {
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, txs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
events, page, limit, err = rest.ParseHTTPArgs(r)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
searchResult, err := authtx.QueryTxsByEvents(clientCtx, events, page, limit, "")
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, txRes := range searchResult.Txs {
|
|
||||||
packStdTxResponse(w, clientCtx, txRes)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkAminoMarshalError(clientCtx, searchResult, "/cosmos/tx/v1beta1/txs")
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, searchResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryTxRequestHandlerFn implements a REST handler that queries a transaction
|
|
||||||
// by hash in a committed block.
|
|
||||||
func QueryTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
hashHexStr := vars["hash"]
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := authtx.QueryTx(clientCtx, hashHexStr)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), hashHexStr) {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = packStdTxResponse(w, clientCtx, output)
|
|
||||||
if err != nil {
|
|
||||||
// Error is already returned by packStdTxResponse.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if output.Empty() {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusNotFound, fmt.Sprintf("no transaction found with hash %s", hashHexStr))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkAminoMarshalError(clientCtx, output, "/cosmos/tx/v1beta1/txs/{txhash}")
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryParamsHandler(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParams)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// packStdTxResponse takes a sdk.TxResponse, converts the Tx into a StdTx, and
|
|
||||||
// packs the StdTx again into the sdk.TxResponse Any. Amino then takes care of
|
|
||||||
// seamlessly JSON-outputting the Any.
|
|
||||||
func packStdTxResponse(w http.ResponseWriter, clientCtx client.Context, txRes *sdk.TxResponse) error {
|
|
||||||
// We just unmarshalled from Tendermint, we take the proto Tx's raw
|
|
||||||
// bytes, and convert them into a StdTx to be displayed.
|
|
||||||
txBytes := txRes.Tx.Value
|
|
||||||
stdTx, err := convertToStdTx(w, clientCtx, txBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pack the amino stdTx into the TxResponse's Any.
|
|
||||||
txRes.Tx = codectypes.UnsafePackAny(stdTx)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkAminoMarshalError checks if there are errors with marshalling non-amino
|
|
||||||
// txs with amino.
|
|
||||||
func checkAminoMarshalError(ctx client.Context, resp interface{}, grpcEndPoint string) error {
|
|
||||||
// LegacyAmino used intentionally here to handle the SignMode errors
|
|
||||||
marshaler := ctx.LegacyAmino
|
|
||||||
|
|
||||||
_, err := marshaler.MarshalJSON(resp)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
// If there's an unmarshalling error, we assume that it's because we're
|
|
||||||
// using amino to unmarshal a non-amino tx.
|
|
||||||
return fmt.Errorf("this transaction cannot be displayed via legacy REST endpoints, because it does not support"+
|
|
||||||
" Amino serialization. Please either use CLI, gRPC, gRPC-gateway, or directly query the Tendermint RPC"+
|
|
||||||
" endpoint to query this transaction. The new REST endpoint (via gRPC-gateway) is %s. Please also see the"+
|
|
||||||
"REST endpoints migration guide at %s for more info", grpcEndPoint, clientrest.DeprecationURL)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// REST query and parameter values
|
|
||||||
const (
|
|
||||||
MethodGet = "GET"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegisterRoutes registers the auth module REST routes.
|
|
||||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router, storeName string) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
r.HandleFunc(
|
|
||||||
"/auth/accounts/{address}", QueryAccountRequestHandlerFn(storeName, clientCtx),
|
|
||||||
).Methods(MethodGet)
|
|
||||||
|
|
||||||
r.HandleFunc(
|
|
||||||
"/auth/params",
|
|
||||||
queryParamsHandler(clientCtx),
|
|
||||||
).Methods(MethodGet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterTxRoutes registers all transaction routes on the provided router.
|
|
||||||
func RegisterTxRoutes(clientCtx client.Context, rtr *mux.Router) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/txs", QueryTxsRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/txs", BroadcastTxRequest(clientCtx)).Methods("POST")
|
|
||||||
r.HandleFunc("/txs/encode", EncodeTxRequestHandlerFn(clientCtx)).Methods("POST")
|
|
||||||
r.HandleFunc("/txs/decode", DecodeTxRequestHandlerFn(clientCtx)).Methods("POST")
|
|
||||||
}
|
|
|
@ -1,536 +0,0 @@
|
||||||
// +build norace
|
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
|
||||||
kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
|
||||||
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
|
||||||
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
|
||||||
authcli "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
|
||||||
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
authtest "github.com/cosmos/cosmos-sdk/x/auth/client/testutil"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
|
||||||
bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
|
|
||||||
stdTx legacytx.StdTx
|
|
||||||
stdTxRes sdk.TxResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
|
||||||
cfg.NumValidators = 2
|
|
||||||
s.cfg = cfg
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
kb := s.network.Validators[0].ClientCtx.Keyring
|
|
||||||
_, _, err = kb.NewMnemonic("newAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
account1, _, err := kb.NewMnemonic("newAccount1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
account2, _, err := kb.NewMnemonic("newAccount2", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
multi := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{account1.GetPubKey(), account2.GetPubKey()})
|
|
||||||
_, err = kb.SaveMultisig("multi", multi)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Broadcast a StdTx used for tests.
|
|
||||||
s.stdTx = s.createTestStdTx(s.network.Validators[0], 0, 1)
|
|
||||||
res, err := s.broadcastReq(s.stdTx, "block")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// NOTE: this uses amino explicitly, don't migrate it!
|
|
||||||
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &s.stdTxRes))
|
|
||||||
s.Require().Equal(uint32(0), s.stdTxRes.Code)
|
|
||||||
|
|
||||||
s.Require().NoError(s.network.WaitForNextBlock())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
||||||
s.T().Log("tearing down integration test suite")
|
|
||||||
s.network.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkStdTx() legacytx.StdTx {
|
|
||||||
// NOTE: this uses StdTx explicitly, don't migrate it!
|
|
||||||
return legacytx.StdTx{
|
|
||||||
Msgs: []sdk.Msg{&types.MsgSend{}},
|
|
||||||
Fee: legacytx.StdFee{
|
|
||||||
Amount: sdk.Coins{sdk.NewInt64Coin("foo", 10)},
|
|
||||||
Gas: 10000,
|
|
||||||
},
|
|
||||||
Memo: "FOOBAR",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestEncodeDecode() {
|
|
||||||
var require = s.Require()
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
stdTx := mkStdTx()
|
|
||||||
|
|
||||||
// NOTE: this uses amino explicitly, don't migrate it!
|
|
||||||
cdc := val.ClientCtx.LegacyAmino
|
|
||||||
|
|
||||||
bz, err := cdc.MarshalJSON(stdTx)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
res, err := rest.PostRequest(fmt.Sprintf("%s/txs/encode", val.APIAddress), "application/json", bz)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
var encodeResp authrest.EncodeResp
|
|
||||||
err = cdc.UnmarshalJSON(res, &encodeResp)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
bz, err = cdc.MarshalJSON(authrest.DecodeReq(encodeResp))
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
res, err = rest.PostRequest(fmt.Sprintf("%s/txs/decode", val.APIAddress), "application/json", bz)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
var respWithHeight rest.ResponseWithHeight
|
|
||||||
err = cdc.UnmarshalJSON(res, &respWithHeight)
|
|
||||||
require.NoError(err)
|
|
||||||
var decodeResp authrest.DecodeResp
|
|
||||||
err = cdc.UnmarshalJSON(respWithHeight.Result, &decodeResp)
|
|
||||||
require.NoError(err)
|
|
||||||
require.Equal(stdTx, legacytx.StdTx(decodeResp))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryAccountWithColon() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
// This address is not a valid simapp address! It is only used to test that addresses with
|
|
||||||
// colon don't 501. See
|
|
||||||
// https://github.com/cosmos/cosmos-sdk/issues/8650
|
|
||||||
addrWithColon := "cosmos:1m4f6lwd9eh8e5nxt0h00d46d3fr03apfh8qf4g"
|
|
||||||
|
|
||||||
res, err := rest.GetRequest(fmt.Sprintf("%s/cosmos/auth/v1beta1/accounts/%s", val.APIAddress, addrWithColon))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().Contains(string(res), "decoding bech32 failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestBroadcastTxRequest() {
|
|
||||||
stdTx := mkStdTx()
|
|
||||||
|
|
||||||
// we just test with async mode because this tx will fail - all we care about is that it got encoded and broadcast correctly
|
|
||||||
res, err := s.broadcastReq(stdTx, "async")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
var txRes sdk.TxResponse
|
|
||||||
// NOTE: this uses amino explicitly, don't migrate it!
|
|
||||||
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &txRes))
|
|
||||||
// we just check for a non-empty TxHash here, the actual hash will depend on the underlying tx configuration
|
|
||||||
s.Require().NotEmpty(txRes.TxHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to test querying txs. We will use it to query StdTx and service `Msg`s.
|
|
||||||
func (s *IntegrationTestSuite) testQueryTx(txHeight int64, txHash, txRecipient string) {
|
|
||||||
val0 := s.network.Validators[0]
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
malleate func() *sdk.TxResponse
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"Query by hash",
|
|
||||||
func() *sdk.TxResponse {
|
|
||||||
txJSON, err := rest.GetRequest(fmt.Sprintf("%s/txs/%s", val0.APIAddress, txHash))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var txResAmino sdk.TxResponse
|
|
||||||
s.Require().NoError(val0.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &txResAmino))
|
|
||||||
return &txResAmino
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Query by height",
|
|
||||||
func() *sdk.TxResponse {
|
|
||||||
txJSON, err := rest.GetRequest(fmt.Sprintf("%s/txs?limit=10&page=1&tx.height=%d", val0.APIAddress, txHeight))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var searchtxResult sdk.SearchTxsResult
|
|
||||||
s.Require().NoError(val0.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &searchtxResult))
|
|
||||||
s.Require().Len(searchtxResult.Txs, 1)
|
|
||||||
return searchtxResult.Txs[0]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Query by event (transfer.recipient)",
|
|
||||||
func() *sdk.TxResponse {
|
|
||||||
txJSON, err := rest.GetRequest(fmt.Sprintf("%s/txs?transfer.recipient=%s", val0.APIAddress, txRecipient))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var searchtxResult sdk.SearchTxsResult
|
|
||||||
s.Require().NoError(val0.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &searchtxResult))
|
|
||||||
s.Require().Len(searchtxResult.Txs, 1)
|
|
||||||
return searchtxResult.Txs[0]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
s.Run(fmt.Sprintf("Case %s", tc.desc), func() {
|
|
||||||
txResponse := tc.malleate()
|
|
||||||
|
|
||||||
// Check that the height is correct.
|
|
||||||
s.Require().Equal(txHeight, txResponse.Height)
|
|
||||||
|
|
||||||
// Check that the events are correct.
|
|
||||||
s.Require().Contains(
|
|
||||||
txResponse.RawLog,
|
|
||||||
fmt.Sprintf("{\"key\":\"recipient\",\"value\":\"%s\"}", txRecipient),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check that the Msg is correct.
|
|
||||||
stdTx, ok := txResponse.Tx.GetCachedValue().(legacytx.StdTx)
|
|
||||||
s.Require().True(ok)
|
|
||||||
msgs := stdTx.GetMsgs()
|
|
||||||
s.Require().Equal(len(msgs), 1)
|
|
||||||
msg, ok := msgs[0].(*types.MsgSend)
|
|
||||||
s.Require().True(ok)
|
|
||||||
s.Require().Equal(txRecipient, msg.ToAddress)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryLegacyStdTx() {
|
|
||||||
val0 := s.network.Validators[0]
|
|
||||||
|
|
||||||
// We broadcasted a StdTx in SetupSuite.
|
|
||||||
// We just check for a non-empty TxHash here, the actual hash will depend on the underlying tx configuration
|
|
||||||
s.Require().NotEmpty(s.stdTxRes.TxHash)
|
|
||||||
|
|
||||||
s.testQueryTx(s.stdTxRes.Height, s.stdTxRes.TxHash, val0.Address.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryTx() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
|
|
||||||
sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10)
|
|
||||||
_, _, addr := testdata.KeyTestPubAddr()
|
|
||||||
|
|
||||||
// Might need to wait a block to refresh sequences from previous setups.
|
|
||||||
s.Require().NoError(s.network.WaitForNextBlock())
|
|
||||||
|
|
||||||
out, err := bankcli.MsgSendExec(
|
|
||||||
val.ClientCtx,
|
|
||||||
val.Address,
|
|
||||||
addr,
|
|
||||||
sdk.NewCoins(sendTokens),
|
|
||||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
|
||||||
fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
|
|
||||||
)
|
|
||||||
|
|
||||||
s.Require().NoError(err)
|
|
||||||
var txRes sdk.TxResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes))
|
|
||||||
s.Require().Equal(uint32(0), txRes.Code)
|
|
||||||
|
|
||||||
s.Require().NoError(s.network.WaitForNextBlock())
|
|
||||||
|
|
||||||
s.testQueryTx(txRes.Height, txRes.TxHash, addr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestMultipleSyncBroadcastTxRequests() {
|
|
||||||
// First test transaction from validator should have sequence=1 (non-genesis tx)
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
sequence uint64
|
|
||||||
shouldErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"First tx (correct sequence)",
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Second tx (correct sequence)",
|
|
||||||
2,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Third tx (incorrect sequence)",
|
|
||||||
9,
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
s.Run(fmt.Sprintf("Case %s", tc.desc), func() {
|
|
||||||
// broadcast test with sync mode, as we want to run CheckTx to verify account sequence is correct
|
|
||||||
stdTx := s.createTestStdTx(s.network.Validators[1], 1, tc.sequence)
|
|
||||||
res, err := s.broadcastReq(stdTx, "sync")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var txRes sdk.TxResponse
|
|
||||||
// NOTE: this uses amino explicitly, don't migrate it!
|
|
||||||
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &txRes))
|
|
||||||
// we check for a exitCode=0, indicating a successful broadcast
|
|
||||||
if tc.shouldErr {
|
|
||||||
var sigVerifyFailureCode uint32 = 4
|
|
||||||
s.Require().Equal(sigVerifyFailureCode, txRes.Code,
|
|
||||||
"Testcase '%s': Expected signature verification failure {Code: %d} from TxResponse. "+
|
|
||||||
"Found {Code: %d, RawLog: '%v'}",
|
|
||||||
tc.desc, sigVerifyFailureCode, txRes.Code, txRes.RawLog,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
s.Require().Equal(uint32(0), txRes.Code,
|
|
||||||
"Testcase '%s': TxResponse errored unexpectedly. Err: {Code: %d, RawLog: '%v'}",
|
|
||||||
tc.desc, txRes.Code, txRes.RawLog,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) createTestStdTx(val *network.Validator, accNum, sequence uint64) legacytx.StdTx {
|
|
||||||
txConfig := legacytx.StdTxConfig{Cdc: s.cfg.LegacyAmino}
|
|
||||||
|
|
||||||
msg := &types.MsgSend{
|
|
||||||
FromAddress: val.Address.String(),
|
|
||||||
ToAddress: val.Address.String(),
|
|
||||||
Amount: sdk.Coins{sdk.NewInt64Coin(fmt.Sprintf("%stoken", val.Moniker), 100)},
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare txBuilder with msg
|
|
||||||
txBuilder := txConfig.NewTxBuilder()
|
|
||||||
feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}
|
|
||||||
gasLimit := testdata.NewTestGasLimit()
|
|
||||||
txBuilder.SetMsgs(msg)
|
|
||||||
txBuilder.SetFeeAmount(feeAmount)
|
|
||||||
txBuilder.SetGasLimit(gasLimit)
|
|
||||||
txBuilder.SetMemo("foobar")
|
|
||||||
|
|
||||||
// setup txFactory
|
|
||||||
txFactory := tx.Factory{}.
|
|
||||||
WithChainID(val.ClientCtx.ChainID).
|
|
||||||
WithKeybase(val.ClientCtx.Keyring).
|
|
||||||
WithTxConfig(txConfig).
|
|
||||||
WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON).
|
|
||||||
WithAccountNumber(accNum).
|
|
||||||
WithSequence(sequence)
|
|
||||||
|
|
||||||
// sign Tx (offline mode so we can manually set sequence number)
|
|
||||||
err := authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, true, true)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
stdTx := txBuilder.GetTx().(legacytx.StdTx)
|
|
||||||
|
|
||||||
return stdTx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) broadcastReq(stdTx legacytx.StdTx, mode string) ([]byte, error) {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
|
|
||||||
// NOTE: this uses amino explicitly, don't migrate it!
|
|
||||||
cdc := val.ClientCtx.LegacyAmino
|
|
||||||
req := authrest.BroadcastReq{
|
|
||||||
Tx: stdTx,
|
|
||||||
Mode: mode,
|
|
||||||
}
|
|
||||||
bz, err := cdc.MarshalJSON(req)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
return rest.PostRequest(fmt.Sprintf("%s/txs", val.APIAddress), "application/json", bz)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testQueryIBCTx is a helper function to test querying txs which:
|
|
||||||
// - show an error message on legacy REST endpoints
|
|
||||||
// - succeed using gRPC
|
|
||||||
// In practice, we call this function on IBC txs.
|
|
||||||
func (s *IntegrationTestSuite) testQueryIBCTx(txRes sdk.TxResponse, cmd *cobra.Command, args []string) {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
|
|
||||||
errMsg := "this transaction cannot be displayed via legacy REST endpoints, because it does not support" +
|
|
||||||
" Amino serialization. Please either use CLI, gRPC, gRPC-gateway, or directly query the Tendermint RPC" +
|
|
||||||
" endpoint to query this transaction. The new REST endpoint (via gRPC-gateway) is "
|
|
||||||
|
|
||||||
// Test that legacy endpoint return the above error message on IBC txs.
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
url string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"Query by hash",
|
|
||||||
fmt.Sprintf("%s/txs/%s", val.APIAddress, txRes.TxHash),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Query by height",
|
|
||||||
fmt.Sprintf("%s/txs?tx.height=%d", val.APIAddress, txRes.Height),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
s.Run(fmt.Sprintf("Case %s", tc.desc), func() {
|
|
||||||
txJSON, err := rest.GetRequest(tc.url)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var errResp rest.ErrorResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &errResp))
|
|
||||||
|
|
||||||
s.Require().Contains(errResp.Error, errMsg)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// try fetching the txn using gRPC req, it will fetch info since it has proto codec.
|
|
||||||
grpcJSON, err := rest.GetRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, txRes.TxHash))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var getTxRes txtypes.GetTxResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(grpcJSON, &getTxRes))
|
|
||||||
s.Require().Equal(getTxRes.Tx.Body.Memo, "foobar")
|
|
||||||
|
|
||||||
// generate broadcast only txn.
|
|
||||||
args = append(args, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
|
|
||||||
out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
txFile := testutil.WriteToNewTempFile(s.T(), string(out.Bytes()))
|
|
||||||
txFileName := txFile.Name()
|
|
||||||
|
|
||||||
// encode the generated txn.
|
|
||||||
out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.GetEncodeCommand(), []string{txFileName})
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
bz, err := val.ClientCtx.LegacyAmino.MarshalJSON(authrest.DecodeReq{Tx: string(out.Bytes())})
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// try to decode the txn using legacy rest, it fails.
|
|
||||||
res, err := rest.PostRequest(fmt.Sprintf("%s/txs/decode", val.APIAddress), "application/json", bz)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var errResp rest.ErrorResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(res, &errResp))
|
|
||||||
s.Require().Contains(errResp.Error, errMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestLegacyMultiSig creates a legacy multisig transaction, and makes sure
|
|
||||||
// we can query it via the legacy REST endpoint.
|
|
||||||
// ref: https://github.com/cosmos/cosmos-sdk/issues/8679
|
|
||||||
func (s *IntegrationTestSuite) TestLegacyMultisig() {
|
|
||||||
val1 := *s.network.Validators[0]
|
|
||||||
|
|
||||||
// Generate 2 accounts and a multisig.
|
|
||||||
account1, err := val1.ClientCtx.Keyring.Key("newAccount1")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
account2, err := val1.ClientCtx.Keyring.Key("newAccount2")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
multisigInfo, err := val1.ClientCtx.Keyring.Key("multi")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Send coins from validator to multisig.
|
|
||||||
sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 1000)
|
|
||||||
_, err = bankcli.MsgSendExec(
|
|
||||||
val1.ClientCtx,
|
|
||||||
val1.Address,
|
|
||||||
multisigInfo.GetAddress(),
|
|
||||||
sdk.NewCoins(sendTokens),
|
|
||||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
|
||||||
fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
|
|
||||||
)
|
|
||||||
|
|
||||||
s.Require().NoError(s.network.WaitForNextBlock())
|
|
||||||
|
|
||||||
// Generate multisig transaction to a random address.
|
|
||||||
_, _, recipient := testdata.KeyTestPubAddr()
|
|
||||||
multiGeneratedTx, err := bankcli.MsgSendExec(
|
|
||||||
val1.ClientCtx,
|
|
||||||
multisigInfo.GetAddress(),
|
|
||||||
recipient,
|
|
||||||
sdk.NewCoins(
|
|
||||||
sdk.NewInt64Coin(s.cfg.BondDenom, 5),
|
|
||||||
),
|
|
||||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
|
||||||
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly),
|
|
||||||
)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Save tx to file
|
|
||||||
multiGeneratedTxFile := testutil.WriteToNewTempFile(s.T(), multiGeneratedTx.String())
|
|
||||||
|
|
||||||
// Sign with account1
|
|
||||||
val1.ClientCtx.HomeDir = strings.Replace(val1.ClientCtx.HomeDir, "simd", "simcli", 1)
|
|
||||||
account1Signature, err := authtest.TxSignExec(val1.ClientCtx, account1.GetAddress(), multiGeneratedTxFile.Name(), "--multisig", multisigInfo.GetAddress().String())
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String())
|
|
||||||
|
|
||||||
// Sign with account1
|
|
||||||
account2Signature, err := authtest.TxSignExec(val1.ClientCtx, account2.GetAddress(), multiGeneratedTxFile.Name(), "--multisig", multisigInfo.GetAddress().String())
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
sign2File := testutil.WriteToNewTempFile(s.T(), account2Signature.String())
|
|
||||||
|
|
||||||
// Does not work in offline mode.
|
|
||||||
_, err = authtest.TxMultiSignExec(val1.ClientCtx, multisigInfo.GetName(), multiGeneratedTxFile.Name(), "--offline", sign1File.Name(), sign2File.Name())
|
|
||||||
s.Require().EqualError(err, fmt.Sprintf("couldn't verify signature for address %s", account1.GetAddress()))
|
|
||||||
|
|
||||||
val1.ClientCtx.Offline = false
|
|
||||||
multiSigWith2Signatures, err := authtest.TxMultiSignExec(val1.ClientCtx, multisigInfo.GetName(), multiGeneratedTxFile.Name(), sign1File.Name(), sign2File.Name())
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Write the output to disk
|
|
||||||
signedTxFile := testutil.WriteToNewTempFile(s.T(), multiSigWith2Signatures.String())
|
|
||||||
|
|
||||||
_, err = authtest.TxValidateSignaturesExec(val1.ClientCtx, signedTxFile.Name())
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
val1.ClientCtx.BroadcastMode = flags.BroadcastBlock
|
|
||||||
out, err := authtest.TxBroadcastExec(val1.ClientCtx, signedTxFile.Name())
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
s.Require().NoError(s.network.WaitForNextBlock())
|
|
||||||
|
|
||||||
var txRes sdk.TxResponse
|
|
||||||
err = val1.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().Equal(uint32(0), txRes.Code)
|
|
||||||
|
|
||||||
s.testQueryTx(txRes.Height, txRes.TxHash, recipient.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
authcli "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
authcli "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||||
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
|
bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
|
||||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
|
@ -227,12 +226,13 @@ func (s *IntegrationTestSuite) TestCLISignAminoJSON() {
|
||||||
"--amino=true", signModeAminoFlag)
|
"--amino=true", signModeAminoFlag)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
var txAmino authrest.BroadcastReq
|
var txAmino authcli.BroadcastReq
|
||||||
err = val1.ClientCtx.LegacyAmino.UnmarshalJSON(res.Bytes(), &txAmino)
|
err = val1.ClientCtx.LegacyAmino.UnmarshalJSON(res.Bytes(), &txAmino)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Len(txAmino.Tx.Signatures, 2)
|
require.Len(txAmino.Tx.Signatures, 2)
|
||||||
require.Equal(txAmino.Tx.Signatures[0].PubKey, valInfo.GetPubKey())
|
require.Equal(txAmino.Tx.Signatures[0].PubKey, valInfo.GetPubKey())
|
||||||
require.Equal(txAmino.Tx.Signatures[1].PubKey, valInfo.GetPubKey())
|
require.Equal(txAmino.Tx.Signatures[1].PubKey, valInfo.GetPubKey())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSignatures(require *require.Assertions, txCfg client.TxConfig, output []byte, pks ...cryptotypes.PubKey) {
|
func checkSignatures(require *require.Assertions, txCfg client.TxConfig, output []byte, pks ...cryptotypes.PubKey) {
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
"github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
"github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
@ -61,9 +60,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the auth module.
|
// RegisterRESTRoutes registers the REST routes for the auth module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/auth` legacy REST implementation
|
||||||
rest.RegisterRoutes(clientCtx, rtr, types.StoreKey)
|
// has been removed from the SDK.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the auth module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the auth module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -19,11 +19,11 @@ import (
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||||
|
|
|
@ -54,7 +54,8 @@ func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConf
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers module's REST handlers. Currently, this is a no-op.
|
// RegisterRESTRoutes registers the REST routes for the vesting module. Currently, this is a no-op.
|
||||||
|
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the module's gRPC Gateway routes. Currently, this
|
// RegisterGRPCGatewayRoutes registers the module's gRPC Gateway routes. Currently, this
|
||||||
|
|
|
@ -1,93 +1,20 @@
|
||||||
// +build norace
|
package testutil
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
"github.com/cosmos/cosmos-sdk/testutil/rest"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/authz"
|
"github.com/cosmos/cosmos-sdk/x/authz"
|
||||||
"github.com/cosmos/cosmos-sdk/x/authz/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/authz/client/cli"
|
||||||
authztestutil "github.com/cosmos/cosmos-sdk/x/authz/client/testutil"
|
|
||||||
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
|
|
||||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
grantee sdk.AccAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeMsgSend = banktypes.SendAuthorization{}.MsgTypeURL()
|
|
||||||
var typeMsgVote = sdk.MsgTypeURL(&govtypes.MsgVote{})
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
|
||||||
cfg.NumValidators = 1
|
|
||||||
s.cfg = cfg
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
// Create new account in the keyring.
|
|
||||||
info, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
newAddr := sdk.AccAddress(info.GetPubKey().Address())
|
|
||||||
|
|
||||||
// Send some funds to the new account.
|
|
||||||
out, err := banktestutil.MsgSendExec(
|
|
||||||
val.ClientCtx,
|
|
||||||
val.Address,
|
|
||||||
newAddr,
|
|
||||||
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(200))), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
|
||||||
)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().Contains(out.String(), `"code":0`)
|
|
||||||
|
|
||||||
// grant authorization
|
|
||||||
out, err = authztestutil.ExecGrant(val, []string{
|
|
||||||
newAddr.String(),
|
|
||||||
"send",
|
|
||||||
fmt.Sprintf("--%s=100steak", cli.FlagSpendLimit),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
|
|
||||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()),
|
|
||||||
fmt.Sprintf("--%s=%d", cli.FlagExpiration, time.Now().Add(time.Minute*time.Duration(120)).Unix()),
|
|
||||||
})
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().Contains(out.String(), `"code":0`)
|
|
||||||
|
|
||||||
s.grantee = newAddr
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
||||||
s.T().Log("tearing down integration test suite")
|
|
||||||
s.network.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
|
func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
grantee := s.grantee[1]
|
||||||
grantsURL := val.APIAddress + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s&msg_type_url=%s"
|
grantsURL := val.APIAddress + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s&msg_type_url=%s"
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -97,7 +24,7 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"fail invalid granter address",
|
"fail invalid granter address",
|
||||||
fmt.Sprintf(grantsURL, "invalid_granter", s.grantee.String(), typeMsgSend),
|
fmt.Sprintf(grantsURL, "invalid_granter", grantee.String(), typeMsgSend),
|
||||||
true,
|
true,
|
||||||
"decoding bech32 failed: invalid index of 1: invalid request",
|
"decoding bech32 failed: invalid index of 1: invalid request",
|
||||||
},
|
},
|
||||||
|
@ -109,7 +36,7 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fail with empty granter",
|
"fail with empty granter",
|
||||||
fmt.Sprintf(grantsURL, "", s.grantee.String(), typeMsgSend),
|
fmt.Sprintf(grantsURL, "", grantee.String(), typeMsgSend),
|
||||||
true,
|
true,
|
||||||
"empty address string is not allowed: invalid request",
|
"empty address string is not allowed: invalid request",
|
||||||
},
|
},
|
||||||
|
@ -121,13 +48,13 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fail invalid msg-type",
|
"fail invalid msg-type",
|
||||||
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String(), "invalidMsg"),
|
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String(), "invalidMsg"),
|
||||||
true,
|
true,
|
||||||
"rpc error: code = NotFound desc = no authorization found for invalidMsg type: key not found",
|
"rpc error: code = NotFound desc = no authorization found for invalidMsg type: key not found",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid query",
|
"valid query",
|
||||||
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String(), typeMsgSend),
|
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String(), typeMsgSend),
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
|
@ -154,6 +81,7 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
|
func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
grantee := s.grantee[1]
|
||||||
grantsURL := val.APIAddress + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s"
|
grantsURL := val.APIAddress + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s"
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -165,7 +93,7 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"valid query: expect single grant",
|
"valid query: expect single grant",
|
||||||
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String()),
|
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String()),
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
func() {},
|
func() {},
|
||||||
|
@ -175,12 +103,12 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid query: expect two grants",
|
"valid query: expect two grants",
|
||||||
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String()),
|
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String()),
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
func() {
|
func() {
|
||||||
_, err := authztestutil.ExecGrant(val, []string{
|
_, err := ExecGrant(val, []string{
|
||||||
s.grantee.String(),
|
grantee.String(),
|
||||||
"generic",
|
"generic",
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
|
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
|
||||||
fmt.Sprintf("--%s=%s", cli.FlagMsgType, typeMsgVote),
|
fmt.Sprintf("--%s=%s", cli.FlagMsgType, typeMsgVote),
|
||||||
|
@ -197,7 +125,7 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid query: expect single grant with pagination",
|
"valid query: expect single grant with pagination",
|
||||||
fmt.Sprintf(grantsURL+"&pagination.limit=1", val.Address.String(), s.grantee.String()),
|
fmt.Sprintf(grantsURL+"&pagination.limit=1", val.Address.String(), grantee.String()),
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
func() {},
|
func() {},
|
||||||
|
@ -207,7 +135,7 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid query: expect two grants with pagination",
|
"valid query: expect two grants with pagination",
|
||||||
fmt.Sprintf(grantsURL+"&pagination.limit=2", val.Address.String(), s.grantee.String()),
|
fmt.Sprintf(grantsURL+"&pagination.limit=2", val.Address.String(), grantee.String()),
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
func() {},
|
func() {},
|
||||||
|
@ -233,7 +161,3 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
func (s *IntegrationTestSuite) TestQueryAuthorizations() {
|
func (s *IntegrationTestSuite) TestQueryAuthorizations() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
|
|
||||||
_, err := ExecGrant(
|
_, err := ExecGrant(
|
||||||
|
@ -95,7 +95,7 @@ func (s *IntegrationTestSuite) TestQueryAuthorizations() {
|
||||||
func (s *IntegrationTestSuite) TestQueryAuthorization() {
|
func (s *IntegrationTestSuite) TestQueryAuthorization() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
|
|
||||||
_, err := ExecGrant(
|
_, err := ExecGrant(
|
||||||
|
|
|
@ -28,7 +28,7 @@ type IntegrationTestSuite struct {
|
||||||
|
|
||||||
cfg network.Config
|
cfg network.Config
|
||||||
network *network.Network
|
network *network.Network
|
||||||
grantee sdk.AccAddress
|
grantee []sdk.AccAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIntegrationTestSuite(cfg network.Config) *IntegrationTestSuite {
|
func NewIntegrationTestSuite(cfg network.Config) *IntegrationTestSuite {
|
||||||
|
@ -43,24 +43,12 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
s.grantee = make([]sdk.AccAddress, 2)
|
||||||
|
|
||||||
// Create new account in the keyring.
|
// Create new account in the keyring.
|
||||||
info, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
s.grantee[0] = s.createAccount("grantee1")
|
||||||
s.Require().NoError(err)
|
|
||||||
newAddr := sdk.AccAddress(info.GetPubKey().Address())
|
|
||||||
|
|
||||||
// Send some funds to the new account.
|
// Send some funds to the new account.
|
||||||
_, err = banktestutil.MsgSendExec(
|
s.msgSendExec(s.grantee[0])
|
||||||
val.ClientCtx,
|
|
||||||
val.Address,
|
|
||||||
newAddr,
|
|
||||||
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(200))), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
|
||||||
)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.grantee = newAddr
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
_, err = s.network.WaitForHeight(1)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
@ -70,10 +58,52 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
fmt.Sprintf("--%s=%s", govcli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, govtypes.DefaultMinDepositTokens).String()))
|
fmt.Sprintf("--%s=%s", govcli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, govtypes.DefaultMinDepositTokens).String()))
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
// Create new account in the keyring.
|
||||||
|
s.grantee[1] = s.createAccount("grantee2")
|
||||||
|
// Send some funds to the new account.
|
||||||
|
s.msgSendExec(s.grantee[1])
|
||||||
|
|
||||||
|
// grant send authorization to grantee2
|
||||||
|
out, err := ExecGrant(val, []string{
|
||||||
|
s.grantee[1].String(),
|
||||||
|
"send",
|
||||||
|
fmt.Sprintf("--%s=100steak", cli.FlagSpendLimit),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
|
||||||
|
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()),
|
||||||
|
fmt.Sprintf("--%s=%d", cli.FlagExpiration, time.Now().Add(time.Minute*time.Duration(120)).Unix()),
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Contains(out.String(), `"code":0`)
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
_, err = s.network.WaitForHeight(1)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationTestSuite) createAccount(uid string) sdk.AccAddress {
|
||||||
|
val := s.network.Validators[0]
|
||||||
|
// Create new account in the keyring.
|
||||||
|
info, _, err := val.ClientCtx.Keyring.NewMnemonic(uid, keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
return sdk.AccAddress(info.GetPubKey().Address())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationTestSuite) msgSendExec(grantee sdk.AccAddress) {
|
||||||
|
val := s.network.Validators[0]
|
||||||
|
// Send some funds to the new account.
|
||||||
|
out, err := banktestutil.MsgSendExec(
|
||||||
|
val.ClientCtx,
|
||||||
|
val.Address,
|
||||||
|
grantee,
|
||||||
|
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(200))), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
||||||
|
)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Contains(out.String(), `"code":0`)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||||
s.T().Log("tearing down integration test suite")
|
s.T().Log("tearing down integration test suite")
|
||||||
s.network.Cleanup()
|
s.network.Cleanup()
|
||||||
|
@ -85,7 +115,7 @@ var typeMsgSubmitProposal = sdk.MsgTypeURL(&govtypes.MsgSubmitProposal{})
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() {
|
func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
|
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
pastHour := time.Now().Add(time.Minute * time.Duration(-60)).Unix()
|
pastHour := time.Now().Add(time.Minute * time.Duration(-60)).Unix()
|
||||||
|
@ -308,7 +338,7 @@ func execDelegate(val *network.Validator, args []string) (testutil.BufferWriter,
|
||||||
func (s *IntegrationTestSuite) TestCmdRevokeAuthorizations() {
|
func (s *IntegrationTestSuite) TestCmdRevokeAuthorizations() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
|
|
||||||
// send-authorization
|
// send-authorization
|
||||||
|
@ -454,7 +484,7 @@ func (s *IntegrationTestSuite) TestCmdRevokeAuthorizations() {
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestExecAuthorizationWithExpiration() {
|
func (s *IntegrationTestSuite) TestExecAuthorizationWithExpiration() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
tenSeconds := time.Now().Add(time.Second * time.Duration(10)).Unix()
|
tenSeconds := time.Now().Add(time.Second * time.Duration(10)).Unix()
|
||||||
|
|
||||||
_, err := ExecGrant(
|
_, err := ExecGrant(
|
||||||
|
@ -494,7 +524,7 @@ func (s *IntegrationTestSuite) TestExecAuthorizationWithExpiration() {
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestNewExecGenericAuthorized() {
|
func (s *IntegrationTestSuite) TestNewExecGenericAuthorized() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
|
|
||||||
_, err := ExecGrant(
|
_, err := ExecGrant(
|
||||||
|
@ -596,7 +626,7 @@ func (s *IntegrationTestSuite) TestNewExecGenericAuthorized() {
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestNewExecGrantAuthorized() {
|
func (s *IntegrationTestSuite) TestNewExecGrantAuthorized() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
|
|
||||||
_, err := ExecGrant(
|
_, err := ExecGrant(
|
||||||
|
@ -681,7 +711,7 @@ func (s *IntegrationTestSuite) TestNewExecGrantAuthorized() {
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestExecDelegateAuthorization() {
|
func (s *IntegrationTestSuite) TestExecDelegateAuthorization() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
|
|
||||||
_, err := ExecGrant(
|
_, err := ExecGrant(
|
||||||
|
@ -884,7 +914,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() {
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() {
|
func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
grantee := s.grantee
|
grantee := s.grantee[0]
|
||||||
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
|
||||||
|
|
||||||
// granting undelegate msg authorization
|
// granting undelegate msg authorization
|
||||||
|
|
|
@ -71,8 +71,8 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config sdkclient.TxEn
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the authz module.
|
// RegisterRESTRoutes registers the REST routes for the authz module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx sdkclient.Context, r *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(_ sdkclient.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the authz module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the authz module.
|
||||||
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {
|
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryBalancesRequestHandlerFn returns a REST handler that queries for all
|
|
||||||
// account balances or a specific balance by denomination.
|
|
||||||
func QueryBalancesRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bech32addr := vars["address"]
|
|
||||||
|
|
||||||
addr, err := sdk.AccAddressFromBech32(bech32addr)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
params interface{}
|
|
||||||
route string
|
|
||||||
)
|
|
||||||
|
|
||||||
denom := r.FormValue("denom")
|
|
||||||
if denom == "" {
|
|
||||||
params = types.NewQueryAllBalancesRequest(addr, nil)
|
|
||||||
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllBalances)
|
|
||||||
} else {
|
|
||||||
params = types.NewQueryBalanceRequest(addr, denom)
|
|
||||||
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance)
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err := ctx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := ctx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = ctx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, ctx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the total supply of coins
|
|
||||||
func totalSupplyHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryTotalSupplyParams(page, limit)
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTotalSupply), bz)
|
|
||||||
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the supply of a single denom
|
|
||||||
func supplyOfHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
denom := mux.Vars(r)["denom"]
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQuerySupplyOfParams(denom)
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySupplyOf), bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
// +build norace
|
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
|
||||||
genesisState := cfg.GenesisState
|
|
||||||
cfg.NumValidators = 1
|
|
||||||
|
|
||||||
var bankGenesis types.GenesisState
|
|
||||||
s.Require().NoError(cfg.Codec.UnmarshalJSON(genesisState[types.ModuleName], &bankGenesis))
|
|
||||||
|
|
||||||
bankGenesis.DenomMetadata = []types.Metadata{
|
|
||||||
{
|
|
||||||
Name: "Cosmos Hub Atom",
|
|
||||||
Symbol: "ATOM",
|
|
||||||
Description: "The native staking token of the Cosmos Hub.",
|
|
||||||
DenomUnits: []*types.DenomUnit{
|
|
||||||
{
|
|
||||||
Denom: "uatom",
|
|
||||||
Exponent: 0,
|
|
||||||
Aliases: []string{"microatom"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Denom: "atom",
|
|
||||||
Exponent: 6,
|
|
||||||
Aliases: []string{"ATOM"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Base: "uatom",
|
|
||||||
Display: "atom",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
bankGenesisBz, err := cfg.Codec.MarshalJSON(&bankGenesis)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
genesisState[types.ModuleName] = bankGenesisBz
|
|
||||||
cfg.GenesisState = genesisState
|
|
||||||
s.cfg = cfg
|
|
||||||
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(2)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
||||||
s.T().Log("tearing down integration test suite")
|
|
||||||
s.network.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryBalancesRequestHandlerFn() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
baseURL := val.APIAddress
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
expHeight int64
|
|
||||||
respType fmt.Stringer
|
|
||||||
expected fmt.Stringer
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"total account balance",
|
|
||||||
fmt.Sprintf("%s/bank/balances/%s", baseURL, val.Address),
|
|
||||||
-1,
|
|
||||||
&sdk.Coins{},
|
|
||||||
sdk.NewCoins(
|
|
||||||
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), s.cfg.AccountTokens),
|
|
||||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Sub(s.cfg.BondedTokens)),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"total account balance with height",
|
|
||||||
fmt.Sprintf("%s/bank/balances/%s?height=1", baseURL, val.Address),
|
|
||||||
1,
|
|
||||||
&sdk.Coins{},
|
|
||||||
sdk.NewCoins(
|
|
||||||
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), s.cfg.AccountTokens),
|
|
||||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Sub(s.cfg.BondedTokens)),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"total account balance of a specific denom",
|
|
||||||
fmt.Sprintf("%s/bank/balances/%s?denom=%s", baseURL, val.Address, s.cfg.BondDenom),
|
|
||||||
-1,
|
|
||||||
&sdk.Coin{},
|
|
||||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Sub(s.cfg.BondedTokens)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"total account balance of a bogus denom",
|
|
||||||
fmt.Sprintf("%s/bank/balances/%s?denom=foobar", baseURL, val.Address),
|
|
||||||
-1,
|
|
||||||
&sdk.Coin{},
|
|
||||||
sdk.NewCoin("foobar", sdk.ZeroInt()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
s.Run(tc.name, func() {
|
|
||||||
respJSON, err := rest.GetRequest(tc.url)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
var resp = rest.ResponseWithHeight{}
|
|
||||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Check height.
|
|
||||||
if tc.expHeight >= 0 {
|
|
||||||
s.Require().Equal(resp.Height, tc.expHeight)
|
|
||||||
} else {
|
|
||||||
// To avoid flakiness, just test that height is positive.
|
|
||||||
s.Require().Greater(resp.Height, int64(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check result.
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, tc.respType))
|
|
||||||
s.Require().Equal(tc.expected.String(), tc.respType.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestTotalSupplyHandlerFn() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
baseURL := val.APIAddress
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
respType fmt.Stringer
|
|
||||||
expected fmt.Stringer
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"total supply",
|
|
||||||
fmt.Sprintf("%s/bank/total?height=1", baseURL),
|
|
||||||
&types.QueryTotalSupplyResponse{},
|
|
||||||
&types.QueryTotalSupplyResponse{
|
|
||||||
Supply: sdk.NewCoins(
|
|
||||||
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), s.cfg.AccountTokens),
|
|
||||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(10))),
|
|
||||||
),
|
|
||||||
Pagination: &query.PageResponse{Total: 2},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"total supply of a specific denom",
|
|
||||||
fmt.Sprintf("%s/bank/total/%s?height=1", baseURL, s.cfg.BondDenom),
|
|
||||||
&sdk.Coin{},
|
|
||||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(10))),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"total supply of a bogus denom",
|
|
||||||
fmt.Sprintf("%s/bank/total/foobar?height=1", baseURL),
|
|
||||||
&sdk.Coin{},
|
|
||||||
sdk.NewCoin("foobar", sdk.ZeroInt()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
s.Run(tc.name, func() {
|
|
||||||
resp, err := rest.GetRequest(tc.url)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
bz, err := rest.ParseResponseWithHeight(val.ClientCtx.LegacyAmino, resp)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(bz, tc.respType))
|
|
||||||
s.Require().Equal(tc.expected.String(), tc.respType.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegisterHandlers registers all x/bank transaction and query HTTP REST handlers
|
|
||||||
// on the provided mux router.
|
|
||||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
r.HandleFunc("/bank/accounts/{address}/transfers", NewSendRequestHandlerFn(clientCtx)).Methods("POST")
|
|
||||||
r.HandleFunc("/bank/balances/{address}", QueryBalancesRequestHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/bank/total", totalSupplyHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/bank/total/{denom}", supplyOfHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendReq defines the properties of a send request's body.
|
|
||||||
type SendReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSendRequestHandlerFn returns an HTTP REST handler for creating a MsgSend
|
|
||||||
// transaction.
|
|
||||||
func NewSendRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bech32Addr := vars["address"]
|
|
||||||
|
|
||||||
toAddr, err := sdk.AccAddressFromBech32(bech32Addr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req SendReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgSend(fromAddr, toAddr, req.Amount)
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
// +build norace
|
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/errors"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
|
||||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
||||||
bankrest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
|
||||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestCoinSend() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
|
|
||||||
account, err := getAccountInfo(val)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
sendReq := generateSendReq(
|
|
||||||
account,
|
|
||||||
types.Coins{types.NewCoin(s.cfg.BondDenom, types.TokensFromConsensusPower(1, sdk.DefaultPowerReduction))},
|
|
||||||
)
|
|
||||||
|
|
||||||
stdTx, err := submitSendReq(val, sendReq)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
s.Require().Nil(stdTx.Signatures)
|
|
||||||
s.Require().Equal([]types.Msg{
|
|
||||||
&banktypes.MsgSend{
|
|
||||||
FromAddress: account.GetAddress().String(),
|
|
||||||
ToAddress: account.GetAddress().String(),
|
|
||||||
Amount: sendReq.Amount,
|
|
||||||
},
|
|
||||||
}, stdTx.GetMsgs())
|
|
||||||
}
|
|
||||||
|
|
||||||
func submitSendReq(val *network.Validator, req bankrest.SendReq) (legacytx.StdTx, error) {
|
|
||||||
url := fmt.Sprintf("%s/bank/accounts/%s/transfers", val.APIAddress, val.Address)
|
|
||||||
|
|
||||||
// NOTE: this uses amino explicitly, don't migrate it!
|
|
||||||
bz, err := val.ClientCtx.LegacyAmino.MarshalJSON(req)
|
|
||||||
if err != nil {
|
|
||||||
return legacytx.StdTx{}, errors.Wrap(err, "error encoding SendReq to json")
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := rest.PostRequest(url, "application/json", bz)
|
|
||||||
if err != nil {
|
|
||||||
return legacytx.StdTx{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tx legacytx.StdTx
|
|
||||||
// NOTE: this uses amino explicitly, don't migrate it!
|
|
||||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(res, &tx)
|
|
||||||
if err != nil {
|
|
||||||
return legacytx.StdTx{}, errors.Wrap(err, "error unmarshaling to StdTx SendReq response")
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateSendReq(from authtypes.AccountI, amount types.Coins) bankrest.SendReq {
|
|
||||||
baseReq := rest.NewBaseReq(
|
|
||||||
from.GetAddress().String(),
|
|
||||||
"someMemo",
|
|
||||||
"some-id",
|
|
||||||
"10000",
|
|
||||||
fmt.Sprintf("%f", 1.0),
|
|
||||||
from.GetAccountNumber(),
|
|
||||||
from.GetSequence(),
|
|
||||||
types.NewCoins(),
|
|
||||||
nil,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
return bankrest.SendReq{
|
|
||||||
BaseReq: baseReq,
|
|
||||||
Amount: amount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAccountInfo(val *network.Validator) (authtypes.AccountI, error) {
|
|
||||||
url := fmt.Sprintf("%s/auth/accounts/%s", val.APIAddress, val.Address)
|
|
||||||
|
|
||||||
resp, err := rest.GetRequest(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err := rest.ParseResponseWithHeight(val.ClientCtx.LegacyAmino, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var acc authtypes.AccountI
|
|
||||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(bz, &acc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc, nil
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
// +build norace
|
package testutil
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -8,10 +6,10 @@ import (
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/rest"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -142,8 +140,26 @@ func (s *IntegrationTestSuite) TestDenomMetadataGRPCHandler() {
|
||||||
Base: "uatom",
|
Base: "uatom",
|
||||||
Display: "atom",
|
Display: "atom",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Ethereum",
|
||||||
|
Symbol: "ETH",
|
||||||
|
Description: "Ethereum mainnet token",
|
||||||
|
DenomUnits: []*types.DenomUnit{
|
||||||
|
{
|
||||||
|
Denom: "wei",
|
||||||
|
Exponent: 0,
|
||||||
},
|
},
|
||||||
Pagination: &query.PageResponse{Total: 1},
|
{
|
||||||
|
Denom: "eth",
|
||||||
|
Exponent: 6,
|
||||||
|
Aliases: []string{"ETH"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Base: "wei",
|
||||||
|
Display: "eth",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Pagination: &query.PageResponse{Total: 2},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
"github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
||||||
v040 "github.com/cosmos/cosmos-sdk/x/bank/migrations/v040"
|
v040 "github.com/cosmos/cosmos-sdk/x/bank/migrations/v040"
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
"github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
||||||
|
@ -63,9 +62,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingCo
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the bank module.
|
// RegisterRESTRoutes registers the REST routes for the bank module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/bank` legacy REST implementation
|
||||||
rest.RegisterHandlers(clientCtx, rtr)
|
// has been removed from the SDK.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the bank module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the bank module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -66,8 +66,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
return genState.Validate()
|
return genState.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the capability module's REST service handlers.
|
// RegisterRESTRoutes registers the REST routes for the capability module.
|
||||||
func (a AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||||
|
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the capability module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the capability module.
|
||||||
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(_ client.Context, _ *runtime.ServeMux) {
|
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(_ client.Context, _ *runtime.ServeMux) {
|
||||||
|
|
|
@ -60,7 +60,8 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
return types.ValidateGenesis(&data)
|
return types.ValidateGenesis(&data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers no REST routes for the crisis module.
|
// RegisterRESTRoutes registers REST routes for the crisis module.
|
||||||
|
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the capability module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the capability module.
|
||||||
|
|
|
@ -2,11 +2,10 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
|
|
||||||
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProposalHandler is the community spend proposal handler.
|
// ProposalHandler is the community spend proposal handler.
|
||||||
var (
|
var (
|
||||||
ProposalHandler = govclient.NewProposalHandler(cli.GetCmdSubmitProposal, rest.ProposalRESTHandler)
|
ProposalHandler = govclient.NewProposalHandler(cli.GetCmdSubmitProposal)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,309 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/common"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
// Get the total rewards balance from all delegations
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/delegators/{delegatorAddr}/rewards",
|
|
||||||
delegatorRewardsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Query a delegation reward
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/delegators/{delegatorAddr}/rewards/{validatorAddr}",
|
|
||||||
delegationRewardsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get the rewards withdrawal address
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/delegators/{delegatorAddr}/withdraw_address",
|
|
||||||
delegatorWithdrawalAddrHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Validator distribution information
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/validators/{validatorAddr}",
|
|
||||||
validatorInfoHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Commission and self-delegation rewards of a single a validator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/validators/{validatorAddr}/rewards",
|
|
||||||
validatorRewardsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Outstanding rewards of a single validator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/validators/{validatorAddr}/outstanding_rewards",
|
|
||||||
outstandingRewardsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get the current distribution parameter values
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/parameters",
|
|
||||||
paramsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get the amount held in the community pool
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/community_pool",
|
|
||||||
communityPoolHandler(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the total rewards balance from all delegations
|
|
||||||
func delegatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delegatorAddr, ok := checkDelegatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryDelegatorParams(delegatorAddr)
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal params: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorTotalRewards)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query a delegation rewards
|
|
||||||
func delegationRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delAddr := mux.Vars(r)["delegatorAddr"]
|
|
||||||
valAddr := mux.Vars(r)["validatorAddr"]
|
|
||||||
|
|
||||||
// query for rewards from a particular delegation
|
|
||||||
res, height, ok := checkResponseQueryDelegationRewards(w, clientCtx, delAddr, valAddr)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query a delegation rewards
|
|
||||||
func delegatorWithdrawalAddrHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
delegatorAddr, ok := checkDelegatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bz := clientCtx.LegacyAmino.MustMarshalJSON(types.NewQueryDelegatorWithdrawAddrParams(delegatorAddr))
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/withdraw_addr", types.QuerierRoute), bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidatorDistInfo defines the properties of
|
|
||||||
// validator distribution information response.
|
|
||||||
type ValidatorDistInfo struct {
|
|
||||||
OperatorAddress sdk.AccAddress `json:"operator_address" yaml:"operator_address"`
|
|
||||||
SelfBondRewards sdk.DecCoins `json:"self_bond_rewards" yaml:"self_bond_rewards"`
|
|
||||||
ValidatorCommission types.ValidatorAccumulatedCommission `json:"val_commission" yaml:"val_commission"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewValidatorDistInfo creates a new instance of ValidatorDistInfo.
|
|
||||||
func NewValidatorDistInfo(operatorAddr sdk.AccAddress, rewards sdk.DecCoins,
|
|
||||||
commission types.ValidatorAccumulatedCommission) ValidatorDistInfo {
|
|
||||||
return ValidatorDistInfo{
|
|
||||||
OperatorAddress: operatorAddr,
|
|
||||||
SelfBondRewards: rewards,
|
|
||||||
ValidatorCommission: commission,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query validator's distribution information
|
|
||||||
func validatorInfoHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
valAddr, ok := checkValidatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// query commission
|
|
||||||
bz, err := common.QueryValidatorCommission(clientCtx, valAddr)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var commission types.ValidatorAccumulatedCommission
|
|
||||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(bz, &commission)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// self bond rewards
|
|
||||||
delAddr := sdk.AccAddress(valAddr)
|
|
||||||
bz, height, ok := checkResponseQueryDelegationRewards(w, clientCtx, delAddr.String(), valAddr.String())
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var rewards sdk.DecCoins
|
|
||||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(bz, &rewards)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err = clientCtx.LegacyAmino.MarshalJSON(NewValidatorDistInfo(delAddr, rewards, commission))
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, bz)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query validator's commission and self-delegation rewards
|
|
||||||
func validatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
valAddr := mux.Vars(r)["validatorAddr"]
|
|
||||||
validatorAddr, ok := checkValidatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delAddr := sdk.AccAddress(validatorAddr).String()
|
|
||||||
bz, height, ok := checkResponseQueryDelegationRewards(w, clientCtx, delAddr, valAddr)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, bz)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the distribution params values
|
|
||||||
func paramsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParams)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func communityPoolHandler(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/community_pool", types.QuerierRoute), nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var result sdk.DecCoins
|
|
||||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &result)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the outstanding rewards
|
|
||||||
func outstandingRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
validatorAddr, ok := checkValidatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bin := clientCtx.LegacyAmino.MustMarshalJSON(types.NewQueryValidatorOutstandingRewardsParams(validatorAddr))
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/validator_outstanding_rewards", types.QuerierRoute), bin)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkResponseQueryDelegationRewards(
|
|
||||||
w http.ResponseWriter, clientCtx client.Context, delAddr, valAddr string,
|
|
||||||
) (res []byte, height int64, ok bool) {
|
|
||||||
|
|
||||||
res, height, err := common.QueryDelegationRewards(clientCtx, delAddr, valAddr)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, height, true
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
|
||||||
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
|
||||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
|
||||||
r := clientrest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
|
|
||||||
registerQueryRoutes(clientCtx, r)
|
|
||||||
registerTxHandlers(clientCtx, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add proto compatible Handler after x/gov migration
|
|
||||||
// ProposalRESTHandler returns a ProposalRESTHandler that exposes the community pool spend REST handler with a given sub-route.
|
|
||||||
func ProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
|
||||||
return govrest.ProposalRESTHandler{
|
|
||||||
SubRoute: "community_pool_spend",
|
|
||||||
Handler: postProposalHandlerFn(clientCtx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func postProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req CommunityPoolSpendProposalReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
content := types.NewCommunityPoolSpendProposal(req.Title, req.Description, req.Recipient, req.Amount)
|
|
||||||
|
|
||||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/common"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
withdrawRewardsReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
}
|
|
||||||
|
|
||||||
setWithdrawalAddrReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
WithdrawAddress sdk.AccAddress `json:"withdraw_address" yaml:"withdraw_address"`
|
|
||||||
}
|
|
||||||
|
|
||||||
fundCommunityPoolReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router) {
|
|
||||||
// Withdraw all delegator rewards
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/delegators/{delegatorAddr}/rewards",
|
|
||||||
newWithdrawDelegatorRewardsHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
|
|
||||||
// Withdraw delegation rewards
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/delegators/{delegatorAddr}/rewards/{validatorAddr}",
|
|
||||||
newWithdrawDelegationRewardsHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
|
|
||||||
// Replace the rewards withdrawal address
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/delegators/{delegatorAddr}/withdraw_address",
|
|
||||||
newSetDelegatorWithdrawalAddrHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
|
|
||||||
// Withdraw validator rewards and commission
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/validators/{validatorAddr}/rewards",
|
|
||||||
newWithdrawValidatorRewardsHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
|
|
||||||
// Fund the community pool
|
|
||||||
r.HandleFunc(
|
|
||||||
"/distribution/community_pool",
|
|
||||||
newFundCommunityPoolHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWithdrawDelegatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req withdrawRewardsReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read and validate URL's variables
|
|
||||||
delAddr, ok := checkDelegatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msgs, err := common.WithdrawAllDelegatorRewards(clientCtx, delAddr)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msgs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWithdrawDelegationRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req withdrawRewardsReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read and validate URL's variables
|
|
||||||
delAddr, ok := checkDelegatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
valAddr, ok := checkValidatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgWithdrawDelegatorReward(delAddr, valAddr)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSetDelegatorWithdrawalAddrHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req setWithdrawalAddrReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read and validate URL's variables
|
|
||||||
delAddr, ok := checkDelegatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgSetWithdrawAddress(delAddr, req.WithdrawAddress)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWithdrawValidatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req withdrawRewardsReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read and validate URL's variable
|
|
||||||
valAddr, ok := checkValidatorAddressVar(w, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare multi-message transaction
|
|
||||||
msgs, err := common.WithdrawValidatorRewardsAndCommission(valAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msgs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFundCommunityPoolHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req fundCommunityPoolReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgFundCommunityPool(req.Amount, fromAddr)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auxiliary
|
|
||||||
|
|
||||||
func checkDelegatorAddressVar(w http.ResponseWriter, r *http.Request) (sdk.AccAddress, bool) {
|
|
||||||
addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["delegatorAddr"])
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkValidatorAddressVar(w http.ResponseWriter, r *http.Request) (sdk.ValAddress, bool) {
|
|
||||||
addr, err := sdk.ValAddressFromBech32(mux.Vars(r)["validatorAddr"])
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr, true
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// CommunityPoolSpendProposalReq defines a community pool spend proposal request body.
|
|
||||||
CommunityPoolSpendProposalReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
|
|
||||||
Title string `json:"title" yaml:"title"`
|
|
||||||
Description string `json:"description" yaml:"description"`
|
|
||||||
Recipient sdk.AccAddress `json:"recipient" yaml:"recipient"`
|
|
||||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
|
||||||
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"`
|
|
||||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
|
||||||
}
|
|
||||||
)
|
|
|
@ -15,3 +15,7 @@ func TestIntegrationTestSuite(t *testing.T) {
|
||||||
cfg.NumValidators = 1
|
cfg.NumValidators = 1
|
||||||
suite.Run(t, NewIntegrationTestSuite(cfg))
|
suite.Run(t, NewIntegrationTestSuite(cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGRPCQueryTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(GRPCQueryTestSuite))
|
||||||
|
}
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
package rest_test
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/rest"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
type GRPCQueryTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
|
||||||
cfg network.Config
|
cfg network.Config
|
||||||
network *network.Network
|
network *network.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
func (s *GRPCQueryTestSuite) SetupSuite() {
|
||||||
s.T().Log("setting up integration test suite")
|
s.T().Log("setting up integration test suite")
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
cfg := network.DefaultConfig()
|
||||||
|
@ -38,7 +37,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
|
func (s *GRPCQueryTestSuite) TestQueryParamsGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -69,7 +68,7 @@ func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryOutstandingRewardsGRPC() {
|
func (s *GRPCQueryTestSuite) TestQueryOutstandingRewardsGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -123,7 +122,7 @@ func (s *IntegrationTestSuite) TestQueryOutstandingRewardsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryValidatorCommissionGRPC() {
|
func (s *GRPCQueryTestSuite) TestQueryValidatorCommissionGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -177,7 +176,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorCommissionGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQuerySlashesGRPC() {
|
func (s *GRPCQueryTestSuite) TestQuerySlashesGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -236,9 +235,9 @@ func (s *IntegrationTestSuite) TestQuerySlashesGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
|
func (s *GRPCQueryTestSuite) TestQueryDelegatorRewardsGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseUrl := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
rewards, err := sdk.ParseDecCoins("9.8stake")
|
rewards, err := sdk.ParseDecCoins("9.8stake")
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
@ -253,7 +252,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"wrong delegator address",
|
"wrong delegator address",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards", baseUrl, "wrongDelegatorAddress"),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards", baseURL, "wrongDelegatorAddress"),
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
true,
|
true,
|
||||||
&types.QueryDelegationTotalRewardsResponse{},
|
&types.QueryDelegationTotalRewardsResponse{},
|
||||||
|
@ -261,7 +260,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid request",
|
"valid request",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards", baseUrl, val.Address.String()),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards", baseURL, val.Address.String()),
|
||||||
map[string]string{
|
map[string]string{
|
||||||
grpctypes.GRPCBlockHeightHeader: "2",
|
grpctypes.GRPCBlockHeightHeader: "2",
|
||||||
},
|
},
|
||||||
|
@ -276,7 +275,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"wrong validator address(specific validator rewards)",
|
"wrong validator address(specific validator rewards)",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards/%s", baseUrl, val.Address.String(), "wrongValAddress"),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards/%s", baseURL, val.Address.String(), "wrongValAddress"),
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
true,
|
true,
|
||||||
&types.QueryDelegationTotalRewardsResponse{},
|
&types.QueryDelegationTotalRewardsResponse{},
|
||||||
|
@ -284,7 +283,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid request(specific validator rewards)",
|
"valid request(specific validator rewards)",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards/%s", baseUrl, val.Address.String(), val.ValAddress.String()),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards/%s", baseURL, val.Address.String(), val.ValAddress.String()),
|
||||||
map[string]string{
|
map[string]string{
|
||||||
grpctypes.GRPCBlockHeightHeader: "2",
|
grpctypes.GRPCBlockHeightHeader: "2",
|
||||||
},
|
},
|
||||||
|
@ -312,9 +311,9 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
|
func (s *GRPCQueryTestSuite) TestQueryDelegatorValidatorsGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseUrl := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -325,21 +324,21 @@ func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"empty delegator address",
|
"empty delegator address",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseUrl, ""),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseURL, ""),
|
||||||
true,
|
true,
|
||||||
&types.QueryDelegatorValidatorsResponse{},
|
&types.QueryDelegatorValidatorsResponse{},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"wrong delegator address",
|
"wrong delegator address",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseUrl, "wrongDelegatorAddress"),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseURL, "wrongDelegatorAddress"),
|
||||||
true,
|
true,
|
||||||
&types.QueryDelegatorValidatorsResponse{},
|
&types.QueryDelegatorValidatorsResponse{},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid request",
|
"valid request",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseUrl, val.Address.String()),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseURL, val.Address.String()),
|
||||||
false,
|
false,
|
||||||
&types.QueryDelegatorValidatorsResponse{},
|
&types.QueryDelegatorValidatorsResponse{},
|
||||||
&types.QueryDelegatorValidatorsResponse{
|
&types.QueryDelegatorValidatorsResponse{
|
||||||
|
@ -364,9 +363,9 @@ func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryWithdrawAddressGRPC() {
|
func (s *GRPCQueryTestSuite) TestQueryWithdrawAddressGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseUrl := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -377,21 +376,21 @@ func (s *IntegrationTestSuite) TestQueryWithdrawAddressGRPC() {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"empty delegator address",
|
"empty delegator address",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseUrl, ""),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseURL, ""),
|
||||||
true,
|
true,
|
||||||
&types.QueryDelegatorWithdrawAddressResponse{},
|
&types.QueryDelegatorWithdrawAddressResponse{},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"wrong delegator address",
|
"wrong delegator address",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseUrl, "wrongDelegatorAddress"),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseURL, "wrongDelegatorAddress"),
|
||||||
true,
|
true,
|
||||||
&types.QueryDelegatorWithdrawAddressResponse{},
|
&types.QueryDelegatorWithdrawAddressResponse{},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid request",
|
"valid request",
|
||||||
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseUrl, val.Address.String()),
|
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/withdraw_address", baseURL, val.Address.String()),
|
||||||
false,
|
false,
|
||||||
&types.QueryDelegatorWithdrawAddressResponse{},
|
&types.QueryDelegatorWithdrawAddressResponse{},
|
||||||
&types.QueryDelegatorWithdrawAddressResponse{
|
&types.QueryDelegatorWithdrawAddressResponse{
|
||||||
|
@ -416,7 +415,7 @@ func (s *IntegrationTestSuite) TestQueryWithdrawAddressGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryValidatorCommunityPoolGRPC() {
|
func (s *GRPCQueryTestSuite) TestQueryValidatorCommunityPoolGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -460,7 +459,3 @@ func (s *IntegrationTestSuite) TestQueryValidatorCommunityPoolGRPC() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
|
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/simulation"
|
"github.com/cosmos/cosmos-sdk/x/distribution/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
||||||
|
@ -64,9 +63,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config sdkclient.TxEn
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the distribution module.
|
// RegisterRESTRoutes registers the REST routes for the distribution module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx sdkclient.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/distribution` legacy REST implementation
|
||||||
rest.RegisterHandlers(clientCtx, rtr)
|
// has been removed from the SDK.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(_ sdkclient.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the distribution module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the distribution module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -1,30 +1,19 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import "github.com/spf13/cobra"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/client/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// RESTHandlerFn defines a REST service handler for evidence submission
|
|
||||||
RESTHandlerFn func(client.Context) rest.EvidenceRESTHandler
|
|
||||||
|
|
||||||
// CLIHandlerFn defines a CLI command handler for evidence submission
|
// CLIHandlerFn defines a CLI command handler for evidence submission
|
||||||
CLIHandlerFn func() *cobra.Command
|
CLIHandlerFn func() *cobra.Command
|
||||||
|
|
||||||
// EvidenceHandler defines a type that exposes REST and CLI client handlers for
|
// EvidenceHandler wraps CLIHandlerFn.
|
||||||
// evidence submission.
|
|
||||||
EvidenceHandler struct {
|
EvidenceHandler struct {
|
||||||
CLIHandler CLIHandlerFn
|
CLIHandler CLIHandlerFn
|
||||||
RESTHandler RESTHandlerFn
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEvidenceHandler(cliHandler CLIHandlerFn, restHandler RESTHandlerFn) EvidenceHandler {
|
func NewEvidenceHandler(cliHandler CLIHandlerFn) EvidenceHandler {
|
||||||
return EvidenceHandler{
|
return EvidenceHandler{
|
||||||
CLIHandler: cliHandler,
|
CLIHandler: cliHandler,
|
||||||
RESTHandler: restHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/types"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc(
|
|
||||||
fmt.Sprintf("/evidence/{%s}", RestParamEvidenceHash),
|
|
||||||
queryEvidenceHandler(clientCtx),
|
|
||||||
).Methods(MethodGet)
|
|
||||||
|
|
||||||
r.HandleFunc(
|
|
||||||
"/evidence",
|
|
||||||
queryAllEvidenceHandler(clientCtx),
|
|
||||||
).Methods(MethodGet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryEvidenceHandler(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
evidenceHash := vars[RestParamEvidenceHash]
|
|
||||||
|
|
||||||
if strings.TrimSpace(evidenceHash) == "" {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "evidence hash required but not specified")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
decodedHash, err := hex.DecodeString(evidenceHash)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid evidence hash")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryEvidenceRequest(decodedHash)
|
|
||||||
bz, err := clientCtx.Codec.MarshalJSON(params)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryEvidence)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryAllEvidenceHandler(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryAllEvidenceParams(page, limit)
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllEvidence)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
// REST query and parameter values
|
|
||||||
const (
|
|
||||||
RestParamEvidenceHash = "evidence-hash"
|
|
||||||
|
|
||||||
MethodGet = "GET"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EvidenceRESTHandler defines a REST service evidence handler implemented in
|
|
||||||
// another module. The sub-route is mounted on the evidence REST handler.
|
|
||||||
type EvidenceRESTHandler struct {
|
|
||||||
SubRoute string
|
|
||||||
Handler func(http.ResponseWriter, *http.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterRoutes registers all Evidence submission handlers for the evidence module's
|
|
||||||
// REST service handler.
|
|
||||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router, handlers []EvidenceRESTHandler) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
|
|
||||||
registerQueryRoutes(clientCtx, r)
|
|
||||||
registerTxRoutes(clientCtx, r, handlers)
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerTxRoutes(clientCtx client.Context, r *mux.Router, handlers []EvidenceRESTHandler) {
|
|
||||||
// TODO: Register tx handlers.
|
|
||||||
}
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
@ -21,7 +21,6 @@ import (
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
eviclient "github.com/cosmos/cosmos-sdk/x/evidence/client"
|
eviclient "github.com/cosmos/cosmos-sdk/x/evidence/client"
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/evidence/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/keeper"
|
"github.com/cosmos/cosmos-sdk/x/evidence/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/simulation"
|
"github.com/cosmos/cosmos-sdk/x/evidence/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/types"
|
"github.com/cosmos/cosmos-sdk/x/evidence/types"
|
||||||
|
@ -75,15 +74,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the evidence module's REST service handlers.
|
// RegisterRESTRoutes registers the evidence module's REST service handlers.
|
||||||
func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/evidence` legacy REST implementation
|
||||||
evidenceRESTHandlers := make([]rest.EvidenceRESTHandler, len(a.evidenceHandlers))
|
// has been removed from the SDK.
|
||||||
|
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||||
for i, evidenceHandler := range a.evidenceHandlers {
|
|
||||||
evidenceRESTHandlers[i] = evidenceHandler.RESTHandler(clientCtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.RegisterRoutes(clientCtx, rtr, evidenceRESTHandlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the evidence module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the evidence module.
|
||||||
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -82,7 +82,8 @@ func (a AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config sdkclient.Tx
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the feegrant module.
|
// RegisterRESTRoutes registers the REST routes for the feegrant module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(ctx sdkclient.Context, rtr *mux.Router) {}
|
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||||
|
func (AppModuleBasic) RegisterRESTRoutes(clientCtx sdkclient.Context, rtr *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the feegrant module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the feegrant module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/genutil/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryGenesisTxs writes the genesis transactions to the response if no error
|
|
||||||
// occurs.
|
|
||||||
func QueryGenesisTxs(clientCtx client.Context, w http.ResponseWriter) {
|
|
||||||
resultGenesis, err := clientCtx.Client.Genesis(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(
|
|
||||||
w, http.StatusInternalServerError,
|
|
||||||
fmt.Sprintf("failed to retrieve genesis from client: %s", err),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
appState, err := types.GenesisStateFromGenDoc(*resultGenesis.Genesis)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(
|
|
||||||
w, http.StatusInternalServerError,
|
|
||||||
fmt.Sprintf("failed to decode genesis doc: %s", err),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
genState := types.GetGenesisStateFromAppState(clientCtx.Codec, appState)
|
|
||||||
genTxs := make([]sdk.Tx, len(genState.GenTxs))
|
|
||||||
for i, tx := range genState.GenTxs {
|
|
||||||
err := clientCtx.LegacyAmino.UnmarshalJSON(tx, &genTxs[i])
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(
|
|
||||||
w, http.StatusInternalServerError,
|
|
||||||
fmt.Sprintf("failed to decode genesis transaction: %s", err),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, genTxs)
|
|
||||||
}
|
|
|
@ -54,6 +54,7 @@ func (b AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, txEncodingConfig cl
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the genutil module.
|
// RegisterRESTRoutes registers the REST routes for the genutil module.
|
||||||
|
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the genutil module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the genutil module.
|
||||||
|
|
|
@ -2,27 +2,19 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// function to create the rest handler
|
|
||||||
type RESTHandlerFn func(client.Context) rest.ProposalRESTHandler
|
|
||||||
|
|
||||||
// function to create the cli handler
|
// function to create the cli handler
|
||||||
type CLIHandlerFn func() *cobra.Command
|
type CLIHandlerFn func() *cobra.Command
|
||||||
|
|
||||||
// The combined type for a proposal handler for both cli and rest
|
// ProposalHandler wraps CLIHandlerFn
|
||||||
type ProposalHandler struct {
|
type ProposalHandler struct {
|
||||||
CLIHandler CLIHandlerFn
|
CLIHandler CLIHandlerFn
|
||||||
RESTHandler RESTHandlerFn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProposalHandler creates a new ProposalHandler object
|
// NewProposalHandler creates a new ProposalHandler object
|
||||||
func NewProposalHandler(cliHandler CLIHandlerFn, restHandler RESTHandlerFn) ProposalHandler {
|
func NewProposalHandler(cliHandler CLIHandlerFn) ProposalHandler {
|
||||||
return ProposalHandler{
|
return ProposalHandler{
|
||||||
CLIHandler: cliHandler,
|
CLIHandler: cliHandler,
|
||||||
RESTHandler: restHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,473 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), queryParamsHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID), queryProposerHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(clientCtx)).Methods("GET")
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryParamsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
paramType := vars[RestParamsType]
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil)
|
|
||||||
if rest.CheckNotFoundError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
|
|
||||||
if len(strProposalID) == 0 {
|
|
||||||
err := errors.New("proposalId required but not specified")
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryProposalParams(proposalID)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryDepositsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryProposalParams(proposalID)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var proposal types.Proposal
|
|
||||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &proposal)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// For inactive proposals we must query the txs directly to get the deposits
|
|
||||||
// as they're no longer in state.
|
|
||||||
propStatus := proposal.Status
|
|
||||||
if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
|
|
||||||
res, err = gcutils.QueryDepositsByTxQuery(clientCtx, params)
|
|
||||||
} else {
|
|
||||||
res, _, err = clientCtx.QueryWithData("custom/gov/deposits", bz)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryProposerHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := gcutils.QueryProposerByTxQuery(clientCtx, proposalID)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryDepositHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
bechDepositorAddr := vars[RestDepositor]
|
|
||||||
|
|
||||||
if len(strProposalID) == 0 {
|
|
||||||
err := errors.New("proposalId required but not specified")
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bechDepositorAddr) == 0 {
|
|
||||||
err := errors.New("depositor address required but not specified")
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryDepositParams(proposalID, depositorAddr)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err := clientCtx.QueryWithData("custom/gov/deposit", bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var deposit types.Deposit
|
|
||||||
if rest.CheckBadRequestError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &deposit)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// For an empty deposit, either the proposal does not exist or is inactive in
|
|
||||||
// which case the deposit would be removed from state and should be queried
|
|
||||||
// for directly via a txs query.
|
|
||||||
if deposit.Empty() {
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(types.NewQueryProposalParams(proposalID))
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err = clientCtx.QueryWithData("custom/gov/proposal", bz)
|
|
||||||
if err != nil || len(res) == 0 {
|
|
||||||
err := fmt.Errorf("proposalID %d does not exist", proposalID)
|
|
||||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err = gcutils.QueryDepositByTxQuery(clientCtx, params)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryVoteHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
bechVoterAddr := vars[RestVoter]
|
|
||||||
|
|
||||||
if len(strProposalID) == 0 {
|
|
||||||
err := errors.New("proposalId required but not specified")
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bechVoterAddr) == 0 {
|
|
||||||
err := errors.New("voter address required but not specified")
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
|
|
||||||
if err != nil {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryVoteParams(proposalID, voterAddr)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err := clientCtx.QueryWithData("custom/gov/vote", bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var vote types.Vote
|
|
||||||
if rest.CheckBadRequestError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &vote)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// For an empty vote, either the proposal does not exist or is inactive in
|
|
||||||
// which case the vote would be removed from state and should be queried for
|
|
||||||
// directly via a txs query.
|
|
||||||
if vote.Empty() {
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(types.NewQueryProposalParams(proposalID))
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err = clientCtx.QueryWithData("custom/gov/proposal", bz)
|
|
||||||
if err != nil || len(res) == 0 {
|
|
||||||
err := fmt.Errorf("proposalID %d does not exist", proposalID)
|
|
||||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err = gcutils.QueryVoteByTxQuery(clientCtx, params)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: Split this functionality into helper functions to remove the above
|
|
||||||
func queryVotesOnProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgs(r)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
|
|
||||||
if len(strProposalID) == 0 {
|
|
||||||
err := errors.New("proposalId required but not specified")
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(types.NewQueryProposalParams(proposalID))
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var proposal types.Proposal
|
|
||||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &proposal)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// For inactive proposals we must query the txs directly to get the votes
|
|
||||||
// as they're no longer in state.
|
|
||||||
params := types.NewQueryProposalVotesParams(proposalID, page, limit)
|
|
||||||
|
|
||||||
propStatus := proposal.Status
|
|
||||||
if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
|
|
||||||
res, err = gcutils.QueryVotesByTxQuery(clientCtx, params)
|
|
||||||
} else {
|
|
||||||
bz, err = clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err = clientCtx.QueryWithData("custom/gov/votes", bz)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query list of governance proposals
|
|
||||||
func queryProposalsWithParameterFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
voterAddr sdk.AccAddress
|
|
||||||
depositorAddr sdk.AccAddress
|
|
||||||
proposalStatus types.ProposalStatus
|
|
||||||
)
|
|
||||||
|
|
||||||
if v := r.URL.Query().Get(RestVoter); len(v) != 0 {
|
|
||||||
voterAddr, err = sdk.AccAddressFromBech32(v)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := r.URL.Query().Get(RestDepositor); len(v) != 0 {
|
|
||||||
depositorAddr, err = sdk.AccAddressFromBech32(v)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := r.URL.Query().Get(RestProposalStatus); len(v) != 0 {
|
|
||||||
proposalStatus, err = types.ProposalStatusFromString(gcutils.NormalizeProposalStatus(v))
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryProposalsParams(page, limit, proposalStatus, voterAddr, depositorAddr)
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryProposals)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: Split this functionality into helper functions to remove the above
|
|
||||||
func queryTallyOnProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
|
|
||||||
if len(strProposalID) == 0 {
|
|
||||||
err := errors.New("proposalId required but not specified")
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryProposalParams(proposalID)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData("custom/gov/tally", bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// REST Variable names
|
|
||||||
const (
|
|
||||||
RestParamsType = "type"
|
|
||||||
RestProposalID = "proposal-id"
|
|
||||||
RestDepositor = "depositor"
|
|
||||||
RestVoter = "voter"
|
|
||||||
RestProposalStatus = "status"
|
|
||||||
RestNumLimit = "limit"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProposalRESTHandler defines a REST handler implemented in another module. The
|
|
||||||
// sub-route is mounted on the governance REST handler.
|
|
||||||
type ProposalRESTHandler struct {
|
|
||||||
SubRoute string
|
|
||||||
Handler func(http.ResponseWriter, *http.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router, phs []ProposalRESTHandler) {
|
|
||||||
r := clientrest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
registerQueryRoutes(clientCtx, r)
|
|
||||||
registerTxHandlers(clientCtx, r, phs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostProposalReq defines the properties of a proposal request's body.
|
|
||||||
type PostProposalReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
Title string `json:"title" yaml:"title"` // Title of the proposal
|
|
||||||
Description string `json:"description" yaml:"description"` // Description of the proposal
|
|
||||||
ProposalType string `json:"proposal_type" yaml:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal }
|
|
||||||
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"` // Address of the proposer
|
|
||||||
InitialDeposit sdk.Coins `json:"initial_deposit" yaml:"initial_deposit"` // Coins to add to the proposal's deposit
|
|
||||||
}
|
|
||||||
|
|
||||||
// DepositReq defines the properties of a deposit request's body.
|
|
||||||
type DepositReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor
|
|
||||||
Amount sdk.Coins `json:"amount" yaml:"amount"` // Coins to add to the proposal's deposit
|
|
||||||
}
|
|
||||||
|
|
||||||
// VoteReq defines the properties of a vote request's body.
|
|
||||||
type VoteReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
Voter sdk.AccAddress `json:"voter" yaml:"voter"` // address of the voter
|
|
||||||
Option string `json:"option" yaml:"option"` // option from OptionSet chosen by the voter
|
|
||||||
}
|
|
|
@ -1,167 +0,0 @@
|
||||||
// +build norace
|
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestLegacyGetAllProposals() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
numProposals int
|
|
||||||
expErr bool
|
|
||||||
expErrMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"get all existing proposals",
|
|
||||||
fmt.Sprintf("%s/gov/proposals", val.APIAddress),
|
|
||||||
3, false, "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"get proposals in deposit period",
|
|
||||||
fmt.Sprintf("%s/gov/proposals?status=deposit_period", val.APIAddress),
|
|
||||||
1, false, "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"get proposals in voting period",
|
|
||||||
fmt.Sprintf("%s/gov/proposals?status=voting_period", val.APIAddress),
|
|
||||||
2, false, "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wrong status parameter",
|
|
||||||
fmt.Sprintf("%s/gov/proposals?status=invalidstatus", val.APIAddress),
|
|
||||||
0, true, "'invalidstatus' is not a valid proposal status",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
s.Run(tc.name, func() {
|
|
||||||
respJSON, err := rest.GetRequest(tc.url)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
if tc.expErr {
|
|
||||||
var errResp rest.ErrorResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
|
||||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
|
||||||
} else {
|
|
||||||
var resp = rest.ResponseWithHeight{}
|
|
||||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Check results.
|
|
||||||
var proposals types.Proposals
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &proposals))
|
|
||||||
s.Require().Equal(tc.numProposals, len(proposals))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestLegacyGetVote() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
voterAddressBech32 := val.Address.String()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
expErr bool
|
|
||||||
expErrMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"get non existing proposal",
|
|
||||||
fmt.Sprintf("%s/gov/proposals/%s/votes/%s", val.APIAddress, "10", voterAddressBech32),
|
|
||||||
true, "proposalID 10 does not exist",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"get proposal with wrong voter address",
|
|
||||||
fmt.Sprintf("%s/gov/proposals/%s/votes/%s", val.APIAddress, "1", "wrongVoterAddress"),
|
|
||||||
true, "decoding bech32 failed: string not all lowercase or all uppercase",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"get proposal with id",
|
|
||||||
fmt.Sprintf("%s/gov/proposals/%s/votes/%s", val.APIAddress, "1", voterAddressBech32),
|
|
||||||
false, "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
s.Run(tc.name, func() {
|
|
||||||
respJSON, err := rest.GetRequest(tc.url)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
if tc.expErr {
|
|
||||||
var errResp rest.ErrorResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
|
||||||
|
|
||||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
|
||||||
} else {
|
|
||||||
var resp = rest.ResponseWithHeight{}
|
|
||||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Check result is not empty.
|
|
||||||
var vote types.Vote
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &vote))
|
|
||||||
s.Require().Equal(val.Address.String(), vote.Voter)
|
|
||||||
// Note that option is now an int.
|
|
||||||
s.Require().Equal([]types.WeightedVoteOption{{types.VoteOption(1), sdk.NewDec(1)}}, vote.Options)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestLegacyGetVotes() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
expErr bool
|
|
||||||
expErrMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"votes with empty proposal id",
|
|
||||||
fmt.Sprintf("%s/gov/proposals/%s/votes", val.APIAddress, ""),
|
|
||||||
true, "'votes' is not a valid uint64",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"get votes with valid id",
|
|
||||||
fmt.Sprintf("%s/gov/proposals/%s/votes", val.APIAddress, "1"),
|
|
||||||
false, "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
s.Run(tc.name, func() {
|
|
||||||
respJSON, err := rest.GetRequest(tc.url)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
if tc.expErr {
|
|
||||||
var errResp rest.ErrorResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
|
||||||
|
|
||||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
|
||||||
} else {
|
|
||||||
var resp = rest.ResponseWithHeight{}
|
|
||||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Check result is not empty.
|
|
||||||
var votes []types.Vote
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &votes))
|
|
||||||
s.Require().Greater(len(votes), 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router, phs []ProposalRESTHandler) {
|
|
||||||
propSubRtr := r.PathPrefix("/gov/proposals").Subrouter()
|
|
||||||
for _, ph := range phs {
|
|
||||||
propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.HandleFunc("/gov/proposals", newPostProposalHandlerFn(clientCtx)).Methods("POST")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), newDepositHandlerFn(clientCtx)).Methods("POST")
|
|
||||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), newVoteHandlerFn(clientCtx)).Methods("POST")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPostProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req PostProposalReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalType := gcutils.NormalizeProposalType(req.ProposalType)
|
|
||||||
content := types.ContentFromProposalType(req.Title, req.Description, proposalType)
|
|
||||||
|
|
||||||
msg, err := types.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDepositHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
|
|
||||||
if len(strProposalID) == 0 {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req DepositReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the message
|
|
||||||
msg := types.NewMsgDeposit(req.Depositor, proposalID, req.Amount)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newVoteHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
strProposalID := vars[RestProposalID]
|
|
||||||
|
|
||||||
if len(strProposalID) == 0 {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req VoteReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
voteOption, err := types.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option))
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the message
|
|
||||||
msg := types.NewMsgVote(req.Voter, proposalID, voteOption)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +1,17 @@
|
||||||
// +build norace
|
package testutil
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/rest"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
|
||||||
govtestutil "github.com/cosmos/cosmos-sdk/x/gov/client/testutil"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
s.cfg = network.DefaultConfig()
|
|
||||||
s.cfg.NumValidators = 1
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
|
|
||||||
// create a proposal with deposit
|
|
||||||
_, err = govtestutil.MsgSubmitProposal(val.ClientCtx, val.Address.String(),
|
|
||||||
"Text Proposal 1", "Where is the title!?", types.ProposalTypeText,
|
|
||||||
fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens).String()))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// vote for proposal
|
|
||||||
_, err = govtestutil.MsgVote(val.ClientCtx, val.Address.String(), "1", "yes")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// create a proposal without deposit
|
|
||||||
_, err = govtestutil.MsgSubmitProposal(val.ClientCtx, val.Address.String(),
|
|
||||||
"Text Proposal 2", "Where is the title!?", types.ProposalTypeText)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// create a proposal3 with deposit
|
|
||||||
_, err = govtestutil.MsgSubmitProposal(val.ClientCtx, val.Address.String(),
|
|
||||||
"Text Proposal 3", "Where is the title!?", types.ProposalTypeText,
|
|
||||||
fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, types.DefaultMinDepositTokens).String()))
|
|
||||||
s.Require().NoError(err)
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// vote for proposal3 as val
|
|
||||||
_, err = govtestutil.MsgVote(val.ClientCtx, val.Address.String(), "3", "yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestGetProposalGRPC() {
|
func (s *IntegrationTestSuite) TestGetProposalGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
|
||||||
|
@ -476,7 +415,3 @@ func (s *IntegrationTestSuite) TestGetParamsGRPC() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/keeper"
|
"github.com/cosmos/cosmos-sdk/x/gov/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/simulation"
|
"github.com/cosmos/cosmos-sdk/x/gov/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||||
|
@ -73,14 +72,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the gov module.
|
// RegisterRESTRoutes registers the REST routes for the gov module.
|
||||||
func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/gov` legacy REST implementation
|
||||||
proposalRESTHandlers := make([]rest.ProposalRESTHandler, 0, len(a.proposalHandlers))
|
// has been removed from the SDK.
|
||||||
for _, proposalHandler := range a.proposalHandlers {
|
func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {}
|
||||||
proposalRESTHandlers = append(proposalRESTHandlers, proposalHandler.RESTHandler(clientCtx))
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.RegisterHandlers(clientCtx, rtr, proposalRESTHandlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the gov module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the gov module.
|
||||||
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc(
|
|
||||||
"/minting/parameters",
|
|
||||||
queryParamsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
r.HandleFunc(
|
|
||||||
"/minting/inflation",
|
|
||||||
queryInflationHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
r.HandleFunc(
|
|
||||||
"/minting/annual-provisions",
|
|
||||||
queryAnnualProvisionsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryParamsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters)
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryInflationHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryInflation)
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryAnnualProvisionsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAnnualProvisions)
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegisterRoutes registers minting module REST handlers on the provided router.
|
|
||||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
registerQueryRoutes(clientCtx, r)
|
|
||||||
}
|
|
|
@ -1,62 +1,17 @@
|
||||||
// +build norace
|
package testutil
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
||||||
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
|
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
|
||||||
|
|
||||||
genesisState := cfg.GenesisState
|
|
||||||
cfg.NumValidators = 1
|
|
||||||
|
|
||||||
var mintData minttypes.GenesisState
|
|
||||||
s.Require().NoError(cfg.Codec.UnmarshalJSON(genesisState[minttypes.ModuleName], &mintData))
|
|
||||||
|
|
||||||
inflation := sdk.MustNewDecFromStr("1.0")
|
|
||||||
mintData.Minter.Inflation = inflation
|
|
||||||
mintData.Params.InflationMin = inflation
|
|
||||||
mintData.Params.InflationMax = inflation
|
|
||||||
|
|
||||||
mintDataBz, err := cfg.Codec.MarshalJSON(&mintData)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
genesisState[minttypes.ModuleName] = mintDataBz
|
|
||||||
cfg.GenesisState = genesisState
|
|
||||||
|
|
||||||
s.cfg = cfg
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
||||||
s.T().Log("tearing down integration test suite")
|
|
||||||
s.network.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryGRPC() {
|
func (s *IntegrationTestSuite) TestQueryGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
@ -107,7 +62,3 @@ func (s *IntegrationTestSuite) TestQueryGRPC() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/mint/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint/keeper"
|
"github.com/cosmos/cosmos-sdk/x/mint/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint/simulation"
|
"github.com/cosmos/cosmos-sdk/x/mint/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint/types"
|
"github.com/cosmos/cosmos-sdk/x/mint/types"
|
||||||
|
@ -65,9 +64,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the mint module.
|
// RegisterRESTRoutes registers the REST routes for the mint module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/mint` legacy REST implementation
|
||||||
rest.RegisterRoutes(clientCtx, rtr)
|
// has been removed from the SDK.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the mint module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the mint module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -3,8 +3,7 @@ package client
|
||||||
import (
|
import (
|
||||||
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
||||||
"github.com/cosmos/cosmos-sdk/x/params/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/params/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/params/client/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProposalHandler is the param change proposal handler.
|
// ProposalHandler is the param change proposal handler.
|
||||||
var ProposalHandler = govclient.NewProposalHandler(cli.NewSubmitParamChangeProposalTxCmd, rest.ProposalRESTHandler)
|
var ProposalHandler = govclient.NewProposalHandler(cli.NewSubmitParamChangeProposalTxCmd)
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
|
||||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
||||||
paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProposalRESTHandler returns a ProposalRESTHandler that exposes the param
|
|
||||||
// change REST handler with a given sub-route.
|
|
||||||
func ProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
|
||||||
return govrest.ProposalRESTHandler{
|
|
||||||
SubRoute: "param_change",
|
|
||||||
Handler: postProposalHandlerFn(clientCtx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func postProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req paramscutils.ParamChangeProposalReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
content := proposal.NewParameterChangeProposal(req.Title, req.Description, req.Changes.ToParamChanges())
|
|
||||||
|
|
||||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +1,14 @@
|
||||||
package rest_test
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
|
||||||
cfg.NumValidators = 1
|
|
||||||
s.cfg = cfg
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
||||||
s.T().Log("tearing down integration test suite")
|
|
||||||
s.network.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
|
func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
@ -126,7 +96,3 @@ func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,17 +29,6 @@ type (
|
||||||
Changes ParamChangesJSON `json:"changes" yaml:"changes"`
|
Changes ParamChangesJSON `json:"changes" yaml:"changes"`
|
||||||
Deposit string `json:"deposit" yaml:"deposit"`
|
Deposit string `json:"deposit" yaml:"deposit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParamChangeProposalReq defines a parameter change proposal request body.
|
|
||||||
ParamChangeProposalReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
|
|
||||||
Title string `json:"title" yaml:"title"`
|
|
||||||
Description string `json:"description" yaml:"description"`
|
|
||||||
Changes ParamChangesJSON `json:"changes" yaml:"changes"`
|
|
||||||
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"`
|
|
||||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewParamChangeJSON(subspace, key string, value json.RawMessage) ParamChangeJSON {
|
func NewParamChangeJSON(subspace, key string, value json.RawMessage) ParamChangeJSON {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
|
@ -53,7 +53,8 @@ func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, config client.TxEncodin
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the params module.
|
// RegisterRESTRoutes registers the REST routes for the params module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||||
|
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the params module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the params module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/bech32/legacybech32" //nolint:staticcheck
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc(
|
|
||||||
"/slashing/validators/{validatorPubKey}/signing_info",
|
|
||||||
signingInfoHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
r.HandleFunc(
|
|
||||||
"/slashing/signing_infos",
|
|
||||||
signingInfoHandlerListFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
r.HandleFunc(
|
|
||||||
"/slashing/parameters",
|
|
||||||
queryParamsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: http request handler to query signing info
|
|
||||||
func signingInfoHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
pk, err := legacybech32.UnmarshalPubKey(legacybech32.ConsPK, vars["validatorPubKey"])
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.QuerySigningInfoRequest{ConsAddress: pk.Address().String()}
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySigningInfo)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// http request handler to query signing info
|
|
||||||
func signingInfoHandlerListFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQuerySigningInfosParams(page, limit)
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySigningInfos)
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryParamsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/parameters", types.QuerierRoute)
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
|
|
||||||
registerQueryRoutes(clientCtx, r)
|
|
||||||
registerTxHandlers(clientCtx, r)
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc("/slashing/validators/{validatorAddr}/unjail", NewUnjailRequestHandlerFn(clientCtx)).Methods("POST")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unjail TX body
|
|
||||||
type UnjailReq struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUnjailRequestHandlerFn returns an HTTP REST handler for creating a MsgUnjail
|
|
||||||
// transaction.
|
|
||||||
func NewUnjailRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bech32Validator := vars["validatorAddr"]
|
|
||||||
|
|
||||||
var req UnjailReq
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
valAddr, err := sdk.ValAddressFromBech32(bech32Validator)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fromAddr, valAddr) {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgUnjail(valAddr)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +1,18 @@
|
||||||
package rest_test
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
|
||||||
cfg.NumValidators = 1
|
|
||||||
s.cfg = cfg
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
||||||
s.T().Log("tearing down integration test suite")
|
|
||||||
s.network.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestGRPCQueries() {
|
func (s *IntegrationTestSuite) TestGRPCQueries() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
@ -130,7 +100,3 @@ func (s *IntegrationTestSuite) TestGRPCQueries() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
@ -20,7 +20,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/slashing/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/keeper"
|
"github.com/cosmos/cosmos-sdk/x/slashing/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
"github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
||||||
|
@ -72,9 +71,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the slashing module.
|
// RegisterRESTRoutes registers the REST routes for the slashing module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/slashing` legacy REST implementation
|
||||||
rest.RegisterHandlers(clientCtx, rtr)
|
// has been removed from the SDK.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the slashig module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the slashig module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -1,400 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
// Get all delegations from a delegator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/delegations",
|
|
||||||
delegatorDelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get all unbonding delegations from a delegator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/unbonding_delegations",
|
|
||||||
delegatorUnbondingDelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get all staking txs (i.e msgs) from a delegator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/txs",
|
|
||||||
delegatorTxsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Query all validators that a delegator is bonded to
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/validators",
|
|
||||||
delegatorValidatorsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Query a validator that a delegator is bonded to
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/validators/{validatorAddr}",
|
|
||||||
delegatorValidatorHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Query a delegation between a delegator and a validator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/delegations/{validatorAddr}",
|
|
||||||
delegationHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Query all unbonding delegations between a delegator and a validator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}",
|
|
||||||
unbondingDelegationHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Query redelegations (filters in query params)
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/redelegations",
|
|
||||||
redelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get all validators
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/validators",
|
|
||||||
validatorsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get a single validator info
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/validators/{validatorAddr}",
|
|
||||||
validatorHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get all delegations to a validator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/validators/{validatorAddr}/delegations",
|
|
||||||
validatorDelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get all unbonding delegations from a validator
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/validators/{validatorAddr}/unbonding_delegations",
|
|
||||||
validatorUnbondingDelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get HistoricalInfo at a given height
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/historical_info/{height}",
|
|
||||||
historicalInfoHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get the current state of the staking pool
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/pool",
|
|
||||||
poolHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
|
|
||||||
// Get the current staking parameter values
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/parameters",
|
|
||||||
paramsHandlerFn(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query a delegator delegations
|
|
||||||
func delegatorDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryDelegator(clientCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorDelegations))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query a delegator unbonding delegations
|
|
||||||
func delegatorUnbondingDelegationsHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryDelegator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorUnbondingDelegations))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query all staking txs (msgs) from a delegator
|
|
||||||
func delegatorTxsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var typesQuerySlice []string
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
delegatorAddr := vars["delegatorAddr"]
|
|
||||||
|
|
||||||
if _, err := sdk.AccAddressFromBech32(delegatorAddr); rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
typesQuery := r.URL.Query().Get("type")
|
|
||||||
trimmedQuery := strings.TrimSpace(typesQuery)
|
|
||||||
|
|
||||||
if len(trimmedQuery) != 0 {
|
|
||||||
typesQuerySlice = strings.Split(trimmedQuery, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
noQuery := len(typesQuerySlice) == 0
|
|
||||||
isBondTx := contains(typesQuerySlice, "bond")
|
|
||||||
isUnbondTx := contains(typesQuerySlice, "unbond")
|
|
||||||
isRedTx := contains(typesQuerySlice, "redelegate")
|
|
||||||
|
|
||||||
var (
|
|
||||||
txs []*sdk.SearchTxsResult
|
|
||||||
actions []string
|
|
||||||
)
|
|
||||||
|
|
||||||
// For each case, we search txs for both:
|
|
||||||
// - legacy messages: their Type() is a custom string, e.g. "delegate"
|
|
||||||
// - service Msgs: their Type() is their FQ method name, e.g. "/cosmos.staking.v1beta1.MsgDelegate"
|
|
||||||
// and we combine the results.
|
|
||||||
switch {
|
|
||||||
case isBondTx:
|
|
||||||
actions = append(actions, types.TypeMsgDelegate)
|
|
||||||
|
|
||||||
case isUnbondTx:
|
|
||||||
actions = append(actions, types.TypeMsgUndelegate)
|
|
||||||
|
|
||||||
case isRedTx:
|
|
||||||
actions = append(actions, types.TypeMsgBeginRedelegate)
|
|
||||||
|
|
||||||
case noQuery:
|
|
||||||
actions = append(actions, types.TypeMsgDelegate)
|
|
||||||
actions = append(actions, types.TypeMsgUndelegate)
|
|
||||||
actions = append(actions, types.TypeMsgBeginRedelegate)
|
|
||||||
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, action := range actions {
|
|
||||||
foundTxs, errQuery := queryTxs(clientCtx, action, delegatorAddr)
|
|
||||||
if rest.CheckInternalServerError(w, errQuery) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txs = append(txs, foundTxs)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := clientCtx.LegacyAmino.MarshalJSON(txs)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponseBare(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query an unbonding-delegation
|
|
||||||
func unbondingDelegationHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryBonds(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryUnbondingDelegation))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query redelegations
|
|
||||||
func redelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var params types.QueryRedelegationParams
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bechDelegatorAddr := r.URL.Query().Get("delegator")
|
|
||||||
bechSrcValidatorAddr := r.URL.Query().Get("validator_from")
|
|
||||||
bechDstValidatorAddr := r.URL.Query().Get("validator_to")
|
|
||||||
|
|
||||||
if len(bechDelegatorAddr) != 0 {
|
|
||||||
delegatorAddr, err := sdk.AccAddressFromBech32(bechDelegatorAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params.DelegatorAddr = delegatorAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bechSrcValidatorAddr) != 0 {
|
|
||||||
srcValidatorAddr, err := sdk.ValAddressFromBech32(bechSrcValidatorAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params.SrcValidatorAddr = srcValidatorAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bechDstValidatorAddr) != 0 {
|
|
||||||
dstValidatorAddr, err := sdk.ValAddressFromBech32(bechDstValidatorAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params.DstValidatorAddr = dstValidatorAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryRedelegations), bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query a delegation
|
|
||||||
func delegationHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryBonds(clientCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegation))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query all delegator bonded validators
|
|
||||||
func delegatorValidatorsHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryDelegator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorValidators))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to get information from a currently bonded validator
|
|
||||||
func delegatorValidatorHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryBonds(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorValidator))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query list of validators
|
|
||||||
func validatorsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
status := r.FormValue("status")
|
|
||||||
// These are query params that were available in =<0.39. We show a nice
|
|
||||||
// error message for this breaking change.
|
|
||||||
if status == "bonded" || status == "unbonding" || status == "unbonded" {
|
|
||||||
err := fmt.Errorf("cosmos sdk v0.40 introduces a breaking change on this endpoint:"+
|
|
||||||
" instead of querying using `?status=%s`, please use `status=BOND_STATUS_%s`. For more"+
|
|
||||||
" info, please see our REST endpoint migration guide at %s", status, strings.ToUpper(status), clientrest.DeprecationURL)
|
|
||||||
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if status == "" {
|
|
||||||
status = types.BondStatusBonded
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryValidatorsParams(page, limit, status)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidators)
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the validator information from a given validator address
|
|
||||||
func validatorHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryValidator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidator))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query all unbonding delegations from a validator
|
|
||||||
func validatorDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryValidator(clientCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidatorDelegations))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query all unbonding delegations from a validator
|
|
||||||
func validatorUnbondingDelegationsHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
|
||||||
return queryValidator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidatorUnbondingDelegations))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query historical info at a given height
|
|
||||||
func historicalInfoHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
heightStr := vars["height"]
|
|
||||||
|
|
||||||
height, err := strconv.ParseInt(heightStr, 10, 64)
|
|
||||||
if err != nil || height < 0 {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Must provide non-negative integer for height: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.QueryHistoricalInfoRequest{Height: height}
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryHistoricalInfo), bz)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the pool information
|
|
||||||
func poolHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryPool), nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP request handler to query the staking params values
|
|
||||||
func paramsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters), nil)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
registerQueryRoutes(clientCtx, r)
|
|
||||||
registerTxHandlers(clientCtx, r)
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
// +build norace
|
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestLegacyGetValidators() {
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
baseURL := val.APIAddress
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
expErr bool
|
|
||||||
expErrMsg string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"old status should show error message",
|
|
||||||
fmt.Sprintf("%s/staking/validators?status=bonded", baseURL),
|
|
||||||
true, "cosmos sdk v0.40 introduces a breaking change on this endpoint: instead of" +
|
|
||||||
" querying using `?status=bonded`, please use `status=BOND_STATUS_BONDED`. For more" +
|
|
||||||
" info, please see our REST endpoint migration guide at https://docs.cosmos.network/master/migrations/rest.html",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"new status should work",
|
|
||||||
fmt.Sprintf("%s/staking/validators?status=BOND_STATUS_BONDED", baseURL),
|
|
||||||
false, "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
s.Run(tc.name, func() {
|
|
||||||
respJSON, err := rest.GetRequest(tc.url)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
if tc.expErr {
|
|
||||||
var errResp rest.ErrorResponse
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
|
||||||
|
|
||||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
|
||||||
} else {
|
|
||||||
var resp = rest.ResponseWithHeight{}
|
|
||||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Check result is not empty.
|
|
||||||
var validators []types.Validator
|
|
||||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &validators))
|
|
||||||
s.Require().Greater(len(validators), 0)
|
|
||||||
// While we're at it, also check that the consensus_pubkey is
|
|
||||||
// an Any, and not bech32 anymore.
|
|
||||||
s.Require().Contains(string(resp.Result), "\"consensus_pubkey\": {\n \"type\": \"tendermint/PubKeyEd25519\",")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/delegations",
|
|
||||||
newPostDelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/unbonding_delegations",
|
|
||||||
newPostUnbondingDelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
r.HandleFunc(
|
|
||||||
"/staking/delegators/{delegatorAddr}/redelegations",
|
|
||||||
newPostRedelegationsHandlerFn(clientCtx),
|
|
||||||
).Methods("POST")
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// DelegateRequest defines the properties of a delegation request's body.
|
|
||||||
DelegateRequest struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
DelegatorAddress sdk.AccAddress `json:"delegator_address" yaml:"delegator_address"` // in bech32
|
|
||||||
ValidatorAddress sdk.ValAddress `json:"validator_address" yaml:"validator_address"` // in bech32
|
|
||||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RedelegateRequest defines the properties of a redelegate request's body.
|
|
||||||
RedelegateRequest struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
DelegatorAddress sdk.AccAddress `json:"delegator_address" yaml:"delegator_address"` // in bech32
|
|
||||||
ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address" yaml:"validator_src_address"` // in bech32
|
|
||||||
ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address" yaml:"validator_dst_address"` // in bech32
|
|
||||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UndelegateRequest defines the properties of a undelegate request's body.
|
|
||||||
UndelegateRequest struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
DelegatorAddress sdk.AccAddress `json:"delegator_address" yaml:"delegator_address"` // in bech32
|
|
||||||
ValidatorAddress sdk.ValAddress `json:"validator_address" yaml:"validator_address"` // in bech32
|
|
||||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newPostDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req DelegateRequest
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgDelegate(req.DelegatorAddress, req.ValidatorAddress, req.Amount)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fromAddr, req.DelegatorAddress) {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPostRedelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req RedelegateRequest
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgBeginRedelegate(req.DelegatorAddress, req.ValidatorSrcAddress, req.ValidatorDstAddress, req.Amount)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fromAddr, req.DelegatorAddress) {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPostUnbondingDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req UndelegateRequest
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := types.NewMsgUndelegate(req.DelegatorAddress, req.ValidatorAddress, req.Amount)
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fromAddr, req.DelegatorAddress) {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// contains checks if the a given query contains one of the tx types
|
|
||||||
func contains(stringSlice []string, txType string) bool {
|
|
||||||
for _, word := range stringSlice {
|
|
||||||
if word == txType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// queries staking txs
|
|
||||||
func queryTxs(clientCtx client.Context, action string, delegatorAddr string) (*sdk.SearchTxsResult, error) {
|
|
||||||
page := 1
|
|
||||||
limit := 100
|
|
||||||
events := []string{
|
|
||||||
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, action),
|
|
||||||
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, delegatorAddr),
|
|
||||||
}
|
|
||||||
|
|
||||||
return authtx.QueryTxsByEvents(clientCtx, events, page, limit, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryBonds(clientCtx client.Context, endpoint string) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bech32delegator := vars["delegatorAddr"]
|
|
||||||
bech32validator := vars["validatorAddr"]
|
|
||||||
|
|
||||||
delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
validatorAddr, err := sdk.ValAddressFromBech32(bech32validator)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.QueryDelegatorValidatorRequest{DelegatorAddr: delegatorAddr.String(), ValidatorAddr: validatorAddr.String()}
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(endpoint, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryDelegator(clientCtx client.Context, endpoint string) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bech32delegator := vars["delegatorAddr"]
|
|
||||||
|
|
||||||
delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryDelegatorParams(delegatorAddr)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(endpoint, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryValidator(clientCtx client.Context, endpoint string) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bech32validatorAddr := vars["validatorAddr"]
|
|
||||||
|
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := types.NewQueryValidatorParams(validatorAddr, page, limit)
|
|
||||||
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, height, err := clientCtx.QueryWithData(endpoint, bz)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientCtx = clientCtx.WithHeight(height)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +1,22 @@
|
||||||
// +build norace
|
package testutil
|
||||||
|
|
||||||
package rest_test
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/rest"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
|
||||||
stakingtestutil "github.com/cosmos/cosmos-sdk/x/staking/client/testutil"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
func (s *IntegrationTestSuite) TestGRPCQueryValidatorsHandler() {
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
cfg network.Config
|
|
||||||
network *network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
|
||||||
s.T().Log("setting up integration test suite")
|
|
||||||
|
|
||||||
cfg := network.DefaultConfig()
|
|
||||||
cfg.NumValidators = 2
|
|
||||||
s.cfg = cfg
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
unbond, err := sdk.ParseCoinNormalized("10stake")
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
val := s.network.Validators[0]
|
|
||||||
val2 := s.network.Validators[1]
|
|
||||||
|
|
||||||
// redelegate
|
|
||||||
_, err = stakingtestutil.MsgRedelegateExec(
|
|
||||||
val.ClientCtx,
|
|
||||||
val.Address,
|
|
||||||
val.ValAddress,
|
|
||||||
val2.ValAddress,
|
|
||||||
unbond,
|
|
||||||
fmt.Sprintf("--%s=%d", flags.FlagGas, 254000),
|
|
||||||
) // expected gas is 202987
|
|
||||||
|
|
||||||
s.Require().NoError(err)
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// unbonding
|
|
||||||
_, err = stakingtestutil.MsgUnbondExec(val.ClientCtx, val.Address, val.ValAddress, unbond)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
||||||
s.T().Log("tearing down integration test suite")
|
|
||||||
s.network.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryValidatorsGRPCHandler() {
|
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -124,7 +64,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorsGRPCHandler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryValidatorGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryValidator() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -170,7 +110,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryValidatorDelegationsGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryValidatorDelegations() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -233,7 +173,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorDelegationsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryValidatorUnbondingDelegationsGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryValidatorUnbondingDelegations() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -280,7 +220,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorUnbondingDelegationsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryDelegationGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryDelegation() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
val2 := s.network.Validators[1]
|
val2 := s.network.Validators[1]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
@ -356,7 +296,7 @@ func (s *IntegrationTestSuite) TestQueryDelegationGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryUnbondingDelegationGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryUnbondingDelegation() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -414,7 +354,7 @@ func (s *IntegrationTestSuite) TestQueryUnbondingDelegationGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryDelegatorDelegationsGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorDelegations() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -495,7 +435,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorDelegationsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryDelegatorUnbondingDelegationsGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorUnbondingDelegations() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -545,7 +485,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorUnbondingDelegationsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryRedelegationsGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryRedelegations() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
val2 := s.network.Validators[1]
|
val2 := s.network.Validators[1]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
@ -577,7 +517,7 @@ func (s *IntegrationTestSuite) TestQueryRedelegationsGRPC() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"valid request with dst address",
|
"valid request with dst address",
|
||||||
fmt.Sprintf("%s/cosmos/staking/v1beta1/delegators/%s/redelegations?dst_validator_addr=%s", baseURL, val.Address.String(), val2.ValAddress.String()),
|
fmt.Sprintf("%s/cosmos/staking/v1beta1/delegators/%s/redelegations?dst_validator_addr=%s", baseURL, val.Address.String(), val.ValAddress.String()),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -610,7 +550,7 @@ func (s *IntegrationTestSuite) TestQueryRedelegationsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorValidators() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -657,7 +597,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryDelegatorValidatorGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorValidator() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -713,7 +653,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorValidatorGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryHistoricalInfoGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryHistoricalInfo() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -759,7 +699,7 @@ func (s *IntegrationTestSuite) TestQueryHistoricalInfoGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryParams() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
|
@ -790,24 +730,28 @@ func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestQueryPoolGRPC() {
|
func (s *IntegrationTestSuite) TestGRPCQueryPool() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
baseURL := val.APIAddress
|
baseURL := val.APIAddress
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
url string
|
url string
|
||||||
|
headers map[string]string
|
||||||
respType proto.Message
|
respType proto.Message
|
||||||
expected proto.Message
|
expected proto.Message
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"gRPC request params",
|
"gRPC request params",
|
||||||
fmt.Sprintf("%s/cosmos/staking/v1beta1/pool", baseURL),
|
fmt.Sprintf("%s/cosmos/staking/v1beta1/pool", baseURL),
|
||||||
|
map[string]string{
|
||||||
|
grpctypes.GRPCBlockHeightHeader: "1",
|
||||||
|
},
|
||||||
&types.QueryPoolResponse{},
|
&types.QueryPoolResponse{},
|
||||||
&types.QueryPoolResponse{
|
&types.QueryPoolResponse{
|
||||||
Pool: types.Pool{
|
Pool: types.Pool{
|
||||||
NotBondedTokens: sdk.NewInt(10),
|
NotBondedTokens: sdk.NewInt(0),
|
||||||
BondedTokens: cli.DefaultTokens.Mul(sdk.NewInt(2)).Sub(sdk.NewInt(10)),
|
BondedTokens: cli.DefaultTokens.Mul(sdk.NewInt(2)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -815,15 +759,11 @@ func (s *IntegrationTestSuite) TestQueryPoolGRPC() {
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tc := tc
|
tc := tc
|
||||||
resp, err := rest.GetRequest(tc.url)
|
resp, err := testutil.GetRequestWithHeaders(tc.url, tc.headers)
|
||||||
s.Run(tc.name, func() {
|
s.Run(tc.name, func() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(resp, tc.respType))
|
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(resp, tc.respType))
|
||||||
s.Require().Equal(tc.expected, tc.respType)
|
s.Require().Equal(tc.expected.String(), tc.respType.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IntegrationTestSuite))
|
|
||||||
}
|
|
|
@ -71,6 +71,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
// unbonding
|
// unbonding
|
||||||
_, err = MsgUnbondExec(val.ClientCtx, val.Address, val.ValAddress, unbond)
|
_, err = MsgUnbondExec(val.ClientCtx, val.Address, val.ValAddress, unbond)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
_, err = s.network.WaitForHeight(1)
|
_, err = s.network.WaitForHeight(1)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/simulation"
|
"github.com/cosmos/cosmos-sdk/x/staking/simulation"
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||||
|
@ -70,9 +69,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers the REST routes for the staking module.
|
// RegisterRESTRoutes registers the REST routes for the staking module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/staking` legacy REST implementation
|
||||||
rest.RegisterHandlers(clientCtx, rtr)
|
// has been removed from the SDK.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the staking module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the staking module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
|
@ -3,8 +3,7 @@ package client
|
||||||
import (
|
import (
|
||||||
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/upgrade/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/client/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitUpgradeProposal, rest.ProposalRESTHandler)
|
var ProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitUpgradeProposal)
|
||||||
var CancelProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitCancelUpgradeProposal, rest.ProposalCancelRESTHandler)
|
var CancelProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitCancelUpgradeProposal)
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
|
||||||
r.HandleFunc(
|
|
||||||
"/upgrade/current", getCurrentPlanHandler(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
r.HandleFunc(
|
|
||||||
"/upgrade/applied/{name}", getDonePlanHandler(clientCtx),
|
|
||||||
).Methods("GET")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCurrentPlanHandler(clientCtx client.Context) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, request *http.Request) {
|
|
||||||
// ignore height for now
|
|
||||||
res, _, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierKey, types.QueryCurrent))
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(res) == 0 {
|
|
||||||
http.NotFound(w, request)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var plan types.Plan
|
|
||||||
err = clientCtx.LegacyAmino.UnmarshalJSON(res, &plan)
|
|
||||||
if rest.CheckInternalServerError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.PostProcessResponse(w, clientCtx, plan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDonePlanHandler(clientCtx client.Context) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
name := mux.Vars(r)["name"]
|
|
||||||
|
|
||||||
params := types.QueryAppliedPlanRequest{Name: name}
|
|
||||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierKey, types.QueryApplied), bz)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) == 0 {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(res) != 8 {
|
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, "unknown format for applied-upgrade")
|
|
||||||
}
|
|
||||||
|
|
||||||
applied := int64(binary.BigEndian.Uint64(res))
|
|
||||||
fmt.Println(applied)
|
|
||||||
rest.PostProcessResponse(w, clientCtx, applied)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegisterRoutes registers REST routes for the upgrade module under the path specified by routeName.
|
|
||||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router) {
|
|
||||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
|
||||||
registerQueryRoutes(clientCtx, r)
|
|
||||||
registerTxHandlers(clientCtx, r)
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
package rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
|
||||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerTxHandlers(
|
|
||||||
clientCtx client.Context,
|
|
||||||
r *mux.Router) {
|
|
||||||
r.HandleFunc("/upgrade/plan", newPostPlanHandler(clientCtx)).Methods("POST")
|
|
||||||
r.HandleFunc("/upgrade/cancel", newCancelPlanHandler(clientCtx)).Methods("POST")
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlanRequest defines a proposal for a new upgrade plan.
|
|
||||||
type PlanRequest struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
Title string `json:"title" yaml:"title"`
|
|
||||||
Description string `json:"description" yaml:"description"`
|
|
||||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
|
||||||
UpgradeName string `json:"upgrade_name" yaml:"upgrade_name"`
|
|
||||||
UpgradeHeight int64 `json:"upgrade_height" yaml:"upgrade_height"`
|
|
||||||
UpgradeInfo string `json:"upgrade_info" yaml:"upgrade_info"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CancelRequest defines a proposal to cancel a current plan.
|
|
||||||
type CancelRequest struct {
|
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
|
||||||
Title string `json:"title" yaml:"title"`
|
|
||||||
Description string `json:"description" yaml:"description"`
|
|
||||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
|
||||||
return govrest.ProposalRESTHandler{
|
|
||||||
SubRoute: "upgrade",
|
|
||||||
Handler: newPostPlanHandler(clientCtx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProposalCancelRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
|
||||||
return govrest.ProposalRESTHandler{
|
|
||||||
SubRoute: "upgrade",
|
|
||||||
Handler: newCancelPlanHandler(clientCtx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPostPlanHandler(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req PlanRequest
|
|
||||||
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
plan := types.Plan{Name: req.UpgradeName, Height: req.UpgradeHeight, Info: req.UpgradeInfo}
|
|
||||||
content := types.NewSoftwareUpgradeProposal(req.Title, req.Description, plan)
|
|
||||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, fromAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCancelPlanHandler(clientCtx client.Context) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req CancelRequest
|
|
||||||
|
|
||||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.BaseReq = req.BaseReq.Sanitize()
|
|
||||||
if !req.BaseReq.ValidateBasic(w) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
content := types.NewCancelSoftwareUpgradeProposal(req.Title, req.Description)
|
|
||||||
|
|
||||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, fromAddr)
|
|
||||||
if rest.CheckBadRequestError(w, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
// +build norace
|
||||||
|
|
||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIntegrationTestSuite(t *testing.T) {
|
func TestIntegrationTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"github.com/cosmos/cosmos-sdk/types/module"
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/upgrade/client/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/client/rest"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
|
"github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
|
||||||
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
||||||
)
|
)
|
||||||
|
@ -42,10 +41,10 @@ func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
||||||
types.RegisterLegacyAminoCodec(cdc)
|
types.RegisterLegacyAminoCodec(cdc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRESTRoutes registers all REST query handlers
|
// RegisterRESTRoutes registers the REST routes for the upgrade module.
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, r *mux.Router) {
|
// Deprecated: RegisterRESTRoutes is deprecated. `x/upgrade` legacy REST implementation
|
||||||
rest.RegisterRoutes(clientCtx, r)
|
// has been removed from the SDK.
|
||||||
}
|
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {}
|
||||||
|
|
||||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the upgrade module.
|
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the upgrade module.
|
||||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||||
|
|
Loading…
Reference in New Issue