feat!: remove legacy REST (#9594)
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description ref: #7517 * [x] Remove the x/{module}/client/rest folder * [x] Remove all glue code between simapp/modules and the REST server <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - see #9615 - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [x] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [x] reviewed API design and naming - [ ] reviewed documentation is accurate - see #9615 - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
2c6b866551
commit
cd221680c0
@ -53,6 +53,14 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* [\#9246](https://github.com/cosmos/cosmos-sdk/pull/9246) The `New` method for the network package now returns an error.
|
||||
* (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
|
||||
|
||||
|
||||
@ -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
5900
client/docs/swagger-ui/swagger.yaml
vendored
5900
client/docs/swagger-ui/swagger.yaml
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ import (
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// DeprecationURL is the URL for migrating deprecated REST endpoints to newer ones.
|
||||
// TODO Switch to `/` (not `/master`) once v0.40 docs are deployed.
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/8019
|
||||
const DeprecationURL = "https://docs.cosmos.network/master/migrations/rest.html"
|
||||
|
||||
// addHTTPDeprecationHeaders is a mux middleware function for adding HTTP
|
||||
// Deprecation headers to a http handler
|
||||
func addHTTPDeprecationHeaders(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Deprecation", "true")
|
||||
w.Header().Set("Link", "<"+DeprecationURL+">; rel=\"deprecation\"")
|
||||
w.Header().Set("Warning", "199 - \"this endpoint is deprecated and may not work as before, see deprecation link for more info\"")
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// WithHTTPDeprecationHeaders returns a new *mux.Router, identical to its input
|
||||
// but with the addition of HTTP Deprecation headers. This is used to mark legacy
|
||||
// amino REST endpoints as deprecated in the REST API.
|
||||
// nolint: gocritic
|
||||
func WithHTTPDeprecationHeaders(r *mux.Router) *mux.Router {
|
||||
subRouter := r.NewRoute().Subrouter()
|
||||
subRouter.Use(addHTTPDeprecationHeaders)
|
||||
return subRouter
|
||||
}
|
||||
@ -3,16 +3,13 @@ package rpc
|
||||
import (
|
||||
"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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
)
|
||||
|
||||
// Register REST endpoints.
|
||||
func RegisterRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc("/node_info", NodeInfoRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/syncing", NodeSyncingRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/blocks/latest", LatestBlockRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/blocks/{height}", BlockRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/validatorsets/latest", LatestValidatorSetRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/validatorsets/{height}", ValidatorSetRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
}
|
||||
@ -5,13 +5,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"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))
|
||||
}
|
||||
|
||||
@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
2
go.sum
@ -874,6 +874,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.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=
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
|
||||
@ -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
44
testutil/rest/rest.go
Normal 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
|
||||
}
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,449 +0,0 @@
|
||||
// Package rest provides HTTP types and primitives for REST
|
||||
// requests validation and responses handling.
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPage = 1
|
||||
DefaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
||||
TxMinHeightKey = "tx.minheight" // Inclusive minimum height filter
|
||||
TxMaxHeightKey = "tx.maxheight" // Inclusive maximum height filter
|
||||
)
|
||||
|
||||
// ResponseWithHeight defines a response object type that wraps an original
|
||||
// response with a height.
|
||||
type ResponseWithHeight struct {
|
||||
Height int64 `json:"height"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
|
||||
// NewResponseWithHeight creates a new ResponseWithHeight instance
|
||||
func NewResponseWithHeight(height int64, result json.RawMessage) ResponseWithHeight {
|
||||
return ResponseWithHeight{
|
||||
Height: height,
|
||||
Result: result,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseResponseWithHeight returns the raw result from a JSON-encoded
|
||||
// ResponseWithHeight object.
|
||||
func ParseResponseWithHeight(cdc *codec.LegacyAmino, bz []byte) ([]byte, error) {
|
||||
r := ResponseWithHeight{}
|
||||
if err := cdc.UnmarshalJSON(bz, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.Result, nil
|
||||
}
|
||||
|
||||
// GasEstimateResponse defines a response definition for tx gas estimation.
|
||||
type GasEstimateResponse struct {
|
||||
GasEstimate uint64 `json:"gas_estimate"`
|
||||
}
|
||||
|
||||
// BaseReq defines a structure that can be embedded in other request structures
|
||||
// that all share common "base" fields.
|
||||
type BaseReq struct {
|
||||
From string `json:"from"`
|
||||
Memo string `json:"memo"`
|
||||
ChainID string `json:"chain_id"`
|
||||
AccountNumber uint64 `json:"account_number"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
TimeoutHeight uint64 `json:"timeout_height"`
|
||||
Fees sdk.Coins `json:"fees"`
|
||||
GasPrices sdk.DecCoins `json:"gas_prices"`
|
||||
Gas string `json:"gas"`
|
||||
GasAdjustment string `json:"gas_adjustment"`
|
||||
Simulate bool `json:"simulate"`
|
||||
}
|
||||
|
||||
// NewBaseReq creates a new basic request instance and sanitizes its values
|
||||
func NewBaseReq(
|
||||
from, memo, chainID string, gas, gasAdjustment string, accNumber, seq uint64,
|
||||
fees sdk.Coins, gasPrices sdk.DecCoins, simulate bool,
|
||||
) BaseReq {
|
||||
return BaseReq{
|
||||
From: strings.TrimSpace(from),
|
||||
Memo: strings.TrimSpace(memo),
|
||||
ChainID: strings.TrimSpace(chainID),
|
||||
Fees: fees,
|
||||
GasPrices: gasPrices,
|
||||
Gas: strings.TrimSpace(gas),
|
||||
GasAdjustment: strings.TrimSpace(gasAdjustment),
|
||||
AccountNumber: accNumber,
|
||||
Sequence: seq,
|
||||
Simulate: simulate,
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize performs basic sanitization on a BaseReq object.
|
||||
func (br BaseReq) Sanitize() BaseReq {
|
||||
return NewBaseReq(
|
||||
br.From, br.Memo, br.ChainID, br.Gas, br.GasAdjustment,
|
||||
br.AccountNumber, br.Sequence, br.Fees, br.GasPrices, br.Simulate,
|
||||
)
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation of a BaseReq. If custom validation
|
||||
// logic is needed, the implementing request handler should perform those
|
||||
// checks manually.
|
||||
func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
|
||||
if !br.Simulate {
|
||||
switch {
|
||||
case len(br.ChainID) == 0:
|
||||
WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified")
|
||||
return false
|
||||
|
||||
case !br.Fees.IsZero() && !br.GasPrices.IsZero():
|
||||
// both fees and gas prices were provided
|
||||
WriteErrorResponse(w, http.StatusBadRequest, "cannot provide both fees and gas prices")
|
||||
return false
|
||||
|
||||
case !br.Fees.IsValid() && !br.GasPrices.IsValid():
|
||||
// neither fees or gas prices were provided
|
||||
WriteErrorResponse(w, http.StatusPaymentRequired, "invalid fees or gas prices provided")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := sdk.AccAddressFromBech32(br.From); err != nil || len(br.From) == 0 {
|
||||
WriteErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("invalid from address: %s", br.From))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadRESTReq reads and unmarshals a Request's body to the the BaseReq struct.
|
||||
// Writes an error response to ResponseWriter and returns false if errors occurred.
|
||||
func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.LegacyAmino, req interface{}) bool {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if CheckBadRequestError(w, err) {
|
||||
return false
|
||||
}
|
||||
|
||||
err = cdc.UnmarshalJSON(body, req)
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ErrorResponse defines the attributes of a JSON error response.
|
||||
type ErrorResponse struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// NewErrorResponse creates a new ErrorResponse instance.
|
||||
func NewErrorResponse(code int, err string) ErrorResponse {
|
||||
return ErrorResponse{Code: code, Error: err}
|
||||
}
|
||||
|
||||
// CheckError takes care of writing an error response if err is not nil.
|
||||
// Returns false when err is nil; it returns true otherwise.
|
||||
func CheckError(w http.ResponseWriter, status int, err error) bool {
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, status, err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckBadRequestError attaches an error message to an HTTP 400 BAD REQUEST response.
|
||||
// Returns false when err is nil; it returns true otherwise.
|
||||
func CheckBadRequestError(w http.ResponseWriter, err error) bool {
|
||||
return CheckError(w, http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
// CheckInternalServerError attaches an error message to an HTTP 500 INTERNAL SERVER ERROR response.
|
||||
// Returns false when err is nil; it returns true otherwise.
|
||||
func CheckInternalServerError(w http.ResponseWriter, err error) bool {
|
||||
return CheckError(w, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
// CheckNotFoundError attaches an error message to an HTTP 404 NOT FOUND response.
|
||||
// Returns false when err is nil; it returns true otherwise.
|
||||
func CheckNotFoundError(w http.ResponseWriter, err error) bool {
|
||||
return CheckError(w, http.StatusNotFound, err)
|
||||
}
|
||||
|
||||
// WriteErrorResponse prepares and writes a HTTP error
|
||||
// given a status code and an error message.
|
||||
func WriteErrorResponse(w http.ResponseWriter, status int, err string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(legacy.Cdc.MustMarshalJSON(NewErrorResponse(0, err)))
|
||||
}
|
||||
|
||||
// WriteSimulationResponse prepares and writes an HTTP
|
||||
// response for transactions simulations.
|
||||
func WriteSimulationResponse(w http.ResponseWriter, cdc *codec.LegacyAmino, gas uint64) {
|
||||
gasEst := GasEstimateResponse{GasEstimate: gas}
|
||||
|
||||
resp, err := cdc.MarshalJSON(gasEst)
|
||||
if CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(resp)
|
||||
}
|
||||
|
||||
// ParseUint64OrReturnBadRequest converts s to a uint64 value.
|
||||
func ParseUint64OrReturnBadRequest(w http.ResponseWriter, s string) (n uint64, ok bool) {
|
||||
var err error
|
||||
|
||||
n, err = strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("'%s' is not a valid uint64", s))
|
||||
|
||||
return n, false
|
||||
}
|
||||
|
||||
return n, true
|
||||
}
|
||||
|
||||
// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a
|
||||
// default value, defaultIfEmpty, if the string is empty.
|
||||
func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) {
|
||||
if len(s) == 0 {
|
||||
return defaultIfEmpty, true
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(s, 64)
|
||||
if CheckBadRequestError(w, err) {
|
||||
return n, false
|
||||
}
|
||||
|
||||
return n, true
|
||||
}
|
||||
|
||||
// ParseQueryHeightOrReturnBadRequest sets the height to execute a query if set by the http request.
|
||||
// It returns false if there was an error parsing the height.
|
||||
func ParseQueryHeightOrReturnBadRequest(w http.ResponseWriter, clientCtx client.Context, r *http.Request) (client.Context, bool) {
|
||||
heightStr := r.FormValue("height")
|
||||
if heightStr != "" {
|
||||
height, err := strconv.ParseInt(heightStr, 10, 64)
|
||||
if CheckBadRequestError(w, err) {
|
||||
return clientCtx, false
|
||||
}
|
||||
|
||||
if height < 0 {
|
||||
WriteErrorResponse(w, http.StatusBadRequest, "height must be equal or greater than zero")
|
||||
return clientCtx, false
|
||||
}
|
||||
|
||||
if height > 0 {
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
}
|
||||
} else {
|
||||
clientCtx = clientCtx.WithHeight(0)
|
||||
}
|
||||
|
||||
return clientCtx, true
|
||||
}
|
||||
|
||||
// PostProcessResponseBare post processes a body similar to PostProcessResponse
|
||||
// except it does not wrap the body and inject the height.
|
||||
func PostProcessResponseBare(w http.ResponseWriter, ctx client.Context, body interface{}) {
|
||||
var (
|
||||
resp []byte
|
||||
err error
|
||||
)
|
||||
|
||||
switch b := body.(type) {
|
||||
case []byte:
|
||||
resp = b
|
||||
|
||||
default:
|
||||
resp, err = ctx.LegacyAmino.MarshalJSON(body)
|
||||
if CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(resp)
|
||||
}
|
||||
|
||||
// PostProcessResponse performs post processing for a REST response. The result
|
||||
// returned to clients will contain two fields, the height at which the resource
|
||||
// was queried at and the original result.
|
||||
func PostProcessResponse(w http.ResponseWriter, ctx client.Context, resp interface{}) {
|
||||
var (
|
||||
result []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if ctx.Height < 0 {
|
||||
WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("negative height in response").Error())
|
||||
return
|
||||
}
|
||||
|
||||
// LegacyAmino used intentionally for REST
|
||||
marshaler := ctx.LegacyAmino
|
||||
|
||||
switch res := resp.(type) {
|
||||
case []byte:
|
||||
result = res
|
||||
|
||||
default:
|
||||
result, err = marshaler.MarshalJSON(resp)
|
||||
if CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
wrappedResp := NewResponseWithHeight(ctx.Height, result)
|
||||
|
||||
output, err := marshaler.MarshalJSON(wrappedResp)
|
||||
if CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(output)
|
||||
}
|
||||
|
||||
// ParseHTTPArgsWithLimit parses the request's URL and returns a slice containing
|
||||
// all arguments pairs. It separates page and limit used for pagination where a
|
||||
// default limit can be provided.
|
||||
func ParseHTTPArgsWithLimit(r *http.Request, defaultLimit int) (tags []string, page, limit int, err error) {
|
||||
tags = make([]string, 0, len(r.Form))
|
||||
|
||||
for key, values := range r.Form {
|
||||
if key == "page" || key == "limit" {
|
||||
continue
|
||||
}
|
||||
|
||||
var value string
|
||||
value, err = url.QueryUnescape(values[0])
|
||||
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
}
|
||||
|
||||
var tag string
|
||||
|
||||
switch key {
|
||||
case types.TxHeightKey:
|
||||
tag = fmt.Sprintf("%s=%s", key, value)
|
||||
|
||||
case TxMinHeightKey:
|
||||
tag = fmt.Sprintf("%s>=%s", types.TxHeightKey, value)
|
||||
|
||||
case TxMaxHeightKey:
|
||||
tag = fmt.Sprintf("%s<=%s", types.TxHeightKey, value)
|
||||
|
||||
default:
|
||||
tag = fmt.Sprintf("%s='%s'", key, value)
|
||||
}
|
||||
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
pageStr := r.FormValue("page")
|
||||
if pageStr == "" {
|
||||
page = DefaultPage
|
||||
} else {
|
||||
page, err = strconv.Atoi(pageStr)
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
} else if page <= 0 {
|
||||
return tags, page, limit, errors.New("page must greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
limitStr := r.FormValue("limit")
|
||||
if limitStr == "" {
|
||||
limit = defaultLimit
|
||||
} else {
|
||||
limit, err = strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
} else if limit <= 0 {
|
||||
return tags, page, limit, errors.New("limit must greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
return tags, page, limit, nil
|
||||
}
|
||||
|
||||
// ParseHTTPArgs parses the request's URL and returns a slice containing all
|
||||
// arguments pairs. It separates page and limit used for pagination.
|
||||
func ParseHTTPArgs(r *http.Request) (tags []string, page, limit int, err error) {
|
||||
return ParseHTTPArgsWithLimit(r, DefaultLimit)
|
||||
}
|
||||
|
||||
// ParseQueryParamBool parses the given param to a boolean. It returns false by
|
||||
// default if the string is not parseable to bool.
|
||||
func ParseQueryParamBool(r *http.Request, param string) bool {
|
||||
if value, err := strconv.ParseBool(r.FormValue(param)); err == nil {
|
||||
return value
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetRequest defines a wrapper around an HTTP GET request with a provided URL.
|
||||
// An error is returned if the request or reading the body fails.
|
||||
func GetRequest(url string) ([]byte, error) {
|
||||
res, err := http.Get(url) // nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = res.Body.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// PostRequest defines a wrapper around an HTTP POST request with a provided URL and data.
|
||||
// An error is returned if the request or reading the body fails.
|
||||
func PostRequest(url string, contentType string, data []byte) ([]byte, error) {
|
||||
res, err := http.Post(url, contentType, bytes.NewBuffer(data)) // nolint:gosec
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while sending post request: %w", err)
|
||||
}
|
||||
|
||||
bz, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if err = res.Body.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bz, nil
|
||||
}
|
||||
@ -1,460 +0,0 @@
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
)
|
||||
|
||||
func TestBaseReq_Sanitize(t *testing.T) {
|
||||
t.Parallel()
|
||||
sanitized := rest.BaseReq{ChainID: " test",
|
||||
Memo: "memo ",
|
||||
From: " cosmos1cq0sxam6x4l0sv9yz3a2vlqhdhvt2k6jtgcse0 ",
|
||||
Gas: " ",
|
||||
GasAdjustment: " 0.3",
|
||||
}.Sanitize()
|
||||
require.Equal(t, rest.BaseReq{ChainID: "test",
|
||||
Memo: "memo",
|
||||
From: "cosmos1cq0sxam6x4l0sv9yz3a2vlqhdhvt2k6jtgcse0",
|
||||
Gas: "",
|
||||
GasAdjustment: "0.3",
|
||||
}, sanitized)
|
||||
}
|
||||
|
||||
func TestBaseReq_ValidateBasic(t *testing.T) {
|
||||
fromAddr := "cosmos1cq0sxam6x4l0sv9yz3a2vlqhdhvt2k6jtgcse0"
|
||||
tenstakes, err := types.ParseCoinsNormalized("10stake")
|
||||
require.NoError(t, err)
|
||||
onestake, err := types.ParseDecCoins("1.0stake")
|
||||
require.NoError(t, err)
|
||||
|
||||
req1 := rest.NewBaseReq(
|
||||
fromAddr, "", "nonempty", "", "", 0, 0, tenstakes, nil, false,
|
||||
)
|
||||
req2 := rest.NewBaseReq(
|
||||
"", "", "nonempty", "", "", 0, 0, tenstakes, nil, false,
|
||||
)
|
||||
req3 := rest.NewBaseReq(
|
||||
fromAddr, "", "", "", "", 0, 0, tenstakes, nil, false,
|
||||
)
|
||||
req4 := rest.NewBaseReq(
|
||||
fromAddr, "", "nonempty", "", "", 0, 0, tenstakes, onestake, false,
|
||||
)
|
||||
req5 := rest.NewBaseReq(
|
||||
fromAddr, "", "nonempty", "", "", 0, 0, types.Coins{}, types.DecCoins{}, false,
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req rest.BaseReq
|
||||
w http.ResponseWriter
|
||||
want bool
|
||||
}{
|
||||
{"ok", req1, httptest.NewRecorder(), true},
|
||||
{"neither fees nor gasprices provided", req5, httptest.NewRecorder(), true},
|
||||
{"empty from", req2, httptest.NewRecorder(), false},
|
||||
{"empty chain-id", req3, httptest.NewRecorder(), false},
|
||||
{"fees and gasprices provided", req4, httptest.NewRecorder(), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, tt.req.ValidateBasic(tt.w))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHTTPArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
req0 := mustNewRequest(t, "", "/", nil)
|
||||
req1 := mustNewRequest(t, "", "/?limit=5", nil)
|
||||
req2 := mustNewRequest(t, "", "/?page=5", nil)
|
||||
req3 := mustNewRequest(t, "", "/?page=5&limit=5", nil)
|
||||
|
||||
reqE1 := mustNewRequest(t, "", "/?page=-1", nil)
|
||||
reqE2 := mustNewRequest(t, "", "/?limit=-1", nil)
|
||||
req4 := mustNewRequest(t, "", "/?foo=faa", nil)
|
||||
|
||||
reqTxH := mustNewRequest(t, "", "/?tx.minheight=12&tx.maxheight=14", nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req *http.Request
|
||||
w http.ResponseWriter
|
||||
tags []string
|
||||
page int
|
||||
limit int
|
||||
err bool
|
||||
}{
|
||||
{"no params", req0, httptest.NewRecorder(), []string{}, rest.DefaultPage, rest.DefaultLimit, false},
|
||||
{"Limit", req1, httptest.NewRecorder(), []string{}, rest.DefaultPage, 5, false},
|
||||
{"Page", req2, httptest.NewRecorder(), []string{}, 5, rest.DefaultLimit, false},
|
||||
{"Page and limit", req3, httptest.NewRecorder(), []string{}, 5, 5, false},
|
||||
|
||||
{"error page 0", reqE1, httptest.NewRecorder(), []string{}, rest.DefaultPage, rest.DefaultLimit, true},
|
||||
{"error limit 0", reqE2, httptest.NewRecorder(), []string{}, rest.DefaultPage, rest.DefaultLimit, true},
|
||||
|
||||
{"tags", req4, httptest.NewRecorder(), []string{"foo='faa'"}, rest.DefaultPage, rest.DefaultLimit, false},
|
||||
{"tags", reqTxH, httptest.NewRecorder(), []string{"tx.height<=14", "tx.height>=12"}, rest.DefaultPage, rest.DefaultLimit, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tags, page, limit, err := rest.ParseHTTPArgs(tt.req)
|
||||
|
||||
sort.Strings(tags)
|
||||
|
||||
if tt.err {
|
||||
require.NotNil(t, err)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tt.tags, tags)
|
||||
require.Equal(t, tt.page, page)
|
||||
require.Equal(t, tt.limit, limit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseQueryHeight(t *testing.T) {
|
||||
t.Parallel()
|
||||
var emptyHeight int64
|
||||
height := int64(1256756)
|
||||
|
||||
req0 := mustNewRequest(t, "", "/", nil)
|
||||
req1 := mustNewRequest(t, "", "/?height=1256756", nil)
|
||||
req2 := mustNewRequest(t, "", "/?height=456yui4567", nil)
|
||||
req3 := mustNewRequest(t, "", "/?height=-1", nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req *http.Request
|
||||
w http.ResponseWriter
|
||||
clientCtx client.Context
|
||||
expectedHeight int64
|
||||
expectedOk bool
|
||||
}{
|
||||
{"no height", req0, httptest.NewRecorder(), client.Context{}, emptyHeight, true},
|
||||
{"height", req1, httptest.NewRecorder(), client.Context{}, height, true},
|
||||
{"invalid height", req2, httptest.NewRecorder(), client.Context{}, emptyHeight, false},
|
||||
{"negative height", req3, httptest.NewRecorder(), client.Context{}, emptyHeight, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(tt.w, tt.clientCtx, tt.req)
|
||||
if tt.expectedOk {
|
||||
require.True(t, ok)
|
||||
require.Equal(t, tt.expectedHeight, clientCtx.Height)
|
||||
} else {
|
||||
require.False(t, ok)
|
||||
require.Empty(t, tt.expectedHeight, clientCtx.Height)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPostResponse(t *testing.T) {
|
||||
// mock account
|
||||
// PubKey field ensures amino encoding is used first since standard
|
||||
// JSON encoding will panic on cryptotypes.PubKey
|
||||
|
||||
t.Parallel()
|
||||
type mockAccount struct {
|
||||
Address types.AccAddress `json:"address"`
|
||||
Coins types.Coins `json:"coins"`
|
||||
PubKey cryptotypes.PubKey `json:"public_key"`
|
||||
AccountNumber uint64 `json:"account_number"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
}
|
||||
|
||||
// setup
|
||||
viper.Set(flags.FlagOffline, true)
|
||||
ctx := client.Context{}
|
||||
height := int64(194423)
|
||||
|
||||
privKey := secp256k1.GenPrivKey()
|
||||
pubKey := privKey.PubKey()
|
||||
addr := types.AccAddress(pubKey.Address())
|
||||
coins := types.NewCoins(types.NewCoin("atom", types.NewInt(100)), types.NewCoin("tree", types.NewInt(125)))
|
||||
accNumber := uint64(104)
|
||||
sequence := uint64(32)
|
||||
|
||||
acc := mockAccount{addr, coins, pubKey, accNumber, sequence}
|
||||
cdc := codec.NewLegacyAmino()
|
||||
cryptocodec.RegisterCrypto(cdc)
|
||||
cdc.RegisterConcrete(&mockAccount{}, "cosmos-sdk/mockAccount", nil)
|
||||
ctx = ctx.WithLegacyAmino(cdc)
|
||||
|
||||
// setup expected results
|
||||
jsonNoIndent, err := ctx.LegacyAmino.MarshalJSON(acc)
|
||||
require.Nil(t, err)
|
||||
|
||||
respNoIndent := rest.NewResponseWithHeight(height, jsonNoIndent)
|
||||
expectedNoIndent, err := ctx.LegacyAmino.MarshalJSON(respNoIndent)
|
||||
require.Nil(t, err)
|
||||
|
||||
// check that negative height writes an error
|
||||
w := httptest.NewRecorder()
|
||||
ctx = ctx.WithHeight(-1)
|
||||
rest.PostProcessResponse(w, ctx, acc)
|
||||
require.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
|
||||
// check that height returns expected response
|
||||
ctx = ctx.WithHeight(height)
|
||||
runPostProcessResponse(t, ctx, acc, expectedNoIndent)
|
||||
}
|
||||
|
||||
func TestReadRESTReq(t *testing.T) {
|
||||
t.Parallel()
|
||||
reqBody := ioutil.NopCloser(strings.NewReader(`{"chain_id":"alessio","memo":"text"}`))
|
||||
req := &http.Request{Body: reqBody}
|
||||
w := httptest.NewRecorder()
|
||||
var br rest.BaseReq
|
||||
|
||||
// test OK
|
||||
rest.ReadRESTReq(w, req, codec.NewLegacyAmino(), &br)
|
||||
res := w.Result() //nolint:bodyclose
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, rest.BaseReq{ChainID: "alessio", Memo: "text"}, br)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
// test non valid JSON
|
||||
reqBody = ioutil.NopCloser(strings.NewReader(`MALFORMED`))
|
||||
req = &http.Request{Body: reqBody}
|
||||
br = rest.BaseReq{}
|
||||
w = httptest.NewRecorder()
|
||||
rest.ReadRESTReq(w, req, codec.NewLegacyAmino(), &br)
|
||||
require.Equal(t, br, br)
|
||||
res = w.Result() //nolint:bodyclose
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
}
|
||||
|
||||
func TestWriteSimulationResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := httptest.NewRecorder()
|
||||
rest.WriteSimulationResponse(w, codec.NewLegacyAmino(), 10)
|
||||
res := w.Result() //nolint:bodyclose
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
bs, err := ioutil.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, `{"gas_estimate":"10"}`, string(bs))
|
||||
}
|
||||
|
||||
func TestParseUint64OrReturnBadRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := httptest.NewRecorder()
|
||||
_, ok := rest.ParseUint64OrReturnBadRequest(w, "100")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
_, ok = rest.ParseUint64OrReturnBadRequest(w, "-100")
|
||||
require.False(t, ok)
|
||||
require.Equal(t, http.StatusBadRequest, w.Result().StatusCode) //nolint:bodyclose
|
||||
}
|
||||
|
||||
func TestParseFloat64OrReturnBadRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := httptest.NewRecorder()
|
||||
_, ok := rest.ParseFloat64OrReturnBadRequest(w, "100", 0)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
_, ok = rest.ParseFloat64OrReturnBadRequest(w, "bad request", 0)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, http.StatusBadRequest, w.Result().StatusCode) //nolint:bodyclose
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
ret, ok := rest.ParseFloat64OrReturnBadRequest(w, "", 9.0)
|
||||
require.Equal(t, float64(9), ret)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose
|
||||
}
|
||||
|
||||
func TestParseQueryParamBool(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/target?boolean=true", nil)
|
||||
require.True(t, rest.ParseQueryParamBool(req, "boolean"))
|
||||
require.False(t, rest.ParseQueryParamBool(req, "nokey"))
|
||||
req = httptest.NewRequest("GET", "/target?boolean=false", nil)
|
||||
require.False(t, rest.ParseQueryParamBool(req, "boolean"))
|
||||
require.False(t, rest.ParseQueryParamBool(req, ""))
|
||||
}
|
||||
|
||||
func TestPostProcessResponseBare(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
encodingConfig := simappparams.MakeTestEncodingConfig()
|
||||
clientCtx := client.Context{}.
|
||||
WithTxConfig(encodingConfig.TxConfig).
|
||||
WithLegacyAmino(encodingConfig.Amino) // amino used intentionally here
|
||||
// write bytes
|
||||
w := httptest.NewRecorder()
|
||||
bs := []byte("text string")
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, bs)
|
||||
|
||||
res := w.Result() //nolint:bodyclose
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
got, err := ioutil.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, "text string", string(got))
|
||||
|
||||
// write struct and indent response
|
||||
w = httptest.NewRecorder()
|
||||
data := struct {
|
||||
X int `json:"x"`
|
||||
S string `json:"s"`
|
||||
}{X: 10, S: "test"}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, data)
|
||||
|
||||
res = w.Result() //nolint:bodyclose
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
got, err = ioutil.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, "{\"x\":\"10\",\"s\":\"test\"}", string(got))
|
||||
|
||||
// write struct, don't indent response
|
||||
w = httptest.NewRecorder()
|
||||
data = struct {
|
||||
X int `json:"x"`
|
||||
S string `json:"s"`
|
||||
}{X: 10, S: "test"}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, data)
|
||||
|
||||
res = w.Result() //nolint:bodyclose
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
got, err = ioutil.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, `{"x":"10","s":"test"}`, string(got))
|
||||
|
||||
// test marshalling failure
|
||||
w = httptest.NewRecorder()
|
||||
data2 := badJSONMarshaller{}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, data2)
|
||||
|
||||
res = w.Result() //nolint:bodyclose
|
||||
require.Equal(t, http.StatusInternalServerError, res.StatusCode)
|
||||
|
||||
got, err = ioutil.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() { res.Body.Close() })
|
||||
require.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
|
||||
require.Equal(t, `{"error":"couldn't marshal"}`, string(got))
|
||||
}
|
||||
|
||||
type badJSONMarshaller struct{}
|
||||
|
||||
func (badJSONMarshaller) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("couldn't marshal")
|
||||
}
|
||||
|
||||
// asserts that ResponseRecorder returns the expected code and body
|
||||
// runs PostProcessResponse on the objects regular interface and on
|
||||
// the marshalled struct.
|
||||
func runPostProcessResponse(t *testing.T, ctx client.Context, obj interface{}, expectedBody []byte) {
|
||||
// test using regular struct
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
rest.PostProcessResponse(w, ctx, obj)
|
||||
require.Equal(t, http.StatusOK, w.Code, w.Body)
|
||||
|
||||
resp := w.Result() //nolint:bodyclose
|
||||
t.Cleanup(func() { resp.Body.Close() })
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, expectedBody, body)
|
||||
|
||||
marshalled, err := ctx.LegacyAmino.MarshalJSON(obj)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test using marshalled struct
|
||||
w = httptest.NewRecorder()
|
||||
rest.PostProcessResponse(w, ctx, marshalled)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code, w.Body)
|
||||
resp = w.Result() //nolint:bodyclose
|
||||
|
||||
t.Cleanup(func() { resp.Body.Close() })
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, string(expectedBody), string(body))
|
||||
}
|
||||
|
||||
func mustNewRequest(t *testing.T, method, url string, body io.Reader) *http.Request {
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
require.NoError(t, err)
|
||||
err = req.ParseForm()
|
||||
require.NoError(t, err)
|
||||
return req
|
||||
}
|
||||
|
||||
func TestCheckErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := errors.New("ERROR")
|
||||
tests := []struct {
|
||||
name string
|
||||
checkerFn func(w http.ResponseWriter, err error) bool
|
||||
error error
|
||||
wantErr bool
|
||||
wantString string
|
||||
wantStatus int
|
||||
}{
|
||||
{"500", rest.CheckInternalServerError, err, true, `{"error":"ERROR"}`, http.StatusInternalServerError},
|
||||
{"500 (no error)", rest.CheckInternalServerError, nil, false, ``, http.StatusInternalServerError},
|
||||
{"400", rest.CheckBadRequestError, err, true, `{"error":"ERROR"}`, http.StatusBadRequest},
|
||||
{"400 (no error)", rest.CheckBadRequestError, nil, false, ``, http.StatusBadRequest},
|
||||
{"404", rest.CheckNotFoundError, err, true, `{"error":"ERROR"}`, http.StatusNotFound},
|
||||
{"404 (no error)", rest.CheckNotFoundError, nil, false, ``, http.StatusNotFound},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
require.Equal(t, tt.wantErr, tt.checkerFn(w, tt.error))
|
||||
if tt.wantErr {
|
||||
require.Equal(t, w.Body.String(), tt.wantString)
|
||||
require.Equal(t, w.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/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)
|
||||
|
||||
|
||||
@ -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",
|
||||
}
|
||||
|
||||
@ -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",
|
||||
}
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
)
|
||||
|
||||
// BroadcastReq defines a tx broadcasting request.
|
||||
type BroadcastReq struct {
|
||||
Tx legacytx.StdTx `json:"tx" yaml:"tx"`
|
||||
Mode string `json:"mode" yaml:"mode"`
|
||||
}
|
||||
|
||||
var _ codectypes.UnpackInterfacesMessage = BroadcastReq{}
|
||||
|
||||
// UnpackInterfaces implements the UnpackInterfacesMessage interface.
|
||||
func (m BroadcastReq) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
|
||||
return m.Tx.UnpackInterfaces(unpacker)
|
||||
}
|
||||
|
||||
// BroadcastTxRequest implements a tx broadcasting handler that is responsible
|
||||
// for broadcasting a valid and signed tx to a full node. The tx can be
|
||||
// broadcasted via a sync|async|block mechanism.
|
||||
func BroadcastTxRequest(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req BroadcastReq
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: amino is used intentionally here, don't migrate it!
|
||||
err = clientCtx.LegacyAmino.UnmarshalJSON(body, &req)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("this transaction cannot be broadcasted via legacy REST endpoints, because it does not support"+
|
||||
" Amino serialization. Please either use CLI, gRPC, gRPC-gateway, or directly query the Tendermint RPC"+
|
||||
" endpoint to broadcast this transaction. The new REST endpoint (via gRPC-gateway) is POST /cosmos/tx/v1beta1/txs."+
|
||||
" Please also see the REST endpoints migration guide at %s for more info", clientrest.DeprecationURL)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
txBytes, err := tx.ConvertAndEncodeStdTx(clientCtx.TxConfig, req.Tx)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithBroadcastMode(req.Mode)
|
||||
|
||||
res, err := clientCtx.BroadcastTx(txBytes)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
)
|
||||
|
||||
type (
|
||||
// DecodeReq defines a tx decoding request.
|
||||
DecodeReq struct {
|
||||
Tx string `json:"tx"`
|
||||
}
|
||||
|
||||
// DecodeResp defines a tx decoding response.
|
||||
DecodeResp legacytx.StdTx
|
||||
)
|
||||
|
||||
// DecodeTxRequestHandlerFn returns the decode tx REST handler. In particular,
|
||||
// it takes base64-decoded bytes, decodes it from the Amino wire protocol,
|
||||
// and responds with a json-formatted transaction.
|
||||
func DecodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req DecodeReq
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: amino is used intentionally here, don't migrate it
|
||||
err = clientCtx.LegacyAmino.UnmarshalJSON(body, &req)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
txBytes, err := base64.StdEncoding.DecodeString(req.Tx)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
stdTx, err := convertToStdTx(w, clientCtx, txBytes)
|
||||
if err != nil {
|
||||
// Error is already returned by convertToStdTx.
|
||||
return
|
||||
}
|
||||
|
||||
response := DecodeResp(stdTx)
|
||||
|
||||
err = checkAminoMarshalError(clientCtx, response, "/cosmos/tx/v1beta1/txs/decode")
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, clientCtx, response)
|
||||
}
|
||||
}
|
||||
|
||||
// convertToStdTx converts tx proto binary bytes retrieved from Tendermint into
|
||||
// a StdTx. Returns the StdTx, as well as a flag denoting if the function
|
||||
// successfully converted or not.
|
||||
func convertToStdTx(w http.ResponseWriter, clientCtx client.Context, txBytes []byte) (legacytx.StdTx, error) {
|
||||
txI, err := clientCtx.TxConfig.TxDecoder()(txBytes)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return legacytx.StdTx{}, err
|
||||
}
|
||||
|
||||
tx, ok := txI.(signing.Tx)
|
||||
if !ok {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("%+v is not backwards compatible with %T", tx, legacytx.StdTx{}))
|
||||
return legacytx.StdTx{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expected %T, got %T", (signing.Tx)(nil), txI)
|
||||
}
|
||||
|
||||
stdTx, err := clienttx.ConvertTxToStdTx(clientCtx.LegacyAmino, tx)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return legacytx.StdTx{}, err
|
||||
}
|
||||
|
||||
return stdTx, nil
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
)
|
||||
|
||||
// EncodeResp defines a tx encoding response.
|
||||
type EncodeResp struct {
|
||||
Tx string `json:"tx" yaml:"tx"`
|
||||
}
|
||||
|
||||
// ErrEncodeDecode is the error to show when encoding/decoding txs that are not
|
||||
// amino-serializable (e.g. IBC txs).
|
||||
var ErrEncodeDecode error = fmt.Errorf("this endpoint does not support txs that are not serializable"+
|
||||
" via Amino, such as txs that contain IBC `Msg`s. For more info, please refer to our"+
|
||||
" REST migration guide at %s", clientrest.DeprecationURL)
|
||||
|
||||
// EncodeTxRequestHandlerFn returns the encode tx REST handler. In particular,
|
||||
// it takes a json-formatted transaction, encodes it to the Amino wire protocol,
|
||||
// and responds with base64-encoded bytes.
|
||||
func EncodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req legacytx.StdTx
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: amino is used intentionally here, don't migrate it
|
||||
err = clientCtx.LegacyAmino.UnmarshalJSON(body, &req)
|
||||
// If there's an unmarshalling error, we assume that it's because we're
|
||||
// using amino to unmarshal a non-amino tx.
|
||||
if err != nil {
|
||||
if rest.CheckBadRequestError(w, ErrEncodeDecode) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// re-encode it in the chain's native binary format
|
||||
txBytes, err := tx.ConvertAndEncodeStdTx(clientCtx.TxConfig, req)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// base64 encode the encoded tx bytes
|
||||
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)
|
||||
|
||||
response := EncodeResp{Tx: txBytesBase64}
|
||||
|
||||
// NOTE: amino is set intentionally here, don't migrate it
|
||||
rest.PostProcessResponseBare(w, clientCtx, response)
|
||||
}
|
||||
}
|
||||
@ -1,220 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
genutilrest "github.com/cosmos/cosmos-sdk/x/genutil/client/rest"
|
||||
)
|
||||
|
||||
// QueryAccountRequestHandlerFn is the query accountREST Handler.
|
||||
func QueryAccountRequestHandlerFn(storeName string, clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bech32addr := vars["address"]
|
||||
|
||||
addr, err := sdk.AccAddressFromBech32(bech32addr)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
accGetter := types.AccountRetriever{}
|
||||
|
||||
account, height, err := accGetter.GetAccountWithHeight(clientCtx, addr)
|
||||
if err != nil {
|
||||
// TODO: Handle more appropriately based on the error type.
|
||||
// Ref: https://github.com/cosmos/cosmos-sdk/issues/4923
|
||||
if err := accGetter.EnsureExists(clientCtx, addr); err != nil {
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, types.BaseAccount{})
|
||||
return
|
||||
}
|
||||
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, account)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryTxsRequestHandlerFn implements a REST handler that searches for transactions.
|
||||
// Genesis transactions are returned if the height parameter is set to zero,
|
||||
// otherwise the transactions are searched for by events.
|
||||
func QueryTxsRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(
|
||||
w, http.StatusBadRequest,
|
||||
fmt.Sprintf("failed to parse query parameters: %s", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// if the height query param is set to zero, query for genesis transactions
|
||||
heightStr := r.FormValue("height")
|
||||
if heightStr != "" {
|
||||
if height, err := strconv.ParseInt(heightStr, 10, 64); err == nil && height == 0 {
|
||||
genutilrest.QueryGenesisTxs(clientCtx, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
events []string
|
||||
txs []sdk.TxResponse
|
||||
page, limit int
|
||||
)
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if len(r.Form) == 0 {
|
||||
rest.PostProcessResponseBare(w, clientCtx, txs)
|
||||
return
|
||||
}
|
||||
|
||||
events, page, limit, err = rest.ParseHTTPArgs(r)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
searchResult, err := authtx.QueryTxsByEvents(clientCtx, events, page, limit, "")
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, txRes := range searchResult.Txs {
|
||||
packStdTxResponse(w, clientCtx, txRes)
|
||||
}
|
||||
|
||||
err = checkAminoMarshalError(clientCtx, searchResult, "/cosmos/tx/v1beta1/txs")
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, searchResult)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryTxRequestHandlerFn implements a REST handler that queries a transaction
|
||||
// by hash in a committed block.
|
||||
func QueryTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
hashHexStr := vars["hash"]
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
output, err := authtx.QueryTx(clientCtx, hashHexStr)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), hashHexStr) {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = packStdTxResponse(w, clientCtx, output)
|
||||
if err != nil {
|
||||
// Error is already returned by packStdTxResponse.
|
||||
return
|
||||
}
|
||||
|
||||
if output.Empty() {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, fmt.Sprintf("no transaction found with hash %s", hashHexStr))
|
||||
}
|
||||
|
||||
err = checkAminoMarshalError(clientCtx, output, "/cosmos/tx/v1beta1/txs/{txhash}")
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, output)
|
||||
}
|
||||
}
|
||||
|
||||
func queryParamsHandler(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParams)
|
||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// packStdTxResponse takes a sdk.TxResponse, converts the Tx into a StdTx, and
|
||||
// packs the StdTx again into the sdk.TxResponse Any. Amino then takes care of
|
||||
// seamlessly JSON-outputting the Any.
|
||||
func packStdTxResponse(w http.ResponseWriter, clientCtx client.Context, txRes *sdk.TxResponse) error {
|
||||
// We just unmarshalled from Tendermint, we take the proto Tx's raw
|
||||
// bytes, and convert them into a StdTx to be displayed.
|
||||
txBytes := txRes.Tx.Value
|
||||
stdTx, err := convertToStdTx(w, clientCtx, txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pack the amino stdTx into the TxResponse's Any.
|
||||
txRes.Tx = codectypes.UnsafePackAny(stdTx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAminoMarshalError checks if there are errors with marshalling non-amino
|
||||
// txs with amino.
|
||||
func checkAminoMarshalError(ctx client.Context, resp interface{}, grpcEndPoint string) error {
|
||||
// LegacyAmino used intentionally here to handle the SignMode errors
|
||||
marshaler := ctx.LegacyAmino
|
||||
|
||||
_, err := marshaler.MarshalJSON(resp)
|
||||
if err != nil {
|
||||
|
||||
// If there's an unmarshalling error, we assume that it's because we're
|
||||
// using amino to unmarshal a non-amino tx.
|
||||
return fmt.Errorf("this transaction cannot be displayed via legacy REST endpoints, because it does not support"+
|
||||
" Amino serialization. Please either use CLI, gRPC, gRPC-gateway, or directly query the Tendermint RPC"+
|
||||
" endpoint to query this transaction. The new REST endpoint (via gRPC-gateway) is %s. Please also see the"+
|
||||
"REST endpoints migration guide at %s for more info", grpcEndPoint, clientrest.DeprecationURL)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
)
|
||||
|
||||
// REST query and parameter values
|
||||
const (
|
||||
MethodGet = "GET"
|
||||
)
|
||||
|
||||
// RegisterRoutes registers the auth module REST routes.
|
||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router, storeName string) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
r.HandleFunc(
|
||||
"/auth/accounts/{address}", QueryAccountRequestHandlerFn(storeName, clientCtx),
|
||||
).Methods(MethodGet)
|
||||
|
||||
r.HandleFunc(
|
||||
"/auth/params",
|
||||
queryParamsHandler(clientCtx),
|
||||
).Methods(MethodGet)
|
||||
}
|
||||
|
||||
// RegisterTxRoutes registers all transaction routes on the provided router.
|
||||
func RegisterTxRoutes(clientCtx client.Context, rtr *mux.Router) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/txs", QueryTxsRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/txs", BroadcastTxRequest(clientCtx)).Methods("POST")
|
||||
r.HandleFunc("/txs/encode", EncodeTxRequestHandlerFn(clientCtx)).Methods("POST")
|
||||
r.HandleFunc("/txs/decode", DecodeTxRequestHandlerFn(clientCtx)).Methods("POST")
|
||||
}
|
||||
@ -1,536 +0,0 @@
|
||||
// +build norace
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/cosmos-sdk/testutil"
|
||||
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
||||
authcli "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||
authtest "github.com/cosmos/cosmos-sdk/x/auth/client/testutil"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
type IntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
cfg network.Config
|
||||
network *network.Network
|
||||
|
||||
stdTx legacytx.StdTx
|
||||
stdTxRes sdk.TxResponse
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) SetupSuite() {
|
||||
s.T().Log("setting up integration test suite")
|
||||
|
||||
cfg := network.DefaultConfig()
|
||||
cfg.NumValidators = 2
|
||||
s.cfg = cfg
|
||||
|
||||
var err error
|
||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
||||
s.Require().NoError(err)
|
||||
|
||||
kb := s.network.Validators[0].ClientCtx.Keyring
|
||||
_, _, err = kb.NewMnemonic("newAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
account1, _, err := kb.NewMnemonic("newAccount1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
account2, _, err := kb.NewMnemonic("newAccount2", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
multi := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{account1.GetPubKey(), account2.GetPubKey()})
|
||||
_, err = kb.SaveMultisig("multi", multi)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = s.network.WaitForHeight(1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Broadcast a StdTx used for tests.
|
||||
s.stdTx = s.createTestStdTx(s.network.Validators[0], 0, 1)
|
||||
res, err := s.broadcastReq(s.stdTx, "block")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// NOTE: this uses amino explicitly, don't migrate it!
|
||||
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &s.stdTxRes))
|
||||
s.Require().Equal(uint32(0), s.stdTxRes.Code)
|
||||
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||
s.T().Log("tearing down integration test suite")
|
||||
s.network.Cleanup()
|
||||
}
|
||||
|
||||
func mkStdTx() legacytx.StdTx {
|
||||
// NOTE: this uses StdTx explicitly, don't migrate it!
|
||||
return legacytx.StdTx{
|
||||
Msgs: []sdk.Msg{&types.MsgSend{}},
|
||||
Fee: legacytx.StdFee{
|
||||
Amount: sdk.Coins{sdk.NewInt64Coin("foo", 10)},
|
||||
Gas: 10000,
|
||||
},
|
||||
Memo: "FOOBAR",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestEncodeDecode() {
|
||||
var require = s.Require()
|
||||
val := s.network.Validators[0]
|
||||
stdTx := mkStdTx()
|
||||
|
||||
// NOTE: this uses amino explicitly, don't migrate it!
|
||||
cdc := val.ClientCtx.LegacyAmino
|
||||
|
||||
bz, err := cdc.MarshalJSON(stdTx)
|
||||
require.NoError(err)
|
||||
|
||||
res, err := rest.PostRequest(fmt.Sprintf("%s/txs/encode", val.APIAddress), "application/json", bz)
|
||||
require.NoError(err)
|
||||
|
||||
var encodeResp authrest.EncodeResp
|
||||
err = cdc.UnmarshalJSON(res, &encodeResp)
|
||||
require.NoError(err)
|
||||
|
||||
bz, err = cdc.MarshalJSON(authrest.DecodeReq(encodeResp))
|
||||
require.NoError(err)
|
||||
|
||||
res, err = rest.PostRequest(fmt.Sprintf("%s/txs/decode", val.APIAddress), "application/json", bz)
|
||||
require.NoError(err)
|
||||
|
||||
var respWithHeight rest.ResponseWithHeight
|
||||
err = cdc.UnmarshalJSON(res, &respWithHeight)
|
||||
require.NoError(err)
|
||||
var decodeResp authrest.DecodeResp
|
||||
err = cdc.UnmarshalJSON(respWithHeight.Result, &decodeResp)
|
||||
require.NoError(err)
|
||||
require.Equal(stdTx, legacytx.StdTx(decodeResp))
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestQueryAccountWithColon() {
|
||||
val := s.network.Validators[0]
|
||||
// This address is not a valid simapp address! It is only used to test that addresses with
|
||||
// colon don't 501. See
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/8650
|
||||
addrWithColon := "cosmos:1m4f6lwd9eh8e5nxt0h00d46d3fr03apfh8qf4g"
|
||||
|
||||
res, err := rest.GetRequest(fmt.Sprintf("%s/cosmos/auth/v1beta1/accounts/%s", val.APIAddress, addrWithColon))
|
||||
s.Require().NoError(err)
|
||||
s.Require().Contains(string(res), "decoding bech32 failed")
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestBroadcastTxRequest() {
|
||||
stdTx := mkStdTx()
|
||||
|
||||
// we just test with async mode because this tx will fail - all we care about is that it got encoded and broadcast correctly
|
||||
res, err := s.broadcastReq(stdTx, "async")
|
||||
s.Require().NoError(err)
|
||||
var txRes sdk.TxResponse
|
||||
// NOTE: this uses amino explicitly, don't migrate it!
|
||||
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &txRes))
|
||||
// we just check for a non-empty TxHash here, the actual hash will depend on the underlying tx configuration
|
||||
s.Require().NotEmpty(txRes.TxHash)
|
||||
}
|
||||
|
||||
// Helper function to test querying txs. We will use it to query StdTx and service `Msg`s.
|
||||
func (s *IntegrationTestSuite) testQueryTx(txHeight int64, txHash, txRecipient string) {
|
||||
val0 := s.network.Validators[0]
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
malleate func() *sdk.TxResponse
|
||||
}{
|
||||
{
|
||||
"Query by hash",
|
||||
func() *sdk.TxResponse {
|
||||
txJSON, err := rest.GetRequest(fmt.Sprintf("%s/txs/%s", val0.APIAddress, txHash))
|
||||
s.Require().NoError(err)
|
||||
|
||||
var txResAmino sdk.TxResponse
|
||||
s.Require().NoError(val0.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &txResAmino))
|
||||
return &txResAmino
|
||||
},
|
||||
},
|
||||
{
|
||||
"Query by height",
|
||||
func() *sdk.TxResponse {
|
||||
txJSON, err := rest.GetRequest(fmt.Sprintf("%s/txs?limit=10&page=1&tx.height=%d", val0.APIAddress, txHeight))
|
||||
s.Require().NoError(err)
|
||||
|
||||
var searchtxResult sdk.SearchTxsResult
|
||||
s.Require().NoError(val0.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &searchtxResult))
|
||||
s.Require().Len(searchtxResult.Txs, 1)
|
||||
return searchtxResult.Txs[0]
|
||||
},
|
||||
},
|
||||
{
|
||||
"Query by event (transfer.recipient)",
|
||||
func() *sdk.TxResponse {
|
||||
txJSON, err := rest.GetRequest(fmt.Sprintf("%s/txs?transfer.recipient=%s", val0.APIAddress, txRecipient))
|
||||
s.Require().NoError(err)
|
||||
|
||||
var searchtxResult sdk.SearchTxsResult
|
||||
s.Require().NoError(val0.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &searchtxResult))
|
||||
s.Require().Len(searchtxResult.Txs, 1)
|
||||
return searchtxResult.Txs[0]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(fmt.Sprintf("Case %s", tc.desc), func() {
|
||||
txResponse := tc.malleate()
|
||||
|
||||
// Check that the height is correct.
|
||||
s.Require().Equal(txHeight, txResponse.Height)
|
||||
|
||||
// Check that the events are correct.
|
||||
s.Require().Contains(
|
||||
txResponse.RawLog,
|
||||
fmt.Sprintf("{\"key\":\"recipient\",\"value\":\"%s\"}", txRecipient),
|
||||
)
|
||||
|
||||
// Check that the Msg is correct.
|
||||
stdTx, ok := txResponse.Tx.GetCachedValue().(legacytx.StdTx)
|
||||
s.Require().True(ok)
|
||||
msgs := stdTx.GetMsgs()
|
||||
s.Require().Equal(len(msgs), 1)
|
||||
msg, ok := msgs[0].(*types.MsgSend)
|
||||
s.Require().True(ok)
|
||||
s.Require().Equal(txRecipient, msg.ToAddress)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestQueryLegacyStdTx() {
|
||||
val0 := s.network.Validators[0]
|
||||
|
||||
// We broadcasted a StdTx in SetupSuite.
|
||||
// We just check for a non-empty TxHash here, the actual hash will depend on the underlying tx configuration
|
||||
s.Require().NotEmpty(s.stdTxRes.TxHash)
|
||||
|
||||
s.testQueryTx(s.stdTxRes.Height, s.stdTxRes.TxHash, val0.Address.String())
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestQueryTx() {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10)
|
||||
_, _, addr := testdata.KeyTestPubAddr()
|
||||
|
||||
// Might need to wait a block to refresh sequences from previous setups.
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
|
||||
out, err := bankcli.MsgSendExec(
|
||||
val.ClientCtx,
|
||||
val.Address,
|
||||
addr,
|
||||
sdk.NewCoins(sendTokens),
|
||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
||||
fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
|
||||
)
|
||||
|
||||
s.Require().NoError(err)
|
||||
var txRes sdk.TxResponse
|
||||
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes))
|
||||
s.Require().Equal(uint32(0), txRes.Code)
|
||||
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
|
||||
s.testQueryTx(txRes.Height, txRes.TxHash, addr.String())
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestMultipleSyncBroadcastTxRequests() {
|
||||
// First test transaction from validator should have sequence=1 (non-genesis tx)
|
||||
testCases := []struct {
|
||||
desc string
|
||||
sequence uint64
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
"First tx (correct sequence)",
|
||||
1,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Second tx (correct sequence)",
|
||||
2,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Third tx (incorrect sequence)",
|
||||
9,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.Run(fmt.Sprintf("Case %s", tc.desc), func() {
|
||||
// broadcast test with sync mode, as we want to run CheckTx to verify account sequence is correct
|
||||
stdTx := s.createTestStdTx(s.network.Validators[1], 1, tc.sequence)
|
||||
res, err := s.broadcastReq(stdTx, "sync")
|
||||
s.Require().NoError(err)
|
||||
|
||||
var txRes sdk.TxResponse
|
||||
// NOTE: this uses amino explicitly, don't migrate it!
|
||||
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &txRes))
|
||||
// we check for a exitCode=0, indicating a successful broadcast
|
||||
if tc.shouldErr {
|
||||
var sigVerifyFailureCode uint32 = 4
|
||||
s.Require().Equal(sigVerifyFailureCode, txRes.Code,
|
||||
"Testcase '%s': Expected signature verification failure {Code: %d} from TxResponse. "+
|
||||
"Found {Code: %d, RawLog: '%v'}",
|
||||
tc.desc, sigVerifyFailureCode, txRes.Code, txRes.RawLog,
|
||||
)
|
||||
} else {
|
||||
s.Require().Equal(uint32(0), txRes.Code,
|
||||
"Testcase '%s': TxResponse errored unexpectedly. Err: {Code: %d, RawLog: '%v'}",
|
||||
tc.desc, txRes.Code, txRes.RawLog,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) createTestStdTx(val *network.Validator, accNum, sequence uint64) legacytx.StdTx {
|
||||
txConfig := legacytx.StdTxConfig{Cdc: s.cfg.LegacyAmino}
|
||||
|
||||
msg := &types.MsgSend{
|
||||
FromAddress: val.Address.String(),
|
||||
ToAddress: val.Address.String(),
|
||||
Amount: sdk.Coins{sdk.NewInt64Coin(fmt.Sprintf("%stoken", val.Moniker), 100)},
|
||||
}
|
||||
|
||||
// prepare txBuilder with msg
|
||||
txBuilder := txConfig.NewTxBuilder()
|
||||
feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}
|
||||
gasLimit := testdata.NewTestGasLimit()
|
||||
txBuilder.SetMsgs(msg)
|
||||
txBuilder.SetFeeAmount(feeAmount)
|
||||
txBuilder.SetGasLimit(gasLimit)
|
||||
txBuilder.SetMemo("foobar")
|
||||
|
||||
// setup txFactory
|
||||
txFactory := tx.Factory{}.
|
||||
WithChainID(val.ClientCtx.ChainID).
|
||||
WithKeybase(val.ClientCtx.Keyring).
|
||||
WithTxConfig(txConfig).
|
||||
WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON).
|
||||
WithAccountNumber(accNum).
|
||||
WithSequence(sequence)
|
||||
|
||||
// sign Tx (offline mode so we can manually set sequence number)
|
||||
err := authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, true, true)
|
||||
s.Require().NoError(err)
|
||||
|
||||
stdTx := txBuilder.GetTx().(legacytx.StdTx)
|
||||
|
||||
return stdTx
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) broadcastReq(stdTx legacytx.StdTx, mode string) ([]byte, error) {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
// NOTE: this uses amino explicitly, don't migrate it!
|
||||
cdc := val.ClientCtx.LegacyAmino
|
||||
req := authrest.BroadcastReq{
|
||||
Tx: stdTx,
|
||||
Mode: mode,
|
||||
}
|
||||
bz, err := cdc.MarshalJSON(req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return rest.PostRequest(fmt.Sprintf("%s/txs", val.APIAddress), "application/json", bz)
|
||||
}
|
||||
|
||||
// testQueryIBCTx is a helper function to test querying txs which:
|
||||
// - show an error message on legacy REST endpoints
|
||||
// - succeed using gRPC
|
||||
// In practice, we call this function on IBC txs.
|
||||
func (s *IntegrationTestSuite) testQueryIBCTx(txRes sdk.TxResponse, cmd *cobra.Command, args []string) {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
errMsg := "this transaction cannot be displayed via legacy REST endpoints, because it does not support" +
|
||||
" Amino serialization. Please either use CLI, gRPC, gRPC-gateway, or directly query the Tendermint RPC" +
|
||||
" endpoint to query this transaction. The new REST endpoint (via gRPC-gateway) is "
|
||||
|
||||
// Test that legacy endpoint return the above error message on IBC txs.
|
||||
testCases := []struct {
|
||||
desc string
|
||||
url string
|
||||
}{
|
||||
{
|
||||
"Query by hash",
|
||||
fmt.Sprintf("%s/txs/%s", val.APIAddress, txRes.TxHash),
|
||||
},
|
||||
{
|
||||
"Query by height",
|
||||
fmt.Sprintf("%s/txs?tx.height=%d", val.APIAddress, txRes.Height),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(fmt.Sprintf("Case %s", tc.desc), func() {
|
||||
txJSON, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
|
||||
var errResp rest.ErrorResponse
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(txJSON, &errResp))
|
||||
|
||||
s.Require().Contains(errResp.Error, errMsg)
|
||||
})
|
||||
}
|
||||
|
||||
// try fetching the txn using gRPC req, it will fetch info since it has proto codec.
|
||||
grpcJSON, err := rest.GetRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, txRes.TxHash))
|
||||
s.Require().NoError(err)
|
||||
|
||||
var getTxRes txtypes.GetTxResponse
|
||||
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(grpcJSON, &getTxRes))
|
||||
s.Require().Equal(getTxRes.Tx.Body.Memo, "foobar")
|
||||
|
||||
// generate broadcast only txn.
|
||||
args = append(args, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
|
||||
out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txFile := testutil.WriteToNewTempFile(s.T(), string(out.Bytes()))
|
||||
txFileName := txFile.Name()
|
||||
|
||||
// encode the generated txn.
|
||||
out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.GetEncodeCommand(), []string{txFileName})
|
||||
s.Require().NoError(err)
|
||||
|
||||
bz, err := val.ClientCtx.LegacyAmino.MarshalJSON(authrest.DecodeReq{Tx: string(out.Bytes())})
|
||||
s.Require().NoError(err)
|
||||
|
||||
// try to decode the txn using legacy rest, it fails.
|
||||
res, err := rest.PostRequest(fmt.Sprintf("%s/txs/decode", val.APIAddress), "application/json", bz)
|
||||
s.Require().NoError(err)
|
||||
|
||||
var errResp rest.ErrorResponse
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(res, &errResp))
|
||||
s.Require().Contains(errResp.Error, errMsg)
|
||||
}
|
||||
|
||||
// TestLegacyMultiSig creates a legacy multisig transaction, and makes sure
|
||||
// we can query it via the legacy REST endpoint.
|
||||
// ref: https://github.com/cosmos/cosmos-sdk/issues/8679
|
||||
func (s *IntegrationTestSuite) TestLegacyMultisig() {
|
||||
val1 := *s.network.Validators[0]
|
||||
|
||||
// Generate 2 accounts and a multisig.
|
||||
account1, err := val1.ClientCtx.Keyring.Key("newAccount1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
account2, err := val1.ClientCtx.Keyring.Key("newAccount2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
multisigInfo, err := val1.ClientCtx.Keyring.Key("multi")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Send coins from validator to multisig.
|
||||
sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 1000)
|
||||
_, err = bankcli.MsgSendExec(
|
||||
val1.ClientCtx,
|
||||
val1.Address,
|
||||
multisigInfo.GetAddress(),
|
||||
sdk.NewCoins(sendTokens),
|
||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
||||
fmt.Sprintf("--gas=%d", flags.DefaultGasLimit),
|
||||
)
|
||||
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
|
||||
// Generate multisig transaction to a random address.
|
||||
_, _, recipient := testdata.KeyTestPubAddr()
|
||||
multiGeneratedTx, err := bankcli.MsgSendExec(
|
||||
val1.ClientCtx,
|
||||
multisigInfo.GetAddress(),
|
||||
recipient,
|
||||
sdk.NewCoins(
|
||||
sdk.NewInt64Coin(s.cfg.BondDenom, 5),
|
||||
),
|
||||
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
||||
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Save tx to file
|
||||
multiGeneratedTxFile := testutil.WriteToNewTempFile(s.T(), multiGeneratedTx.String())
|
||||
|
||||
// Sign with account1
|
||||
val1.ClientCtx.HomeDir = strings.Replace(val1.ClientCtx.HomeDir, "simd", "simcli", 1)
|
||||
account1Signature, err := authtest.TxSignExec(val1.ClientCtx, account1.GetAddress(), multiGeneratedTxFile.Name(), "--multisig", multisigInfo.GetAddress().String())
|
||||
s.Require().NoError(err)
|
||||
|
||||
sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String())
|
||||
|
||||
// Sign with account1
|
||||
account2Signature, err := authtest.TxSignExec(val1.ClientCtx, account2.GetAddress(), multiGeneratedTxFile.Name(), "--multisig", multisigInfo.GetAddress().String())
|
||||
s.Require().NoError(err)
|
||||
|
||||
sign2File := testutil.WriteToNewTempFile(s.T(), account2Signature.String())
|
||||
|
||||
// Does not work in offline mode.
|
||||
_, err = authtest.TxMultiSignExec(val1.ClientCtx, multisigInfo.GetName(), multiGeneratedTxFile.Name(), "--offline", sign1File.Name(), sign2File.Name())
|
||||
s.Require().EqualError(err, fmt.Sprintf("couldn't verify signature for address %s", account1.GetAddress()))
|
||||
|
||||
val1.ClientCtx.Offline = false
|
||||
multiSigWith2Signatures, err := authtest.TxMultiSignExec(val1.ClientCtx, multisigInfo.GetName(), multiGeneratedTxFile.Name(), sign1File.Name(), sign2File.Name())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := testutil.WriteToNewTempFile(s.T(), multiSigWith2Signatures.String())
|
||||
|
||||
_, err = authtest.TxValidateSignaturesExec(val1.ClientCtx, signedTxFile.Name())
|
||||
s.Require().NoError(err)
|
||||
|
||||
val1.ClientCtx.BroadcastMode = flags.BroadcastBlock
|
||||
out, err := authtest.TxBroadcastExec(val1.ClientCtx, signedTxFile.Name())
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
|
||||
var txRes sdk.TxResponse
|
||||
err = val1.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(uint32(0), txRes.Code)
|
||||
|
||||
s.testQueryTx(txRes.Height, txRes.TxHash, recipient.String())
|
||||
}
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(IntegrationTestSuite))
|
||||
}
|
||||
@ -28,7 +28,6 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,118 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
// QueryBalancesRequestHandlerFn returns a REST handler that queries for all
|
||||
// account balances or a specific balance by denomination.
|
||||
func QueryBalancesRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bech32addr := vars["address"]
|
||||
|
||||
addr, err := sdk.AccAddressFromBech32(bech32addr)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
params interface{}
|
||||
route string
|
||||
)
|
||||
|
||||
denom := r.FormValue("denom")
|
||||
if denom == "" {
|
||||
params = types.NewQueryAllBalancesRequest(addr, nil)
|
||||
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllBalances)
|
||||
} else {
|
||||
params = types.NewQueryBalanceRequest(addr, denom)
|
||||
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance)
|
||||
}
|
||||
|
||||
bz, err := ctx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := ctx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = ctx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, ctx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query the total supply of coins
|
||||
func totalSupplyHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryTotalSupplyParams(page, limit)
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTotalSupply), bz)
|
||||
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query the supply of a single denom
|
||||
func supplyOfHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
denom := mux.Vars(r)["denom"]
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQuerySupplyOfParams(denom)
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySupplyOf), bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,199 +0,0 @@
|
||||
// +build norace
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/query"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
type IntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
cfg network.Config
|
||||
network *network.Network
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) SetupSuite() {
|
||||
s.T().Log("setting up integration test suite")
|
||||
|
||||
cfg := network.DefaultConfig()
|
||||
genesisState := cfg.GenesisState
|
||||
cfg.NumValidators = 1
|
||||
|
||||
var bankGenesis types.GenesisState
|
||||
s.Require().NoError(cfg.Codec.UnmarshalJSON(genesisState[types.ModuleName], &bankGenesis))
|
||||
|
||||
bankGenesis.DenomMetadata = []types.Metadata{
|
||||
{
|
||||
Name: "Cosmos Hub Atom",
|
||||
Symbol: "ATOM",
|
||||
Description: "The native staking token of the Cosmos Hub.",
|
||||
DenomUnits: []*types.DenomUnit{
|
||||
{
|
||||
Denom: "uatom",
|
||||
Exponent: 0,
|
||||
Aliases: []string{"microatom"},
|
||||
},
|
||||
{
|
||||
Denom: "atom",
|
||||
Exponent: 6,
|
||||
Aliases: []string{"ATOM"},
|
||||
},
|
||||
},
|
||||
Base: "uatom",
|
||||
Display: "atom",
|
||||
},
|
||||
}
|
||||
|
||||
bankGenesisBz, err := cfg.Codec.MarshalJSON(&bankGenesis)
|
||||
s.Require().NoError(err)
|
||||
genesisState[types.ModuleName] = bankGenesisBz
|
||||
cfg.GenesisState = genesisState
|
||||
s.cfg = cfg
|
||||
|
||||
s.network, err = network.New(s.T(), s.T().TempDir(), cfg)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = s.network.WaitForHeight(2)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||
s.T().Log("tearing down integration test suite")
|
||||
s.network.Cleanup()
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestQueryBalancesRequestHandlerFn() {
|
||||
val := s.network.Validators[0]
|
||||
baseURL := val.APIAddress
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
expHeight int64
|
||||
respType fmt.Stringer
|
||||
expected fmt.Stringer
|
||||
}{
|
||||
{
|
||||
"total account balance",
|
||||
fmt.Sprintf("%s/bank/balances/%s", baseURL, val.Address),
|
||||
-1,
|
||||
&sdk.Coins{},
|
||||
sdk.NewCoins(
|
||||
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), s.cfg.AccountTokens),
|
||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Sub(s.cfg.BondedTokens)),
|
||||
),
|
||||
},
|
||||
{
|
||||
"total account balance with height",
|
||||
fmt.Sprintf("%s/bank/balances/%s?height=1", baseURL, val.Address),
|
||||
1,
|
||||
&sdk.Coins{},
|
||||
sdk.NewCoins(
|
||||
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), s.cfg.AccountTokens),
|
||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Sub(s.cfg.BondedTokens)),
|
||||
),
|
||||
},
|
||||
{
|
||||
"total account balance of a specific denom",
|
||||
fmt.Sprintf("%s/bank/balances/%s?denom=%s", baseURL, val.Address, s.cfg.BondDenom),
|
||||
-1,
|
||||
&sdk.Coin{},
|
||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Sub(s.cfg.BondedTokens)),
|
||||
},
|
||||
{
|
||||
"total account balance of a bogus denom",
|
||||
fmt.Sprintf("%s/bank/balances/%s?denom=foobar", baseURL, val.Address),
|
||||
-1,
|
||||
&sdk.Coin{},
|
||||
sdk.NewCoin("foobar", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
s.Run(tc.name, func() {
|
||||
respJSON, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
|
||||
var resp = rest.ResponseWithHeight{}
|
||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check height.
|
||||
if tc.expHeight >= 0 {
|
||||
s.Require().Equal(resp.Height, tc.expHeight)
|
||||
} else {
|
||||
// To avoid flakiness, just test that height is positive.
|
||||
s.Require().Greater(resp.Height, int64(0))
|
||||
}
|
||||
|
||||
// Check result.
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, tc.respType))
|
||||
s.Require().Equal(tc.expected.String(), tc.respType.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestTotalSupplyHandlerFn() {
|
||||
val := s.network.Validators[0]
|
||||
baseURL := val.APIAddress
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
respType fmt.Stringer
|
||||
expected fmt.Stringer
|
||||
}{
|
||||
{
|
||||
"total supply",
|
||||
fmt.Sprintf("%s/bank/total?height=1", baseURL),
|
||||
&types.QueryTotalSupplyResponse{},
|
||||
&types.QueryTotalSupplyResponse{
|
||||
Supply: sdk.NewCoins(
|
||||
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), s.cfg.AccountTokens),
|
||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(10))),
|
||||
),
|
||||
Pagination: &query.PageResponse{Total: 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
"total supply of a specific denom",
|
||||
fmt.Sprintf("%s/bank/total/%s?height=1", baseURL, s.cfg.BondDenom),
|
||||
&sdk.Coin{},
|
||||
sdk.NewCoin(s.cfg.BondDenom, s.cfg.StakingTokens.Add(sdk.NewInt(10))),
|
||||
},
|
||||
{
|
||||
"total supply of a bogus denom",
|
||||
fmt.Sprintf("%s/bank/total/foobar?height=1", baseURL),
|
||||
&sdk.Coin{},
|
||||
sdk.NewCoin("foobar", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
s.Run(tc.name, func() {
|
||||
resp, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
|
||||
bz, err := rest.ParseResponseWithHeight(val.ClientCtx.LegacyAmino, resp)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(bz, tc.respType))
|
||||
s.Require().Equal(tc.expected.String(), tc.respType.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(IntegrationTestSuite))
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
)
|
||||
|
||||
// RegisterHandlers registers all x/bank transaction and query HTTP REST handlers
|
||||
// on the provided mux router.
|
||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
r.HandleFunc("/bank/accounts/{address}/transfers", NewSendRequestHandlerFn(clientCtx)).Methods("POST")
|
||||
r.HandleFunc("/bank/balances/{address}", QueryBalancesRequestHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/bank/total", totalSupplyHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/bank/total/{denom}", supplyOfHandlerFn(clientCtx)).Methods("GET")
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
// SendReq defines the properties of a send request's body.
|
||||
type SendReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||
}
|
||||
|
||||
// NewSendRequestHandlerFn returns an HTTP REST handler for creating a MsgSend
|
||||
// transaction.
|
||||
func NewSendRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bech32Addr := vars["address"]
|
||||
|
||||
toAddr, err := sdk.AccAddressFromBech32(bech32Addr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
var req SendReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgSend(fromAddr, toAddr, req.Amount)
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
@ -1,107 +0,0 @@
|
||||
// +build norace
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
bankrest "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
)
|
||||
|
||||
func (s *IntegrationTestSuite) TestCoinSend() {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
account, err := getAccountInfo(val)
|
||||
s.Require().NoError(err)
|
||||
|
||||
sendReq := generateSendReq(
|
||||
account,
|
||||
types.Coins{types.NewCoin(s.cfg.BondDenom, types.TokensFromConsensusPower(1, sdk.DefaultPowerReduction))},
|
||||
)
|
||||
|
||||
stdTx, err := submitSendReq(val, sendReq)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Nil(stdTx.Signatures)
|
||||
s.Require().Equal([]types.Msg{
|
||||
&banktypes.MsgSend{
|
||||
FromAddress: account.GetAddress().String(),
|
||||
ToAddress: account.GetAddress().String(),
|
||||
Amount: sendReq.Amount,
|
||||
},
|
||||
}, stdTx.GetMsgs())
|
||||
}
|
||||
|
||||
func submitSendReq(val *network.Validator, req bankrest.SendReq) (legacytx.StdTx, error) {
|
||||
url := fmt.Sprintf("%s/bank/accounts/%s/transfers", val.APIAddress, val.Address)
|
||||
|
||||
// NOTE: this uses amino explicitly, don't migrate it!
|
||||
bz, err := val.ClientCtx.LegacyAmino.MarshalJSON(req)
|
||||
if err != nil {
|
||||
return legacytx.StdTx{}, errors.Wrap(err, "error encoding SendReq to json")
|
||||
}
|
||||
|
||||
res, err := rest.PostRequest(url, "application/json", bz)
|
||||
if err != nil {
|
||||
return legacytx.StdTx{}, err
|
||||
}
|
||||
|
||||
var tx legacytx.StdTx
|
||||
// NOTE: this uses amino explicitly, don't migrate it!
|
||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(res, &tx)
|
||||
if err != nil {
|
||||
return legacytx.StdTx{}, errors.Wrap(err, "error unmarshaling to StdTx SendReq response")
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func generateSendReq(from authtypes.AccountI, amount types.Coins) bankrest.SendReq {
|
||||
baseReq := rest.NewBaseReq(
|
||||
from.GetAddress().String(),
|
||||
"someMemo",
|
||||
"some-id",
|
||||
"10000",
|
||||
fmt.Sprintf("%f", 1.0),
|
||||
from.GetAccountNumber(),
|
||||
from.GetSequence(),
|
||||
types.NewCoins(),
|
||||
nil,
|
||||
false,
|
||||
)
|
||||
|
||||
return bankrest.SendReq{
|
||||
BaseReq: baseReq,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
func getAccountInfo(val *network.Validator) (authtypes.AccountI, error) {
|
||||
url := fmt.Sprintf("%s/auth/accounts/%s", val.APIAddress, val.Address)
|
||||
|
||||
resp, err := rest.GetRequest(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bz, err := rest.ParseResponseWithHeight(val.ClientCtx.LegacyAmino, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var acc authtypes.AccountI
|
||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(bz, &acc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return acc, nil
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
// +build norace
|
||||
|
||||
package 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},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
)
|
||||
|
||||
@ -1,309 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/common"
|
||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
// Get the total rewards balance from all delegations
|
||||
r.HandleFunc(
|
||||
"/distribution/delegators/{delegatorAddr}/rewards",
|
||||
delegatorRewardsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Query a delegation reward
|
||||
r.HandleFunc(
|
||||
"/distribution/delegators/{delegatorAddr}/rewards/{validatorAddr}",
|
||||
delegationRewardsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get the rewards withdrawal address
|
||||
r.HandleFunc(
|
||||
"/distribution/delegators/{delegatorAddr}/withdraw_address",
|
||||
delegatorWithdrawalAddrHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Validator distribution information
|
||||
r.HandleFunc(
|
||||
"/distribution/validators/{validatorAddr}",
|
||||
validatorInfoHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Commission and self-delegation rewards of a single a validator
|
||||
r.HandleFunc(
|
||||
"/distribution/validators/{validatorAddr}/rewards",
|
||||
validatorRewardsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Outstanding rewards of a single validator
|
||||
r.HandleFunc(
|
||||
"/distribution/validators/{validatorAddr}/outstanding_rewards",
|
||||
outstandingRewardsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get the current distribution parameter values
|
||||
r.HandleFunc(
|
||||
"/distribution/parameters",
|
||||
paramsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get the amount held in the community pool
|
||||
r.HandleFunc(
|
||||
"/distribution/community_pool",
|
||||
communityPoolHandler(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
}
|
||||
|
||||
// HTTP request handler to query the total rewards balance from all delegations
|
||||
func delegatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
delegatorAddr, ok := checkDelegatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryDelegatorParams(delegatorAddr)
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal params: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorTotalRewards)
|
||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query a delegation rewards
|
||||
func delegationRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
delAddr := mux.Vars(r)["delegatorAddr"]
|
||||
valAddr := mux.Vars(r)["validatorAddr"]
|
||||
|
||||
// query for rewards from a particular delegation
|
||||
res, height, ok := checkResponseQueryDelegationRewards(w, clientCtx, delAddr, valAddr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query a delegation rewards
|
||||
func delegatorWithdrawalAddrHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
delegatorAddr, ok := checkDelegatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
bz := clientCtx.LegacyAmino.MustMarshalJSON(types.NewQueryDelegatorWithdrawAddrParams(delegatorAddr))
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/withdraw_addr", types.QuerierRoute), bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatorDistInfo defines the properties of
|
||||
// validator distribution information response.
|
||||
type ValidatorDistInfo struct {
|
||||
OperatorAddress sdk.AccAddress `json:"operator_address" yaml:"operator_address"`
|
||||
SelfBondRewards sdk.DecCoins `json:"self_bond_rewards" yaml:"self_bond_rewards"`
|
||||
ValidatorCommission types.ValidatorAccumulatedCommission `json:"val_commission" yaml:"val_commission"`
|
||||
}
|
||||
|
||||
// NewValidatorDistInfo creates a new instance of ValidatorDistInfo.
|
||||
func NewValidatorDistInfo(operatorAddr sdk.AccAddress, rewards sdk.DecCoins,
|
||||
commission types.ValidatorAccumulatedCommission) ValidatorDistInfo {
|
||||
return ValidatorDistInfo{
|
||||
OperatorAddress: operatorAddr,
|
||||
SelfBondRewards: rewards,
|
||||
ValidatorCommission: commission,
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query validator's distribution information
|
||||
func validatorInfoHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
valAddr, ok := checkValidatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// query commission
|
||||
bz, err := common.QueryValidatorCommission(clientCtx, valAddr)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
var commission types.ValidatorAccumulatedCommission
|
||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(bz, &commission)) {
|
||||
return
|
||||
}
|
||||
|
||||
// self bond rewards
|
||||
delAddr := sdk.AccAddress(valAddr)
|
||||
bz, height, ok := checkResponseQueryDelegationRewards(w, clientCtx, delAddr.String(), valAddr.String())
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var rewards sdk.DecCoins
|
||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(bz, &rewards)) {
|
||||
return
|
||||
}
|
||||
|
||||
bz, err = clientCtx.LegacyAmino.MarshalJSON(NewValidatorDistInfo(delAddr, rewards, commission))
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, bz)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query validator's commission and self-delegation rewards
|
||||
func validatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
valAddr := mux.Vars(r)["validatorAddr"]
|
||||
validatorAddr, ok := checkValidatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
delAddr := sdk.AccAddress(validatorAddr).String()
|
||||
bz, height, ok := checkResponseQueryDelegationRewards(w, clientCtx, delAddr, valAddr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, bz)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query the distribution params values
|
||||
func paramsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParams)
|
||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func communityPoolHandler(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/community_pool", types.QuerierRoute), nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
var result sdk.DecCoins
|
||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &result)) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, result)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query the outstanding rewards
|
||||
func outstandingRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
validatorAddr, ok := checkValidatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
bin := clientCtx.LegacyAmino.MustMarshalJSON(types.NewQueryValidatorOutstandingRewardsParams(validatorAddr))
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/validator_outstanding_rewards", types.QuerierRoute), bin)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func checkResponseQueryDelegationRewards(
|
||||
w http.ResponseWriter, clientCtx client.Context, delAddr, valAddr string,
|
||||
) (res []byte, height int64, ok bool) {
|
||||
|
||||
res, height, err := common.QueryDelegationRewards(clientCtx, delAddr, valAddr)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
return res, height, true
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
||||
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
)
|
||||
|
||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
||||
r := clientrest.WithHTTPDeprecationHeaders(rtr)
|
||||
|
||||
registerQueryRoutes(clientCtx, r)
|
||||
registerTxHandlers(clientCtx, r)
|
||||
}
|
||||
|
||||
// TODO add proto compatible Handler after x/gov migration
|
||||
// ProposalRESTHandler returns a ProposalRESTHandler that exposes the community pool spend REST handler with a given sub-route.
|
||||
func ProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
||||
return govrest.ProposalRESTHandler{
|
||||
SubRoute: "community_pool_spend",
|
||||
Handler: postProposalHandlerFn(clientCtx),
|
||||
}
|
||||
}
|
||||
|
||||
func postProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req CommunityPoolSpendProposalReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
content := types.NewCommunityPoolSpendProposal(req.Title, req.Description, req.Recipient, req.Amount)
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
@ -1,222 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/distribution/client/common"
|
||||
"github.com/cosmos/cosmos-sdk/x/distribution/types"
|
||||
)
|
||||
|
||||
type (
|
||||
withdrawRewardsReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
}
|
||||
|
||||
setWithdrawalAddrReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
WithdrawAddress sdk.AccAddress `json:"withdraw_address" yaml:"withdraw_address"`
|
||||
}
|
||||
|
||||
fundCommunityPoolReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||
}
|
||||
)
|
||||
|
||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router) {
|
||||
// Withdraw all delegator rewards
|
||||
r.HandleFunc(
|
||||
"/distribution/delegators/{delegatorAddr}/rewards",
|
||||
newWithdrawDelegatorRewardsHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
|
||||
// Withdraw delegation rewards
|
||||
r.HandleFunc(
|
||||
"/distribution/delegators/{delegatorAddr}/rewards/{validatorAddr}",
|
||||
newWithdrawDelegationRewardsHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
|
||||
// Replace the rewards withdrawal address
|
||||
r.HandleFunc(
|
||||
"/distribution/delegators/{delegatorAddr}/withdraw_address",
|
||||
newSetDelegatorWithdrawalAddrHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
|
||||
// Withdraw validator rewards and commission
|
||||
r.HandleFunc(
|
||||
"/distribution/validators/{validatorAddr}/rewards",
|
||||
newWithdrawValidatorRewardsHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
|
||||
// Fund the community pool
|
||||
r.HandleFunc(
|
||||
"/distribution/community_pool",
|
||||
newFundCommunityPoolHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
}
|
||||
|
||||
func newWithdrawDelegatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req withdrawRewardsReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
// read and validate URL's variables
|
||||
delAddr, ok := checkDelegatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
msgs, err := common.WithdrawAllDelegatorRewards(clientCtx, delAddr)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msgs...)
|
||||
}
|
||||
}
|
||||
|
||||
func newWithdrawDelegationRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req withdrawRewardsReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
// read and validate URL's variables
|
||||
delAddr, ok := checkDelegatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
valAddr, ok := checkValidatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgWithdrawDelegatorReward(delAddr, valAddr)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func newSetDelegatorWithdrawalAddrHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req setWithdrawalAddrReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
// read and validate URL's variables
|
||||
delAddr, ok := checkDelegatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgSetWithdrawAddress(delAddr, req.WithdrawAddress)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func newWithdrawValidatorRewardsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req withdrawRewardsReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
// read and validate URL's variable
|
||||
valAddr, ok := checkValidatorAddressVar(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// prepare multi-message transaction
|
||||
msgs, err := common.WithdrawValidatorRewardsAndCommission(valAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msgs...)
|
||||
}
|
||||
}
|
||||
|
||||
func newFundCommunityPoolHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req fundCommunityPoolReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgFundCommunityPool(req.Amount, fromAddr)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Auxiliary
|
||||
|
||||
func checkDelegatorAddressVar(w http.ResponseWriter, r *http.Request) (sdk.AccAddress, bool) {
|
||||
addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["delegatorAddr"])
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return addr, true
|
||||
}
|
||||
|
||||
func checkValidatorAddressVar(w http.ResponseWriter, r *http.Request) (sdk.ValAddress, bool) {
|
||||
addr, err := sdk.ValAddressFromBech32(mux.Vars(r)["validatorAddr"])
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return addr, true
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
)
|
||||
|
||||
type (
|
||||
// CommunityPoolSpendProposalReq defines a community pool spend proposal request body.
|
||||
CommunityPoolSpendProposalReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Recipient sdk.AccAddress `json:"recipient" yaml:"recipient"`
|
||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"`
|
||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
||||
}
|
||||
)
|
||||
@ -15,3 +15,7 @@ func TestIntegrationTestSuite(t *testing.T) {
|
||||
cfg.NumValidators = 1
|
||||
suite.Run(t, NewIntegrationTestSuite(cfg))
|
||||
}
|
||||
|
||||
func TestGRPCQueryTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(GRPCQueryTestSuite))
|
||||
}
|
||||
|
||||
@ -1,29 +1,28 @@
|
||||
package rest_test
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"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))
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/evidence/types"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc(
|
||||
fmt.Sprintf("/evidence/{%s}", RestParamEvidenceHash),
|
||||
queryEvidenceHandler(clientCtx),
|
||||
).Methods(MethodGet)
|
||||
|
||||
r.HandleFunc(
|
||||
"/evidence",
|
||||
queryAllEvidenceHandler(clientCtx),
|
||||
).Methods(MethodGet)
|
||||
}
|
||||
|
||||
func queryEvidenceHandler(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
evidenceHash := vars[RestParamEvidenceHash]
|
||||
|
||||
if strings.TrimSpace(evidenceHash) == "" {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "evidence hash required but not specified")
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
decodedHash, err := hex.DecodeString(evidenceHash)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid evidence hash")
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryEvidenceRequest(decodedHash)
|
||||
bz, err := clientCtx.Codec.MarshalJSON(params)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryEvidence)
|
||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryAllEvidenceHandler(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryAllEvidenceParams(page, limit)
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllEvidence)
|
||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// REST query and parameter values
|
||||
const (
|
||||
RestParamEvidenceHash = "evidence-hash"
|
||||
|
||||
MethodGet = "GET"
|
||||
)
|
||||
|
||||
// EvidenceRESTHandler defines a REST service evidence handler implemented in
|
||||
// another module. The sub-route is mounted on the evidence REST handler.
|
||||
type EvidenceRESTHandler struct {
|
||||
SubRoute string
|
||||
Handler func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all Evidence submission handlers for the evidence module's
|
||||
// REST service handler.
|
||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router, handlers []EvidenceRESTHandler) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
|
||||
registerQueryRoutes(clientCtx, r)
|
||||
registerTxRoutes(clientCtx, r, handlers)
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func registerTxRoutes(clientCtx client.Context, r *mux.Router, handlers []EvidenceRESTHandler) {
|
||||
// TODO: Register tx handlers.
|
||||
}
|
||||
@ -6,9 +6,9 @@ import (
|
||||
"fmt"
|
||||
"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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||
)
|
||||
|
||||
// QueryGenesisTxs writes the genesis transactions to the response if no error
|
||||
// occurs.
|
||||
func QueryGenesisTxs(clientCtx client.Context, w http.ResponseWriter) {
|
||||
resultGenesis, err := clientCtx.Client.Genesis(context.Background())
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(
|
||||
w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("failed to retrieve genesis from client: %s", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
appState, err := types.GenesisStateFromGenDoc(*resultGenesis.Genesis)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(
|
||||
w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("failed to decode genesis doc: %s", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
genState := types.GetGenesisStateFromAppState(clientCtx.Codec, appState)
|
||||
genTxs := make([]sdk.Tx, len(genState.GenTxs))
|
||||
for i, tx := range genState.GenTxs {
|
||||
err := clientCtx.LegacyAmino.UnmarshalJSON(tx, &genTxs[i])
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(
|
||||
w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("failed to decode genesis transaction: %s", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, genTxs)
|
||||
}
|
||||
@ -54,6 +54,7 @@ func (b AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, txEncodingConfig cl
|
||||
}
|
||||
|
||||
// RegisterRESTRoutes registers the REST routes for the genutil module.
|
||||
// Deprecated: RegisterRESTRoutes is deprecated.
|
||||
func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {}
|
||||
|
||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the genutil module.
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,473 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc(fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), queryParamsHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID), queryProposerHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(clientCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(clientCtx)).Methods("GET")
|
||||
}
|
||||
|
||||
func queryParamsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
paramType := vars[RestParamsType]
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil)
|
||||
if rest.CheckNotFoundError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
err := errors.New("proposalId required but not specified")
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryProposalParams(proposalID)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryDepositsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryProposalParams(proposalID)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
var proposal types.Proposal
|
||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &proposal)) {
|
||||
return
|
||||
}
|
||||
|
||||
// For inactive proposals we must query the txs directly to get the deposits
|
||||
// as they're no longer in state.
|
||||
propStatus := proposal.Status
|
||||
if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
|
||||
res, err = gcutils.QueryDepositsByTxQuery(clientCtx, params)
|
||||
} else {
|
||||
res, _, err = clientCtx.QueryWithData("custom/gov/deposits", bz)
|
||||
}
|
||||
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryProposerHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := gcutils.QueryProposerByTxQuery(clientCtx, proposalID)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryDepositHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
bechDepositorAddr := vars[RestDepositor]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
err := errors.New("proposalId required but not specified")
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if len(bechDepositorAddr) == 0 {
|
||||
err := errors.New("depositor address required but not specified")
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryDepositParams(proposalID, depositorAddr)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err := clientCtx.QueryWithData("custom/gov/deposit", bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
var deposit types.Deposit
|
||||
if rest.CheckBadRequestError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &deposit)) {
|
||||
return
|
||||
}
|
||||
|
||||
// For an empty deposit, either the proposal does not exist or is inactive in
|
||||
// which case the deposit would be removed from state and should be queried
|
||||
// for directly via a txs query.
|
||||
if deposit.Empty() {
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(types.NewQueryProposalParams(proposalID))
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err = clientCtx.QueryWithData("custom/gov/proposal", bz)
|
||||
if err != nil || len(res) == 0 {
|
||||
err := fmt.Errorf("proposalID %d does not exist", proposalID)
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res, err = gcutils.QueryDepositByTxQuery(clientCtx, params)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryVoteHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
bechVoterAddr := vars[RestVoter]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
err := errors.New("proposalId required but not specified")
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if len(bechVoterAddr) == 0 {
|
||||
err := errors.New("voter address required but not specified")
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryVoteParams(proposalID, voterAddr)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err := clientCtx.QueryWithData("custom/gov/vote", bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
var vote types.Vote
|
||||
if rest.CheckBadRequestError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &vote)) {
|
||||
return
|
||||
}
|
||||
|
||||
// For an empty vote, either the proposal does not exist or is inactive in
|
||||
// which case the vote would be removed from state and should be queried for
|
||||
// directly via a txs query.
|
||||
if vote.Empty() {
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(types.NewQueryProposalParams(proposalID))
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err = clientCtx.QueryWithData("custom/gov/proposal", bz)
|
||||
if err != nil || len(res) == 0 {
|
||||
err := fmt.Errorf("proposalID %d does not exist", proposalID)
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res, err = gcutils.QueryVoteByTxQuery(clientCtx, params)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: Split this functionality into helper functions to remove the above
|
||||
func queryVotesOnProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgs(r)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
err := errors.New("proposalId required but not specified")
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(types.NewQueryProposalParams(proposalID))
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
var proposal types.Proposal
|
||||
if rest.CheckInternalServerError(w, clientCtx.LegacyAmino.UnmarshalJSON(res, &proposal)) {
|
||||
return
|
||||
}
|
||||
|
||||
// For inactive proposals we must query the txs directly to get the votes
|
||||
// as they're no longer in state.
|
||||
params := types.NewQueryProposalVotesParams(proposalID, page, limit)
|
||||
|
||||
propStatus := proposal.Status
|
||||
if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
|
||||
res, err = gcutils.QueryVotesByTxQuery(clientCtx, params)
|
||||
} else {
|
||||
bz, err = clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err = clientCtx.QueryWithData("custom/gov/votes", bz)
|
||||
}
|
||||
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query list of governance proposals
|
||||
func queryProposalsWithParameterFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
voterAddr sdk.AccAddress
|
||||
depositorAddr sdk.AccAddress
|
||||
proposalStatus types.ProposalStatus
|
||||
)
|
||||
|
||||
if v := r.URL.Query().Get(RestVoter); len(v) != 0 {
|
||||
voterAddr, err = sdk.AccAddressFromBech32(v)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if v := r.URL.Query().Get(RestDepositor); len(v) != 0 {
|
||||
depositorAddr, err = sdk.AccAddressFromBech32(v)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if v := r.URL.Query().Get(RestProposalStatus); len(v) != 0 {
|
||||
proposalStatus, err = types.ProposalStatusFromString(gcutils.NormalizeProposalStatus(v))
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
params := types.NewQueryProposalsParams(page, limit, proposalStatus, voterAddr, depositorAddr)
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryProposals)
|
||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: Split this functionality into helper functions to remove the above
|
||||
func queryTallyOnProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
err := errors.New("proposalId required but not specified")
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryProposalParams(proposalID)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData("custom/gov/tally", bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
)
|
||||
|
||||
// REST Variable names
|
||||
const (
|
||||
RestParamsType = "type"
|
||||
RestProposalID = "proposal-id"
|
||||
RestDepositor = "depositor"
|
||||
RestVoter = "voter"
|
||||
RestProposalStatus = "status"
|
||||
RestNumLimit = "limit"
|
||||
)
|
||||
|
||||
// ProposalRESTHandler defines a REST handler implemented in another module. The
|
||||
// sub-route is mounted on the governance REST handler.
|
||||
type ProposalRESTHandler struct {
|
||||
SubRoute string
|
||||
Handler func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router, phs []ProposalRESTHandler) {
|
||||
r := clientrest.WithHTTPDeprecationHeaders(rtr)
|
||||
registerQueryRoutes(clientCtx, r)
|
||||
registerTxHandlers(clientCtx, r, phs)
|
||||
}
|
||||
|
||||
// PostProposalReq defines the properties of a proposal request's body.
|
||||
type PostProposalReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Title string `json:"title" yaml:"title"` // Title of the proposal
|
||||
Description string `json:"description" yaml:"description"` // Description of the proposal
|
||||
ProposalType string `json:"proposal_type" yaml:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal }
|
||||
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"` // Address of the proposer
|
||||
InitialDeposit sdk.Coins `json:"initial_deposit" yaml:"initial_deposit"` // Coins to add to the proposal's deposit
|
||||
}
|
||||
|
||||
// DepositReq defines the properties of a deposit request's body.
|
||||
type DepositReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor
|
||||
Amount sdk.Coins `json:"amount" yaml:"amount"` // Coins to add to the proposal's deposit
|
||||
}
|
||||
|
||||
// VoteReq defines the properties of a vote request's body.
|
||||
type VoteReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Voter sdk.AccAddress `json:"voter" yaml:"voter"` // address of the voter
|
||||
Option string `json:"option" yaml:"option"` // option from OptionSet chosen by the voter
|
||||
}
|
||||
@ -1,167 +0,0 @@
|
||||
// +build norace
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
)
|
||||
|
||||
func (s *IntegrationTestSuite) TestLegacyGetAllProposals() {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
numProposals int
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
"get all existing proposals",
|
||||
fmt.Sprintf("%s/gov/proposals", val.APIAddress),
|
||||
3, false, "",
|
||||
},
|
||||
{
|
||||
"get proposals in deposit period",
|
||||
fmt.Sprintf("%s/gov/proposals?status=deposit_period", val.APIAddress),
|
||||
1, false, "",
|
||||
},
|
||||
{
|
||||
"get proposals in voting period",
|
||||
fmt.Sprintf("%s/gov/proposals?status=voting_period", val.APIAddress),
|
||||
2, false, "",
|
||||
},
|
||||
{
|
||||
"wrong status parameter",
|
||||
fmt.Sprintf("%s/gov/proposals?status=invalidstatus", val.APIAddress),
|
||||
0, true, "'invalidstatus' is not a valid proposal status",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
s.Run(tc.name, func() {
|
||||
respJSON, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
|
||||
if tc.expErr {
|
||||
var errResp rest.ErrorResponse
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
||||
} else {
|
||||
var resp = rest.ResponseWithHeight{}
|
||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check results.
|
||||
var proposals types.Proposals
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &proposals))
|
||||
s.Require().Equal(tc.numProposals, len(proposals))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestLegacyGetVote() {
|
||||
val := s.network.Validators[0]
|
||||
voterAddressBech32 := val.Address.String()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
"get non existing proposal",
|
||||
fmt.Sprintf("%s/gov/proposals/%s/votes/%s", val.APIAddress, "10", voterAddressBech32),
|
||||
true, "proposalID 10 does not exist",
|
||||
},
|
||||
{
|
||||
"get proposal with wrong voter address",
|
||||
fmt.Sprintf("%s/gov/proposals/%s/votes/%s", val.APIAddress, "1", "wrongVoterAddress"),
|
||||
true, "decoding bech32 failed: string not all lowercase or all uppercase",
|
||||
},
|
||||
{
|
||||
"get proposal with id",
|
||||
fmt.Sprintf("%s/gov/proposals/%s/votes/%s", val.APIAddress, "1", voterAddressBech32),
|
||||
false, "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
s.Run(tc.name, func() {
|
||||
respJSON, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
|
||||
if tc.expErr {
|
||||
var errResp rest.ErrorResponse
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
||||
|
||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
||||
} else {
|
||||
var resp = rest.ResponseWithHeight{}
|
||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check result is not empty.
|
||||
var vote types.Vote
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &vote))
|
||||
s.Require().Equal(val.Address.String(), vote.Voter)
|
||||
// Note that option is now an int.
|
||||
s.Require().Equal([]types.WeightedVoteOption{{types.VoteOption(1), sdk.NewDec(1)}}, vote.Options)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestLegacyGetVotes() {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
"votes with empty proposal id",
|
||||
fmt.Sprintf("%s/gov/proposals/%s/votes", val.APIAddress, ""),
|
||||
true, "'votes' is not a valid uint64",
|
||||
},
|
||||
{
|
||||
"get votes with valid id",
|
||||
fmt.Sprintf("%s/gov/proposals/%s/votes", val.APIAddress, "1"),
|
||||
false, "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
s.Run(tc.name, func() {
|
||||
respJSON, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
|
||||
if tc.expErr {
|
||||
var errResp rest.ErrorResponse
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
||||
|
||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
||||
} else {
|
||||
var resp = rest.ResponseWithHeight{}
|
||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check result is not empty.
|
||||
var votes []types.Vote
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &votes))
|
||||
s.Require().Greater(len(votes), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
)
|
||||
|
||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router, phs []ProposalRESTHandler) {
|
||||
propSubRtr := r.PathPrefix("/gov/proposals").Subrouter()
|
||||
for _, ph := range phs {
|
||||
propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST")
|
||||
}
|
||||
|
||||
r.HandleFunc("/gov/proposals", newPostProposalHandlerFn(clientCtx)).Methods("POST")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), newDepositHandlerFn(clientCtx)).Methods("POST")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), newVoteHandlerFn(clientCtx)).Methods("POST")
|
||||
}
|
||||
|
||||
func newPostProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req PostProposalReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
proposalType := gcutils.NormalizeProposalType(req.ProposalType)
|
||||
content := types.ContentFromProposalType(req.Title, req.Description, proposalType)
|
||||
|
||||
msg, err := types.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func newDepositHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified")
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req DepositReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := types.NewMsgDeposit(req.Depositor, proposalID, req.Amount)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func newVoteHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[RestProposalID]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified")
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req VoteReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
voteOption, err := types.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option))
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := types.NewMsgVote(req.Voter, proposalID, voteOption)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
@ -1,78 +1,17 @@
|
||||
// +build norace
|
||||
|
||||
package 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))
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/mint/types"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc(
|
||||
"/minting/parameters",
|
||||
queryParamsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
r.HandleFunc(
|
||||
"/minting/inflation",
|
||||
queryInflationHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
r.HandleFunc(
|
||||
"/minting/annual-provisions",
|
||||
queryAnnualProvisionsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
}
|
||||
|
||||
func queryParamsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters)
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryInflationHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryInflation)
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryAnnualProvisionsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAnnualProvisions)
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
)
|
||||
|
||||
// RegisterRoutes registers minting module REST handlers on the provided router.
|
||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
registerQueryRoutes(clientCtx, r)
|
||||
}
|
||||
@ -1,62 +1,17 @@
|
||||
// +build norace
|
||||
|
||||
package 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))
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
|
||||
)
|
||||
|
||||
// ProposalRESTHandler returns a ProposalRESTHandler that exposes the param
|
||||
// change REST handler with a given sub-route.
|
||||
func ProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
||||
return govrest.ProposalRESTHandler{
|
||||
SubRoute: "param_change",
|
||||
Handler: postProposalHandlerFn(clientCtx),
|
||||
}
|
||||
}
|
||||
|
||||
func postProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req paramscutils.ParamChangeProposalReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
content := proposal.NewParameterChangeProposal(req.Title, req.Description, req.Changes.ToParamChanges())
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
@ -1,44 +1,14 @@
|
||||
package rest_test
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"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))
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,111 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/types/bech32/legacybech32" //nolint:staticcheck
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc(
|
||||
"/slashing/validators/{validatorPubKey}/signing_info",
|
||||
signingInfoHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
r.HandleFunc(
|
||||
"/slashing/signing_infos",
|
||||
signingInfoHandlerListFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
r.HandleFunc(
|
||||
"/slashing/parameters",
|
||||
queryParamsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
}
|
||||
|
||||
// Deprecated: http request handler to query signing info
|
||||
func signingInfoHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
pk, err := legacybech32.UnmarshalPubKey(legacybech32.ConsPK, vars["validatorPubKey"])
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.QuerySigningInfoRequest{ConsAddress: pk.Address().String()}
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySigningInfo)
|
||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// http request handler to query signing info
|
||||
func signingInfoHandlerListFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQuerySigningInfosParams(page, limit)
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySigningInfos)
|
||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryParamsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/parameters", types.QuerierRoute)
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(route, nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
)
|
||||
|
||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
|
||||
registerQueryRoutes(clientCtx, r)
|
||||
registerTxHandlers(clientCtx, r)
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing/types"
|
||||
)
|
||||
|
||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc("/slashing/validators/{validatorAddr}/unjail", NewUnjailRequestHandlerFn(clientCtx)).Methods("POST")
|
||||
}
|
||||
|
||||
// Unjail TX body
|
||||
type UnjailReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
}
|
||||
|
||||
// NewUnjailRequestHandlerFn returns an HTTP REST handler for creating a MsgUnjail
|
||||
// transaction.
|
||||
func NewUnjailRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bech32Validator := vars["validatorAddr"]
|
||||
|
||||
var req UnjailReq
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
valAddr, err := sdk.ValAddressFromBech32(bech32Validator)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(fromAddr, valAddr) {
|
||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address")
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgUnjail(valAddr)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
@ -1,48 +1,18 @@
|
||||
package rest_test
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"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))
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -1,400 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
// Get all delegations from a delegator
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/delegations",
|
||||
delegatorDelegationsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get all unbonding delegations from a delegator
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/unbonding_delegations",
|
||||
delegatorUnbondingDelegationsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get all staking txs (i.e msgs) from a delegator
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/txs",
|
||||
delegatorTxsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Query all validators that a delegator is bonded to
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/validators",
|
||||
delegatorValidatorsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Query a validator that a delegator is bonded to
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/validators/{validatorAddr}",
|
||||
delegatorValidatorHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Query a delegation between a delegator and a validator
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/delegations/{validatorAddr}",
|
||||
delegationHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Query all unbonding delegations between a delegator and a validator
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}",
|
||||
unbondingDelegationHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Query redelegations (filters in query params)
|
||||
r.HandleFunc(
|
||||
"/staking/redelegations",
|
||||
redelegationsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get all validators
|
||||
r.HandleFunc(
|
||||
"/staking/validators",
|
||||
validatorsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get a single validator info
|
||||
r.HandleFunc(
|
||||
"/staking/validators/{validatorAddr}",
|
||||
validatorHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get all delegations to a validator
|
||||
r.HandleFunc(
|
||||
"/staking/validators/{validatorAddr}/delegations",
|
||||
validatorDelegationsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get all unbonding delegations from a validator
|
||||
r.HandleFunc(
|
||||
"/staking/validators/{validatorAddr}/unbonding_delegations",
|
||||
validatorUnbondingDelegationsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get HistoricalInfo at a given height
|
||||
r.HandleFunc(
|
||||
"/staking/historical_info/{height}",
|
||||
historicalInfoHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get the current state of the staking pool
|
||||
r.HandleFunc(
|
||||
"/staking/pool",
|
||||
poolHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
|
||||
// Get the current staking parameter values
|
||||
r.HandleFunc(
|
||||
"/staking/parameters",
|
||||
paramsHandlerFn(clientCtx),
|
||||
).Methods("GET")
|
||||
}
|
||||
|
||||
// HTTP request handler to query a delegator delegations
|
||||
func delegatorDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return queryDelegator(clientCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorDelegations))
|
||||
}
|
||||
|
||||
// HTTP request handler to query a delegator unbonding delegations
|
||||
func delegatorUnbondingDelegationsHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
||||
return queryDelegator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorUnbondingDelegations))
|
||||
}
|
||||
|
||||
// HTTP request handler to query all staking txs (msgs) from a delegator
|
||||
func delegatorTxsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var typesQuerySlice []string
|
||||
|
||||
vars := mux.Vars(r)
|
||||
delegatorAddr := vars["delegatorAddr"]
|
||||
|
||||
if _, err := sdk.AccAddressFromBech32(delegatorAddr); rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
typesQuery := r.URL.Query().Get("type")
|
||||
trimmedQuery := strings.TrimSpace(typesQuery)
|
||||
|
||||
if len(trimmedQuery) != 0 {
|
||||
typesQuerySlice = strings.Split(trimmedQuery, " ")
|
||||
}
|
||||
|
||||
noQuery := len(typesQuerySlice) == 0
|
||||
isBondTx := contains(typesQuerySlice, "bond")
|
||||
isUnbondTx := contains(typesQuerySlice, "unbond")
|
||||
isRedTx := contains(typesQuerySlice, "redelegate")
|
||||
|
||||
var (
|
||||
txs []*sdk.SearchTxsResult
|
||||
actions []string
|
||||
)
|
||||
|
||||
// For each case, we search txs for both:
|
||||
// - legacy messages: their Type() is a custom string, e.g. "delegate"
|
||||
// - service Msgs: their Type() is their FQ method name, e.g. "/cosmos.staking.v1beta1.MsgDelegate"
|
||||
// and we combine the results.
|
||||
switch {
|
||||
case isBondTx:
|
||||
actions = append(actions, types.TypeMsgDelegate)
|
||||
|
||||
case isUnbondTx:
|
||||
actions = append(actions, types.TypeMsgUndelegate)
|
||||
|
||||
case isRedTx:
|
||||
actions = append(actions, types.TypeMsgBeginRedelegate)
|
||||
|
||||
case noQuery:
|
||||
actions = append(actions, types.TypeMsgDelegate)
|
||||
actions = append(actions, types.TypeMsgUndelegate)
|
||||
actions = append(actions, types.TypeMsgBeginRedelegate)
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
foundTxs, errQuery := queryTxs(clientCtx, action, delegatorAddr)
|
||||
if rest.CheckInternalServerError(w, errQuery) {
|
||||
return
|
||||
}
|
||||
|
||||
txs = append(txs, foundTxs)
|
||||
}
|
||||
|
||||
res, err := clientCtx.LegacyAmino.MarshalJSON(txs)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponseBare(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query an unbonding-delegation
|
||||
func unbondingDelegationHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
||||
return queryBonds(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryUnbondingDelegation))
|
||||
}
|
||||
|
||||
// HTTP request handler to query redelegations
|
||||
func redelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var params types.QueryRedelegationParams
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
bechDelegatorAddr := r.URL.Query().Get("delegator")
|
||||
bechSrcValidatorAddr := r.URL.Query().Get("validator_from")
|
||||
bechDstValidatorAddr := r.URL.Query().Get("validator_to")
|
||||
|
||||
if len(bechDelegatorAddr) != 0 {
|
||||
delegatorAddr, err := sdk.AccAddressFromBech32(bechDelegatorAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
params.DelegatorAddr = delegatorAddr
|
||||
}
|
||||
|
||||
if len(bechSrcValidatorAddr) != 0 {
|
||||
srcValidatorAddr, err := sdk.ValAddressFromBech32(bechSrcValidatorAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
params.SrcValidatorAddr = srcValidatorAddr
|
||||
}
|
||||
|
||||
if len(bechDstValidatorAddr) != 0 {
|
||||
dstValidatorAddr, err := sdk.ValAddressFromBech32(bechDstValidatorAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
params.DstValidatorAddr = dstValidatorAddr
|
||||
}
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryRedelegations), bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query a delegation
|
||||
func delegationHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return queryBonds(clientCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegation))
|
||||
}
|
||||
|
||||
// HTTP request handler to query all delegator bonded validators
|
||||
func delegatorValidatorsHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
||||
return queryDelegator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorValidators))
|
||||
}
|
||||
|
||||
// HTTP request handler to get information from a currently bonded validator
|
||||
func delegatorValidatorHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
||||
return queryBonds(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryDelegatorValidator))
|
||||
}
|
||||
|
||||
// HTTP request handler to query list of validators
|
||||
func validatorsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
status := r.FormValue("status")
|
||||
// These are query params that were available in =<0.39. We show a nice
|
||||
// error message for this breaking change.
|
||||
if status == "bonded" || status == "unbonding" || status == "unbonded" {
|
||||
err := fmt.Errorf("cosmos sdk v0.40 introduces a breaking change on this endpoint:"+
|
||||
" instead of querying using `?status=%s`, please use `status=BOND_STATUS_%s`. For more"+
|
||||
" info, please see our REST endpoint migration guide at %s", status, strings.ToUpper(status), clientrest.DeprecationURL)
|
||||
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if status == "" {
|
||||
status = types.BondStatusBonded
|
||||
}
|
||||
|
||||
params := types.NewQueryValidatorsParams(page, limit, status)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidators)
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(route, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query the validator information from a given validator address
|
||||
func validatorHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
||||
return queryValidator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidator))
|
||||
}
|
||||
|
||||
// HTTP request handler to query all unbonding delegations from a validator
|
||||
func validatorDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return queryValidator(clientCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidatorDelegations))
|
||||
}
|
||||
|
||||
// HTTP request handler to query all unbonding delegations from a validator
|
||||
func validatorUnbondingDelegationsHandlerFn(cliCtx client.Context) http.HandlerFunc {
|
||||
return queryValidator(cliCtx, fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidatorUnbondingDelegations))
|
||||
}
|
||||
|
||||
// HTTP request handler to query historical info at a given height
|
||||
func historicalInfoHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
heightStr := vars["height"]
|
||||
|
||||
height, err := strconv.ParseInt(heightStr, 10, 64)
|
||||
if err != nil || height < 0 {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Must provide non-negative integer for height: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
params := types.QueryHistoricalInfoRequest{Height: height}
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryHistoricalInfo), bz)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query the pool information
|
||||
func poolHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryPool), nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request handler to query the staking params values
|
||||
func paramsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters), nil)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
)
|
||||
|
||||
func RegisterHandlers(clientCtx client.Context, rtr *mux.Router) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
registerQueryRoutes(clientCtx, r)
|
||||
registerTxHandlers(clientCtx, r)
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
// +build norace
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
func (s *IntegrationTestSuite) TestLegacyGetValidators() {
|
||||
val := s.network.Validators[0]
|
||||
baseURL := val.APIAddress
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
"old status should show error message",
|
||||
fmt.Sprintf("%s/staking/validators?status=bonded", baseURL),
|
||||
true, "cosmos sdk v0.40 introduces a breaking change on this endpoint: instead of" +
|
||||
" querying using `?status=bonded`, please use `status=BOND_STATUS_BONDED`. For more" +
|
||||
" info, please see our REST endpoint migration guide at https://docs.cosmos.network/master/migrations/rest.html",
|
||||
},
|
||||
{
|
||||
"new status should work",
|
||||
fmt.Sprintf("%s/staking/validators?status=BOND_STATUS_BONDED", baseURL),
|
||||
false, "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
s.Run(tc.name, func() {
|
||||
respJSON, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
|
||||
if tc.expErr {
|
||||
var errResp rest.ErrorResponse
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &errResp))
|
||||
|
||||
s.Require().Equal(errResp.Error, tc.expErrMsg)
|
||||
} else {
|
||||
var resp = rest.ResponseWithHeight{}
|
||||
err = val.ClientCtx.LegacyAmino.UnmarshalJSON(respJSON, &resp)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check result is not empty.
|
||||
var validators []types.Validator
|
||||
s.Require().NoError(val.ClientCtx.LegacyAmino.UnmarshalJSON(resp.Result, &validators))
|
||||
s.Require().Greater(len(validators), 0)
|
||||
// While we're at it, also check that the consensus_pubkey is
|
||||
// an Any, and not bech32 anymore.
|
||||
s.Require().Contains(string(resp.Result), "\"consensus_pubkey\": {\n \"type\": \"tendermint/PubKeyEd25519\",")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,149 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
func registerTxHandlers(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/delegations",
|
||||
newPostDelegationsHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/unbonding_delegations",
|
||||
newPostUnbondingDelegationsHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
r.HandleFunc(
|
||||
"/staking/delegators/{delegatorAddr}/redelegations",
|
||||
newPostRedelegationsHandlerFn(clientCtx),
|
||||
).Methods("POST")
|
||||
}
|
||||
|
||||
type (
|
||||
// DelegateRequest defines the properties of a delegation request's body.
|
||||
DelegateRequest struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
DelegatorAddress sdk.AccAddress `json:"delegator_address" yaml:"delegator_address"` // in bech32
|
||||
ValidatorAddress sdk.ValAddress `json:"validator_address" yaml:"validator_address"` // in bech32
|
||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
||||
}
|
||||
|
||||
// RedelegateRequest defines the properties of a redelegate request's body.
|
||||
RedelegateRequest struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
DelegatorAddress sdk.AccAddress `json:"delegator_address" yaml:"delegator_address"` // in bech32
|
||||
ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address" yaml:"validator_src_address"` // in bech32
|
||||
ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address" yaml:"validator_dst_address"` // in bech32
|
||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
||||
}
|
||||
|
||||
// UndelegateRequest defines the properties of a undelegate request's body.
|
||||
UndelegateRequest struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
DelegatorAddress sdk.AccAddress `json:"delegator_address" yaml:"delegator_address"` // in bech32
|
||||
ValidatorAddress sdk.ValAddress `json:"validator_address" yaml:"validator_address"` // in bech32
|
||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
||||
}
|
||||
)
|
||||
|
||||
func newPostDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req DelegateRequest
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgDelegate(req.DelegatorAddress, req.ValidatorAddress, req.Amount)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(fromAddr, req.DelegatorAddress) {
|
||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address")
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func newPostRedelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req RedelegateRequest
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgBeginRedelegate(req.DelegatorAddress, req.ValidatorSrcAddress, req.ValidatorDstAddress, req.Amount)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(fromAddr, req.DelegatorAddress) {
|
||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address")
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func newPostUnbondingDelegationsHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req UndelegateRequest
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgUndelegate(req.DelegatorAddress, req.ValidatorAddress, req.Amount)
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(fromAddr, req.DelegatorAddress) {
|
||||
rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own delegator address")
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
// contains checks if the a given query contains one of the tx types
|
||||
func contains(stringSlice []string, txType string) bool {
|
||||
for _, word := range stringSlice {
|
||||
if word == txType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// queries staking txs
|
||||
func queryTxs(clientCtx client.Context, action string, delegatorAddr string) (*sdk.SearchTxsResult, error) {
|
||||
page := 1
|
||||
limit := 100
|
||||
events := []string{
|
||||
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, action),
|
||||
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, delegatorAddr),
|
||||
}
|
||||
|
||||
return authtx.QueryTxsByEvents(clientCtx, events, page, limit, "")
|
||||
}
|
||||
|
||||
func queryBonds(clientCtx client.Context, endpoint string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bech32delegator := vars["delegatorAddr"]
|
||||
bech32validator := vars["validatorAddr"]
|
||||
|
||||
delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
validatorAddr, err := sdk.ValAddressFromBech32(bech32validator)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.QueryDelegatorValidatorRequest{DelegatorAddr: delegatorAddr.String(), ValidatorAddr: validatorAddr.String()}
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(endpoint, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryDelegator(clientCtx client.Context, endpoint string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bech32delegator := vars["delegatorAddr"]
|
||||
|
||||
delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryDelegatorParams(delegatorAddr)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(endpoint, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryValidator(clientCtx client.Context, endpoint string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bech32validatorAddr := vars["validatorAddr"]
|
||||
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryValidatorParams(validatorAddr, page, limit)
|
||||
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := clientCtx.QueryWithData(endpoint, bz)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, clientCtx, res)
|
||||
}
|
||||
}
|
||||
@ -1,82 +1,22 @@
|
||||
// +build norace
|
||||
|
||||
package 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))
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
|
||||
r.HandleFunc(
|
||||
"/upgrade/current", getCurrentPlanHandler(clientCtx),
|
||||
).Methods("GET")
|
||||
r.HandleFunc(
|
||||
"/upgrade/applied/{name}", getDonePlanHandler(clientCtx),
|
||||
).Methods("GET")
|
||||
}
|
||||
|
||||
func getCurrentPlanHandler(clientCtx client.Context) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, request *http.Request) {
|
||||
// ignore height for now
|
||||
res, _, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierKey, types.QueryCurrent))
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
if len(res) == 0 {
|
||||
http.NotFound(w, request)
|
||||
return
|
||||
}
|
||||
|
||||
var plan types.Plan
|
||||
err = clientCtx.LegacyAmino.UnmarshalJSON(res, &plan)
|
||||
if rest.CheckInternalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, clientCtx, plan)
|
||||
}
|
||||
}
|
||||
|
||||
func getDonePlanHandler(clientCtx client.Context) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
|
||||
params := types.QueryAppliedPlanRequest{Name: name}
|
||||
bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierKey, types.QueryApplied), bz)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if len(res) != 8 {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, "unknown format for applied-upgrade")
|
||||
}
|
||||
|
||||
applied := int64(binary.BigEndian.Uint64(res))
|
||||
fmt.Println(applied)
|
||||
rest.PostProcessResponse(w, clientCtx, applied)
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
)
|
||||
|
||||
// RegisterRoutes registers REST routes for the upgrade module under the path specified by routeName.
|
||||
func RegisterRoutes(clientCtx client.Context, rtr *mux.Router) {
|
||||
r := rest.WithHTTPDeprecationHeaders(rtr)
|
||||
registerQueryRoutes(clientCtx, r)
|
||||
registerTxHandlers(clientCtx, r)
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
||||
)
|
||||
|
||||
func registerTxHandlers(
|
||||
clientCtx client.Context,
|
||||
r *mux.Router) {
|
||||
r.HandleFunc("/upgrade/plan", newPostPlanHandler(clientCtx)).Methods("POST")
|
||||
r.HandleFunc("/upgrade/cancel", newCancelPlanHandler(clientCtx)).Methods("POST")
|
||||
}
|
||||
|
||||
// PlanRequest defines a proposal for a new upgrade plan.
|
||||
type PlanRequest struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
||||
UpgradeName string `json:"upgrade_name" yaml:"upgrade_name"`
|
||||
UpgradeHeight int64 `json:"upgrade_height" yaml:"upgrade_height"`
|
||||
UpgradeInfo string `json:"upgrade_info" yaml:"upgrade_info"`
|
||||
}
|
||||
|
||||
// CancelRequest defines a proposal to cancel a current plan.
|
||||
type CancelRequest struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
|
||||
}
|
||||
|
||||
func ProposalRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
||||
return govrest.ProposalRESTHandler{
|
||||
SubRoute: "upgrade",
|
||||
Handler: newPostPlanHandler(clientCtx),
|
||||
}
|
||||
}
|
||||
|
||||
func ProposalCancelRESTHandler(clientCtx client.Context) govrest.ProposalRESTHandler {
|
||||
return govrest.ProposalRESTHandler{
|
||||
SubRoute: "upgrade",
|
||||
Handler: newCancelPlanHandler(clientCtx),
|
||||
}
|
||||
}
|
||||
|
||||
func newPostPlanHandler(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req PlanRequest
|
||||
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
plan := types.Plan{Name: req.UpgradeName, Height: req.UpgradeHeight, Info: req.UpgradeInfo}
|
||||
content := types.NewSoftwareUpgradeProposal(req.Title, req.Description, plan)
|
||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, fromAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func newCancelPlanHandler(clientCtx client.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req CancelRequest
|
||||
|
||||
if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
content := types.NewCancelSoftwareUpgradeProposal(req.Title, req.Description)
|
||||
|
||||
msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, fromAddr)
|
||||
if rest.CheckBadRequestError(w, err) {
|
||||
return
|
||||
}
|
||||
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
|
||||
return
|
||||
}
|
||||
|
||||
tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg)
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,12 @@
|
||||
// +build norace
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user