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:
MD Aleem 2021-07-06 15:34:54 +05:30 committed by GitHub
parent 2c6b866551
commit cd221680c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 321 additions and 14706 deletions

View File

@ -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.
* (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`.
* [\#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

View File

@ -1,17 +1,11 @@
{
"swagger": "2.0",
"info": {
"title": "Cosmos SDK - Legacy REST and gRPC Gateway docs",
"description": "A REST interface for state queries, legacy transactions",
"title": "Cosmos SDK - gRPC Gateway docs",
"description": "A REST interface for state queries",
"version": "1.0.0"
},
"apis": [
{
"url": "./client/docs/swagger_legacy.yaml",
"dereference": {
"circular": "ignore"
}
},
{
"url": "./tmp-swagger-gen/cosmos/auth/v1beta1/query.swagger.json",
"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

View File

@ -12,7 +12,7 @@ import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/network"
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"
)

View File

@ -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
}

View File

@ -3,16 +3,13 @@ package rpc
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/types/rest"
)
// 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
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)
}
}

View File

@ -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")
}

View File

@ -5,13 +5,10 @@ import (
"testing"
"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/codec/legacy"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/cosmos/cosmos-sdk/testutil/network"
"github.com/cosmos/cosmos-sdk/types/rest"
)
type IntegrationTestSuite struct {
@ -46,17 +43,6 @@ func (s *IntegrationTestSuite) TestStatusCommand() {
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) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -2,7 +2,6 @@ package rpc
import (
"context"
"net/http"
"github.com/spf13/cobra"
@ -14,8 +13,6 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
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,
@ -88,45 +85,3 @@ func getNodeStatus(clientCtx client.Context) (*ctypes.ResultStatus, error) {
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})
}
}

View File

@ -3,11 +3,9 @@ package rpc
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
tmtypes "github.com/tendermint/tendermint/types"
@ -16,7 +14,7 @@ import (
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/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
@ -60,7 +58,7 @@ func ValidatorCommand() *cobra.Command {
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().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")
return cmd
@ -148,57 +146,3 @@ func GetValidators(ctx context.Context, clientCtx client.Context, height *int64,
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)
}
}

View File

@ -5,19 +5,16 @@ import (
"context"
"errors"
"fmt"
"net/http"
"os"
gogogrpc "github.com/gogo/protobuf/grpc"
"github.com/spf13/pflag"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
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/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)
}
// 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
// simulation response obtained by the query and the adjusted gas amount.
func CalculateGas(

View File

@ -11,7 +11,6 @@ import (
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/simapp"
"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"
)
@ -130,12 +129,4 @@ func TestAminoCodecFullDecodeAndEncode(t *testing.T) {
marshaledTx, err := legacyCdc.MarshalJSON(tx)
require.NoError(t, err)
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
View File

@ -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.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.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
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-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.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
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/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=

View File

@ -15,10 +15,10 @@ import (
tmrpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
"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/telemetry"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/types/rest"
// unnamed import of statik for swagger UI support
_ "github.com/cosmos/cosmos-sdk/client/docs/statik"
@ -133,7 +133,7 @@ func (s *Server) registerMetrics() {
gr, err := s.metrics.Gather(format)
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
}
@ -143,3 +143,22 @@ func (s *Server) registerMetrics() {
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)))
}

View File

@ -19,7 +19,6 @@ import (
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"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/types"
"github.com/cosmos/cosmos-sdk/server/api"
@ -32,7 +31,6 @@ import (
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth"
"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"
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
@ -513,9 +511,6 @@ func (app *SimApp) SimulationManager() *module.SimulationManager {
// API server.
func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) {
clientCtx := apiSvr.ClientCtx
rpc.RegisterRoutes(clientCtx, apiSvr.Router)
// Register legacy tx routes.
authrest.RegisterTxRoutes(clientCtx, apiSvr.Router)
// Register new tx routes from grpc-gateway.
authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
// Register new tendermint queries routes from grpc-gateway.

44
testutil/rest/rest.go Normal file
View File

@ -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
}

View File

@ -6,9 +6,9 @@ import (
"testing"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/gorilla/mux"
"github.com/golang/mock/gomock"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
@ -38,9 +38,9 @@ func TestBasicManager(t *testing.T) {
mockAppModuleBasic1.EXPECT().Name().AnyTimes().Return("mockAppModuleBasic1")
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().RegisterRESTRoutes(gomock.Eq(client.Context{}), gomock.Eq(&mux.Router{})).Times(1)
mockAppModuleBasic1.EXPECT().RegisterLegacyAminoCodec(gomock.Eq(legacyAmino)).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().GetQueryCmd().Times(1).Return(nil)

View File

@ -11,6 +11,10 @@ import (
"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
// if the `limit` is not supplied, paginate will use `DefaultLimit`
const DefaultLimit = 100

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -10,7 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
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"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"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)
cmd.Flags().Int(flags.FlagPage, rest.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.FlagPage, query.DefaultPage, "Query a specific page of paginated results")
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.MarkFlagRequired(flagEvents)

View File

@ -20,10 +20,16 @@ import (
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/version"
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"
)
// 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
func GetMultiSignCommand() *cobra.Command {
cmd := &cobra.Command{
@ -155,7 +161,7 @@ func makeMultiSignCmd() func(cmd *cobra.Command, args []string) (err error) {
return err
}
req := rest.BroadcastReq{
req := BroadcastReq{
Tx: stdTx,
Mode: "block|sync|async",
}
@ -338,7 +344,7 @@ func makeBatchMultisignCmd() func(cmd *cobra.Command, args []string) error {
return err
}
req := rest.BroadcastReq{
req := BroadcastReq{
Tx: stdTx,
Mode: "block|sync|async",
}

View File

@ -10,7 +10,6 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
"github.com/cosmos/cosmos-sdk/x/auth/client/rest"
)
const (
@ -263,7 +262,7 @@ func makeSignCmd() func(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
req := rest.BroadcastReq{
req := BroadcastReq{
Tx: stdTx,
Mode: "block|sync|async",
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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))
}

View File

@ -28,7 +28,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
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"
bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
@ -227,12 +226,13 @@ func (s *IntegrationTestSuite) TestCLISignAminoJSON() {
"--amino=true", signModeAminoFlag)
require.NoError(err)
var txAmino authrest.BroadcastReq
var txAmino authcli.BroadcastReq
err = val1.ClientCtx.LegacyAmino.UnmarshalJSON(res.Bytes(), &txAmino)
require.NoError(err)
require.Len(txAmino.Tx.Signatures, 2)
require.Equal(txAmino.Tx.Signatures[0].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) {

View File

@ -6,9 +6,9 @@ import (
"fmt"
"math/rand"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
@ -19,7 +19,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
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/rest"
"github.com/cosmos/cosmos-sdk/x/auth/keeper"
"github.com/cosmos/cosmos-sdk/x/auth/simulation"
"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.
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
rest.RegisterRoutes(clientCtx, rtr, types.StoreKey)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/auth` legacy REST implementation
// has been removed from the SDK.
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the auth module.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -19,11 +19,11 @@ import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/network"
"github.com/cosmos/cosmos-sdk/testutil/rest"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"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/signing"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"

View File

@ -54,7 +54,8 @@ func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConf
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) {}
// RegisterGRPCGatewayRoutes registers the module's gRPC Gateway routes. Currently, this

