Skip to content

Commit

Permalink
Add IBC REST endpoints (#5310)
Browse files Browse the repository at this point in the history
* add rest framework

* add rest endpoints for ibc connection

* add rest endpoints for ibc client

* add rest endpoints for ibc channel

* modify ibc rest api

* add rest endpoints for ibc transfer

* fix query route

* fix receive packet

* fix query client state api

* use sub module name instead of icsxx

* use const for prove judgement

* modify ibc rest api

* add api docs to swagger

* add ibc config

* fix proof path in swagger

* return query result proof

* update swagger docs

* parse prove

* clean up

* fix ibc rest api and swagger docs

* fix host validate

* fix typo

* add submitMisbehaviour error response in swagger

* fix rest queryRoot and swagger doc

* add response comments for each REST functions

* fix rest function comments
  • Loading branch information
chengwenxi authored and jackzampolin committed Jan 15, 2020
1 parent 2966286 commit 77718c1
Show file tree
Hide file tree
Showing 26 changed files with 2,985 additions and 34 deletions.
2 changes: 1 addition & 1 deletion client/lcd/statik/statik.go

Large diffs are not rendered by default.

1,199 changes: 1,198 additions & 1 deletion client/lcd/swagger-ui/swagger.yaml

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions types/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,14 @@ func ParseHTTPArgsWithLimit(r *http.Request, defaultLimit int) (tags []string, p
func ParseHTTPArgs(r *http.Request) (tags []string, page, limit int, err error) {
return ParseHTTPArgsWithLimit(r, DefaultLimit)
}

// ParseQueryProve sets the prove to execute a query if set by the http request.
// It returns false if there was an error parsing the prove.
func ParseQueryProve(r *http.Request) bool {
proveStr := r.FormValue("prove")
prove := false
if ok, err := strconv.ParseBool(proveStr); err == nil {
prove = ok
}
return prove
}
248 changes: 248 additions & 0 deletions x/ibc/02-client/client/rest/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package rest

import (
"fmt"
"net/http"
"strconv"

"github.com/gorilla/mux"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/client/utils"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint"
commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment"
)

func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, queryRoute string) {
r.HandleFunc(fmt.Sprintf("/ibc/clients/{%s}/consensus-state", RestClientID), queryConsensusStateHandlerFn(cliCtx, queryRoute)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/ibc/clients/{%s}/client-state", RestClientID), queryClientStateHandlerFn(cliCtx, queryRoute)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/ibc/clients/{%s}/roots/{%s}", RestClientID, RestRootHeight), queryRootHandlerFn(cliCtx, queryRoute)).Methods("GET")
r.HandleFunc("/ibc/header", queryHeaderHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/ibc/node-state", queryNodeConsensusStateHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/ibc/path", queryPathHandlerFn(cliCtx)).Methods("GET")
}

// queryConsensusStateHandlerFn implements a consensus state querying route
//
// @Summary Query cliet consensus-state
// @Tags IBC
// @Produce json
// @Param client-id path string true "Client ID"
// @Param prove query boolean false "Proof of result"
// @Success 200 {object} QueryConsensusState "OK"
// @Failure 400 {object} rest.ErrorResponse "Invalid client id"
// @Failure 500 {object} rest.ErrorResponse "Internal Server Error"
// @Router /ibc/clients/{client-id}/consensus-state [get]
func queryConsensusStateHandlerFn(cliCtx context.CLIContext, queryRoute string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
clientID := vars[RestClientID]

cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}

req := abci.RequestQuery{
Path: "store/ibc/key",
Data: types.KeyConsensusState(clientID),
Prove: rest.ParseQueryProve(r),
}

res, err := cliCtx.QueryABCI(req)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

var cs tendermint.ConsensusState
if err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &cs); err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
}

cliCtx = cliCtx.WithHeight(res.Height)
rest.PostProcessResponse(w, cliCtx, types.NewConsensusStateResponse(clientID, cs, res.Proof, res.Height))
}
}

// queryHeaderHandlerFn implements a header querying route
//
// @Summary Query header
// @Tags IBC
// @Produce json
// @Success 200 {object} QueryHeader "OK"
// @Failure 500 {object} rest.ErrorResponse "Internal Server Error"
// @Router /ibc/header [get]
func queryHeaderHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
header, err := utils.GetTendermintHeader(cliCtx)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

rest.PostProcessResponse(w, cliCtx, header)
}
}

// queryClientStateHandlerFn implements a client state querying route
//
// @Summary Query client state
// @Tags IBC
// @Produce json
// @Param client-id path string true "Client ID"
// @Param prove query boolean false "Proof of result"
// @Success 200 {object} QueryClientState "OK"
// @Failure 400 {object} rest.ErrorResponse "Invalid client id"
// @Failure 500 {object} rest.ErrorResponse "Internal Server Error"
// @Router /ibc/clients/{client-id}/client-state [get]
func queryClientStateHandlerFn(cliCtx context.CLIContext, queryRoute string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
clientID := vars[RestClientID]

cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}

req := abci.RequestQuery{
Path: "store/ibc/key",
Data: types.KeyClientState(clientID),
Prove: rest.ParseQueryProve(r),
}

res, err := cliCtx.QueryABCI(req)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

var state types.State
if err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &state); err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
}

cliCtx = cliCtx.WithHeight(res.Height)
rest.PostProcessResponse(w, cliCtx, types.NewClientStateResponse(clientID, state, res.Proof, res.Height))
}
}

