diff --git a/client/context/context.go b/client/context/context.go index 08f9bc3554ea..3e00abced534 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -123,7 +123,7 @@ func (ctx CLIContext) WithCert(cert tendermintLite.Certifier) CLIContext { return ctx } -// WithCert - return a copy of the context with an updated ClientMgr +// WithClientMgr - return a copy of the context with an updated ClientMgr func (ctx CLIContext) WithClientMgr(clientMgr *ClientManager) CLIContext { ctx.ClientMgr = clientMgr return ctx diff --git a/client/context/loadbalancing.go b/client/context/loadbalancing.go index 46cd2d5accd6..664ca3385747 100644 --- a/client/context/loadbalancing.go +++ b/client/context/loadbalancing.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" ) -// This is a manager of a set of rpc clients to full nodes. +// ClientManager is a manager of a set of rpc clients to full nodes. // This manager can do load balancing upon these rpc clients. type ClientManager struct { clients []rpcclient.Client @@ -15,6 +15,7 @@ type ClientManager struct { mutex sync.RWMutex } +// NewClientManager create a new ClientManager func NewClientManager(nodeURIs string) (*ClientManager,error) { if nodeURIs != "" { nodeURLArray := strings.Split(nodeURIs, ",") diff --git a/client/context/query.go b/client/context/query.go index 9fdbc2f272a9..77cdf5b3d88d 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -284,6 +284,7 @@ func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error { return nil } +// nolint: gocyclo // query performs a query from a Tendermint node with the provided store name // and path. func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err error) { @@ -312,8 +313,12 @@ func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err e return resp.Value,nil } + // TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node if ctx.Cert == nil { - return resp.Value,errors.Errorf("missing valid certifier to verify data from untrusted node") + if ctx.Logger != nil { + io.WriteString(ctx.Logger, fmt.Sprintf("Missing valid certifier to verify data from untrusted node\n")) + } + return resp.Value, nil } // AppHash for height H is in header H+1 diff --git a/client/httputils/httputils.go b/client/httputils/httputils.go index 435afa23cc29..45747c22a947 100644 --- a/client/httputils/httputils.go +++ b/client/httputils/httputils.go @@ -5,33 +5,27 @@ import ( "net/http" ) -// Create error http response +// NewError create error http response func NewError(ctx *gin.Context, errCode int, err error) { - errorResponse := httpError{ + errorResponse := HTTPError{ API: "2.0", Code: errCode, - ErrMsg: err.Error(), } - ctx.JSON(errCode, errorResponse) -} - -// Create normal http response -func NormalResponse(ctx *gin.Context, data interface{}) { - response := httpResponse{ - API: "2.0", - Code: 0, - Result: data, + if err != nil { + errorResponse.ErrMsg = err.Error() } - ctx.JSON(http.StatusOK, response) + + ctx.JSON(errCode, errorResponse) } -type httpResponse struct { - API string `json:"rest api" example:"2.0"` - Code int `json:"code" example:"0"` - Result interface{} `json:"result"` +// NormalResponse create normal http response +func NormalResponse(ctx *gin.Context, data []byte) { + ctx.Status(http.StatusOK) + ctx.Writer.Write(data) } -type httpError struct { +// HTTPError is http response with error +type HTTPError struct { API string `json:"rest api" example:"2.0"` Code int `json:"code" example:"500"` ErrMsg string `json:"error message"` diff --git a/client/keys/add.go b/client/keys/add.go index 0c67fd4e6dc7..904e17af0567 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -239,37 +239,39 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(bz) } -// Handler of adding new key in swagger rest server +// AddNewKeyRequest is the handler of adding new key in swagger rest server +// nolint: gocyclo func AddNewKeyRequest(gtx *gin.Context) { - var kb keys.Keybase var m NewKeyBody - - kb, err := GetKeyBase() + body, err := ioutil.ReadAll(gtx.Request.Body) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } - - if err := gtx.BindJSON(&m); err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } + err = json.Unmarshal(body, &m) if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } + if len(m.Name) < 1 || len(m.Name) > 16 { httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account name length should not be longer than 16")) return } for _, char := range []rune(m.Name) { if !syntax.IsWordChar(char) { - httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account name should not contains any char beyond [0-9A-Za-z]")) + httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account name should not contains any char beyond [_0-9A-Za-z]")) return } } if len(m.Password) < 8 || len(m.Password) > 16 { - httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account password length should be between 8 and 16")) + httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account password length should be no less than 8 and no greater than 16")) + return + } + + kb, err := GetKeyBase() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) return } @@ -282,7 +284,7 @@ func AddNewKeyRequest(gtx *gin.Context) { for _, i := range infos { if i.GetName() == m.Name { - httputils.NewError(gtx, http.StatusConflict, fmt.Errorf("Account with name %s already exists.", m.Name)) + httputils.NewError(gtx, http.StatusConflict, fmt.Errorf("account with name %s already exists", m.Name)) return } } @@ -306,7 +308,13 @@ func AddNewKeyRequest(gtx *gin.Context) { keyOutput.Seed = seed - httputils.NormalResponse(gtx, keyOutput) + bz, err := json.Marshal(keyOutput) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.NormalResponse(gtx, bz) } @@ -333,12 +341,12 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(seed)) } -// Handler of creating seed in swagger rest server +// SeedRequest is the handler of creating seed in swagger rest server func SeedRequest(gtx *gin.Context) { algo := keys.SigningAlgo("secp256k1") seed := getSeed(algo) - httputils.NormalResponse(gtx, seed) + httputils.NormalResponse(gtx, []byte(seed)) } \ No newline at end of file diff --git a/client/keys/delete.go b/client/keys/delete.go index 30db0b93b390..62cef116c7a4 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -93,7 +93,7 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } -// Handler of deleting specified key in swagger rest server +// DeleteKeyRequest is the handler of deleting specified key in swagger rest server func DeleteKeyRequest(gtx *gin.Context) { name := gtx.Param("name") var kb keys.Keybase @@ -117,5 +117,5 @@ func DeleteKeyRequest(gtx *gin.Context) { return } - httputils.NormalResponse(gtx, "success") + httputils.NormalResponse(gtx, []byte("success")) } \ No newline at end of file diff --git a/client/keys/list.go b/client/keys/list.go index 9ae74bda7fc8..934c0743f007 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -70,7 +70,7 @@ func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(output) } -// Handler of listing all keys in swagger rest server +// DeleteKeyRequest is the handler of listing all keys in swagger rest server func QueryKeysRequest(gtx *gin.Context) { kb, err := GetKeyBase() if err != nil { @@ -92,5 +92,10 @@ func QueryKeysRequest(gtx *gin.Context) { httputils.NewError(gtx, http.StatusInternalServerError, err) return } - httputils.NormalResponse(gtx, keysOutput) + output, err := json.MarshalIndent(keysOutput, "", " ") + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + httputils.NormalResponse(gtx, output) } \ No newline at end of file diff --git a/client/keys/root.go b/client/keys/root.go index d7b32c81a9bb..1a6edc40d21f 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -40,7 +40,7 @@ func RegisterRoutes(r *mux.Router) { r.HandleFunc("/keys/{name}", DeleteKeyRequestHandler).Methods("DELETE") } -// resgister swagger REST routes +// RegisterSwaggerRoutes - Central function to define key management related routes that get registered by the main application func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/keys", QueryKeysRequest) routerGroup.POST("/keys", AddNewKeyRequest) diff --git a/client/keys/show.go b/client/keys/show.go index 87d2c181b368..caff452c95ee 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -68,7 +68,7 @@ func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(output) } -// Handler of getting specified key in swagger rest server +// GetKeyRequest is the handler of getting specified key in swagger rest server func GetKeyRequest(gtx *gin.Context) { name := gtx.Param("name") @@ -84,5 +84,10 @@ func GetKeyRequest(gtx *gin.Context) { httputils.NewError(gtx, http.StatusInternalServerError, err) return } - httputils.NormalResponse(gtx, keyOutput) + output, err := json.MarshalIndent(keyOutput, "", " ") + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + httputils.NormalResponse(gtx, output) } \ No newline at end of file diff --git a/client/keys/update.go b/client/keys/update.go index edd66dd35c1b..3f7ab7c0944e 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -97,7 +97,7 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } -// Handler of updating specified key in swagger rest server +// UpdateKeyRequest is the handler of updating specified key in swagger rest server func UpdateKeyRequest(gtx *gin.Context) { name := gtx.Param("name") var kb keys.Keybase @@ -128,5 +128,5 @@ func UpdateKeyRequest(gtx *gin.Context) { return } - httputils.NormalResponse(gtx, "success") + httputils.NormalResponse(gtx, []byte("success")) } \ No newline at end of file diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go index 5bda80b26352..0af26374a551 100644 --- a/client/lcd/docs/docs.go +++ b/client/lcd/docs/docs.go @@ -36,9 +36,9 @@ var doc = `{ "host": "localhost:1317", "basePath": "/", "paths": { - "/stake/{delegator}/delegation/{validator}": { + "/stake/delegators/{delegatorAddr}": { "get": { - "description": "Get the delegation information between specific delegator and validator", + "description": "Get all delegations (delegation, undelegation and redelegation) from a delegator", "consumes": [ "application/json" ], @@ -48,18 +48,118 @@ var doc = `{ "tags": [ "Stake Operation" ], - "summary": "Query delegation information", + "summary": "Get all delegations from a delegator", "parameters": [ { "type": "string", "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", - "name": "delegator", + "name": "delegatorAddr", "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/stake.DelegationSummary" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, + "/stake/delegators/{delegatorAddr}/txs": { + "get": { + "description": "Get all staking txs (i.e msgs) from a delegator", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Get all staking txs from a delegator", + "parameters": [ { "type": "string", - "description": "validator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", - "name": "validator", + "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "delegatorAddr", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/stake.txInfoArray" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, + "/stake/delegators/{delegatorAddr}/validators": { + "get": { + "description": "Query all validators that a delegator is bonded to", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query all validators that a delegator is bonded to", + "parameters": [ + { + "type": "string", + "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "delegatorAddr", "in": "path" } ], @@ -68,7 +168,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.stake_delegation" + "$ref": "#/definitions/stake.BechValidatorArray" } }, "400": { @@ -95,9 +195,9 @@ var doc = `{ } } }, - "/stake/{delegator}/ubd/{validator}": { + "/stake/delegators/{delegatorAddr}/validators/{validatorAddr}": { "get": { - "description": "Get the unbound information between specific delegator and validator", + "description": "Query a validator that a delegator is bonded to", "consumes": [ "application/json" ], @@ -107,18 +207,18 @@ var doc = `{ "tags": [ "Stake Operation" ], - "summary": "Query unbound information", + "summary": "Query a validator that a delegator is bonded to", "parameters": [ { "type": "string", "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", - "name": "delegator", + "name": "delegatorAddr", "in": "path" }, { "type": "string", "description": "validator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", - "name": "validator", + "name": "validatorAddr", "in": "path" } ], @@ -127,7 +227,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.stake_ubd" + "$ref": "#/definitions/stake.BechValidator" } }, "400": { @@ -154,9 +254,9 @@ var doc = `{ } } }, - "/stake/{delegator}/red/{validator_src}/{validator_dst}": { + "/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}": { "get": { - "description": "Get the re-delegation information between specific delegator and validator", + "description": "Query a delegation between a delegator and a validator", "consumes": [ "application/json" ], @@ -166,24 +266,77 @@ var doc = `{ "tags": [ "Stake Operation" ], - "summary": "Query re-delegation information", + "summary": "Query a delegation between a delegator and a validator", "parameters": [ { "type": "string", "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", - "name": "delegator", + "name": "delegatorAddr", "in": "path" }, { "type": "string", - "description": "validator source address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", - "name": "validator_src", + "description": "validator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "validatorAddr", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/stake.DelegationWithoutRat" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, + "/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}": { + "get": { + "description": "Query all unbonding_delegations between a delegator and a validator", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query all unbonding_delegations between a delegator and a validator", + "parameters": [ + { + "type": "string", + "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "delegatorAddr", "in": "path" }, { "type": "string", - "description": "validator destination address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", - "name": "validator_dst", + "description": "validator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "validatorAddr", "in": "path" } ], @@ -192,7 +345,52 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.stake_red" + "$ref": "#/definitions/stake.UnbondingDelegationArray" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, + "/stake/validators": { + "get": { + "description": "Get all validators", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Get all validators", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/stake.BechValidatorArray" } }, "400": { @@ -219,9 +417,9 @@ var doc = `{ } } }, - "/stake_validators": { + "/stake/validators/{addr}": { "get": { - "description": "Query all validators' information", + "description": "Get a single validator info", "consumes": [ "application/json" ], @@ -231,13 +429,84 @@ var doc = `{ "tags": [ "Stake Operation" ], - "summary": "Query all validators' information", + "summary": "Get a single validator info", + "parameters": [ + { + "type": "string", + "description": "validator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "addr", + "in": "path" + } + ], "responses": { "200": { "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.stake_validators" + "$ref": "#/definitions/stake.BechValidator" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, + "/stake/delegators/:delegatorAddr/delegations": { + "post": { + "description": "Send stake related transactions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Send stake related transaction", + "parameters": [ + { + "type": "string", + "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "delegatorAddr", + "in": "path" + }, + { + "description": "delegation parameters", + "name": "EditDelegationsBody", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/stake.EditDelegationsBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/stake.transactionResult" } }, "400": { @@ -290,7 +559,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.acoount_query" + "$ref": "#/definitions/auth.BaseAccount" } }, "400": { @@ -346,8 +615,7 @@ var doc = `{ "200": { "description": "OK. The returned string is base64 encoding", "schema": { - "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.string" + "type": "string" } }, "400": { @@ -404,7 +672,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.tx_commit" + "$ref": "#/definitions/bank.ResultBroadcastTxCommit" } }, "400": { @@ -467,7 +735,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.tx_commit" + "$ref": "#/definitions/bank.ResultBroadcastTxCommit" } }, "400": { @@ -513,7 +781,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.keys.list" + "$ref": "#/definitions/keys.KeyOutputs" } }, "400": { @@ -568,7 +836,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.keys.add" + "$ref": "#/definitions/keys.NewKeyResponse" } }, "400": { @@ -621,7 +889,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.keys.get" + "$ref": "#/definitions/keys.KeyOutput" } }, "400": { @@ -665,8 +933,7 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.string" + "type": "string" } }, "400": { @@ -727,8 +994,7 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.string" + "type": "string" } }, "400": { @@ -787,8 +1053,7 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.string" + "type": "string" } }, "400": { @@ -832,8 +1097,7 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.string" + "type": "string" } }, "400": { @@ -877,8 +1141,7 @@ var doc = `{ "200": { "description": "OK", "schema": { - "type": "object", - "$ref": "#/definitions/httputil.HTTPResponse.string" + "type": "string" } }, "400": { @@ -937,10 +1200,6 @@ var doc = `{ "data": { "type": "string" }, - "fee": { - "type": "object", - "$ref": "#/definitions/common.KI64Pair" - }, "gas_used": { "type": "integer" }, @@ -971,10 +1230,6 @@ var doc = `{ "data": { "type": "string" }, - "fee": { - "type": "object", - "$ref": "#/definitions/common.KI64Pair" - }, "gas_used": { "type": "integer" }, @@ -1050,7 +1305,7 @@ var doc = `{ "ensure_account_sequence": { "type": "boolean" }, - "chain_id": { + "chain_id": { "type": "string" }, "from_address": { @@ -1061,7 +1316,7 @@ var doc = `{ }, "fee": { "type": "string", - "example": "10 monikerToken" + "example": "10 monikerToken" }, "sequence": { "type": "integer" @@ -1095,7 +1350,7 @@ var doc = `{ }, "fee": { "type": "string", - "example": "10 monikerToken" + "example": "10 monikerToken" }, "sequence": { "type": "integer" @@ -1210,7 +1465,7 @@ var doc = `{ "password": { "type": "string" }, - "seed": { + "seed": { "type": "string" } } @@ -1242,125 +1497,143 @@ var doc = `{ } } }, - "httputil.HTTPResponse.keys.list": { + "stake.msgDelegationsInput": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" + "delegator_addr": { + "type": "string" }, - "code": { - "type": "integer", - "example": 0 + "validator_addr": { + "type": "string" }, - "result": { + "delegation": { "type": "object", - "$ref": "#/definitions/keys.KeyOutputs" + "$ref": "#/definitions/sdk.Coin" } } }, - "httputil.HTTPResponse.keys.add": { + "stake.msgBeginUnbondingInput": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" + "delegator_addr": { + "type": "string" }, - "code": { - "type": "integer", - "example": 0 + "validator_addr": { + "type": "string" }, - "result": { - "type": "object", - "$ref": "#/definitions/keys.NewKeyResponse" + "shares": { + "type": "string" } } }, - "httputil.HTTPResponse.keys.get": { + "stake.msgCompleteUnbondingInput": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" - }, - "code": { - "type": "integer", - "example": 0 + "delegator_addr": { + "type": "string" }, - "result": { - "type": "object", - "$ref": "#/definitions/keys.KeyOutput" + "validator_addr": { + "type": "string" } } }, - "httputil.HTTPResponse.string": { + "stake.msgBeginRedelegateInput": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" - }, - "code": { - "type": "integer", - "example": 0 + "delegator_addr": { + "type": "string" }, - "result": { + "validator_src_addr": { "type": "string" - } - } - }, - "httputil.HTTPResponse.acoount_query": { - "type": "object", - "properties": { - "rest api": { - "type": "string", - "example": "2.0" }, - "code": { - "type": "integer", - "example": 0 + "validator_dst_addr": { + "type": "string" }, - "result": { - "type": "object", - "$ref": "#/definitions/auth.BaseAccount" + "shares": { + "type": "string" } } }, - "httputil.HTTPResponse.tx_commit": { + "stake.msgCompleteRedelegateInput": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" + "delegator_addr": { + "type": "string" }, - "code": { - "type": "integer", - "example": 0 + "validator_src_addr": { + "type": "string" }, - "result": { - "type": "object", - "$ref": "#/definitions/bank.ResultBroadcastTxCommit" + "validator_dst_addr": { + "type": "string" } } }, - "stake.Delegation":{ + "stake.EditDelegationsBody": { "type": "object", "properties": { - "delegator_addr": { + "name": { "type": "string" }, - "validator_addr": { + "password": { "type": "string" }, - "shares": { + "chain_id": { "type": "string" }, - "height": { + "account_number": { + "type": "integer" + }, + "sequence": { + "type": "integer" + }, + "gas": { "type": "integer" + }, + "delegations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.msgDelegationsInput" + } + }, + "begin_unbondings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.msgBeginUnbondingInput" + } + }, + "complete_unbondings": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.msgCompleteUnbondingInput" + } + }, + "begin_redelegates": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.msgBeginRedelegateInput" + } + }, + "complete_redelegates": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.msgCompleteRedelegateInput" + } } } }, - "stake.UnbondingDelegation":{ + "stake.transactionResult": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/bank.ResultBroadcastTxCommit" + } + }, + "stake.UnbondingDelegation": { "type": "object", "properties": { "delegator_addr": { @@ -1385,7 +1658,14 @@ var doc = `{ } } }, - "stake.Redelegation":{ + "stake.UnbondingDelegationArray": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.UnbondingDelegation" + } + }, + "stake.Redelegation": { "type": "object", "properties": { "delegator_addr": { @@ -1419,32 +1699,34 @@ var doc = `{ } } }, - "stake.PoolShares": { - "status": { - "type": "string" - }, - "amount": { - "type": "string" + "stake.RedelegationArray": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.Redelegation" } }, "stake.Description": { - "moniker": { - "type": "string" - }, - "identity": { - "type": "string" - }, - "website": { - "type": "string" - }, - "details": { - "type": "string" + "type": "object", + "properties": { + "moniker": { + "type": "string" + }, + "identity": { + "type": "string" + }, + "website": { + "type": "string" + }, + "details": { + "type": "string" + } } }, - "stake.StakeValidatorOutput":{ + "stake.BechValidator": { "type": "object", "properties": { - "owner": { + "operator": { "type": "string" }, "pub_key": { @@ -1453,9 +1735,11 @@ var doc = `{ "revoked": { "type": "boolean" }, - "pool_shares": { - "type": "object", - "$ref": "#/definitions/stake.PoolShares" + "status": { + "type": "integer" + }, + "tokens": { + "type": "string" }, "delegator_shares": { "type": "string" @@ -1485,78 +1769,83 @@ var doc = `{ }, "commission_change_today": { "type": "string" + }, + "prev_bonded_shares": { + "type": "string" } } }, - "httputil.HTTPResponse.stake_delegation": { + "stake.BechValidatorArray": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.BechValidator" + } + }, + "stake.DelegationWithoutRat": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" + "delegator_addr": { + "type": "string" }, - "code": { - "type": "integer", - "example": 0 + "validator_addr": { + "type": "string" }, - "result": { - "type": "object", - "$ref": "#/definitions/stake.Delegation" + "shares": { + "type": "string" + }, + "height": { + "type": "integer" } } }, - "httputil.HTTPResponse.stake_ubd": { + "stake.DelegationWithoutRatArray": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.DelegationWithoutRat" + } + }, + "stake.DelegationSummary": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" + "redelegations": { + "type": "object", + "$ref": "#/definitions/stake.RedelegationArray" }, - "code": { - "type": "integer", - "example": 0 + "unbonding_delegations": { + "type": "object", + "$ref": "#/definitions/stake.UnbondingDelegationArray" }, - "result": { + "delegations": { "type": "object", - "$ref": "#/definitions/stake.UnbondingDelegation" + "$ref": "#/definitions/stake.DelegationWithoutRatArray" } } }, - "httputil.HTTPResponse.stake_red": { + "stake.txInfo": { "type": "object", "properties": { - "rest api": { - "type": "string", - "example": "2.0" + "hash": { + "type": "string" }, - "code": { - "type": "integer", - "example": 0 + "height": { + "type": "integer" + }, + "tx": { + "type": "string" }, "result": { "type": "object", - "$ref": "#/definitions/stake.Redelegation" + "$ref": "#/definitions/bank.ResponseDeliverTx" } } }, - "httputil.HTTPResponse.stake_validators": { - "type": "object", - "properties": { - "rest api": { - "type": "string", - "example": "2.0" - }, - "code": { - "type": "integer", - "example": 0 - }, - "result": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/stake.StakeValidatorOutput" - } - } + "stake.txInfoArray": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/stake.txInfo" } } } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 09c0eddd1116..f0b8c8695b1e 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -29,6 +29,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/client/rest" + "encoding/json" ) func init() { @@ -115,6 +116,88 @@ func TestKeys(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode, body) } +func TestKeysSwaggerLCD(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, _, port := InitializeTestSwaggerLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // get seed + // TODO Do we really need this endpoint? + res, body := Request(t, port, "GET", "/keys/seed", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + reg, err := regexp.Compile(`([a-z]+ ){12}`) + require.Nil(t, err) + match := reg.MatchString(seed) + require.True(t, match, "Returned seed has wrong format", seed) + + newName := "test_newname" + newPassword := "0987654321" + + // add key + jsonStr := []byte(fmt.Sprintf(`{"name":"%s", "password":"%s", "seed":"%s"}`, newName, newPassword, seed)) + res, body = Request(t, port, "POST", "/keys", jsonStr) + + require.Equal(t, http.StatusOK, res.StatusCode, body) + var keyOutput keys.KeyOutput + err = json.Unmarshal([]byte(body), &keyOutput) + require.Nil(t, err, body) + + addr2Bech32 := keyOutput.Address.String() + _, err = sdk.AccAddressFromBech32(addr2Bech32) + require.NoError(t, err, "Failed to return a correct bech32 address") + + // test if created account is the correct account + expectedInfo, _ := GetKeyBase(t).CreateKey(newName, seed, newPassword) + expectedAccount := sdk.AccAddress(expectedInfo.GetPubKey().Address().Bytes()) + assert.Equal(t, expectedAccount.String(), addr2Bech32) + + // existing keys + res, body = Request(t, port, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var m [2]keys.KeyOutput + err = cdc.UnmarshalJSON([]byte(body), &m) + require.Nil(t, err) + + addrBech32 := addr.String() + + require.Equal(t, name, m[0].Name, "Did not serve keys name correctly") + require.Equal(t, addrBech32, m[0].Address.String(), "Did not serve keys Address correctly") + require.Equal(t, newName, m[1].Name, "Did not serve keys name correctly") + require.Equal(t, addr2Bech32, m[1].Address.String(), "Did not serve keys Address correctly") + + // select key + keyEndpoint := fmt.Sprintf("/keys/get/%s", newName) + res, body = Request(t, port, "GET", keyEndpoint, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var m2 keys.KeyOutput + err = cdc.UnmarshalJSON([]byte(body), &m2) + require.Nil(t, err) + + require.Equal(t, newName, m2.Name, "Did not serve keys name correctly") + require.Equal(t, addr2Bech32, m2.Address.String(), "Did not serve keys Address correctly") + + // update key + jsonStr = []byte(fmt.Sprintf(`{ + "old_password":"%s", + "new_password":"12345678901" + }`, newPassword)) + + keyEndpoint = fmt.Sprintf("/keys/%s", newName) + res, body = Request(t, port, "PUT", keyEndpoint, jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + // here it should say unauthorized as we changed the password before + res, body = Request(t, port, "PUT", keyEndpoint, jsonStr) + require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) + + // delete key + jsonStr = []byte(`{"password":"12345678901"}`) + res, body = Request(t, port, "DELETE", keyEndpoint, jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) +} + func TestVersion(t *testing.T) { cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() @@ -138,6 +221,29 @@ func TestVersion(t *testing.T) { require.True(t, match, body) } +func TestVersionSwaggerLCD(t *testing.T) { + cleanup, _, port := InitializeTestSwaggerLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + + // node info + res, body := Request(t, port, "GET", "/version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + require.Nil(t, err) + match := reg.MatchString(body) + require.True(t, match, body) + + // node info + res, body = Request(t, port, "GET", "/node_version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err = regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + require.Nil(t, err) + match = reg.MatchString(body) + require.True(t, match, body) +} + func TestNodeStatus(t *testing.T) { cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() @@ -265,6 +371,50 @@ func TestCoinSend(t *testing.T) { require.Equal(t, int64(1), mycoins.Amount.Int64()) } +func TestCoinSendSwaggerLCD(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, _, port := InitializeTestSwaggerLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") + require.NoError(t, err) + someFakeAddr := sdk.AccAddress(bz) + + // query empty + res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", someFakeAddr), nil) + require.Equal(t, http.StatusNoContent, res.StatusCode, body) + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create TX + receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) + // TODO current lcd rest server with swagger doesn't implement block interface + //tests.WaitForHeight(resultTx.Height+1, port) + time.Sleep(5 * time.Second) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + mycoins := coins[0] + + require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, initialBalance[0].Amount.SubRaw(1), mycoins.Amount) + + // query receiver + acc = getAccount(t, port, receiveAddr) + coins = acc.GetCoins() + mycoins = coins[0] + + require.Equal(t, "steak", mycoins.Denom) + require.Equal(t, int64(1), mycoins.Amount.Int64()) +} + func TestIBCTransfer(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) @@ -370,6 +520,23 @@ func TestValidatorsQuery(t *testing.T) { require.True(t, foundVal, "pkBech %v, operator %v", pkBech, validators[0].Operator) } +func TestValidatorsQueryFromSwaggerLCD(t *testing.T) { + cleanup, pks, port := InitializeTestSwaggerLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + require.Equal(t, 1, len(pks)) + + validators := getValidators(t, port) + require.Equal(t, len(validators), 1) + + // make sure all the validators were found (order unknown because sorted by operator addr) + foundVal := false + pkBech := sdk.MustBech32ifyValPub(pks[0]) + if validators[0].PubKey == pkBech { + foundVal = true + } + require.True(t, foundVal, "pkBech %v, operator %v", pkBech, validators[0].Operator) +} + func TestValidatorQuery(t *testing.T) { cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() @@ -380,6 +547,16 @@ func TestValidatorQuery(t *testing.T) { assert.Equal(t, validator.Operator, validator1Operator, "The returned validator does not hold the correct data") } +func TestValidatorQueryFromSwaggerLCD(t *testing.T) { + cleanup, pks, port := InitializeTestSwaggerLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + require.Equal(t, 1, len(pks)) + + validator1Operator := sdk.AccAddress(pks[0].Address()) + validator := getValidator(t, port, validator1Operator) + assert.Equal(t, validator.Operator, validator1Operator, "The returned validator does not hold the correct data") +} + func TestBonding(t *testing.T) { name, password, denom := "test", "1234567890", "steak" addr, seed := CreateAddr(t, name, password, GetKeyBase(t)) @@ -465,6 +642,95 @@ func TestBonding(t *testing.T) { assert.Len(t, txs, 1, "All unbonding txs found") } +func TestBondingFromSwaggerLCD(t *testing.T) { + name, password, denom := "test", "1234567890", "steak" + addr, seed := CreateAddr(t, name, password, GetKeyBase(t)) + cleanup, pks, port := InitializeTestSwaggerLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + validator1Operator := sdk.AccAddress(pks[0].Address()) + validator := getValidator(t, port, validator1Operator) + + // create bond TX + resultTx := doDelegate(t, port, seed, name, password, addr, validator1Operator, 60) + // TODO current lcd rest server with swagger doesn't implement block interface + //tests.WaitForHeight(resultTx.Height+1, port) + time.Sleep(6 * time.Second) + + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + acc := getAccount(t, port, addr) + coins := acc.GetCoins() + + require.Equal(t, int64(40), coins.AmountOf(denom).Int64()) + + // query validator + bond := getDelegation(t, port, addr, validator1Operator) + require.Equal(t, "60.0000000000", bond.Shares) + + summary := getDelegationSummary(t, port, addr) + + require.Len(t, summary.Delegations, 1, "Delegation summary holds all delegations") + require.Equal(t, "60.0000000000", summary.Delegations[0].Shares) + require.Len(t, summary.UnbondingDelegations, 0, "Delegation summary holds all unbonding-delegations") + + bondedValidators := getDelegatorValidators(t, port, addr) + require.Len(t, bondedValidators, 1) + require.Equal(t, validator1Operator, bondedValidators[0].Operator) + require.Equal(t, validator.DelegatorShares.Add(sdk.NewDec(60)).String(), bondedValidators[0].DelegatorShares.String()) + + bondedValidator := getDelegatorValidator(t, port, addr, validator1Operator) + require.Equal(t, validator1Operator, bondedValidator.Operator) + + ////////////////////// + // testing unbonding + + // create unbond TX + resultTx = doBeginUnbonding(t, port, seed, name, password, addr, validator1Operator, 60) + // TODO current lcd rest server with swagger doesn't implement block interface + //tests.WaitForHeight(resultTx.Height+1, port) + time.Sleep(6 * time.Second) + + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // sender should have not received any coins as the unbonding has only just begun + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + require.Equal(t, int64(40), coins.AmountOf("steak").Int64()) + + unbondings := getUndelegations(t, port, addr, validator1Operator) + require.Len(t, unbondings, 1, "Unbondings holds all unbonding-delegations") + require.Equal(t, "60", unbondings[0].Balance.Amount.String()) + + summary = getDelegationSummary(t, port, addr) + + require.Len(t, summary.Delegations, 0, "Delegation summary holds all delegations") + require.Len(t, summary.UnbondingDelegations, 1, "Delegation summary holds all unbonding-delegations") + require.Equal(t, "60", summary.UnbondingDelegations[0].Balance.Amount.String()) + + bondedValidators = getDelegatorValidators(t, port, addr) + require.Len(t, bondedValidators, 0, "There's no delegation as the user withdraw all funds") + + // TODO Undonding status not currently implemented + // require.Equal(t, sdk.Unbonding, bondedValidators[0].Status) + + // TODO add redelegation, need more complex capabilities such to mock context and + // TODO check summary for redelegation + // assert.Len(t, summary.Redelegations, 1, "Delegation summary holds all redelegations") + + // query txs + txs := getBondingTxs(t, port, addr, "") + assert.Len(t, txs, 2, "All Txs found") + + txs = getBondingTxs(t, port, addr, "bond") + assert.Len(t, txs, 1, "All bonding txs found") + + txs = getBondingTxs(t, port, addr, "unbond") + assert.Len(t, txs, 1, "All unbonding txs found") +} + func TestSubmitProposal(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) diff --git a/client/lcd/root.go b/client/lcd/root.go index 06a0b4a18185..23488c05aee0 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -28,7 +28,6 @@ import ( "strings" "github.com/tendermint/tendermint/libs/cli" tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" - keyTypes "github.com/cosmos/cosmos-sdk/crypto/keys" "fmt" ) @@ -115,37 +114,10 @@ func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). With("module", "rest-server-swagger") - - rootDir := viper.GetString(cli.HomeFlag) - nodeAddrs := viper.GetString(client.FlagNodeList) - chainID := viper.GetString(client.FlagChainID) listenAddr := viper.GetString(client.FlagListenAddr) - //Get key store - kb, err := keys.GetKeyBase() - if err != nil { - panic(err) - } - //Split the node list string into multi full node URIs - nodeAddrArray := strings.Split(nodeAddrs,",") - if len(nodeAddrArray) < 1 { - panic(fmt.Errorf("missing node URIs")) - } - //Tendermint certifier can only connect to one full node. Here we assign the first full node to it - cert,err := tendermintLiteProxy.GetCertifier(chainID, rootDir, nodeAddrArray[0]) - if err != nil { - panic(err) - } - //Create load balancing engine - clientMgr,err := context.NewClientManager(nodeAddrs) - if err != nil { - panic(err) - } - //Assign tendermint certifier and load balancing engine to ctx - ctx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout).WithCert(cert).WithClientMgr(clientMgr) - //Create rest server server := gin.New() - createSwaggerHandler(server, ctx, cdc, kb) + createSwaggerHandler(server, cdc) go server.Run(listenAddr) logger.Info("REST server started") @@ -169,12 +141,37 @@ func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { return cmd } -func createSwaggerHandler(server *gin.Engine, ctx context.CLIContext, cdc *wire.Codec, kb keyTypes.Keybase) { +func createSwaggerHandler(server *gin.Engine, cdc *wire.Codec) { + rootDir := viper.GetString(cli.HomeFlag) + nodeAddrs := viper.GetString(client.FlagNodeList) + chainID := viper.GetString(client.FlagChainID) + modules := viper.GetString(client.FlagModules) + //Get key store + kb, err := keys.GetKeyBase() + if err != nil { + panic(err) + } + //Split the node list string into multi full node URIs + nodeAddrArray := strings.Split(nodeAddrs,",") + if len(nodeAddrArray) < 1 { + panic(fmt.Errorf("missing node URIs")) + } + //Tendermint certifier can only connect to one full node. Here we assign the first full node to it + cert,err := tendermintLiteProxy.GetCertifier(chainID, rootDir, nodeAddrArray[0]) + if err != nil { + panic(err) + } + //Create load balancing engine + clientMgr,err := context.NewClientManager(nodeAddrs) + if err != nil { + panic(err) + } + //Assign tendermint certifier and load balancing engine to ctx + ctx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout).WithCert(cert).WithClientMgr(clientMgr) + server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) - modules := viper.GetString(client.FlagModules) moduleArray := strings.Split(modules,",") - if moduleEnabled(moduleArray,"general") { server.GET("/version", CLIVersionRequest) server.GET("/node_version", NodeVersionRequest(ctx)) @@ -190,7 +187,7 @@ func createSwaggerHandler(server *gin.Engine, ctx context.CLIContext, cdc *wire. } if moduleEnabled(moduleArray,"stake") { - stake.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc) + stake.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, kb) } } diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 818eae1e86f6..e711fbea9b01 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -37,6 +37,8 @@ import ( "github.com/tendermint/tendermint/proxy" tmrpc "github.com/tendermint/tendermint/rpc/lib/server" tmtypes "github.com/tendermint/tendermint/types" + "github.com/gin-gonic/gin" + "time" ) // makePathname creates a unique pathname for each test. It will panic if it @@ -206,6 +208,106 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress return cleanup, validatorsPKs, port } +// InitializeTestSwaggerLCD starts Tendermint and the LCD swagger rest server in process, listening on +// their respective sockets where nValidators is the total number of validators +// and initAddrs are the accounts to initialize with some steak tokens. It +// returns a cleanup function, a set of validator public keys, and a port. +func InitializeTestSwaggerLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress) (func(), []crypto.PubKey, string) { + config := GetConfig() + config.Consensus.TimeoutCommit = 100 + config.Consensus.SkipTimeoutCommit = false + config.TxIndex.IndexAllTags = true + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger = log.NewFilter(logger, log.AllowError()) + + privValidatorFile := config.PrivValidatorFile() + privVal := pvm.LoadOrGenFilePV(privValidatorFile) + privVal.Reset() + + db := dbm.NewMemDB() + app := gapp.NewGaiaApp(logger, db, nil) + cdc = gapp.MakeCodec() + + genesisFile := config.GenesisFile() + genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) + require.NoError(t, err) + + if nValidators < 1 { + panic("InitializeTestLCD must use at least one validator") + } + + for i := 1; i < nValidators; i++ { + genDoc.Validators = append(genDoc.Validators, + tmtypes.GenesisValidator{ + PubKey: ed25519.GenPrivKey().PubKey(), + Power: 1, + Name: "val", + }, + ) + } + + var validatorsPKs []crypto.PubKey + + // NOTE: It's bad practice to reuse public key address for the owner + // address but doing in the test for simplicity. + var appGenTxs []json.RawMessage + for _, gdValidator := range genDoc.Validators { + pk := gdValidator.PubKey + validatorsPKs = append(validatorsPKs, pk) + + appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, sdk.AccAddress(pk.Address()), "test_val1") + require.NoError(t, err) + + appGenTxs = append(appGenTxs, appGenTx) + } + + genesisState, err := gapp.GaiaAppGenState(cdc, appGenTxs[:]) + require.NoError(t, err) + + // add some tokens to init accounts + for _, addr := range initAddrs { + accAuth := auth.NewBaseAccountWithAddress(addr) + accAuth.Coins = sdk.Coins{sdk.NewInt64Coin("steak", 100)} + acc := gapp.NewGenesisAccount(&accAuth) + genesisState.Accounts = append(genesisState.Accounts, acc) + genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewDec(100)) + } + + appState, err := wire.MarshalJSONIndent(cdc, genesisState) + require.NoError(t, err) + genDoc.AppState = appState + + _, port, err := server.FreeTCPAddr() + require.NoError(t, err) + + // XXX: Need to set this so LCD knows the tendermint node address! + viper.Set(client.FlagNodeList, config.RPC.ListenAddress) + viper.Set(client.FlagChainID, genDoc.ChainID) + viper.Set(client.FlagListenAddr, fmt.Sprintf("localhost:%s",port)) + viper.Set(client.FlagSwaggerHostIP, "localhost") + viper.Set(client.FlagModules, "general,key,token,stake") + viper.Set(client.FlagTrustNode, false) + + node, err := startTM(config, logger, genDoc, privVal, app) + require.NoError(t, err) + + time.Sleep(2 * time.Second) + startSwaggerLCD(cdc) + time.Sleep(1 * time.Second) + + //tests.WaitForLCDStart(port) + //tests.WaitForHeight(1, port) + + cleanup := func() { + logger.Debug("cleaning up LCD initialization") + node.Stop() + node.Wait() + } + + return cleanup, validatorsPKs, port +} + // startTM creates and starts an in-process Tendermint node with memDB and // in-process ABCI application. It returns the new node or any error that // occurred. @@ -248,6 +350,14 @@ func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listen return tmrpc.StartHTTPServer(listenAddr, createHandler(cdc), logger, tmrpc.Config{}) } +func startSwaggerLCD(cdc *wire.Codec) { + //Create swagger rest server + ginServer := gin.New() + createSwaggerHandler(ginServer, cdc) + listenAddr := viper.GetString(client.FlagListenAddr) + go ginServer.Run(listenAddr) +} + // Request makes a test LCD test request. It returns a response object and a // stringified response body. func Request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { diff --git a/client/lcd/version.go b/client/lcd/version.go index e7dd6ee8f961..925fd419e926 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -31,13 +31,13 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { } } -// handler of getting rest server version +// CLIVersionRequest is the handler of getting rest server version func CLIVersionRequest(gtx *gin.Context) { v := version.GetVersion() - httputils.NormalResponse(gtx,v) + httputils.NormalResponse(gtx, []byte(v)) } -// handler of getting connected node version +// NodeVersionRequest is the handler of getting connected node version func NodeVersionRequest(cliCtx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { appVersion, err := cliCtx.Query("/app/version") @@ -45,6 +45,6 @@ func NodeVersionRequest(cliCtx context.CLIContext) gin.HandlerFunc { httputil.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("could't query version. error: %s", err.Error())) return } - httputils.NormalResponse(gtx,string(appVersion)) + httputils.NormalResponse(gtx, appVersion) } } \ No newline at end of file diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 6a9a40a60a71..aae2a1da2a41 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -72,13 +72,12 @@ func QueryAccountRequestHandlerFn( } } -// register to Cosmos-LCD swagger routes +// RegisterSwaggerRoutes - Central function to define account query related routes that get registered by the main application func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, storeName string) { - routerGroup.GET("accounts/:address",QueryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) + routerGroup.GET("accounts/:address",queryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) } -// handler of query account in swagger rest server -func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { +func queryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { bech32addr := gtx.Param("address") @@ -97,7 +96,7 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth. // the query will return empty if there is no data for this account if len(res) == 0 { - httputils.NormalResponse(gtx,nil) + httputils.NewError(gtx, http.StatusNoContent, fmt.Errorf("this account info is nil+")) return } @@ -108,6 +107,13 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth. return } - httputils.NormalResponse(gtx,account) + // print out whole account + output, err := cdc.MarshalJSON(account) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't marshall query result. Error: %s", err.Error())) + return + } + + httputils.NormalResponse(gtx,output) } } \ No newline at end of file diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 4534302a1cd8..554155f3daac 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -211,14 +211,20 @@ func composeTx(cdc *wire.Codec, ctx context.CLIContext, transferBody transferBod func createTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { var transferBody transferBody - if err := gtx.BindJSON(&transferBody); err != nil { + body, err := ioutil.ReadAll(gtx.Request.Body) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + err = cdc.UnmarshalJSON(body, &transferBody) + if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } - txForSign, _, err := composeTx(cdc, ctx, transferBody) + txForSign, _, errMsg := composeTx(cdc, ctx, transferBody) if err != nil { - if err.Code() == sdk.CodeInternal { + if errMsg.Code() == sdk.CodeInternal { httputils.NewError(gtx, http.StatusInternalServerError, err) } else { httputils.NewError(gtx, http.StatusBadRequest, err) @@ -229,15 +235,22 @@ func createTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.Hand base64TxData := make([]byte, base64.StdEncoding.EncodedLen(len(txForSign.Bytes()))) base64.StdEncoding.Encode(base64TxData,txForSign.Bytes()) - httputils.NormalResponse(gtx,string(base64TxData)) + httputils.NormalResponse(gtx,base64TxData) } } +// nolint: gocyclo // handler of composing and broadcasting transactions in swagger rest server func composeAndBroadcastSignedTransferTxFn(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { var signedTransaction signedBody - if err := gtx.BindJSON(&signedTransaction); err != nil { + body, err := ioutil.ReadAll(gtx.Request.Body) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + err = cdc.UnmarshalJSON(body, &signedTransaction) + if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } @@ -279,7 +292,13 @@ func composeAndBroadcastSignedTransferTxFn(cdc *wire.Codec, ctx context.CLIConte return } - httputils.NormalResponse(gtx,res) + output, err := wire.MarshalJSONIndent(cdc, res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.NormalResponse(gtx, output) } } @@ -296,7 +315,13 @@ func sendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin } var m sendBody - if err := gtx.BindJSON(&m); err != nil { + body, err := ioutil.ReadAll(gtx.Request.Body) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + err = msgCdc.UnmarshalJSON(body, &m) + if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } @@ -343,6 +368,12 @@ func sendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin return } - httputils.NormalResponse(gtx,res) + output, err := wire.MarshalJSONIndent(cdc, res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.NormalResponse(gtx, output) } } \ No newline at end of file diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 8e729486bb5c..dcfbc1350a94 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -558,18 +558,23 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.Handler } func registerSwaggerQueryRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { - routerGroup.GET("/stake/:delegator/delegation/:validator", delegationHandlerFun(cdc, ctx)) - routerGroup.GET("/stake/:delegator/ubd/:validator", ubdHandlerFun(cdc, ctx)) - routerGroup.GET("/stake/:delegator/red/:validator_src/:validator_dst", redHandlerFun(cdc, ctx)) - routerGroup.GET("/stake_validators", validatorsHandlerFun(cdc, ctx)) + routerGroup.GET("/stake/delegators/:delegatorAddr", delegatorHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/delegators/:delegatorAddr/txs", delegatorTxsHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/delegators/:delegatorAddr/validators", delegatorValidatorsHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/delegators/:delegatorAddr/validators/:validatorAddr", delegatorValidatorHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/delegators/:delegatorAddr/delegations/:validatorAddr", delegationHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/delegators/:delegatorAddr/unbonding_delegations/:validatorAddr", unbondingDelegationsHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/validators", validatorsHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/validators/:addr", validatorHandlerCreation(cdc, ctx)) } -func delegationHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { +func delegatorHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { + var validatorAddr sdk.AccAddress + var delegationSummary = DelegationSummary{} // read parameters - bech32delegator := gtx.Param("delegator") - bech32validator := gtx.Param("validator") + bech32delegator := gtx.Param("delegatorAddr") delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { @@ -577,43 +582,137 @@ func delegationHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFu return } - validatorAddr, err := sdk.AccAddressFromBech32(bech32validator) + // Get all validators using key + validators, statusCode, errMsg, err := getBech32Validators(storeName, cliCtx, cdc) if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, statusCode, fmt.Errorf("%s%s", errMsg, err.Error())) return } - key := stake.GetDelegationKey(delegatorAddr, validatorAddr) + for _, validator := range validators { + validatorAddr = validator.Operator + + // Delegations + delegations, statusCode, errMsg, err := getDelegatorDelegations(cliCtx, cdc, delegatorAddr, validatorAddr) + if err != nil { + httputils.NewError(gtx, statusCode, fmt.Errorf("%s%s", errMsg, err.Error())) + return + } + if statusCode != http.StatusNoContent { + delegationSummary.Delegations = append(delegationSummary.Delegations, delegations) + } + + // Undelegations + unbondingDelegation, statusCode, errMsg, err := getDelegatorUndelegations(cliCtx, cdc, delegatorAddr, validatorAddr) + if err != nil { + httputils.NewError(gtx, statusCode, fmt.Errorf("%s%s", errMsg, err.Error())) + return + } + if statusCode != http.StatusNoContent { + delegationSummary.UnbondingDelegations = append(delegationSummary.UnbondingDelegations, unbondingDelegation) + } - res, err := ctx.QueryStore(key, storeName) + // Redelegations + // only querying redelegations to a validator as this should give us already all relegations + // if we also would put in redelegations from, we would have every redelegation double + redelegations, statusCode, errMsg, err := getDelegatorRedelegations(cliCtx, cdc, delegatorAddr, validatorAddr) + if err != nil { + httputils.NewError(gtx, statusCode, fmt.Errorf("%s%s", errMsg, err.Error())) + return + } + if statusCode != http.StatusNoContent { + delegationSummary.Redelegations = append(delegationSummary.Redelegations, redelegations) + } + } + + output, err := cdc.MarshalJSON(delegationSummary) if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("couldn't query delegation. Error: %s", err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } - // the query will return empty if there is no data for this record - if len(res) == 0 { - httputils.NormalResponse(gtx,nil) + httputils.NormalResponse(gtx, output) + } +} + +// nolint: gocyclo +func delegatorTxsHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + var output []byte + var typesQuerySlice []string + + delegatorAddr := gtx.Param("delegatorAddr") + + _, err := sdk.AccAddressFromBech32(delegatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) return } - delegation, err := types.UnmarshalDelegation(cdc, key, res) + node, err := cliCtx.GetNode() if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("Couldn't get current Node information. Error: %s", err.Error())) return } - httputils.NormalResponse(gtx,delegation) + // Get values from query + typesQuery := gtx.Query("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 = []tx.Info{} + var actions []string + + switch { + case isBondTx: + actions = append(actions, string(tags.ActionDelegate)) + case isUnbondTx: + actions = append(actions, string(tags.ActionBeginUnbonding)) + actions = append(actions, string(tags.ActionCompleteUnbonding)) + case isRedTx: + actions = append(actions, string(tags.ActionBeginRedelegation)) + actions = append(actions, string(tags.ActionCompleteRedelegation)) + case noQuery: + actions = append(actions, string(tags.ActionDelegate)) + actions = append(actions, string(tags.ActionBeginUnbonding)) + actions = append(actions, string(tags.ActionCompleteUnbonding)) + actions = append(actions, string(tags.ActionBeginRedelegation)) + actions = append(actions, string(tags.ActionCompleteRedelegation)) + default: + httputils.NewError(gtx, http.StatusNoContent, nil) + return + } + + for _, action := range actions { + foundTxs, errQuery := queryTxs(node, cdc, action, delegatorAddr) + if errQuery != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("error querying transactions. Error: %s", errQuery.Error())) + } + txs = append(txs, foundTxs...) + } + + output, err = cdc.MarshalJSON(txs) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + httputils.NormalResponse(gtx, output) } } -// http request handler to query an unbonding-delegation -func ubdHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { +func delegatorValidatorsHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { + var validatorAccAddr sdk.AccAddress + var bondedValidators []types.BechValidator // read parameters - bech32delegator := gtx.Param("delegator") - bech32validator := gtx.Param("validator") + bech32delegator := gtx.Param("delegatorAddr") delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { @@ -621,43 +720,84 @@ func ubdHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { return } - validatorAddr, err := sdk.AccAddressFromBech32(bech32validator) + // Get all validators using key + kvs, err := cliCtx.QuerySubspace(stake.ValidatorsKey, storeName) if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query validators. Error: %s", err.Error())) + return + } else if len(kvs) == 0 { + // the query will return empty if there are no validators + httputils.NewError(gtx, http.StatusNoContent, nil) return } - key := stake.GetUBDKey(delegatorAddr, validatorAddr) - - res, err := ctx.QueryStore(key, storeName) + validators, err := getValidators(kvs, cdc) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } - // the query will return empty if there is no data for this record - if len(res) == 0 { - httputils.NormalResponse(gtx,nil) + for _, validator := range validators { + // get all transactions from the delegator to val and append + validatorAccAddr = validator.Operator + + validator, statusCode, errMsg, errRes := getDelegatorValidator(cliCtx, cdc, delegatorAddr, validatorAccAddr) + if errRes != nil { + httputils.NewError(gtx, statusCode, fmt.Errorf("%s%s", errMsg, errRes.Error())) + return + } else if statusCode == http.StatusNoContent { + continue + } + + bondedValidators = append(bondedValidators, validator) + } + output, err := cdc.MarshalJSON(bondedValidators) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) return } + httputils.NormalResponse(gtx, output) + } +} - ubd, err := types.UnmarshalUBD(cdc, key, res) +func delegatorValidatorHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + // read parameters + var output []byte + bech32delegator := gtx.Param("delegatorAddr") + bech32validator := gtx.Param("validatorAddr") + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + validatorAccAddr, err := sdk.AccAddressFromBech32(bech32validator) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) + httputils.NewError(gtx, http.StatusBadRequest, err) return } - httputils.NormalResponse(gtx,ubd) + // Check if there if the delegator is bonded or redelegated to the validator + + validator, statusCode, errMsg, err := getDelegatorValidator(cliCtx, cdc, delegatorAddr, validatorAccAddr) + if err != nil { + httputils.NewError(gtx, statusCode, fmt.Errorf("%s%s", errMsg, err.Error())) + return + } else if statusCode == http.StatusNoContent { + httputils.NewError(gtx, http.StatusNoContent, nil) + return + } + output, err = cdc.MarshalJSON(validator) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + httputils.NormalResponse(gtx, output) } } -// http request handler to query an redelegation -func redHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { +func delegationHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { // read parameters - bech32delegator := gtx.Param("delegator") - bech32validatorSrc := gtx.Param("validator_src") - bech32validatorDst := gtx.Param("validator_dst") + bech32delegator := gtx.Param("delegatorAddr") + bech32validator := gtx.Param("validatorAddr") delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { @@ -665,45 +805,104 @@ func redHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { return } - validatorSrcAddr, err := sdk.AccAddressFromBech32(bech32validatorSrc) + validatorAddr, err := sdk.AccAddressFromBech32(bech32validator) if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } + validatorAddrAcc := sdk.AccAddress(validatorAddr) + + key := stake.GetDelegationKey(delegatorAddr, validatorAddrAcc) - validatorDstAddr, err := sdk.AccAddressFromBech32(bech32validatorDst) + res, err := cliCtx.QueryStore(key, storeName) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query delegation. Error: %s", err.Error())) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + httputils.NewError(gtx, http.StatusNoContent, nil) + return + } + + delegation, err := types.UnmarshalDelegation(cdc, key, res) if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } - key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr) + outputDelegation := DelegationWithoutRat{ + DelegatorAddr: delegation.DelegatorAddr, + ValidatorAddr: delegation.ValidatorAddr, + Height: delegation.Height, + Shares: delegation.Shares.String(), + } - res, err := ctx.QueryStore(key, storeName) + output, err := cdc.MarshalJSON(outputDelegation) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query redelegation. Error: %s", err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.NormalResponse(gtx, output) + } +} + +func unbondingDelegationsHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + bech32delegator := gtx.Param("delegatorAddr") + bech32validator := gtx.Param("validatorAddr") + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + validatorAddr, err := sdk.AccAddressFromBech32(bech32validator) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + validatorAddrAcc := sdk.AccAddress(validatorAddr) + + key := stake.GetUBDKey(delegatorAddr, validatorAddrAcc) + + res, err := cliCtx.QueryStore(key, storeName) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) return } // the query will return empty if there is no data for this record if len(res) == 0 { - httputils.NormalResponse(gtx,nil) + httputils.NewError(gtx, http.StatusNoContent, nil) return } - red, err := types.UnmarshalRED(cdc, key, res) + ubd, err := types.UnmarshalUBD(cdc, key, res) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't unmarshall unbonding-delegation. Error: %s", err.Error())) + return + } + + // unbondings will be a list in the future but is not yet, but we want to keep the API consistent + ubdArray := []stake.UnbondingDelegation{ubd} + + output, err := cdc.MarshalJSON(ubdArray) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't marshall unbonding-delegation. Error: %s", err.Error())) return } - httputils.NormalResponse(gtx,red) + httputils.NormalResponse(gtx, output) } } -func validatorsHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { +func validatorsHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { - kvs, err := ctx.QuerySubspace(stake.ValidatorsKey, storeName) + kvs, err := cliCtx.QuerySubspace(stake.ValidatorsKey, storeName) if err != nil { httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query validators. Error: %s", err.Error())) return @@ -711,29 +910,73 @@ func validatorsHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFu // the query will return empty if there are no validators if len(kvs) == 0 { - httputils.NormalResponse(gtx,nil) + httputils.NewError(gtx, http.StatusNoContent, nil) return } - // parse out the validators - validators := make([]types.BechValidator, len(kvs)) - for i, kv := range kvs { + validators, err := getValidators(kvs, cdc) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } - addr := kv.Key[1:] - validator, err := types.UnmarshalValidator(cdc, addr, kv.Value) - if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) - return - } + output, err := cdc.MarshalJSON(validators) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } - bech32Validator, err := validator.Bech32Validator() - if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - return - } - validators[i] = bech32Validator + httputils.NormalResponse(gtx, output) + } +} + +func validatorHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + var output []byte + // read parameters + bech32validatorAddr := gtx.Param("addr") + valAddress, err := sdk.AccAddressFromBech32(bech32validatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return } - httputils.NormalResponse(gtx,validators) + key := stake.GetValidatorKey(valAddress) + + res, err := cliCtx.QueryStore(key, storeName) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query validator, error: %s", err.Error())) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + httputils.NewError(gtx, http.StatusNoContent, nil) + return + } + + validator, err := types.UnmarshalValidator(cdc, valAddress, res) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + bech32Validator, err := validator.Bech32Validator() + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + output, err = cdc.MarshalJSON(bech32Validator) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("Error: %s", err.Error())) + return + } + + if output == nil { + httputils.NewError(gtx, http.StatusNoContent, nil) + return + } + httputils.NormalResponse(gtx, output) } } \ No newline at end of file diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index 521ff9fe9ef9..aa2cfce5935c 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -15,7 +15,8 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, k registerTxRoutes(cliCtx, r, cdc, kb) } -// RegisterSwaggerRoutes registers staking status query REST API handlers to a router -func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { +// RegisterSwaggerRoutes - Central function to define stake related routes that get registered by the main application +func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { registerSwaggerQueryRoutes(routerGroup, ctx, cdc) + registerSwaggerTxRoutes(routerGroup, ctx, cdc, kb) } \ No newline at end of file diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index d8b9b6011366..7a21435085fd 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -16,6 +16,8 @@ import ( "github.com/gorilla/mux" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/client/httputils" ) func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { @@ -328,3 +330,244 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex w.Write(output) } } + +func registerSwaggerTxRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { + routerGroup.POST("/stake/delegators/:delegatorAddr/delegations", delegationsSwaggerTxHandlerFn(cdc, ctx, kb)) +} + +// nolint: gocyclo +// TODO: Split this up into several smaller functions, and remove the above nolint +// TODO: use sdk.ValAddress instead of sdk.AccAddress for validators in messages +func delegationsSwaggerTxHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext, kb keys.Keybase) gin.HandlerFunc { + return func(gtx *gin.Context) { + var m EditDelegationsBody + + body, err := ioutil.ReadAll(gtx.Request.Body) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + err = cdc.UnmarshalJSON(body, &m) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + info, err := kb.Get(m.LocalAccountName) + if err != nil { + httputils.NewError(gtx, http.StatusUnauthorized, err) + return + } + + // build messages + messages := make([]sdk.Msg, len(m.Delegations)+ + len(m.BeginRedelegates)+ + len(m.CompleteRedelegates)+ + len(m.BeginUnbondings)+ + len(m.CompleteUnbondings)) + + i := 0 + for _, msg := range m.Delegations { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode delegator. error: %s", err.Error())) + return + } + + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode validator. error: %s", err.Error())) + return + } + + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + httputils.NewError(gtx, http.StatusUnauthorized, fmt.Errorf("must use own delegator address")) + return + } + + messages[i] = stake.MsgDelegate{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Delegation: msg.Delegation, + } + + i++ + } + + for _, msg := range m.BeginRedelegates { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode delegator. error: %s", err.Error())) + return + } + + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + httputils.NewError(gtx, http.StatusUnauthorized, fmt.Errorf("must use own delegator address")) + return + } + + validatorSrcAddr, err := sdk.AccAddressFromBech32(msg.ValidatorSrcAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode validator. error: %s", err.Error())) + return + } + validatorDstAddr, err := sdk.AccAddressFromBech32(msg.ValidatorDstAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode validator. error: %s", err.Error())) + return + } + + shares, err := sdk.NewDecFromStr(msg.SharesAmount) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode shares amount. error: %s", err.Error())) + return + } + + messages[i] = stake.MsgBeginRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + SharesAmount: shares, + } + + i++ + } + + for _, msg := range m.CompleteRedelegates { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode delegator. error: %s", err.Error())) + return + } + + validatorSrcAddr, err := sdk.AccAddressFromBech32(msg.ValidatorSrcAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode validator. error: %s", err.Error())) + return + } + validatorDstAddr, err := sdk.AccAddressFromBech32(msg.ValidatorDstAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode validator. error: %s", err.Error())) + return + } + + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + httputils.NewError(gtx, http.StatusUnauthorized, fmt.Errorf("must use own delegator address")) + return + } + + messages[i] = stake.MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + } + + i++ + } + + for _, msg := range m.BeginUnbondings { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode delegator. error: %s", err.Error())) + return + } + + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + httputils.NewError(gtx, http.StatusUnauthorized, fmt.Errorf("must use own delegator address")) + return + } + + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode validator. error: %s", err.Error())) + return + } + + shares, err := sdk.NewDecFromStr(msg.SharesAmount) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode shares amount. error: %s", err.Error())) + return + } + + messages[i] = stake.MsgBeginUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + SharesAmount: shares, + } + + i++ + } + + for _, msg := range m.CompleteUnbondings { + delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode delegator. error: %s", err.Error())) + return + } + + validatorAddr, err := sdk.AccAddressFromBech32(msg.ValidatorAddr) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't decode validator. error: %s", err.Error())) + return + } + + if !bytes.Equal(info.GetPubKey().Address(), delegatorAddr) { + httputils.NewError(gtx, http.StatusUnauthorized, fmt.Errorf("must use own delegator address")) + return + } + + messages[i] = stake.MsgCompleteUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } + + i++ + } + + txCtx := authcliCtx.TxContext{ + Codec: cdc, + ChainID: m.ChainID, + Gas: m.Gas, + } + + // sign messages + signedTxs := make([][]byte, len(messages[:])) + for i, msg := range messages { + // increment sequence for each message + txCtx = txCtx.WithAccountNumber(m.AccountNumber) + txCtx = txCtx.WithSequence(m.Sequence) + + m.Sequence++ + + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if err != nil { + httputils.NewError(gtx, http.StatusUnauthorized, fmt.Errorf(err.Error())) + return + } + + signedTxs[i] = txBytes + } + + // send + // XXX the operation might not be atomic if a tx fails + // should we have a sdk.MultiMsg type to make sending atomic? + results := make([]*ctypes.ResultBroadcastTxCommit, len(signedTxs[:])) + for i, txBytes := range signedTxs { + res, err := cliCtx.BroadcastTx(txBytes) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf(err.Error())) + return + } + + results[i] = res + } + + output, err := wire.MarshalJSONIndent(cdc, results) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.NormalResponse(gtx, output) + } +} \ No newline at end of file