View File

@ -1,93 +1,20 @@
// +build norace
package rest_test
package testutil
import (
"fmt"
"testing"
"time"
"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/keyring"
"github.com/cosmos/cosmos-sdk/testutil/network"
"github.com/cosmos/cosmos-sdk/testutil/rest"
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/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"
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() {
val := s.network.Validators[0]
grantee := s.grantee[1]
grantsURL := val.APIAddress + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s&msg_type_url=%s"
testCases := []struct {
name string
@ -97,7 +24,7 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
}{
{
"fail invalid granter address",
fmt.Sprintf(grantsURL, "invalid_granter", s.grantee.String(), typeMsgSend),
fmt.Sprintf(grantsURL, "invalid_granter", grantee.String(), typeMsgSend),
true,
"decoding bech32 failed: invalid index of 1: invalid request",
},
@ -109,7 +36,7 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
},
{
"fail with empty granter",
fmt.Sprintf(grantsURL, "", s.grantee.String(), typeMsgSend),
fmt.Sprintf(grantsURL, "", grantee.String(), typeMsgSend),
true,
"empty address string is not allowed: invalid request",
},
@ -121,13 +48,13 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
},
{
"fail invalid msg-type",
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String(), "invalidMsg"),
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String(), "invalidMsg"),
true,
"rpc error: code = NotFound desc = no authorization found for invalidMsg type: key not found",
},
{
"valid query",
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String(), typeMsgSend),
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String(), typeMsgSend),
false,
"",
},
@ -154,6 +81,7 @@ func (s *IntegrationTestSuite) TestQueryGrantGRPC() {
func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
val := s.network.Validators[0]
grantee := s.grantee[1]
grantsURL := val.APIAddress + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s"
testCases := []struct {
name string
@ -165,7 +93,7 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
}{
{
"valid query: expect single grant",
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String()),
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String()),
false,
"",
func() {},
@ -175,12 +103,12 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
},
{
"valid query: expect two grants",
fmt.Sprintf(grantsURL, val.Address.String(), s.grantee.String()),
fmt.Sprintf(grantsURL, val.Address.String(), grantee.String()),
false,
"",
func() {
_, err := authztestutil.ExecGrant(val, []string{
s.grantee.String(),
_, err := ExecGrant(val, []string{
grantee.String(),
"generic",
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=%s", cli.FlagMsgType, typeMsgVote),
@ -197,7 +125,7 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
},
{
"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,
"",
func() {},
@ -207,7 +135,7 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
},
{
"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,
"",
func() {},
@ -233,7 +161,3 @@ func (s *IntegrationTestSuite) TestQueryGrantsGRPC() {
})
}
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -17,7 +17,7 @@ import (
func (s *IntegrationTestSuite) TestQueryAuthorizations() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
_, err := ExecGrant(
@ -95,7 +95,7 @@ func (s *IntegrationTestSuite) TestQueryAuthorizations() {
func (s *IntegrationTestSuite) TestQueryAuthorization() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
_, err := ExecGrant(

View File

@ -28,7 +28,7 @@ type IntegrationTestSuite struct {
cfg network.Config
network *network.Network
grantee sdk.AccAddress
grantee []sdk.AccAddress
}
func NewIntegrationTestSuite(cfg network.Config) *IntegrationTestSuite {
@ -43,24 +43,12 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.Require().NoError(err)
val := s.network.Validators[0]
s.grantee = make([]sdk.AccAddress, 2)
// 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())
s.grantee[0] = s.createAccount("grantee1")
// Send some funds to the new account.
_, 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.grantee = newAddr
s.msgSendExec(s.grantee[0])
_, err = s.network.WaitForHeight(1)
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()))
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)
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() {
s.T().Log("tearing down integration test suite")
s.network.Cleanup()
@ -85,7 +115,7 @@ var typeMsgSubmitProposal = sdk.MsgTypeURL(&govtypes.MsgSubmitProposal{})
func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).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() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
// send-authorization
@ -454,7 +484,7 @@ func (s *IntegrationTestSuite) TestCmdRevokeAuthorizations() {
func (s *IntegrationTestSuite) TestExecAuthorizationWithExpiration() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
tenSeconds := time.Now().Add(time.Second * time.Duration(10)).Unix()
_, err := ExecGrant(
@ -494,7 +524,7 @@ func (s *IntegrationTestSuite) TestExecAuthorizationWithExpiration() {
func (s *IntegrationTestSuite) TestNewExecGenericAuthorized() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
_, err := ExecGrant(
@ -596,7 +626,7 @@ func (s *IntegrationTestSuite) TestNewExecGenericAuthorized() {
func (s *IntegrationTestSuite) TestNewExecGrantAuthorized() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
_, err := ExecGrant(
@ -681,7 +711,7 @@ func (s *IntegrationTestSuite) TestNewExecGrantAuthorized() {
func (s *IntegrationTestSuite) TestExecDelegateAuthorization() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
_, err := ExecGrant(
@ -884,7 +914,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() {
func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() {
val := s.network.Validators[0]
grantee := s.grantee
grantee := s.grantee[0]
twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix()
// granting undelegate msg authorization

View File

@ -71,8 +71,8 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config sdkclient.TxEn
}
// 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.
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {

View File

@ -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)
}
}

View File

@ -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))
}

View File

@ -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")
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -1,6 +1,4 @@
// +build norace
package rest_test
package testutil
import (
"fmt"
@ -8,10 +6,10 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/rest"
sdk "github.com/cosmos/cosmos-sdk/types"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
@ -142,8 +140,26 @@ func (s *IntegrationTestSuite) TestDenomMetadataGRPCHandler() {
Base: "uatom",
Display: "atom",
},
{
Name: "Ethereum",
Symbol: "ETH",
Description: "Ethereum mainnet token",
DenomUnits: []*types.DenomUnit{
{
Denom: "wei",
Exponent: 0,
},
{
Denom: "eth",
Exponent: 6,
Aliases: []string{"ETH"},
},
},
Base: "wei",
Display: "eth",
},
},
Pagination: &query.PageResponse{Total: 1},
Pagination: &query.PageResponse{Total: 2},
},
},
{

View File

@ -20,7 +20,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
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/rest"
"github.com/cosmos/cosmos-sdk/x/bank/keeper"
v040 "github.com/cosmos/cosmos-sdk/x/bank/migrations/v040"
"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.
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
rest.RegisterHandlers(clientCtx, rtr)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/bank` legacy REST implementation
// has been removed from the SDK.
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the bank module.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -66,8 +66,9 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
return genState.Validate()
}
// RegisterRESTRoutes registers the capability module's REST service handlers.
func (a AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
// RegisterRESTRoutes registers the REST routes for the capability module.
// Deprecated: RegisterRESTRoutes is deprecated.
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the capability module.
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(_ client.Context, _ *runtime.ServeMux) {

View File

@ -60,7 +60,8 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncod
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) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the capability module.

View File

@ -2,11 +2,10 @@ package client
import (
"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"
)
// ProposalHandler is the community spend proposal handler.
var (
ProposalHandler = govclient.NewProposalHandler(cli.GetCmdSubmitProposal, rest.ProposalRESTHandler)
ProposalHandler = govclient.NewProposalHandler(cli.GetCmdSubmitProposal)
)

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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"`
}
)

View File

@ -15,3 +15,7 @@ func TestIntegrationTestSuite(t *testing.T) {
cfg.NumValidators = 1
suite.Run(t, NewIntegrationTestSuite(cfg))
}
func TestGRPCQueryTestSuite(t *testing.T) {
suite.Run(t, new(GRPCQueryTestSuite))
}

View File

@ -1,29 +1,28 @@
package rest_test
package testutil
import (
"fmt"
"testing"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
"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"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
type IntegrationTestSuite struct {
type GRPCQueryTestSuite struct {
suite.Suite
cfg network.Config
network *network.Network
}
func (s *IntegrationTestSuite) SetupSuite() {
func (s *GRPCQueryTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")
cfg := network.DefaultConfig()
@ -38,7 +37,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.Require().NoError(err)
}
func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
func (s *GRPCQueryTestSuite) TestQueryParamsGRPC() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -69,7 +68,7 @@ func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryOutstandingRewardsGRPC() {
func (s *GRPCQueryTestSuite) TestQueryOutstandingRewardsGRPC() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -123,7 +122,7 @@ func (s *IntegrationTestSuite) TestQueryOutstandingRewardsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryValidatorCommissionGRPC() {
func (s *GRPCQueryTestSuite) TestQueryValidatorCommissionGRPC() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -177,7 +176,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorCommissionGRPC() {
}
}
func (s *IntegrationTestSuite) TestQuerySlashesGRPC() {
func (s *GRPCQueryTestSuite) TestQuerySlashesGRPC() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -236,9 +235,9 @@ func (s *IntegrationTestSuite) TestQuerySlashesGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
func (s *GRPCQueryTestSuite) TestQueryDelegatorRewardsGRPC() {
val := s.network.Validators[0]
baseUrl := val.APIAddress
baseURL := val.APIAddress
rewards, err := sdk.ParseDecCoins("9.8stake")
s.Require().NoError(err)
@ -253,7 +252,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
}{
{
"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{},
true,
&types.QueryDelegationTotalRewardsResponse{},
@ -261,7 +260,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
},
{
"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{
grpctypes.GRPCBlockHeightHeader: "2",
},
@ -276,7 +275,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
},
{
"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{},
true,
&types.QueryDelegationTotalRewardsResponse{},
@ -284,7 +283,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
},
{
"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{
grpctypes.GRPCBlockHeightHeader: "2",
},
@ -312,9 +311,9 @@ func (s *IntegrationTestSuite) TestQueryDelegatorRewardsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
func (s *GRPCQueryTestSuite) TestQueryDelegatorValidatorsGRPC() {
val := s.network.Validators[0]
baseUrl := val.APIAddress
baseURL := val.APIAddress
testCases := []struct {
name string
@ -325,21 +324,21 @@ func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
}{
{
"empty delegator address",
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseUrl, ""),
fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/validators", baseURL, ""),
true,
&types.QueryDelegatorValidatorsResponse{},
nil,
},
{
"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,
&types.QueryDelegatorValidatorsResponse{},
nil,
},
{
"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,
&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]
baseUrl := val.APIAddress
baseURL := val.APIAddress
testCases := []struct {
name string
@ -377,21 +376,21 @@ func (s *IntegrationTestSuite) TestQueryWithdrawAddressGRPC() {
}{
{
"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,
&types.QueryDelegatorWithdrawAddressResponse{},
nil,
},
{
"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,
&types.QueryDelegatorWithdrawAddressResponse{},
nil,
},
{
"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,
&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]
baseURL := val.APIAddress
@ -460,7 +459,3 @@ func (s *IntegrationTestSuite) TestQueryValidatorCommunityPoolGRPC() {
})
}
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -6,9 +6,9 @@ import (
"fmt"
"math/rand"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
@ -19,7 +19,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
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/rest"
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
"github.com/cosmos/cosmos-sdk/x/distribution/simulation"
"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.
func (AppModuleBasic) RegisterRESTRoutes(clientCtx sdkclient.Context, rtr *mux.Router) {
rest.RegisterHandlers(clientCtx, rtr)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/distribution` legacy REST implementation
// has been removed from the SDK.
func (AppModuleBasic) RegisterRESTRoutes(_ sdkclient.Context, _ *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the distribution module.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {

View File

@ -1,30 +1,19 @@
package client
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/x/evidence/client/rest"
)
import "github.com/spf13/cobra"
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 func() *cobra.Command
// EvidenceHandler defines a type that exposes REST and CLI client handlers for
// evidence submission.
// EvidenceHandler wraps CLIHandlerFn.
EvidenceHandler struct {
CLIHandler CLIHandlerFn
RESTHandler RESTHandlerFn
CLIHandler CLIHandlerFn
}
)
func NewEvidenceHandler(cliHandler CLIHandlerFn, restHandler RESTHandlerFn) EvidenceHandler {
func NewEvidenceHandler(cliHandler CLIHandlerFn) EvidenceHandler {
return EvidenceHandler{
CLIHandler: cliHandler,
RESTHandler: restHandler,
CLIHandler: cliHandler,
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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.
}

View File

@ -6,9 +6,9 @@ import (
"fmt"
"math/rand"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
@ -21,7 +21,6 @@ import (
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
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/rest"
"github.com/cosmos/cosmos-sdk/x/evidence/keeper"
"github.com/cosmos/cosmos-sdk/x/evidence/simulation"
"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.
func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
evidenceRESTHandlers := make([]rest.EvidenceRESTHandler, len(a.evidenceHandlers))
for i, evidenceHandler := range a.evidenceHandlers {
evidenceRESTHandlers[i] = evidenceHandler.RESTHandler(clientCtx)
}
rest.RegisterRoutes(clientCtx, rtr, evidenceRESTHandlers)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/evidence` legacy REST implementation
// has been removed from the SDK.
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the evidence module.
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -82,7 +82,8 @@ func (a AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config sdkclient.Tx
}
// 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.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {

View File

@ -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)
}

View File

@ -54,6 +54,7 @@ func (b AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, txEncodingConfig cl
}
// RegisterRESTRoutes registers the REST routes for the genutil module.
// Deprecated: RegisterRESTRoutes is deprecated.
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the genutil module.

View File

@ -2,27 +2,19 @@ package client
import (
"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
type CLIHandlerFn func() *cobra.Command
// The combined type for a proposal handler for both cli and rest
// ProposalHandler wraps CLIHandlerFn
type ProposalHandler struct {
CLIHandler CLIHandlerFn
RESTHandler RESTHandlerFn
CLIHandler CLIHandlerFn
}
// NewProposalHandler creates a new ProposalHandler object
func NewProposalHandler(cliHandler CLIHandlerFn, restHandler RESTHandlerFn) ProposalHandler {
func NewProposalHandler(cliHandler CLIHandlerFn) ProposalHandler {
return ProposalHandler{
CLIHandler: cliHandler,
RESTHandler: restHandler,
CLIHandler: cliHandler,
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}
}

View File

@ -1,78 +1,17 @@
// +build norace
package rest_test
package testutil
import (
"fmt"
"testing"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/suite"
"github.com/gogo/protobuf/proto"
"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"
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"
)
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() {
val := s.network.Validators[0]
@ -476,7 +415,3 @@ func (s *IntegrationTestSuite) TestGetParamsGRPC() {
})
}
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -21,7 +21,6 @@ import (
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
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/rest"
"github.com/cosmos/cosmos-sdk/x/gov/keeper"
"github.com/cosmos/cosmos-sdk/x/gov/simulation"
"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.
func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
proposalRESTHandlers := make([]rest.ProposalRESTHandler, 0, len(a.proposalHandlers))
for _, proposalHandler := range a.proposalHandlers {
proposalRESTHandlers = append(proposalRESTHandlers, proposalHandler.RESTHandler(clientCtx))
}
rest.RegisterHandlers(clientCtx, rtr, proposalRESTHandlers)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/gov` legacy REST implementation
// has been removed from the SDK.
func (a AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the gov module.
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -1,62 +1,17 @@
// +build norace
package rest_test
package testutil
import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"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"
)
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() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -107,7 +62,3 @@ func (s *IntegrationTestSuite) TestQueryGRPC() {
})
}
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -18,7 +18,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
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/rest"
"github.com/cosmos/cosmos-sdk/x/mint/keeper"
"github.com/cosmos/cosmos-sdk/x/mint/simulation"
"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.
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
rest.RegisterRoutes(clientCtx, rtr)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/mint` legacy REST implementation
// 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.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -3,8 +3,7 @@ package client
import (
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/rest"
)
// ProposalHandler is the param change proposal handler.
var ProposalHandler = govclient.NewProposalHandler(cli.NewSubmitParamChangeProposalTxCmd, rest.ProposalRESTHandler)
var ProposalHandler = govclient.NewProposalHandler(cli.NewSubmitParamChangeProposalTxCmd)

View File

@ -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)
}
}

View File

@ -1,44 +1,14 @@
package rest_test
package testutil
import (
"fmt"
"testing"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/network"
"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() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -126,7 +96,3 @@ func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
})
}
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -5,8 +5,6 @@ import (
"io/ioutil"
"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"
)
@ -31,17 +29,6 @@ type (
Changes ParamChangesJSON `json:"changes" yaml:"changes"`
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 {

View File

@ -5,9 +5,9 @@ import (
"encoding/json"
"math/rand"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
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.
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.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -1,48 +1,18 @@
package rest_test
package testutil
import (
"fmt"
"testing"
"time"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/types/query"
"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() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -130,7 +100,3 @@ func (s *IntegrationTestSuite) TestGRPCQueries() {
})
}
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -6,9 +6,9 @@ import (
"fmt"
"math/rand"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
@ -20,7 +20,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
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/rest"
"github.com/cosmos/cosmos-sdk/x/slashing/keeper"
"github.com/cosmos/cosmos-sdk/x/slashing/simulation"
"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.
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
rest.RegisterHandlers(clientCtx, rtr)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/slashing` legacy REST implementation
// 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.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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\",")
}
})
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -1,82 +1,22 @@
// +build norace
package rest_test
package testutil
import (
"fmt"
"testing"
"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/keyring"
"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"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/types/rest"
"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"
)
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 = 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() {
func (s *IntegrationTestSuite) TestGRPCQueryValidatorsHandler() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -124,7 +64,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorsGRPCHandler() {
}
}
func (s *IntegrationTestSuite) TestQueryValidatorGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryValidator() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -170,7 +110,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryValidatorDelegationsGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryValidatorDelegations() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -233,7 +173,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorDelegationsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryValidatorUnbondingDelegationsGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryValidatorUnbondingDelegations() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -280,7 +220,7 @@ func (s *IntegrationTestSuite) TestQueryValidatorUnbondingDelegationsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryDelegationGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryDelegation() {
val := s.network.Validators[0]
val2 := s.network.Validators[1]
baseURL := val.APIAddress
@ -356,7 +296,7 @@ func (s *IntegrationTestSuite) TestQueryDelegationGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryUnbondingDelegationGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryUnbondingDelegation() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -414,7 +354,7 @@ func (s *IntegrationTestSuite) TestQueryUnbondingDelegationGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryDelegatorDelegationsGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorDelegations() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -495,7 +435,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorDelegationsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryDelegatorUnbondingDelegationsGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorUnbondingDelegations() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -545,7 +485,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorUnbondingDelegationsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryRedelegationsGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryRedelegations() {
val := s.network.Validators[0]
val2 := s.network.Validators[1]
baseURL := val.APIAddress
@ -577,7 +517,7 @@ func (s *IntegrationTestSuite) TestQueryRedelegationsGRPC() {
},
{
"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,
},
{
@ -610,7 +550,7 @@ func (s *IntegrationTestSuite) TestQueryRedelegationsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorValidators() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -657,7 +597,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorValidatorsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryDelegatorValidatorGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryDelegatorValidator() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -713,7 +653,7 @@ func (s *IntegrationTestSuite) TestQueryDelegatorValidatorGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryHistoricalInfoGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryHistoricalInfo() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -759,7 +699,7 @@ func (s *IntegrationTestSuite) TestQueryHistoricalInfoGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryParams() {
val := s.network.Validators[0]
baseURL := val.APIAddress
@ -790,24 +730,28 @@ func (s *IntegrationTestSuite) TestQueryParamsGRPC() {
}
}
func (s *IntegrationTestSuite) TestQueryPoolGRPC() {
func (s *IntegrationTestSuite) TestGRPCQueryPool() {
val := s.network.Validators[0]
baseURL := val.APIAddress
testCases := []struct {
name string
url string
headers map[string]string
respType proto.Message
expected proto.Message
}{
{
"gRPC request params",
fmt.Sprintf("%s/cosmos/staking/v1beta1/pool", baseURL),
map[string]string{
grpctypes.GRPCBlockHeightHeader: "1",
},
&types.QueryPoolResponse{},
&types.QueryPoolResponse{
Pool: types.Pool{
NotBondedTokens: sdk.NewInt(10),
BondedTokens: cli.DefaultTokens.Mul(sdk.NewInt(2)).Sub(sdk.NewInt(10)),
NotBondedTokens: sdk.NewInt(0),
BondedTokens: cli.DefaultTokens.Mul(sdk.NewInt(2)),
},
},
},
@ -815,15 +759,11 @@ func (s *IntegrationTestSuite) TestQueryPoolGRPC() {
for _, tc := range testCases {
tc := tc
resp, err := rest.GetRequest(tc.url)
resp, err := testutil.GetRequestWithHeaders(tc.url, tc.headers)
s.Run(tc.name, func() {
s.Require().NoError(err)
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))
}

View File

@ -71,6 +71,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
// unbonding
_, err = MsgUnbondExec(val.ClientCtx, val.Address, val.ValAddress, unbond)
s.Require().NoError(err)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
}

View File

@ -6,9 +6,9 @@ import (
"fmt"
"math/rand"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
@ -19,7 +19,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
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/rest"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/simulation"
"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.
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
rest.RegisterHandlers(clientCtx, rtr)
}
// Deprecated: RegisterRESTRoutes is deprecated. `x/staking` legacy REST implementation
// 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.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {

View File

@ -3,8 +3,7 @@ package client
import (
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/rest"
)
var ProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitUpgradeProposal, rest.ProposalRESTHandler)
var CancelProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitCancelUpgradeProposal, rest.ProposalCancelRESTHandler)
var ProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitUpgradeProposal)
var CancelProposalHandler = govclient.NewProposalHandler(cli.NewCmdSubmitCancelUpgradeProposal)

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -1,9 +1,12 @@
// +build norace
package testutil
import (
"testing"
"github.com/cosmos/cosmos-sdk/testutil/network"
"github.com/stretchr/testify/suite"
"testing"
)
func TestIntegrationTestSuite(t *testing.T) {

View File

@ -15,7 +15,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"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/types"
)
@ -42,10 +41,10 @@ func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
types.RegisterLegacyAminoCodec(cdc)
}
// RegisterRESTRoutes registers all REST query handlers
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, r *mux.Router) {
rest.RegisterRoutes(clientCtx, r)
}
// RegisterRESTRoutes registers the REST routes for the upgrade module.
// Deprecated: RegisterRESTRoutes is deprecated. `x/upgrade` legacy REST implementation
// 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.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {