Merge PR #4777: Fix Height Queries
This commit is contained in:
parent
b4ff0a11f6
commit
0ba74bb4b7
|
@ -0,0 +1,2 @@
|
||||||
|
All REST responses now wrap the original resource/result. The response
|
||||||
|
will contain two fields: height and result.
|
|
@ -0,0 +1 @@
|
||||||
|
Return height in responses when querying against BaseApp
|
|
@ -463,6 +463,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
Code: uint32(sdk.CodeOK),
|
Code: uint32(sdk.CodeOK),
|
||||||
Codespace: string(sdk.CodespaceRoot),
|
Codespace: string(sdk.CodespaceRoot),
|
||||||
|
Height: req.Height,
|
||||||
Value: []byte(app.appVersion),
|
Value: []byte(app.appVersion),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,6 +475,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
Code: uint32(sdk.CodeOK),
|
Code: uint32(sdk.CodeOK),
|
||||||
Codespace: string(sdk.CodespaceRoot),
|
Codespace: string(sdk.CodespaceRoot),
|
||||||
|
Height: req.Height,
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,7 +484,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abc
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
|
func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery {
|
||||||
// "/store" prefix for store queries
|
// "/store" prefix for store queries
|
||||||
queryable, ok := app.cms.(sdk.Queryable)
|
queryable, ok := app.cms.(sdk.Queryable)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -491,7 +493,11 @@ func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res a
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Path = "/" + strings.Join(path[1:], "/")
|
req.Path = "/" + strings.Join(path[1:], "/")
|
||||||
return queryable.Query(req)
|
|
||||||
|
resp := queryable.Query(req)
|
||||||
|
resp.Height = req.Height
|
||||||
|
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleQueryP2P(app *BaseApp, path []string, _ abci.RequestQuery) (res abci.ResponseQuery) {
|
func handleQueryP2P(app *BaseApp, path []string, _ abci.RequestQuery) (res abci.ResponseQuery) {
|
||||||
|
@ -503,9 +509,11 @@ func handleQueryP2P(app *BaseApp, path []string, _ abci.RequestQuery) (res abci.
|
||||||
switch typ {
|
switch typ {
|
||||||
case "addr":
|
case "addr":
|
||||||
return app.FilterPeerByAddrPort(arg)
|
return app.FilterPeerByAddrPort(arg)
|
||||||
|
|
||||||
case "id":
|
case "id":
|
||||||
return app.FilterPeerByID(arg)
|
return app.FilterPeerByID(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
msg := "Expected second parameter to be filter"
|
msg := "Expected second parameter to be filter"
|
||||||
return sdk.ErrUnknownRequest(msg).QueryResult()
|
return sdk.ErrUnknownRequest(msg).QueryResult()
|
||||||
|
@ -554,13 +562,15 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
Code: uint32(err.Code()),
|
Code: uint32(err.Code()),
|
||||||
Codespace: string(err.Codespace()),
|
Codespace: string(err.Codespace()),
|
||||||
|
Height: req.Height,
|
||||||
Log: err.ABCILog(),
|
Log: err.ABCILog(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return abci.ResponseQuery{
|
return abci.ResponseQuery{
|
||||||
Code: uint32(sdk.CodeOK),
|
Code: uint32(sdk.CodeOK),
|
||||||
Value: resBytes,
|
Height: req.Height,
|
||||||
|
Value: resBytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,16 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, height i
|
||||||
return res, height, err
|
return res, height, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When a client did not provide a query height, manually query for it so it can
|
||||||
|
// be injected downstream into responses.
|
||||||
|
if ctx.Height == 0 {
|
||||||
|
status, err := node.Status()
|
||||||
|
if err != nil {
|
||||||
|
return res, height, err
|
||||||
|
}
|
||||||
|
ctx = ctx.WithHeight(status.SyncInfo.LatestBlockHeight)
|
||||||
|
}
|
||||||
|
|
||||||
opts := rpcclient.ABCIQueryOptions{
|
opts := rpcclient.ABCIQueryOptions{
|
||||||
Height: ctx.Height,
|
Height: ctx.Height,
|
||||||
Prove: !ctx.TrustNode,
|
Prove: !ctx.TrustNode,
|
||||||
|
|
|
@ -24,6 +24,21 @@ const (
|
||||||
DefaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
DefaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GasEstimateResponse defines a response definition for tx gas estimation.
|
// GasEstimateResponse defines a response definition for tx gas estimation.
|
||||||
type GasEstimateResponse struct {
|
type GasEstimateResponse struct {
|
||||||
GasEstimate uint64 `json:"gas_estimate"`
|
GasEstimate uint64 `json:"gas_estimate"`
|
||||||
|
@ -222,28 +237,27 @@ func ParseQueryHeightOrReturnBadRequest(w http.ResponseWriter, cliCtx context.CL
|
||||||
return cliCtx, true
|
return cliCtx, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostProcessResponse performs post processing for a REST response.
|
// PostProcessResponse performs post processing for a REST response. The result
|
||||||
// If the height is greater than zero it will be injected into the body
|
// returned to clients will contain two fields, the height at which the resource
|
||||||
// of the response. An internal server error is written to the response
|
// was queried at and the original result.
|
||||||
// if the height is negative or an encoding/decoding error occurs.
|
func PostProcessResponse(w http.ResponseWriter, cliCtx context.CLIContext, resp interface{}) {
|
||||||
func PostProcessResponse(w http.ResponseWriter, cliCtx context.CLIContext, response interface{}) {
|
var result []byte
|
||||||
var output []byte
|
|
||||||
|
|
||||||
if cliCtx.Height < 0 {
|
if cliCtx.Height < 0 {
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("negative height in response").Error())
|
WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("negative height in response").Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch response.(type) {
|
switch resp.(type) {
|
||||||
case []byte:
|
case []byte:
|
||||||
output = response.([]byte)
|
result = resp.([]byte)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var err error
|
var err error
|
||||||
if cliCtx.Indent {
|
if cliCtx.Indent {
|
||||||
output, err = cliCtx.Codec.MarshalJSONIndent(response, "", " ")
|
result, err = cliCtx.Codec.MarshalJSONIndent(resp, "", " ")
|
||||||
} else {
|
} else {
|
||||||
output, err = cliCtx.Codec.MarshalJSON(response)
|
result, err = cliCtx.Codec.MarshalJSON(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -252,29 +266,22 @@ func PostProcessResponse(w http.ResponseWriter, cliCtx context.CLIContext, respo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inject the height into the response by:
|
wrappedResp := NewResponseWithHeight(cliCtx.Height, result)
|
||||||
// - decoding into a map
|
|
||||||
// - adding the height to the map
|
|
||||||
// - encoding using standard JSON library
|
|
||||||
if cliCtx.Height > 0 {
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
err := json.Unmarshal(output, &m)
|
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m["height"] = cliCtx.Height
|
var (
|
||||||
|
output []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if cliCtx.Indent {
|
if cliCtx.Indent {
|
||||||
output, err = json.MarshalIndent(m, "", " ")
|
output, err = cliCtx.Codec.MarshalJSONIndent(wrappedResp, "", " ")
|
||||||
} else {
|
} else {
|
||||||
output, err = json.Marshal(m)
|
output, err = cliCtx.Codec.MarshalJSON(wrappedResp)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
if err != nil {
|
||||||
return
|
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
|
@ -3,12 +3,10 @@
|
||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -148,6 +146,7 @@ func TestProcessPostResponse(t *testing.T) {
|
||||||
// mock account
|
// mock account
|
||||||
// PubKey field ensures amino encoding is used first since standard
|
// PubKey field ensures amino encoding is used first since standard
|
||||||
// JSON encoding will panic on crypto.PubKey
|
// JSON encoding will panic on crypto.PubKey
|
||||||
|
|
||||||
type mockAccount struct {
|
type mockAccount struct {
|
||||||
Address types.AccAddress `json:"address"`
|
Address types.AccAddress `json:"address"`
|
||||||
Coins types.Coins `json:"coins"`
|
Coins types.Coins `json:"coins"`
|
||||||
|
@ -173,23 +172,17 @@ func TestProcessPostResponse(t *testing.T) {
|
||||||
cdc.RegisterConcrete(&mockAccount{}, "cosmos-sdk/mockAccount", nil)
|
cdc.RegisterConcrete(&mockAccount{}, "cosmos-sdk/mockAccount", nil)
|
||||||
ctx = ctx.WithCodec(cdc)
|
ctx = ctx.WithCodec(cdc)
|
||||||
|
|
||||||
// setup expected json responses with zero height
|
// setup expected results
|
||||||
jsonNoHeight, err := cdc.MarshalJSON(acc)
|
jsonNoIndent, err := ctx.Codec.MarshalJSON(acc)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, jsonNoHeight)
|
jsonWithIndent, err := ctx.Codec.MarshalJSONIndent(acc, "", " ")
|
||||||
jsonIndentNoHeight, err := cdc.MarshalJSONIndent(acc, "", " ")
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, jsonIndentNoHeight)
|
respNoIndent := NewResponseWithHeight(height, jsonNoIndent)
|
||||||
|
respWithIndent := NewResponseWithHeight(height, jsonWithIndent)
|
||||||
// decode into map to order alphabetically
|
expectedNoIndent, err := ctx.Codec.MarshalJSON(respNoIndent)
|
||||||
m := make(map[string]interface{})
|
|
||||||
err = json.Unmarshal(jsonNoHeight, &m)
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
jsonMap, err := json.Marshal(m)
|
expectedWithIndent, err := ctx.Codec.MarshalJSONIndent(respWithIndent, "", " ")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
jsonWithHeight := append(append([]byte(`{"height":`), []byte(strconv.Itoa(int(height))+",")...), jsonMap[1:]...)
|
|
||||||
jsonIndentMap, err := json.MarshalIndent(m, "", " ")
|
|
||||||
jsonIndentWithHeight := append(append([]byte(`{`+"\n "+` "height": `), []byte(strconv.Itoa(int(height))+",")...), jsonIndentMap[1:]...)
|
|
||||||
|
|
||||||
// check that negative height writes an error
|
// check that negative height writes an error
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -197,24 +190,17 @@ func TestProcessPostResponse(t *testing.T) {
|
||||||
PostProcessResponse(w, ctx, acc)
|
PostProcessResponse(w, ctx, acc)
|
||||||
require.Equal(t, http.StatusInternalServerError, w.Code)
|
require.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
|
|
||||||
// check that zero height returns expected response
|
|
||||||
ctx = ctx.WithHeight(0)
|
|
||||||
runPostProcessResponse(t, ctx, acc, jsonNoHeight, false)
|
|
||||||
// check zero height with indent
|
|
||||||
runPostProcessResponse(t, ctx, acc, jsonIndentNoHeight, true)
|
|
||||||
// check that height returns expected response
|
// check that height returns expected response
|
||||||
ctx = ctx.WithHeight(height)
|
ctx = ctx.WithHeight(height)
|
||||||
runPostProcessResponse(t, ctx, acc, jsonWithHeight, false)
|
runPostProcessResponse(t, ctx, acc, expectedNoIndent, false)
|
||||||
// check height with indent
|
// check height with indent
|
||||||
runPostProcessResponse(t, ctx, acc, jsonIndentWithHeight, true)
|
runPostProcessResponse(t, ctx, acc, expectedWithIndent, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// asserts that ResponseRecorder returns the expected code and body
|
// asserts that ResponseRecorder returns the expected code and body
|
||||||
// runs PostProcessResponse on the objects regular interface and on
|
// runs PostProcessResponse on the objects regular interface and on
|
||||||
// the marshalled struct.
|
// the marshalled struct.
|
||||||
func runPostProcessResponse(t *testing.T, ctx context.CLIContext, obj interface{},
|
func runPostProcessResponse(t *testing.T, ctx context.CLIContext, obj interface{}, expectedBody []byte, indent bool) {
|
||||||
expectedBody []byte, indent bool,
|
|
||||||
) {
|
|
||||||
if indent {
|
if indent {
|
||||||
ctx.Indent = indent
|
ctx.Indent = indent
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue