diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5be5c9bddfa..25a486f96f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,6 +62,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Client Breaking
+* (crypto) [\#7419](https://github.com/cosmos/cosmos-sdk/pull/7419) The SDK doesn't use Tendermint's `crypto.PubKey` interface anymore, and uses instead it's own `PubKey` interface, defined in `crypto/types`. Replace all instances of `crypto.PubKey` by `cryptotypes.Pubkey`.
* (x/staking) [\#7419](https://github.com/cosmos/cosmos-sdk/pull/7419) The `TmConsPubKey` method on ValidatorI has been removed and replaced instead by `ConsPubKey` (which returns a SDK `cryptotypes.PubKey`) and `TmConsPublicKey` (which returns a Tendermint proto PublicKey).
### Improvements
diff --git a/docs/migrations/rest.md b/docs/migrations/rest.md
index f27635bf350..82881669533 100644
--- a/docs/migrations/rest.md
+++ b/docs/migrations/rest.md
@@ -14,12 +14,15 @@ Some important information concerning all legacy REST endpoints:
## Breaking Changes in Legacy REST Endpoints
-| Legacy REST Endpoint | Description | Breaking Change |
-| ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `POST /txs` | Query tx by hash | Endpoint will error when trying to broadcast transactions that don't support Amino serialization (e.g. IBC txs)1. |
-| `GET /txs/{hash}` | Query tx by hash | Endpoint will error when trying to output transactions that don't support Amino serialization (e.g. IBC txs)1. |
-| `GET /txs` | Query tx by events | Endpoint will error when trying to output transactions that don't support Amino serialization (e.g. IBC txs)1. |
-| `GET /staking/validators` | Get all validators | BondStatus is now a protobuf enum instead of an int32, and JSON serialized using its protobuf name, so expect query parameters like `?status=BOND_STATUS_{BONDED,UNBONDED,UNBONDING}` as opposed to `?status={bonded,unbonded,unbonding}`. |
+| Legacy REST Endpoint | Description | Breaking Change |
+| ------------------------------------------------------------------------ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `POST /txs` | Broadcast tx | Endpoint will error when trying to broadcast transactions that don't support Amino serialization (e.g. IBC txs)1. |
+| `POST /txs/encode`, `POST /txs/decode` | Encode/decode Amino txs from JSON to binary | Endpoint will error when trying to encode/decode transactions that don't support Amino serialization (e.g. IBC txs)1. |
+| `GET /txs/{hash}` | Query tx by hash | Endpoint will error when trying to output transactions that don't support Amino serialization (e.g. IBC txs)1. |
+| `GET /txs` | Query tx by events | Endpoint will error when trying to output transactions that don't support Amino serialization (e.g. IBC txs)1. |
+| `GET /gov/proposals/{id}/votes`, `GET /gov/proposals/{id}/votes/{voter}` | Gov endpoints for querying votes | All gov endpoints which return votes return int32 in the `option` field instead of string: `1=VOTE_OPTION_YES, 2=VOTE_OPTION_ABSTAIN, 3=VOTE_OPTION_NO, 4=VOTE_OPTION_NO_WITH_VETO`. |
+| `GET /staking/*` | Staking query endpoints | All staking endpoints which return validators have two breaking changes. First, the validator's `consensus_pubkey` field returns an Amino-encoded struct representing an `Any` instead of a bech32-encoded string representing the pubkey. The `value` field of the `Any` is the pubkey's raw key as base64-encoded bytes. Second, the validator's `status` field now returns an int32 instead of string: `1=BOND_STATUS_UNBONDED`, `2=BOND_STATUS_UNBONDING`, `3=BOND_STATUS_BONDED`. |
+| `GET /staking/validators` | Get all validators | BondStatus is now a protobuf enum instead of an int32, and JSON serialized using its protobuf name, so expect query parameters like `?status=BOND_STATUS_{BONDED,UNBONDED,UNBONDING}` as opposed to `?status={bonded,unbonded,unbonding}`. |
1: Transactions that don't support Amino serialization are the ones that contain one or more `Msg`s that are not registered with the Amino codec. Currently in the SDK, only IBC `Msg`s fall into this case.
diff --git a/x/auth/client/rest/broadcast.go b/x/auth/client/rest/broadcast.go
index e0020515d80..4fd4dcb1a90 100644
--- a/x/auth/client/rest/broadcast.go
+++ b/x/auth/client/rest/broadcast.go
@@ -1,9 +1,11 @@
package rest
import (
+ "fmt"
"io/ioutil"
"net/http"
+ clientrest "github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/client"
@@ -30,8 +32,15 @@ func BroadcastTxRequest(clientCtx client.Context) http.HandlerFunc {
}
// NOTE: amino is used intentionally here, don't migrate it!
- if err := clientCtx.LegacyAmino.UnmarshalJSON(body, &req); rest.CheckBadRequestError(w, err) {
- return
+ 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)
diff --git a/x/auth/client/rest/decode.go b/x/auth/client/rest/decode.go
index 3061d71cf48..5b732fa0a19 100644
--- a/x/auth/client/rest/decode.go
+++ b/x/auth/client/rest/decode.go
@@ -55,7 +55,7 @@ func DecodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
response := DecodeResp(stdTx)
- err = checkSignModeError(clientCtx, response, "/cosmos/tx/v1beta1/txs/decode")
+ err = checkAminoMarshalError(clientCtx, response, "/cosmos/tx/v1beta1/txs/decode")
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
diff --git a/x/auth/client/rest/encode.go b/x/auth/client/rest/encode.go
index 1fbc3b94f43..63881780153 100644
--- a/x/auth/client/rest/encode.go
+++ b/x/auth/client/rest/encode.go
@@ -2,12 +2,13 @@ package rest
import (
"encoding/base64"
+ "fmt"
"io/ioutil"
"net/http"
- "github.com/cosmos/cosmos-sdk/client/tx"
-
"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/legacy/legacytx"
)
@@ -17,6 +18,12 @@ 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.
@@ -31,8 +38,12 @@ func EncodeTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
// NOTE: amino is used intentionally here, don't migrate it
err = clientCtx.LegacyAmino.UnmarshalJSON(body, &req)
- if rest.CheckBadRequestError(w, err) {
- return
+ // 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
diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go
index 6fe54404beb..d11d4b3416c 100644
--- a/x/auth/client/rest/query.go
+++ b/x/auth/client/rest/query.go
@@ -108,7 +108,7 @@ func QueryTxsRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
packStdTxResponse(w, clientCtx, txRes)
}
- err = checkSignModeError(clientCtx, searchResult, "/cosmos/tx/v1beta1/txs")
+ err = checkAminoMarshalError(clientCtx, searchResult, "/cosmos/tx/v1beta1/txs")
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
@@ -151,7 +151,7 @@ func QueryTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
rest.WriteErrorResponse(w, http.StatusNotFound, fmt.Sprintf("no transaction found with hash %s", hashHexStr))
}
- err = checkSignModeError(clientCtx, output, "/cosmos/tx/v1beta1/txs/{txhash}")
+ err = checkAminoMarshalError(clientCtx, output, "/cosmos/tx/v1beta1/txs/{txhash}")
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
@@ -198,9 +198,9 @@ func packStdTxResponse(w http.ResponseWriter, clientCtx client.Context, txRes *s
return nil
}
-// checkSignModeError checks if there are errors with marshalling non-amino
+// checkAminoMarshalError checks if there are errors with marshalling non-amino
// txs with amino.
-func checkSignModeError(ctx client.Context, resp interface{}, grpcEndPoint string) error {
+func checkAminoMarshalError(ctx client.Context, resp interface{}, grpcEndPoint string) error {
// LegacyAmino used intentionally here to handle the SignMode errors
marshaler := ctx.LegacyAmino
diff --git a/x/auth/client/rest/rest_test.go b/x/auth/client/rest/rest_test.go
index 67c3cfc3fc1..fb9dd73d75f 100644
--- a/x/auth/client/rest/rest_test.go
+++ b/x/auth/client/rest/rest_test.go
@@ -21,7 +21,7 @@ import (
"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"
- rest2 "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
+ authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
"github.com/cosmos/cosmos-sdk/x/bank/types"
@@ -72,7 +72,7 @@ func (s *IntegrationTestSuite) TearDownSuite() {
s.network.Cleanup()
}
-func mkTx() legacytx.StdTx {
+func mkStdTx() legacytx.StdTx {
// NOTE: this uses StdTx explicitly, don't migrate it!
return legacytx.StdTx{
Msgs: []sdk.Msg{&types.MsgSend{}},
@@ -84,10 +84,49 @@ func mkTx() legacytx.StdTx {
}
}
+// Create an IBC tx that's encoded as amino-JSON. Since we can't amino-marshal
+// a tx with "cosmos-sdk/MsgTransfer" using the SDK, we just hardcode the tx
+// here. But external clients might, see https://github.com/cosmos/cosmos-sdk/issues/8022.
+func mkIBCStdTx() []byte {
+ ibcTx := `{
+ "account_number": "68",
+ "chain_id": "stargate-4",
+ "fee": {
+ "amount": [
+ {
+ "amount": "3500",
+ "denom": "umuon"
+ }
+ ],
+ "gas": "350000"
+ },
+ "memo": "",
+ "msg": [
+ {
+ "type": "cosmos-sdk/MsgTransfer",
+ "value": {
+ "receiver": "cosmos1q9wtnlwdjrhwtcjmt2uq77jrgx7z3usrq2yz7z",
+ "sender": "cosmos1q9wtnlwdjrhwtcjmt2uq77jrgx7z3usrq2yz7z",
+ "source_channel": "THEslipperCHANNEL",
+ "source_port": "transfer",
+ "token": {
+ "amount": "1000000",
+ "denom": "umuon"
+ }
+ }
+ }
+ ],
+ "sequence": "24"
+ }`
+ req := fmt.Sprintf(`{"tx":%s,"mode":"async"}`, ibcTx)
+
+ return []byte(req)
+}
+
func (s *IntegrationTestSuite) TestEncodeDecode() {
var require = s.Require()
val := s.network.Validators[0]
- stdTx := mkTx()
+ stdTx := mkStdTx()
// NOTE: this uses amino explicitly, don't migrate it!
cdc := val.ClientCtx.LegacyAmino
@@ -98,11 +137,11 @@ func (s *IntegrationTestSuite) TestEncodeDecode() {
res, err := rest.PostRequest(fmt.Sprintf("%s/txs/encode", val.APIAddress), "application/json", bz)
require.NoError(err)
- var encodeResp rest2.EncodeResp
+ var encodeResp authrest.EncodeResp
err = cdc.UnmarshalJSON(res, &encodeResp)
require.NoError(err)
- bz, err = cdc.MarshalJSON(rest2.DecodeReq{Tx: encodeResp.Tx})
+ bz, err = cdc.MarshalJSON(authrest.DecodeReq{Tx: encodeResp.Tx})
require.NoError(err)
res, err = rest.PostRequest(fmt.Sprintf("%s/txs/decode", val.APIAddress), "application/json", bz)
@@ -111,14 +150,24 @@ func (s *IntegrationTestSuite) TestEncodeDecode() {
var respWithHeight rest.ResponseWithHeight
err = cdc.UnmarshalJSON(res, &respWithHeight)
require.NoError(err)
- var decodeResp rest2.DecodeResp
+ var decodeResp authrest.DecodeResp
err = cdc.UnmarshalJSON(respWithHeight.Result, &decodeResp)
require.NoError(err)
require.Equal(stdTx, legacytx.StdTx(decodeResp))
}
+func (s *IntegrationTestSuite) TestEncodeIBCTx() {
+ val := s.network.Validators[0]
+
+ req := mkIBCStdTx()
+ res, err := rest.PostRequest(fmt.Sprintf("%s/txs/encode", val.APIAddress), "application/json", []byte(req))
+ s.Require().NoError(err)
+
+ s.Require().Contains(string(res), authrest.ErrEncodeDecode.Error())
+}
+
func (s *IntegrationTestSuite) TestBroadcastTxRequest() {
- stdTx := mkTx()
+ 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")
@@ -130,6 +179,17 @@ func (s *IntegrationTestSuite) TestBroadcastTxRequest() {
s.Require().NotEmpty(txRes.TxHash)
}
+func (s *IntegrationTestSuite) TestBroadcastIBCTxRequest() {
+ val := s.network.Validators[0]
+
+ req := mkIBCStdTx()
+ res, err := rest.PostRequest(fmt.Sprintf("%s/txs", val.APIAddress), "application/json", []byte(req))
+ s.Require().NoError(err)
+
+ // Make sure the error message is correct.
+ s.Require().Contains(string(res), "this transaction cannot be broadcasted via legacy REST endpoints")
+}
+
// 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]
@@ -332,7 +392,7 @@ func (s *IntegrationTestSuite) broadcastReq(stdTx legacytx.StdTx, mode string) (
// NOTE: this uses amino explicitly, don't migrate it!
cdc := val.ClientCtx.LegacyAmino
- req := rest2.BroadcastReq{
+ req := authrest.BroadcastReq{
Tx: stdTx,
Mode: mode,
}
@@ -401,7 +461,7 @@ func (s *IntegrationTestSuite) testQueryIBCTx(txRes sdk.TxResponse, cmd *cobra.C
out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.GetEncodeCommand(), []string{txFileName})
s.Require().NoError(err)
- bz, err := val.ClientCtx.LegacyAmino.MarshalJSON(rest2.DecodeReq{Tx: string(out.Bytes())})
+ 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.
diff --git a/x/gov/client/rest/grpc_query_test.go b/x/gov/client/rest/grpc_query_test.go
index 23a282d5d31..8e2d4efa9ff 100644
--- a/x/gov/client/rest/grpc_query_test.go
+++ b/x/gov/client/rest/grpc_query_test.go
@@ -150,7 +150,7 @@ func (s *IntegrationTestSuite) TestGetProposalsGRPC() {
func (s *IntegrationTestSuite) TestGetProposalVoteGRPC() {
val := s.network.Validators[0]
- voterAddressBase64 := val.Address.String()
+ voterAddressBech32 := val.Address.String()
testCases := []struct {
name string
@@ -159,12 +159,12 @@ func (s *IntegrationTestSuite) TestGetProposalVoteGRPC() {
}{
{
"empty proposal",
- fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%s/votes/%s", val.APIAddress, "", voterAddressBase64),
+ fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%s/votes/%s", val.APIAddress, "", voterAddressBech32),
true,
},
{
"get non existing proposal",
- fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%s/votes/%s", val.APIAddress, "10", voterAddressBase64),
+ fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%s/votes/%s", val.APIAddress, "10", voterAddressBech32),
true,
},
{
@@ -174,7 +174,7 @@ func (s *IntegrationTestSuite) TestGetProposalVoteGRPC() {
},
{
"get proposal with id",
- fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%s/votes/%s", val.APIAddress, "1", voterAddressBase64),
+ fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%s/votes/%s", val.APIAddress, "1", voterAddressBech32),
false,
},
}
diff --git a/x/gov/client/rest/rest_test.go b/x/gov/client/rest/rest_test.go
new file mode 100644
index 00000000000..0b1a274022c
--- /dev/null
+++ b/x/gov/client/rest/rest_test.go
@@ -0,0 +1,110 @@
+// +build norace
+
+package rest_test
+
+import (
+ "fmt"
+
+ "github.com/cosmos/cosmos-sdk/types/rest"
+ "github.com/cosmos/cosmos-sdk/x/gov/types"
+)
+
+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.VoteOption(1), vote.Option)
+ }
+ })
+ }
+}
+
+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)
+ }
+ })
+ }
+}
diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go
index cbe0813c32e..d515d5dfed9 100644
--- a/x/staking/client/rest/query.go
+++ b/x/staking/client/rest/query.go
@@ -9,6 +9,7 @@ import (
"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"
@@ -278,6 +279,19 @@ func validatorsHandlerFn(clientCtx client.Context) http.HandlerFunc {
}
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
}
diff --git a/x/staking/client/rest/rest_test.go b/x/staking/client/rest/rest_test.go
new file mode 100644
index 00000000000..43b9afdb6dd
--- /dev/null
+++ b/x/staking/client/rest/rest_test.go
@@ -0,0 +1,62 @@
+// +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\",")
+ }
+ })
+ }
+}