// queryRootHandlerFn implements a root querying route
//
// @Summary Query client root
// @Tags IBC
// @Produce json
// @Param client-id path string true "Client ID"
// @Param height path number true "Root height"
// @Success 200 {object} QueryRoot "OK"
// @Failure 400 {object} rest.ErrorResponse "Invalid client id or height"
// @Failure 500 {object} rest.ErrorResponse "Internal Server Error"
// @Router /ibc/clients/{client-id}/roots/{height} [get]
func queryRootHandlerFn(cliCtx context.CLIContext, queryRoute string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
clientID := vars[RestClientID]
height, err := strconv.ParseUint(vars[RestRootHeight], 10, 64)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}

req := abci.RequestQuery{
Path: "store/ibc/key",
Data: types.KeyRoot(clientID, height),
Prove: rest.ParseQueryProve(r),
}

res, err := cliCtx.QueryABCI(req)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

var root commitment.Root
if err := cliCtx.Codec.UnmarshalJSON(res.Value, &root); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

cliCtx = cliCtx.WithHeight(res.Height)
rest.PostProcessResponse(w, cliCtx, types.NewRootResponse(clientID, height, root, res.Proof, res.Height))
}
}

// queryNodeConsensusStateHandlerFn implements a node consensus state querying route
//
// @Summary Query node consensus-state
// @Tags IBC
// @Produce json
// @Success 200 {object} QueryNodeConsensusState "OK"
// @Failure 500 {object} rest.ErrorResponse "Internal Server Error"
// @Router /ibc/node-state [get]
func queryNodeConsensusStateHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
node, err := cliCtx.GetNode()
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

info, err := node.ABCIInfo()
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

height := info.Response.LastBlockHeight
prevHeight := height - 1

commit, err := node.Commit(&height)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

validators, err := node.Validators(&prevHeight)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

state := tendermint.ConsensusState{
ChainID: commit.ChainID,
Height: uint64(commit.Height),
Root: commitment.NewRoot(commit.AppHash),
NextValidatorSet: tmtypes.NewValidatorSet(validators.Validators),
}

res := cliCtx.Codec.MustMarshalJSON(state)
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}

// queryPathHandlerFn implements a node consensus path querying route
//
// @Summary Query IBC path
// @Tags IBC
// @Produce json
// @Success 200 {object} QueryPath "OK"
// @Failure 500 {object} rest.ErrorResponse "Internal Server Error"
// @Router /ibc/path [get]
func queryPathHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
path := commitment.NewPrefix([]byte("ibc"))
res := cliCtx.Codec.MustMarshalJSON(path)
rest.PostProcessResponse(w, cliCtx, res)
}
}
39 changes: 39 additions & 0 deletions x/ibc/02-client/client/rest/rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package rest

import (
"github.com/gorilla/mux"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
)

const (
RestClientID = "client-id"
RestRootHeight = "height"
)

// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, queryRoute string) {
registerQueryRoutes(cliCtx, r, queryRoute)
registerTxRoutes(cliCtx, r)
}

// CreateClientReq defines the properties of a create client request's body.
type CreateClientReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
ClientID string `json:"client_id" yaml:"client_id"`
ConsensusState exported.ConsensusState `json:"consensus_state" yaml:"consensus_state"`
}

// UpdateClientReq defines the properties of a update client request's body.
type UpdateClientReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Header exported.Header `json:"header" yaml:"header"`
}

// SubmitMisbehaviourReq defines the properties of a submit misbehaviour request's body.
type SubmitMisbehaviourReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Evidence exported.Evidence `json:"evidence" yaml:"evidence"`
}
61 changes: 61 additions & 0 deletions x/ibc/02-client/client/rest/swagger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package rest

import (
auth "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
"github.com/cosmos/cosmos-sdk/x/ibc/02-client/types/tendermint"
commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment"
)

type (
QueryConsensusState struct {
Height int64 `json:"height"`
Result types.ConsensusStateResponse `json:"result"`
}

QueryHeader struct {
Height int64 `json:"height"`
Result tendermint.Header `json:"result"`
}

QueryClientState struct {
Height int64 `json:"height"`
Result types.StateResponse `json:"result"`
}

QueryRoot struct {
Height int64 `json:"height"`
Result types.RootResponse `json:"result"`
}

QueryNodeConsensusState struct {
Height int64 `json:"height"`
Result tendermint.ConsensusState `json:"result"`
}

QueryPath struct {
Height int64 `json:"height"`
Result commitment.Prefix `json:"result"`
}

PostCreateClient struct {
Msgs []types.MsgCreateClient `json:"msg" yaml:"msg"`
Fee auth.StdFee `json:"fee" yaml:"fee"`
Signatures []auth.StdSignature `json:"signatures" yaml:"signatures"`
Memo string `json:"memo" yaml:"memo"`
}

PostUpdateClient struct {
Msgs []types.MsgUpdateClient `json:"msg" yaml:"msg"`
Fee auth.StdFee `json:"fee" yaml:"fee"`
Signatures []auth.StdSignature `json:"signatures" yaml:"signatures"`
Memo string `json:"memo" yaml:"memo"`
}

PostSubmitMisbehaviour struct {
Msgs []types.MsgSubmitMisbehaviour `json:"msg" yaml:"msg"`
Fee auth.StdFee `json:"fee" yaml:"fee"`
Signatures []auth.StdSignature `json:"signatures" yaml:"signatures"`
Memo string `json:"memo" yaml:"memo"`
}
)
Loading

0 comments on commit 77718c1

Please sign in to comment.