Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added rest interface #30

Merged
merged 8 commits into from
Jan 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions x/wasm/client/rest/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package rest

import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmwasm/wasmd/x/wasm/internal/keeper"
"github.com/cosmwasm/wasmd/x/wasm/internal/types"
"net/http"
"strconv"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/gorilla/mux"
)

func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file looks good

r.HandleFunc("/wasm/code/", listCodesHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/code/{codeID}", queryCodeHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/", listAllContractsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}", queryContractHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}/state", queryContractStateAllHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}/smart", queryContractStateSmartHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}/raw", queryContractStateRawHandlerFn(cliCtx)).Methods("GET")
}

func listCodesHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListCode)
res, _, err := cliCtx.Query(route)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, string(res))
}
}

func queryCodeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
codeID, err := strconv.ParseUint(mux.Vars(r)["codeId"], 10, 64)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

route := fmt.Sprintf("custom/%s/%s/%d", types.QuerierRoute, keeper.QueryGetCode, codeID)
res, _, err := cliCtx.Query(route)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

if len(res) == 0 {
rest.WriteErrorResponse(w, http.StatusInternalServerError, "contract not found")
return
}
var code keeper.GetCodeResponse
err = json.Unmarshal(res, &code)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

if len(code.Code) == 0 {
rest.WriteErrorResponse(w, http.StatusInternalServerError, "contract not found")
return
}

rest.PostProcessResponse(w, cliCtx, string(code.Code))
}
}

func listAllContractsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, keeper.QueryListContracts)
res, _, err := cliCtx.Query(route)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())

return
}
rest.PostProcessResponse(w, cliCtx, string(res))
}
}

func queryContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["codeId"])
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContract, addr.String())
res, _, err := cliCtx.Query(route)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, string(res))
}
}

func queryContractStateAllHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["contractAddr"])
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

route := fmt.Sprintf("custom/%s/%s/%s/%s", types.QuerierRoute, keeper.QueryGetContractState, addr.String(), keeper.QueryMethodContractStateAll)
res, _, err := cliCtx.Query(route)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, string(res))
}
}

func queryContractStateSmartHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two should be marked TODO, but as long as you know and will follow up shortly, this is fine

return func(w http.ResponseWriter, r *http.Request) {

}
}

func queryContractStateRawHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

}
}
13 changes: 13 additions & 0 deletions x/wasm/client/rest/rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package rest

import (
"github.com/gorilla/mux"

"github.com/cosmos/cosmos-sdk/client/context"
)

// RegisterRoutes registers staking-related REST handlers to a router
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
registerQueryRoutes(cliCtx, r)
registerTxRoutes(cliCtx, r)
}
166 changes: 166 additions & 0 deletions x/wasm/client/rest/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package rest

import (
"net/http"
"strconv"

"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
"github.com/gorilla/mux"

wasmUtils "github.com/cosmwasm/wasmd/x/wasm/client/utils"
"github.com/cosmwasm/wasmd/x/wasm/internal/types"
)

func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

r.HandleFunc("/wasm/code/", storeCodeHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/wasm/code/{codeId}", instantiateContractHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc("/wasm/contract/{contractAddr}", executeContractHandlerFn(cliCtx)).Methods("POST")
}

// limit max bytes read to prevent gzip bombs
const maxSize = 400 * 1024

type storeCodeReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking this should be "squash", but see that it is not in the cosmos-sdk either.
I guess I should look more into this generic format used everywhere.
Do you know where the docs are?

WasmBytes []byte `json:"wasm_bytes"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing yaml

}

type instantiateContractReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
InitMsg []byte `json:"init_msg" yaml:"init_msg"`
}

type executeContractReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
ExecMsg []byte `json:"exec_msg" yaml:"exec_msg"`
Amount sdk.Coins `json:"coins" yaml:"coins"`
}

func storeCodeHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req storeCodeReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}

req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}

var err error
wasm := req.WasmBytes
if len(wasm) > maxSize {
rest.WriteErrorResponse(w, http.StatusBadRequest, "Binary size exceeds maximum limit")
return
}

// gzip the wasm file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to auto-gzip it here as well 👍

if wasmUtils.IsWasm(wasm) {
wasm, err = wasmUtils.GzipIt(wasm)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
} else if !wasmUtils.IsGzip(wasm) {
rest.WriteErrorResponse(w, http.StatusBadRequest, "Invalid input file, use wasm binary or zip")
return
}

fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// build and sign the transaction, then broadcast to Tendermint
msg := types.MsgStoreCode{
Sender: fromAddr,
WASMByteCode: wasm,
}

err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}

func instantiateContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req instantiateContractReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}
vars := mux.Vars(r)
codeId := vars["codeId"]

req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}

// get the id of the code to instantiate
codeID, err := strconv.ParseUint(codeId, 10, 64)
if err != nil {
return
}

msg := types.MsgInstantiateContract{
Sender: cliCtx.GetFromAddress(),
Code: codeID,
InitFunds: req.Deposit,
InitMsg: req.InitMsg,
}

err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}

func executeContractHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req executeContractReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}
vars := mux.Vars(r)
contractAddr := vars["contractAddr"]

req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}

contractAddress, err := sdk.AccAddressFromBech32(contractAddr)
if err != nil {
return
}

msg := types.MsgExecuteContract{
Sender: cliCtx.GetFromAddress(),
Contract: contractAddress,
Msg: req.ExecMsg,
SentFunds: req.Amount,
}

err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}