From 34548db80b0751dcd2e204eb12c3343187a44b7c Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 16 Aug 2018 15:52:04 +0800 Subject: [PATCH 01/25] lcd implemeation --- client/context/context.go | 15 + client/context/loadbalancing.go | 44 + client/context/loadbalancing_test.go | 16 + client/context/query.go | 59 +- client/flags.go | 4 + client/lcd/docs/docs.go | 1732 ++++++++++++++++++++++++++ client/lcd/root.go | 105 ++ client/lcd/version.go | 20 + client/utils/utils.go | 32 + cmd/gaia/cmd/gaiacli/main.go | 1 + store/multistoreproof.go | 135 ++ store/multistoreproof_test.go | 96 ++ store/rootmultistore.go | 17 + store/transientstore.go | 6 + x/auth/client/context/context.go | 32 + x/auth/client/rest/query.go | 42 + x/bank/client/rest/sendtx.go | 229 ++++ 17 files changed, 2584 insertions(+), 1 deletion(-) create mode 100644 client/context/loadbalancing.go create mode 100644 client/context/loadbalancing_test.go create mode 100644 client/lcd/docs/docs.go create mode 100644 store/multistoreproof.go create mode 100644 store/multistoreproof_test.go diff --git a/client/context/context.go b/client/context/context.go index 1b0443b0c7c7..c7818f836b33 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/viper" rpcclient "github.com/tendermint/tendermint/rpc/client" + tendermintLite"github.com/tendermint/tendermint/lite" ) const ctxAccStoreName = "acc" @@ -30,6 +31,8 @@ type CLIContext struct { Async bool JSON bool PrintResponse bool + Cert tendermintLite.Certifier + ClientMgr *ClientManager } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -113,3 +116,15 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { ctx.UseLedger = useLedger return ctx } + +// WithCert - return a copy of the context with an updated Cert +func (c CLIContext) WithCert(cert tendermintLite.Certifier) CLIContext { + c.Cert = cert + return c +} + +// WithCert - return a copy of the context with an updated ClientMgr +func (c CLIContext) WithClientMgr(clientMgr *ClientManager) CLIContext { + c.ClientMgr = clientMgr + return c +} \ No newline at end of file diff --git a/client/context/loadbalancing.go b/client/context/loadbalancing.go new file mode 100644 index 000000000000..9a8c6e4b49ab --- /dev/null +++ b/client/context/loadbalancing.go @@ -0,0 +1,44 @@ +package context + +import ( + rpcclient "github.com/tendermint/tendermint/rpc/client" + "strings" + "sync" + "github.com/pkg/errors" +) + +type ClientManager struct { + clients []rpcclient.Client + currentIndex int + mutex sync.RWMutex +} + +func NewClientManager(nodeURIs string) (*ClientManager,error) { + if nodeURIs != "" { + nodeUrlArray := strings.Split(nodeURIs, ",") + var clients []rpcclient.Client + for _, url := range nodeUrlArray { + client := rpcclient.NewHTTP(url, "/websocket") + clients = append(clients, client) + } + mgr := &ClientManager{ + currentIndex: 0, + clients: clients, + } + return mgr, nil + } else { + return nil, errors.New("missing node URIs") + } +} + +func (mgr *ClientManager) getClient() rpcclient.Client { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + client := mgr.clients[mgr.currentIndex] + mgr.currentIndex++ + if mgr.currentIndex >= len(mgr.clients){ + mgr.currentIndex = 0 + } + return client +} \ No newline at end of file diff --git a/client/context/loadbalancing_test.go b/client/context/loadbalancing_test.go new file mode 100644 index 000000000000..893048854850 --- /dev/null +++ b/client/context/loadbalancing_test.go @@ -0,0 +1,16 @@ +package context + +import ( + "testing" + "github.com/stretchr/testify/assert" +) + +func TestLoadBalancing(t *testing.T) { + nodeURIs := "10.10.10.10:26657,20.20.20.20:26657,30.30.30.30:26657" + clientMgr,err := NewClientManager(nodeURIs) + assert.Empty(t,err) + endpoint := clientMgr.getClient() + assert.NotEqual(t,endpoint,clientMgr.getClient()) + clientMgr.getClient() + assert.Equal(t,endpoint,clientMgr.getClient()) +} \ No newline at end of file diff --git a/client/context/query.go b/client/context/query.go index 081f723b5c9c..9fdbc2f272a9 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -14,11 +14,18 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/wire" + "strings" + tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" ) // GetNode returns an RPC client. If the context's client is not defined, an // error is returned. func (ctx CLIContext) GetNode() (rpcclient.Client, error) { + if ctx.ClientMgr != nil { + return ctx.ClientMgr.getClient(), nil + } if ctx.Client == nil { return nil, errors.New("no RPC client defined") } @@ -296,10 +303,42 @@ func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err e } resp := result.Response - if !resp.IsOK() { + if resp.Code != uint32(0) { return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) } + // Data from trusted node or subspace doesn't need verification + if ctx.TrustNode || !isQueryStoreWithProof(path) { + return resp.Value,nil + } + + if ctx.Cert == nil { + return resp.Value,errors.Errorf("missing valid certifier to verify data from untrusted node") + } + + // AppHash for height H is in header H+1 + commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Cert) + if err != nil { + return nil, err + } + + var multiStoreProof store.MultiStoreProof + cdc := wire.NewCodec() + err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) + if err != nil { + return res, errors.Wrap(err, "failed to unmarshalBinary rangeProof") + } + + // Validate the substore commit hash against trusted appHash + substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, multiStoreProof.CommitIDList, commit.Header.AppHash) + if err != nil { + return nil, errors.Wrap(err, "failed in verifying the proof against appHash") + } + err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) + if err != nil { + return nil, errors.Wrap(err, "failed in the range proof verification") + } + return resp.Value, nil } @@ -309,3 +348,21 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ path := fmt.Sprintf("/store/%s/%s", storeName, endPath) return ctx.query(path, key) } + +// isQueryStoreWithProof expects a format like /// +// queryType can be app or store +// if subpath equals to store or key, then return true +func isQueryStoreWithProof(path string) (bool) { + if !strings.HasPrefix(path, "/") { + return false + } + paths := strings.SplitN(path[1:], "/", 3) + if len(paths) != 3 { + return false + } + // WARNING This should be consistent with query method in iavlstore.go + if paths[2] == "store" || paths[2] == "key" { + return true + } + return false +} \ No newline at end of file diff --git a/client/flags.go b/client/flags.go index 8616f9e78d4a..2577f99f8286 100644 --- a/client/flags.go +++ b/client/flags.go @@ -19,6 +19,10 @@ const ( FlagAsync = "async" FlagJson = "json" FlagPrintResponse = "print-response" + FlagListenAddr = "laddr" + FlagSwaggerHostIP = "swagger-host-ip" + FlagModules = "modules" + FlagNodeList = "node-list" ) // LineBreak can be included in a command list to provide a blank line diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go new file mode 100644 index 000000000000..8e525dc91034 --- /dev/null +++ b/client/lcd/docs/docs.go @@ -0,0 +1,1732 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag at +// 2018-07-09 10:52:33.917498846 +0800 CST m=+0.182625290 + +package docs + +import ( + "github.com/swaggo/swag" + "github.com/spf13/viper" + "encoding/json" + "strings" + "errors" + "github.com/cosmos/cosmos-sdk/client" + "bytes" + "fmt" + "reflect" + "github.com/tendermint/tendermint/libs/cli" +) + +var doc = `{ + "swagger": "2.0", + "info": { + "description": "All cosmos-lcd supported APIs will be shown by this swagger-ui page. You can access these APIs through this page.", + "title": "Swagger Cosmos-LCD API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "host": "localhost:1317", + "basePath": "/", + "paths": { + "/stake/{delegator}/delegation/{validator}": { + "get": { + "description": "Get the delegation information between specific delegator and validator", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query delegation information", + "parameters": [ + { + "type": "string", + "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "delegator", + "in": "path" + }, + { + "type": "string", + "description": "validator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "validator", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.stake_delegation" + } + }, + "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/{delegator}/ubd/{validator}": { + "get": { + "description": "Get the unbound information between specific delegator and validator", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query unbound information", + "parameters": [ + { + "type": "string", + "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "delegator", + "in": "path" + }, + { + "type": "string", + "description": "validator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "validator", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.stake_ubd" + } + }, + "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/{delegator}/red/{validator_src}/{validator_dst}": { + "get": { + "description": "Get the re-delegation information between specific delegator and validator", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query re-delegation information", + "parameters": [ + { + "type": "string", + "description": "delegator address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "delegator", + "in": "path" + }, + { + "type": "string", + "description": "validator source address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "validator_src", + "in": "path" + }, + { + "type": "string", + "description": "validator destination address, example: cosmosaccaddr1t48m77vw08fqygkz96l3neqdzrnuvh6ansk7ks", + "name": "validator_dst", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.stake_red" + } + }, + "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": "Query all validators' information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query all validators' information", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.stake_validators" + } + }, + "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" + } + } + } + } + }, + "/accounts/{address}": { + "get": { + "description": "Get the detailed information for specific address", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Token Operation" + ], + "summary": "Query account information", + "parameters": [ + { + "type": "string", + "description": "address", + "name": "address", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.acoount_query" + } + }, + "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" + } + } + } + } + }, + "/create_transfer": { + "post": { + "description": "Build transaction", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Token Operation" + ], + "summary": "Build transaction", + "parameters": [ + { + "description": "create transaction parameters", + "name": "transferBody", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/bank.transferBody" + } + } + ], + "responses": { + "200": { + "description": "OK. The returned string is base64 encoding", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.string" + } + }, + "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" + } + } + } + } + }, + "/signed_transfer": { + "post": { + "description": "Broadcast signed transaction.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Token Operation" + ], + "summary": "Broadcast signed transaction", + "parameters": [ + { + "description": "Signed transaction. Transaction data, signatures and public keys should be base64 encoding", + "name": "signedTransaction", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/bank.signedBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.tx_commit" + } + }, + "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" + } + } + } + } + }, + "/accounts/{address}/send": { + "post": { + "description": "This API require the Cosmos-LCD have keystore module. It will ask keystore module for transaction signature", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Token Operation" + ], + "summary": "Send coins to a address", + "parameters": [ + { + "type": "string", + "description": "address to send asset", + "name": "address", + "in": "path" + }, + { + "description": "transfer asset", + "name": "sendAsset", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/bank.sendBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.tx_commit" + } + }, + "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" + } + } + } + } + }, + "/keys": { + "get": { + "description": "Get all keys in the key store", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "summary": "list all keys", + "operationId": "queryKeysRequest", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.keys.list" + } + }, + "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" + } + } + } + }, + "post": { + "description": "Create a new key and persistent it to the key store", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "summary": "Create a account", + "parameters": [ + { + "description": "name and password for a new key", + "name": "nameAndPwd", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/keys.NewKeyBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.keys.add" + } + }, + "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" + } + } + } + } + }, + "/keys/get/{name}": { + "get": { + "description": "Get detailed information for specific key name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "summary": "Get key information", + "parameters": [ + { + "type": "string", + "description": "key name", + "name": "name", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.keys.get" + } + }, + "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" + } + } + } + } + }, + "/keys/seed": { + "get": { + "description": "Get a seed for creating key", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "summary": "Get a seed", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.string" + } + }, + "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" + } + } + } + } + }, + "/keys/{name}": { + "put": { + "description": "The keys are protected by the password, here this API provides a way to change the password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "summary": "Change key password", + "parameters": [ + { + "type": "string", + "description": "key name", + "name": "name", + "in": "path" + }, + { + "description": "key name", + "name": "pwd", + "in": "body", + "schema": { + "type": "object", + "$ref": "#/definitions/keys.UpdateKeyBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.string" + } + }, + "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" + } + } + } + }, + "delete": { + "description": "delete specific name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "summary": "Delete key", + "parameters": [ + { + "type": "string", + "description": "key name", + "name": "name", + "in": "path" + }, + { + "description": "password", + "name": "pwd", + "in": "body", + "schema": { + "type": "object", + "$ref": "#/definitions/keys.DeleteKeyBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.string" + } + }, + "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" + } + } + } + } + }, + "/node_version": { + "get": { + "description": "Get connected full node version", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "General" + ], + "summary": "Get connected full node version", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.string" + } + }, + "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" + } + } + } + } + }, + "/version": { + "get": { + "description": "Get Cosmos-LCD version", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "General" + ], + "summary": "Get Cosmos-LCD version", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/httputil.HTTPResponse.string" + } + }, + "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" + } + } + } + } + } + }, + "definitions": { + "auth.BaseAccount": { + "type": "object", + "properties": { + "account_number": { + "type": "integer" + }, + "address": { + "type": "string" + }, + "coins": { + "type": "object", + "$ref": "#/definitions/sdk.Coins" + }, + "public_key": { + "type": "string" + }, + "sequence": { + "type": "integer" + } + } + }, + "bank.ResponseCheckTx": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": { + "type": "string" + }, + "fee": { + "type": "object", + "$ref": "#/definitions/common.KI64Pair" + }, + "gas_used": { + "type": "integer" + }, + "gas_wanted": { + "type": "integer" + }, + "info": { + "type": "string" + }, + "log": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/common.KVPairs" + } + } + } + }, + "bank.ResponseDeliverTx": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": { + "type": "string" + }, + "fee": { + "type": "object", + "$ref": "#/definitions/common.KI64Pair" + }, + "gas_used": { + "type": "integer" + }, + "gas_wanted": { + "type": "integer" + }, + "info": { + "type": "string" + }, + "log": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/common.KVPairs" + } + } + } + }, + "bank.ResultBroadcastTxCommit": { + "type": "object", + "properties": { + "check_tx": { + "type": "object", + "$ref": "#/definitions/bank.ResponseCheckTx" + }, + "deliver_tx": { + "type": "object", + "$ref": "#/definitions/bank.ResponseDeliverTx" + }, + "hash": { + "type": "string" + }, + "height": { + "type": "integer" + } + } + }, + "bank.signedBody": { + "type": "object", + "properties": { + "public_key_list": { + "type": "array", + "items": { + "type": "string" + } + }, + "signature_list": { + "type": "array", + "items": { + "type": "string" + } + }, + "transaction_data": { + "type": "string" + } + } + }, + "bank.transferBody": { + "type": "object", + "properties": { + "account_number": { + "type": "integer" + }, + "amount": { + "type": "integer" + }, + "denomination": { + "type": "string" + }, + "ensure_account_sequence": { + "type": "boolean" + }, + "chain_id": { + "type": "string" + }, + "from_address": { + "type": "string" + }, + "gas": { + "type": "integer" + }, + "fee": { + "type": "string", + "example": "10 monikerToken" + }, + "sequence": { + "type": "integer" + }, + "to_address": { + "type": "string" + } + } + }, + "bank.sendBody": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "amount": { + "type": "object", + "$ref": "#/definitions/sdk.Coins" + }, + "password": { + "type": "string" + }, + "chain_id": { + "type": "string" + }, + "account_number": { + "type": "integer" + }, + "gas": { + "type": "integer" + }, + "fee": { + "type": "string", + "example": "10 monikerToken" + }, + "sequence": { + "type": "integer" + } + } + }, + "sdk.Coin": { + "type": "object", + "properties": { + "denom": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "sdk.Coins": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/sdk.Coin" + } + }, + "common.KVPair": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "integer" + } + } + }, + "common.KVPairs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/common.KVPair" + } + }, + "common.KI64Pair": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "integer" + } + } + }, + "keys.DeleteKeyBody": { + "type": "object", + "properties": { + "password": { + "type": "string" + } + } + }, + "keys.KeyOutput": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pub_key": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "keys.KeyOutputs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/keys.KeyOutput" + } + }, + "keys.NewKeyResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "pub_key": { + "type": "string" + }, + "seed": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "keys.NewKeyBody": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "seed": { + "type": "string" + } + } + }, + "keys.UpdateKeyBody": { + "type": "object", + "properties": { + "new_password": { + "type": "string" + }, + "old_password": { + "type": "string" + } + } + }, + "httputil.HTTPError": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 500 + }, + "error message": { + "type": "string" + } + } + }, + "httputil.HTTPResponse.keys.list": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/keys.KeyOutputs" + } + } + }, + "httputil.HTTPResponse.keys.add": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/keys.NewKeyResponse" + } + } + }, + "httputil.HTTPResponse.keys.get": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/keys.KeyOutput" + } + } + }, + "httputil.HTTPResponse.string": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "string" + } + } + }, + "httputil.HTTPResponse.acoount_query": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/auth.BaseAccount" + } + } + }, + "httputil.HTTPResponse.tx_commit": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/bank.ResultBroadcastTxCommit" + } + } + }, + "stake.Delegation":{ + "type": "object", + "properties": { + "delegator_addr": { + "type": "string" + }, + "validator_addr": { + "type": "string" + }, + "shares": { + "type": "string" + }, + "height": { + "type": "integer" + } + } + }, + "stake.UnbondingDelegation":{ + "type": "object", + "properties": { + "delegator_addr": { + "type": "string" + }, + "validator_addr": { + "type": "string" + }, + "initial_balance": { + "type": "object", + "$ref": "#/definitions/sdk.Coin" + }, + "balance": { + "type": "object", + "$ref": "#/definitions/sdk.Coin" + }, + "creation_height": { + "type": "integer" + }, + "min_time": { + "type": "integer" + } + } + }, + "stake.Redelegation":{ + "type": "object", + "properties": { + "delegator_addr": { + "type": "string" + }, + "validator_src_addr": { + "type": "string" + }, + "validator_dst_addr": { + "type": "string" + }, + "creation_height": { + "type": "integer" + }, + "min_time": { + "type": "integer" + }, + "initial_balance": { + "type": "object", + "$ref": "#/definitions/sdk.Coin" + }, + "balance": { + "type": "object", + "$ref": "#/definitions/sdk.Coin" + }, + "shares_src": { + "type": "string" + }, + "shares_dst": { + "type": "string" + } + } + }, + "stake.PoolShares": { + "status": { + "type": "string" + }, + "amount": { + "type": "string" + } + }, + "stake.Description": { + "moniker": { + "type": "string" + }, + "identity": { + "type": "string" + }, + "website": { + "type": "string" + }, + "details": { + "type": "string" + } + }, + "stake.StakeValidatorOutput":{ + "type": "object", + "properties": { + "owner": { + "type": "string" + }, + "pub_key": { + "type": "string" + }, + "revoked": { + "type": "boolean" + }, + "pool_shares": { + "type": "object", + "$ref": "#/definitions/stake.PoolShares" + }, + "delegator_shares": { + "type": "string" + }, + "description": { + "type": "object", + "$ref": "#/definitions/stake.Description" + }, + "bond_height": { + "type": "integer" + }, + "bond_intra_tx_counter": { + "type": "integer" + }, + "proposer_reward_pool": { + "type": "object", + "$ref": "#/definitions/sdk.Coins" + }, + "commission": { + "type": "string" + }, + "commission_max": { + "type": "string" + }, + "commission_change_rate": { + "type": "string" + }, + "commission_change_today": { + "type": "string" + } + } + }, + "httputil.HTTPResponse.stake_delegation": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/stake.Delegation" + } + } + }, + "httputil.HTTPResponse.stake_ubd": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/stake.UnbondingDelegation" + } + } + }, + "httputil.HTTPResponse.stake_red": { + "type": "object", + "properties": { + "rest api": { + "type": "string", + "example": "2.0" + }, + "code": { + "type": "integer", + "example": 0 + }, + "result": { + "type": "object", + "$ref": "#/definitions/stake.Redelegation" + } + } + }, + "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" + } + } + } + } + } +}` + +var TagToModuleDesc = ` +{ + "General":"general", + "Key Management":"key", + "Token Operation":"token", + "Stake Operation":"stake" +} +` + +type s struct{} + +func addOptionsToDesc (desc string) string { + home := viper.GetString(cli.HomeFlag) + listenAddr := viper.GetString(client.FlagListenAddr) + swaggerHost := viper.GetString(client.FlagSwaggerHostIP) + nodeList := viper.GetString(client.FlagNodeList) + chainID := viper.GetString(client.FlagChainID) + trustNode := viper.GetString(client.FlagTrustNode) + modules := viper.GetString(client.FlagModules) + + var buffer bytes.Buffer + buffer.WriteString(desc) + buffer.WriteString("\n") + + buffer.WriteString("Cosmos-LCD starting options:") + buffer.WriteString("\n") + + buffer.WriteString(cli.HomeFlag) + buffer.WriteString(": ") + buffer.WriteString(home) + buffer.WriteString("\n") + + buffer.WriteString(client.FlagListenAddr) + buffer.WriteString(": ") + buffer.WriteString(listenAddr) + buffer.WriteString("\n") + + buffer.WriteString(client.FlagSwaggerHostIP) + buffer.WriteString(": ") + buffer.WriteString(swaggerHost) + buffer.WriteString("\n") + + buffer.WriteString(client.FlagNodeList) + buffer.WriteString(": ") + buffer.WriteString(nodeList) + buffer.WriteString("\n") + + buffer.WriteString(client.FlagChainID) + buffer.WriteString(": ") + buffer.WriteString(chainID) + buffer.WriteString("\n") + + buffer.WriteString(client.FlagTrustNode) + buffer.WriteString(": ") + buffer.WriteString(trustNode) + buffer.WriteString("\n") + + buffer.WriteString(client.FlagModules) + buffer.WriteString(": ") + buffer.WriteString(modules) + buffer.WriteString("\n") + + return buffer.String() +} + +func moduleEnabled(modules []string, name string) bool { + for _, moduleName := range modules { + if moduleName == name { + return true + } + } + return false +} + +func modularizeAPIs(modules string, paths map[string]interface{}) map[string]interface{} { + filteredAPIs := make(map[string]interface{}) + + var moduleToTag map[string]string + if err := json.Unmarshal([]byte(TagToModuleDesc), &moduleToTag); err != nil { + panic(err) + } + moduleArray := strings.Split(modules,",") + + for path,operations := range paths { + if reflect.TypeOf(operations).String() != "map[string]interface {}" { + panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(operations).String()))) + } + operationAPIs := operations.(map[string]interface{}) + for operation,API := range operationAPIs { + if reflect.TypeOf(API).String() != "map[string]interface {}" { + panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(API).String()))) + } + APIInfo := API.(map[string]interface{}) + tags := APIInfo["tags"].([]interface{}) + if len(tags) != 1 { + panic(errors.New(fmt.Sprintf("only support one tag, got %d tags",len(tags)))) + } + + if reflect.TypeOf(tags[0]).String() != "string" { + panic(errors.New(fmt.Sprintf("unexpected data type, expected: string, got: %s", + reflect.TypeOf(tags[0]).String()))) + } + moduleName := moduleToTag[tags[0].(string)] + enable := moduleEnabled(moduleArray,moduleName) + + if enable { + if filteredAPIs[path] != nil { + originalOperations := filteredAPIs[path].(map[string]interface{}) + originalOperations[operation]=API + filteredAPIs[path] = originalOperations + } else { + originalOperations := make(map[string]interface{}) + originalOperations[operation]=API + filteredAPIs[path] = originalOperations + } + } + } + } + return filteredAPIs +} + +func (s *s) ReadDoc() string { + listenAddr := viper.GetString(client.FlagListenAddr) + swaggerHost := viper.GetString(client.FlagSwaggerHostIP) + modules := viper.GetString(client.FlagModules) + + var docs map[string]interface{} + if err := json.Unmarshal([]byte(doc), &docs); err != nil { + panic(err) + } + + addrInfo := strings.Split(listenAddr,":") + if len(addrInfo) != 2{ + panic(errors.New("invalid listen address")) + } + listenPort := addrInfo[1] + docs["host"] = swaggerHost + ":" + listenPort + + if reflect.TypeOf(docs["info"]).String() != "map[string]interface {}" { + panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(docs["info"]).String()))) + } + infos := docs["info"].(map[string]interface{}) + description := infos["description"].(string) + + infos["description"] = addOptionsToDesc(description) + docs["info"] = infos + + if reflect.TypeOf(docs["paths"]).String() != "map[string]interface {}" { + panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(docs["paths"]).String()))) + } + paths := docs["paths"].(map[string]interface{}) + docs["paths"] = modularizeAPIs(modules,paths) + + docString,err := json.Marshal(docs) + if err != nil { + panic(err) + } + + return string(docString) +} +func init() { + swag.Register(swag.Name, &s{}) +} \ No newline at end of file diff --git a/client/lcd/root.go b/client/lcd/root.go index bfa62f1cf0aa..993e89112140 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -22,6 +22,15 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" tmserver "github.com/tendermint/tendermint/rpc/lib/server" + "github.com/gin-gonic/gin" + "github.com/swaggo/gin-swagger" + "github.com/swaggo/gin-swagger/swaggerFiles" + "strings" + "errors" + _ "github.com/cosmos/cosmos-sdk/client/lcd/docs" + "github.com/tendermint/tendermint/libs/cli" + tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + keyTypes "github.com/cosmos/cosmos-sdk/crypto/keys" ) // ServeCommand will generate a long-running rest server @@ -96,3 +105,99 @@ func createHandler(cdc *wire.Codec) http.Handler { return r } + +func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "rest-server-swagger", + Short: "Start LCD (light-client daemon), a local REST server with swagger-ui, default uri: http://localhost:1317/swagger/index.html", + 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(errors.New("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) + go server.Run(listenAddr) + + logger.Info("REST server started") + + // Wait forever and cleanup + cmn.TrapSignal(func() { + logger.Info("Closing rest server...") + }) + + return nil + }, + } + + cmd.Flags().String(client.FlagListenAddr, "localhost:1317", "Address for server to listen on.") + cmd.Flags().String(client.FlagNodeList, "tcp://localhost:26657", "Node list to connect to, example: \"tcp://10.10.10.10:26657,tcp://20.20.20.20:26657\".") + cmd.Flags().String(client.FlagChainID, "", "ID of chain we connect to, must be specified.") + cmd.Flags().String(client.FlagSwaggerHostIP, "localhost", "The host IP of the Cosmos-LCD server, swagger will send request to this host.") + cmd.Flags().String(client.FlagModules, "general,key,token", "Enabled modules.") + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust full nodes or not.") + + return cmd +} + +func createSwaggerHandler(server *gin.Engine, ctx context.CLIContext, cdc *wire.Codec, kb keyTypes.Keybase) { + 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)) + } +/* + if moduleEnabled(moduleArray,"key") { + keys.RegisterAll(server.Group("/")) + } +*/ + if moduleEnabled(moduleArray,"token") { + auth.RegisterLCDRoutes(server.Group("/"), ctx, cdc, "acc") + bank.RegisterLCDRoutes(server.Group("/"), ctx, cdc, kb) + } +/* + if moduleEnabled(moduleArray,"stake") { + stake.RegisterQueryLCDRoutes(server.Group("/"), ctx, cdc) + } +*/ +} + +func moduleEnabled(modules []string, name string) bool { + for _, moduleName := range modules { + if moduleName == name { + return true + } + } + return false +} \ No newline at end of file diff --git a/client/lcd/version.go b/client/lcd/version.go index 377d7ca2681f..594116564645 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -6,6 +6,10 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/version" + "github.com/gin-gonic/gin" + "github.com/swaggo/swag/example/celler/httputil" + "errors" + "github.com/cosmos/cosmos-sdk/client/utils" ) // cli version REST handler endpoint @@ -27,3 +31,19 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { w.Write(version) } } + +func CLIVersionRequest(gtx *gin.Context) { + v := version.GetVersion() + utils.Response(gtx,v) +} + +func NodeVersionRequest(cliCtx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + appVersion, err := cliCtx.Query("/app/version") + if err != nil { + httputil.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) + return + } + utils.Response(gtx,string(appVersion)) + } +} \ No newline at end of file diff --git a/client/utils/utils.go b/client/utils/utils.go index 8a058b56fda0..710efbe70fad 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -5,6 +5,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" + "github.com/gin-gonic/gin" + "net/http" ) // SendTx implements a auxiliary handler that facilitates sending a series of @@ -58,3 +60,33 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) // broadcast to a Tendermint node return cliCtx.EnsureBroadcastTx(txBytes) } + +func NewError(ctx *gin.Context, errCode int, err error) { + errorResponse := HTTPError{ + Api: "2.0", + Code: errCode, + ErrMsg: err.Error(), + } + ctx.JSON(errCode, errorResponse) +} + +func Response(ctx *gin.Context, data interface{}) { + response := HTTPResponse{ + Api: "2.0", + Code: 0, + Result: data, + } + ctx.JSON(http.StatusOK, response) +} + +type HTTPResponse struct { + Api string `json:"rest api" example:"2.0"` + Code int `json:"code" example:"0"` + Result interface{} `json:"result"` +} + +type HTTPError struct { + Api string `json:"rest api" example:"2.0"` + Code int `json:"code" example:"500"` + ErrMsg string `json:"error message"` +} \ No newline at end of file diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 9c4d67b8c4a8..241f7eeedfb9 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -71,6 +71,7 @@ func main() { tendermintCmd, ibcCmd, lcd.ServeCommand(cdc), + lcd.ServeSwaggerCommand(cdc), ) rootCmd.AddCommand( advancedCmd, diff --git a/store/multistoreproof.go b/store/multistoreproof.go new file mode 100644 index 000000000000..abf5ce07a9e4 --- /dev/null +++ b/store/multistoreproof.go @@ -0,0 +1,135 @@ +package store + +import ( + "github.com/tendermint/tendermint/crypto/merkle" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/iavl" + "bytes" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" +) + +type SubstoreCommitID struct { + Name string `json:"name"` + Version int64 `json:"version"` + CommitHash cmn.HexBytes `json:"commit_hash"` +} + +type MultiStoreProof struct { + CommitIDList []SubstoreCommitID `json:"commit_id_list"` + StoreName string `json:"store_name"` + RangeProof iavl.RangeProof `json:"range_proof"` +} + +func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) ([]byte, error) { + var rangeProof iavl.RangeProof + err := cdc.UnmarshalBinary(iavlProof,&rangeProof) + if err != nil { + return nil, err + } + + var multiStoreProof MultiStoreProof + for _,storeInfo := range storeInfos { + + commitId := SubstoreCommitID{ + Name: storeInfo.Name, + Version:storeInfo.Core.CommitID.Version, + CommitHash:storeInfo.Core.CommitID.Hash, + } + multiStoreProof.CommitIDList = append(multiStoreProof.CommitIDList,commitId) + } + multiStoreProof.StoreName = storeName + multiStoreProof.RangeProof = rangeProof + + proof,err := cdc.MarshalBinary(multiStoreProof) + if err != nil { + return nil, err + } + + return proof, nil +} + +func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []SubstoreCommitID, appHash []byte) ([]byte, error) { + var substoreCommitHash []byte + var kvPairs cmn.KVPairs + for _,multiStoreCommitID := range multiStoreCommitInfo { + + if multiStoreCommitID.Name == storeName { + substoreCommitHash = multiStoreCommitID.CommitHash; + } + + kHash := []byte(multiStoreCommitID.Name) + storeInfo := storeInfo{ + Core:storeCore{ + CommitID:sdk.CommitID{ + Version: multiStoreCommitID.Version, + Hash: multiStoreCommitID.CommitHash, + }, + }, + } + + kvPairs = append(kvPairs, cmn.KVPair{ + Key: kHash, + Value: storeInfo.Hash(), + }) + } + if len(substoreCommitHash) == 0 { + return nil, cmn.NewError("failed to get substore root commit hash by store name") + } + if kvPairs == nil { + return nil, cmn.NewError("failed to extract information from multiStoreCommitInfo") + } + //sort the kvPair list + kvPairs.Sort() + + //Rebuild simple merkle hash tree + var hashList [][]byte + for _, kvPair := range kvPairs { + hashResult := merkle.SimpleHashFromTwoHashes(kvPair.Key,kvPair.Value) + hashList=append(hashList,hashResult) + } + + if !bytes.Equal(appHash,simpleHashFromHashes(hashList)){ + return nil, cmn.NewError("The merkle root of multiStoreCommitInfo doesn't equal to appHash") + } + return substoreCommitHash, nil +} + +func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) (error){ + + // Validate the proof to ensure data integrity. + err := rangeProof.Verify(substoreCommitHash) + if err != nil { + return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") + } + + if len(value) != 0 { + // Validate existence proof + err = rangeProof.VerifyItem(key, value) + if err != nil { + return errors.Wrap(err, "failed in existence verification") + } + } else { + // Validate absence proof + err = rangeProof.VerifyAbsence(key) + if err != nil { + return errors.Wrap(err, "failed in absence verification") + } + } + + return nil +} + +func simpleHashFromHashes(hashes [][]byte) []byte { + // Recursive impl. + switch len(hashes) { + case 0: + return nil + case 1: + return hashes[0] + default: + left := simpleHashFromHashes(hashes[:(len(hashes)+1)/2]) + right := simpleHashFromHashes(hashes[(len(hashes)+1)/2:]) + return merkle.SimpleHashFromTwoHashes(left,right) + } +} \ No newline at end of file diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go new file mode 100644 index 000000000000..9c4b0721aed5 --- /dev/null +++ b/store/multistoreproof_test.go @@ -0,0 +1,96 @@ +package store + +import ( + "testing" + "github.com/tendermint/iavl" + cmn "github.com/tendermint/tendermint/libs/common" + "encoding/hex" + "github.com/stretchr/testify/assert" +) + +func TestVerifyMultiStoreCommitInfo(t *testing.T) { + appHash,_ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84") + + substoreRootHash,_ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") + storeName := "acc" + + var multiStoreCommitInfo []SubstoreCommitID + + gocRootHash,_ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78") + multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ + Name:"gov", + Version:689, + CommitHash:gocRootHash, + }) + + multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ + Name:"main", + Version:689, + CommitHash:nil, + }) + + accRootHash,_ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") + multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ + Name:"acc", + Version:689, + CommitHash:accRootHash, + }) + + multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ + Name:"ibc", + Version:689, + CommitHash:nil, + }) + + stakeRootHash,_ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4") + multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ + Name:"stake", + Version:689, + CommitHash:stakeRootHash, + }) + + slashingRootHash,_ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e") + multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ + Name:"slashing", + Version:689, + CommitHash:slashingRootHash, + }) + + commitHash, err := VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + assert.Nil(t, err) + assert.Equal(t, commitHash, substoreRootHash) + + appHash,_ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") + + _,err = VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + assert.Error(t,err,"appHash doesn't match to the merkle root of multiStoreCommitInfo") +} + +func TestVerifyRangeProof(t *testing.T) { + tree := iavl.NewTree(nil, 0) + + rand := cmn.NewRand() + rand.Seed(0) // for determinism + for _, ikey := range []byte{0x11, 0x32, 0x50, 0x72, 0x99} { + key := []byte{ikey} + tree.Set(key, []byte(rand.Str(8))) + } + + root := tree.Hash() + + key := []byte{0x32} + val, proof, err := tree.GetWithProof(key) + assert.Nil(t, err) + assert.NotEmpty(t, val) + assert.NotEmpty(t, proof) + err = VerifyRangeProof(key, val, root, proof) + assert.Nil(t, err) + + key = []byte{0x40} + val, proof, err = tree.GetWithProof(key) + assert.Nil(t, err) + assert.Empty(t, val) + assert.NotEmpty(t, proof) + err = VerifyRangeProof(key, val, root, proof) + assert.Nil(t, err) +} \ No newline at end of file diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 8eb1c33dd55f..021c06dc261d 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -288,6 +288,23 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { // trim the path and make the query req.Path = subpath res := queryable.Query(req) + + // WARNING This should be consistent with query method in iavlstore.go + if !req.Prove || subpath != "/store" && subpath != "/key" { + return res + } + + //Load commit info from db + commitInfo, errMsg := getCommitInfo(rs.db,res.Height) + if errMsg != nil { + return sdk.ErrInternal(errMsg.Error()).QueryResult() + } + + res.Proof, errMsg = BuildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) + if errMsg != nil { + return sdk.ErrInternal(errMsg.Error()).QueryResult() + } + return res } diff --git a/store/transientstore.go b/store/transientstore.go index 1c099fa0ddea..e3e0b6bf4b58 100644 --- a/store/transientstore.go +++ b/store/transientstore.go @@ -2,6 +2,7 @@ package store import ( dbm "github.com/tendermint/tendermint/libs/db" + sdk "github.com/cosmos/cosmos-sdk/types" ) var _ KVStore = (*transientStore)(nil) @@ -41,3 +42,8 @@ func (ts *transientStore) Prefix(prefix []byte) KVStore { func (ts *transientStore) Gas(meter GasMeter, config GasConfig) KVStore { return NewGasKVStore(meter, config, ts) } + +// Implements Store. +func (st *transientStore) GetStoreType() StoreType { + return sdk.StoreTypeTransient +} \ No newline at end of file diff --git a/x/auth/client/context/context.go b/x/auth/client/context/context.go index 1cfa435ee516..b4804dc8aaaf 100644 --- a/x/auth/client/context/context.go +++ b/x/auth/client/context/context.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/viper" + "github.com/tendermint/tendermint/crypto" ) // TxContext implements a transaction context created in SDK modules. @@ -150,3 +151,34 @@ func (ctx TxContext) BuildAndSign(name, passphrase string, msgs []sdk.Msg) ([]by return ctx.Sign(name, passphrase, msg) } + +// build the transaction from the msg +func (ctx TxContext) BuildTxWithSignature(cdc *wire.Codec, msgs auth.StdSignMsg, signatureBytes []byte, publicKeyBytes []byte) ([]byte, error) { + + var publicKey crypto.PubKey + err := cdc.UnmarshalBinaryBare(publicKeyBytes, &publicKey) + if err != nil { + return nil, err + } + + stdSignatures := []auth.StdSignature{{ + AccountNumber: ctx.AccountNumber, + Sequence: ctx.Sequence, + PubKey: publicKey, + Signature: signatureBytes, + }} + + memo := ctx.Memo + fee := sdk.Coin{} + if ctx.Fee != "" { + parsedFee, err := sdk.ParseCoin(ctx.Fee) + if err != nil { + return nil, err + } + fee = parsedFee + } + + tx := auth.NewStdTx(msgs.Msgs, auth.NewStdFee(ctx.Gas, fee), stdSignatures, memo) + + return ctx.Codec.MarshalBinary(tx) +} \ No newline at end of file diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 431d3d27d53f..d319357bdbe5 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -11,6 +11,9 @@ import ( authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/gorilla/mux" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/client/utils" + "errors" ) // register REST routes @@ -69,3 +72,42 @@ func QueryAccountRequestHandlerFn( w.Write(output) } } + +// register to Cosmos-LCD swagger routes +func RegisterLCDRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, storeName string) { + routerGroup.GET("accounts/:address",QueryKeysRequestHandlerFn(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) +} + +func QueryKeysRequestHandlerFn(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + + bech32addr := gtx.Param("address") + + addr, err := sdk.AccAddressFromBech32(bech32addr) + if err != nil { + utils.NewError(gtx, http.StatusConflict, err) + return + } + + res, err := ctx.QueryStore(auth.AddressStoreKey(addr), storeName) + if err != nil { + utils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query account. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this account + if len(res) == 0 { + utils.Response(gtx,nil) + return + } + + // decode the value + account, err := decoder(res) + if err != nil { + utils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error()))) + return + } + + utils.Response(gtx,account) + } +} \ No newline at end of file diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index e060a3bb4fd1..48e5a1ed135a 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -13,6 +13,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank/client" "github.com/gorilla/mux" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/x/auth" + "encoding/base64" + "github.com/cosmos/cosmos-sdk/client/utils" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "errors" ) // RegisterRoutes - Central function to define routes that get registered by the main application @@ -30,6 +36,26 @@ type sendBody struct { AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` + Fee string `json:"fee"` +} + +type transferBody struct { + ChainID string `json:"chain_id"` + FromAddress string `json:"from_address"` + ToAddress string `json:"to_address"` + Amount sdk.Int `json:"amount"` + Denomination string `json:"denomination"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + EnsureAccAndSeq bool `json:"ensure_account_sequence"` + Gas int64 `json:"gas"` + Fee string `json:"fee"` +} + +type signedBody struct { + TransferBody transferBody `json:"transfer_body"` + Signature []byte `json:"signature_list"` + PublicKey []byte `json:"public_key_list"` } var msgCdc = wire.NewCodec() @@ -84,6 +110,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo txCtx := authctx.TxContext{ Codec: cdc, Gas: m.Gas, + Fee: m.Fee, ChainID: m.ChainID, AccountNumber: m.AccountNumber, Sequence: m.Sequence, @@ -113,3 +140,205 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo w.Write(output) } } + +func RegisterLCDRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { + routerGroup.POST("/accounts/:address/send", SendRequestFn(cdc, ctx, kb)) + routerGroup.POST("/create_transfer", CreateTransferTxForSignFn(cdc, ctx)) + routerGroup.POST("/signed_transfer", ComposeAndBroadcastSignedTransferTxFn(cdc, ctx)) +} + +func composeTx(cdc *wire.Codec, ctx context.CLIContext, transferBody transferBody) (auth.StdSignMsg, authctx.TxContext, sdk.Error) { + + emptyMsg := auth.StdSignMsg{} + emptyTxContext := authctx.TxContext{} + + amount := sdk.NewCoin(transferBody.Denomination, transferBody.Amount) + var amounts sdk.Coins + amounts = append(amounts, amount) + + fromAddress, err := sdk.AccAddressFromBech32(transferBody.FromAddress) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInvalidAddress(err.Error()) + } + + toAddress, err := sdk.AccAddressFromBech32(transferBody.ToAddress) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInvalidAddress(err.Error()) + } + + // build message + msg := client.BuildMsg(fromAddress, toAddress, amounts) + + accountNumber := transferBody.AccountNumber + sequence := transferBody.Sequence + gas := transferBody.Gas + fee := transferBody.Fee + + if transferBody.EnsureAccAndSeq { + if ctx.AccDecoder == nil { + ctx = ctx.WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + } + accountNumber, err = ctx.GetAccountNumber(fromAddress) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) + } + + sequence, err = ctx.GetAccountSequence(fromAddress) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) + } + } + + txCtx := authctx.TxContext{ + Codec: cdc, + Gas: gas, + Fee: fee, + ChainID: transferBody.ChainID, + AccountNumber: accountNumber, + Sequence: sequence, + } + + txForSign, err := txCtx.Build([]sdk.Msg{msg}) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) + } + + return txForSign, txCtx, nil +} + +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 { + utils.NewError(gtx, http.StatusBadRequest, err) + return + } + + txForSign, _, err := composeTx(cdc, ctx, transferBody) + if err != nil { + if err.Code() == sdk.CodeInternal { + utils.NewError(gtx, http.StatusInternalServerError, err) + } else { + utils.NewError(gtx, http.StatusBadRequest, err) + } + return + } + + base64TxData := make([]byte, base64.StdEncoding.EncodedLen(len(txForSign.Bytes()))) + base64.StdEncoding.Encode(base64TxData,txForSign.Bytes()) + + utils.Response(gtx,string(base64TxData)) + } +} + +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 { + utils.NewError(gtx, http.StatusBadRequest, err) + return + } + + if signedTransaction.Signature == nil || signedTransaction.PublicKey == nil { + utils.NewError(gtx, http.StatusBadRequest, errors.New("signature or public key is empty")) + return + } + + signature, err := base64.StdEncoding.DecodeString(string(signedTransaction.Signature)) + if err != nil { + utils.NewError(gtx, http.StatusBadRequest, err) + return + } + publicKey, err := base64.StdEncoding.DecodeString(string(signedTransaction.PublicKey)) + if err != nil { + utils.NewError(gtx, http.StatusBadRequest, err) + return + } + + txForSign, txCtx, errMsg := composeTx(cdc, ctx, signedTransaction.TransferBody) + if errMsg != nil { + if errMsg.Code() == sdk.CodeInternal { + utils.NewError(gtx, http.StatusInternalServerError, errMsg) + } else { + utils.NewError(gtx, http.StatusBadRequest, errMsg) + } + } + + txDataForBroadcast, err := txCtx.BuildTxWithSignature(cdc, txForSign, signature, publicKey) + if err != nil { + utils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + res, err := ctx.BroadcastTx(txDataForBroadcast) + if err != nil { + utils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + utils.Response(gtx,res) + } +} + +func SendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin.HandlerFunc { + return func(gtx *gin.Context) { + + bech32addr := gtx.Param("address") + + address, err := sdk.AccAddressFromBech32(bech32addr) + if err != nil { + utils.NewError(gtx, http.StatusBadRequest, err) + return + } + + var m sendBody + if err := gtx.BindJSON(&m); err != nil { + utils.NewError(gtx, http.StatusBadRequest, err) + return + } + + info, err := kb.Get(m.LocalAccountName) + if err != nil { + utils.NewError(gtx, http.StatusUnauthorized, err) + return + } + + from := sdk.AccAddress(info.GetPubKey().Address()) + + to, err := sdk.AccAddressFromBech32(address.String()) + if err != nil { + utils.NewError(gtx, http.StatusBadRequest, err) + return + } + + // build message + msg := client.BuildMsg(from, to, m.Amount) + if err != nil { // XXX rechecking same error ? + utils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + txCtx := authctx.TxContext{ + Codec: cdc, + Gas: m.Gas, + Fee: m.Fee, + ChainID: m.ChainID, + AccountNumber: m.AccountNumber, + Sequence: m.Sequence, + } + + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if err != nil { + utils.NewError(gtx, http.StatusUnauthorized, err) + return + } + + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + utils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + utils.Response(gtx,res) + } +} \ No newline at end of file From eccb889fb82680a0ec483e8ff6c37afa1b3dfb12 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 16 Aug 2018 16:22:00 +0800 Subject: [PATCH 02/25] Add golang dependency for swagger --- Gopkg.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Gopkg.toml b/Gopkg.toml index 0651fe1f5f5f..3c7b3ed162e7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -47,6 +47,14 @@ name = "github.com/stretchr/testify" version = "=1.2.1" +[[constraint]] + name = "github.com/swaggo/gin-swagger" + version = "=v1.0.0" + +[[constraint]] + name = "github.com/swaggo/swag" + version = "=v1.3.2" + [[override]] name = "github.com/tendermint/go-amino" version = "=0.10.1" From 2047673ee4ddca426c4b3aeee6b60bb78c8dbef2 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 16 Aug 2018 18:10:27 +0800 Subject: [PATCH 03/25] Add key rest api --- Gopkg.lock | 18 ++++++++ client/httputils/httputils.go | 36 ++++++++++++++++ client/keys/add.go | 77 +++++++++++++++++++++++++++++++++++ client/keys/delete.go | 28 +++++++++++++ client/keys/list.go | 26 ++++++++++++ client/keys/root.go | 11 +++++ client/keys/show.go | 22 +++++++++- client/keys/update.go | 36 ++++++++++++++++ client/lcd/root.go | 4 +- client/lcd/version.go | 6 +-- client/utils/utils.go | 32 --------------- x/auth/client/rest/query.go | 12 +++--- x/bank/client/rest/sendtx.go | 44 ++++++++++---------- 13 files changed, 286 insertions(+), 66 deletions(-) create mode 100644 client/httputils/httputils.go diff --git a/Gopkg.lock b/Gopkg.lock index de244c162c5d..55e910ee7c6a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -374,6 +374,24 @@ revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" version = "v1.2.1" +[[projects]] + name = "github.com/swaggo/gin-swagger" + packages = [ + ".", + "swaggerFiles" + ] + revision = "8cf3fa9932e247205d1cf2be6fd13a9d09ceb9a4" + version = "v1.0.0" + +[[projects]] + name = "github.com/swaggo/swag" + packages = [ + ".", + "example/celler/httputil" + ] + revision = "db2ee6c14a35e4fca95b0d75054296ef2b699050" + version = "v1.3.2" + [[projects]] branch = "master" digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b" diff --git a/client/httputils/httputils.go b/client/httputils/httputils.go new file mode 100644 index 000000000000..39f9a1558585 --- /dev/null +++ b/client/httputils/httputils.go @@ -0,0 +1,36 @@ +package httputils + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +func NewError(ctx *gin.Context, errCode int, err error) { + errorResponse := HTTPError{ + Api: "2.0", + Code: errCode, + ErrMsg: err.Error(), + } + ctx.JSON(errCode, errorResponse) +} + +func Response(ctx *gin.Context, data interface{}) { + response := HTTPResponse{ + Api: "2.0", + Code: 0, + Result: data, + } + ctx.JSON(http.StatusOK, response) +} + +type HTTPResponse struct { + Api string `json:"rest api" example:"2.0"` + Code int `json:"code" example:"0"` + Result interface{} `json:"result"` +} + +type HTTPError struct { + Api string `json:"rest api" example:"2.0"` + Code int `json:"code" example:"500"` + ErrMsg string `json:"error message"` +} \ No newline at end of file diff --git a/client/keys/add.go b/client/keys/add.go index d462db1c0697..ba185171c1f4 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -16,6 +16,9 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/tendermint/tendermint/libs/cli" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/client/httputils" + "regexp/syntax" ) const ( @@ -236,6 +239,71 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(bz) } +func AddNewKeyRequest(gtx *gin.Context) { + var kb keys.Keybase + var m NewKeyBody + + kb, err := GetKeyBase() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + if err := gtx.BindJSON(&m); err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + if len(m.Name) < 1 || len(m.Name) > 16 { + httputils.NewError(gtx, http.StatusBadRequest, errors.New("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, errors.New("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, errors.New("Account password length should be between 8 and 16")) + return + } + + // check if already exists + infos, err := kb.List() + for _, i := range infos { + if i.GetName() == m.Name { + httputils.NewError(gtx, http.StatusConflict, errors.New(fmt.Sprintf("Account with name %s already exists.", m.Name))) + return + } + } + + // create account + seed := m.Seed + if seed == "" { + seed = getSeed(keys.Secp256k1) + } + info, err := kb.CreateKey(m.Name, seed, m.Password) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + keyOutput, err := Bech32KeyOutput(info) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + keyOutput.Seed = seed + + httputils.Response(gtx, keyOutput) + +} + // function to just a new seed to display in the UI before actually persisting it in the keybase func getSeed(algo keys.SigningAlgo) string { kb := client.MockKeyBase() @@ -258,3 +326,12 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { seed := getSeed(algo) w.Write([]byte(seed)) } + +func SeedRequest(gtx *gin.Context) { + + algo := keys.SigningAlgo("secp256k1") + + seed := getSeed(algo) + + httputils.Response(gtx, seed) +} \ No newline at end of file diff --git a/client/keys/delete.go b/client/keys/delete.go index 944feb4b19b2..e0bb9348c43b 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -10,6 +10,8 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/client/httputils" ) func deleteKeyCommand() *cobra.Command { @@ -90,3 +92,29 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } + +func DeleteKeyRequest(gtx *gin.Context) { + name := gtx.Param("name") + var kb keys.Keybase + var m DeleteKeyBody + + if err := gtx.BindJSON(&m); err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + kb, err := GetKeyBase() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + // TODO handle error if key is not available or pass is wrong + err = kb.Delete(name, m.Password) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.Response(gtx, "success") +} \ No newline at end of file diff --git a/client/keys/list.go b/client/keys/list.go index 22f163f1d8ff..6ed5c0c598ee 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -5,6 +5,8 @@ import ( "net/http" "github.com/spf13/cobra" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/client/httputils" ) // CMD @@ -67,3 +69,27 @@ func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { } w.Write(output) } + +func QueryKeysRequest(gtx *gin.Context) { + kb, err := GetKeyBase() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + infos, err := kb.List() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + // an empty list will be JSONized as null, but we want to keep the empty list + if len(infos) == 0 { + httputils.Response(gtx, nil) + return + } + keysOutput, err := Bech32KeysOutput(infos) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + httputils.Response(gtx, keysOutput) +} \ No newline at end of file diff --git a/client/keys/root.go b/client/keys/root.go index c8f6aea693af..ffcf730cf111 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -4,6 +4,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/gorilla/mux" "github.com/spf13/cobra" + "github.com/gin-gonic/gin" ) // Commands registers a sub-tree of commands to interact with @@ -38,3 +39,13 @@ func RegisterRoutes(r *mux.Router) { r.HandleFunc("/keys/{name}", UpdateKeyRequestHandler).Methods("PUT") r.HandleFunc("/keys/{name}", DeleteKeyRequestHandler).Methods("DELETE") } + +// resgister swagger REST routes +func RegisterAll(routerGroup *gin.RouterGroup) { + routerGroup.GET("/keys", QueryKeysRequest) + routerGroup.POST("/keys", AddNewKeyRequest) + routerGroup.GET("/keys/seed", SeedRequest) + routerGroup.GET("/keys/get/:name", GetKeyRequest) + routerGroup.PUT("/keys/:name", UpdateKeyRequest) + routerGroup.DELETE("/keys/:name", DeleteKeyRequest) +} \ No newline at end of file diff --git a/client/keys/show.go b/client/keys/show.go index 873c45a4b605..51c67c4828da 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -4,10 +4,12 @@ import ( "encoding/json" "net/http" - keys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/gorilla/mux" "github.com/spf13/cobra" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/client/httputils" ) var showKeysCmd = &cobra.Command{ @@ -65,3 +67,21 @@ func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(output) } + +func GetKeyRequest(gtx *gin.Context) { + name := gtx.Param("name") + + info, err := getKey(name) + // TODO check for the error if key actually does not exist, instead of assuming this as the reason + if err != nil { + httputils.NewError(gtx, http.StatusNotFound, err) + return + } + + keyOutput, err := Bech32KeyOutput(info) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + httputils.Response(gtx, keyOutput) +} \ No newline at end of file diff --git a/client/keys/update.go b/client/keys/update.go index 78a81bf0e605..e9a3118c91f2 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -10,6 +10,9 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" + "github.com/gin-gonic/gin" + "errors" + "github.com/cosmos/cosmos-sdk/client/httputils" ) func updateKeyCommand() *cobra.Command { @@ -93,3 +96,36 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } + +func UpdateKeyRequest(gtx *gin.Context) { + name := gtx.Param("name") + var kb keys.Keybase + var m UpdateKeyBody + + if err := gtx.BindJSON(&m); err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + kb, err := GetKeyBase() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + if len(m.NewPassword) < 8 || len(m.NewPassword) > 16 { + httputils.NewError(gtx, http.StatusBadRequest, errors.New("account password length should be between 8 and 16")) + return + } + + getNewpass := func() (string, error) { return m.NewPassword, nil } + + // TODO check if account exists and if password is correct + err = kb.Update(name, m.OldPassword, getNewpass) + if err != nil { + httputils.NewError(gtx, http.StatusUnauthorized, err) + return + } + + httputils.Response(gtx, "success") +} \ No newline at end of file diff --git a/client/lcd/root.go b/client/lcd/root.go index 993e89112140..26a3f49383c6 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -177,11 +177,11 @@ func createSwaggerHandler(server *gin.Engine, ctx context.CLIContext, cdc *wire. server.GET("/version", CLIVersionRequest) server.GET("/node_version", NodeVersionRequest(ctx)) } -/* + if moduleEnabled(moduleArray,"key") { keys.RegisterAll(server.Group("/")) } -*/ + if moduleEnabled(moduleArray,"token") { auth.RegisterLCDRoutes(server.Group("/"), ctx, cdc, "acc") bank.RegisterLCDRoutes(server.Group("/"), ctx, cdc, kb) diff --git a/client/lcd/version.go b/client/lcd/version.go index 594116564645..64beeb2d06d7 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -9,7 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/swaggo/swag/example/celler/httputil" "errors" - "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/client/httputils" ) // cli version REST handler endpoint @@ -34,7 +34,7 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { func CLIVersionRequest(gtx *gin.Context) { v := version.GetVersion() - utils.Response(gtx,v) + httputils.Response(gtx,v) } func NodeVersionRequest(cliCtx context.CLIContext) gin.HandlerFunc { @@ -44,6 +44,6 @@ func NodeVersionRequest(cliCtx context.CLIContext) gin.HandlerFunc { httputil.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) return } - utils.Response(gtx,string(appVersion)) + httputils.Response(gtx,string(appVersion)) } } \ No newline at end of file diff --git a/client/utils/utils.go b/client/utils/utils.go index 710efbe70fad..8a058b56fda0 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -5,8 +5,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" - "github.com/gin-gonic/gin" - "net/http" ) // SendTx implements a auxiliary handler that facilitates sending a series of @@ -60,33 +58,3 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) // broadcast to a Tendermint node return cliCtx.EnsureBroadcastTx(txBytes) } - -func NewError(ctx *gin.Context, errCode int, err error) { - errorResponse := HTTPError{ - Api: "2.0", - Code: errCode, - ErrMsg: err.Error(), - } - ctx.JSON(errCode, errorResponse) -} - -func Response(ctx *gin.Context, data interface{}) { - response := HTTPResponse{ - Api: "2.0", - Code: 0, - Result: data, - } - ctx.JSON(http.StatusOK, response) -} - -type HTTPResponse struct { - Api string `json:"rest api" example:"2.0"` - Code int `json:"code" example:"0"` - Result interface{} `json:"result"` -} - -type HTTPError struct { - Api string `json:"rest api" example:"2.0"` - Code int `json:"code" example:"500"` - ErrMsg string `json:"error message"` -} \ No newline at end of file diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index d319357bdbe5..6d3f47eb0ff7 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -12,7 +12,7 @@ import ( "github.com/gorilla/mux" "github.com/gin-gonic/gin" - "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/client/httputils" "errors" ) @@ -85,29 +85,29 @@ func QueryKeysRequestHandlerFn(storeName string, cdc *wire.Codec, decoder auth.A addr, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { - utils.NewError(gtx, http.StatusConflict, err) + httputils.NewError(gtx, http.StatusConflict, err) return } res, err := ctx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { - utils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query account. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query account. Error: %s", err.Error()))) return } // the query will return empty if there is no data for this account if len(res) == 0 { - utils.Response(gtx,nil) + httputils.Response(gtx,nil) return } // decode the value account, err := decoder(res) if err != nil { - utils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error()))) return } - utils.Response(gtx,account) + httputils.Response(gtx,account) } } \ No newline at end of file diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 48e5a1ed135a..a5f787f8816e 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -16,7 +16,7 @@ import ( "github.com/gin-gonic/gin" "github.com/cosmos/cosmos-sdk/x/auth" "encoding/base64" - "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/client/httputils" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "errors" ) @@ -210,16 +210,16 @@ func CreateTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.Hand return func(gtx *gin.Context) { var transferBody transferBody if err := gtx.BindJSON(&transferBody); err != nil { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) return } txForSign, _, err := composeTx(cdc, ctx, transferBody) if err != nil { if err.Code() == sdk.CodeInternal { - utils.NewError(gtx, http.StatusInternalServerError, err) + httputils.NewError(gtx, http.StatusInternalServerError, err) } else { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) } return } @@ -227,7 +227,7 @@ 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()) - utils.Response(gtx,string(base64TxData)) + httputils.Response(gtx,string(base64TxData)) } } @@ -235,48 +235,48 @@ func ComposeAndBroadcastSignedTransferTxFn(cdc *wire.Codec, ctx context.CLIConte return func(gtx *gin.Context) { var signedTransaction signedBody if err := gtx.BindJSON(&signedTransaction); err != nil { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) return } if signedTransaction.Signature == nil || signedTransaction.PublicKey == nil { - utils.NewError(gtx, http.StatusBadRequest, errors.New("signature or public key is empty")) + httputils.NewError(gtx, http.StatusBadRequest, errors.New("signature or public key is empty")) return } signature, err := base64.StdEncoding.DecodeString(string(signedTransaction.Signature)) if err != nil { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) return } publicKey, err := base64.StdEncoding.DecodeString(string(signedTransaction.PublicKey)) if err != nil { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) return } txForSign, txCtx, errMsg := composeTx(cdc, ctx, signedTransaction.TransferBody) if errMsg != nil { if errMsg.Code() == sdk.CodeInternal { - utils.NewError(gtx, http.StatusInternalServerError, errMsg) + httputils.NewError(gtx, http.StatusInternalServerError, errMsg) } else { - utils.NewError(gtx, http.StatusBadRequest, errMsg) + httputils.NewError(gtx, http.StatusBadRequest, errMsg) } } txDataForBroadcast, err := txCtx.BuildTxWithSignature(cdc, txForSign, signature, publicKey) if err != nil { - utils.NewError(gtx, http.StatusInternalServerError, err) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } res, err := ctx.BroadcastTx(txDataForBroadcast) if err != nil { - utils.NewError(gtx, http.StatusInternalServerError, err) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } - utils.Response(gtx,res) + httputils.Response(gtx,res) } } @@ -287,19 +287,19 @@ func SendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin address, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) return } var m sendBody if err := gtx.BindJSON(&m); err != nil { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) return } info, err := kb.Get(m.LocalAccountName) if err != nil { - utils.NewError(gtx, http.StatusUnauthorized, err) + httputils.NewError(gtx, http.StatusUnauthorized, err) return } @@ -307,14 +307,14 @@ func SendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin to, err := sdk.AccAddressFromBech32(address.String()) if err != nil { - utils.NewError(gtx, http.StatusBadRequest, err) + httputils.NewError(gtx, http.StatusBadRequest, err) return } // build message msg := client.BuildMsg(from, to, m.Amount) if err != nil { // XXX rechecking same error ? - utils.NewError(gtx, http.StatusInternalServerError, err) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } @@ -329,16 +329,16 @@ func SendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { - utils.NewError(gtx, http.StatusUnauthorized, err) + httputils.NewError(gtx, http.StatusUnauthorized, err) return } res, err := ctx.BroadcastTx(txBytes) if err != nil { - utils.NewError(gtx, http.StatusInternalServerError, err) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } - utils.Response(gtx,res) + httputils.Response(gtx,res) } } \ No newline at end of file From 85f47d27e7adf42e5f0f3677fcc7f13a99485143 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 16 Aug 2018 18:54:07 +0800 Subject: [PATCH 04/25] Add stake query api to lcd swagger --- client/keys/root.go | 2 +- client/lcd/root.go | 12 +-- x/auth/client/rest/query.go | 2 +- x/bank/client/rest/sendtx.go | 2 +- x/stake/client/rest/query.go | 184 +++++++++++++++++++++++++++++++++++ x/stake/client/rest/rest.go | 5 + 6 files changed, 198 insertions(+), 9 deletions(-) diff --git a/client/keys/root.go b/client/keys/root.go index ffcf730cf111..d7b32c81a9bb 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -41,7 +41,7 @@ func RegisterRoutes(r *mux.Router) { } // resgister swagger REST routes -func RegisterAll(routerGroup *gin.RouterGroup) { +func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/keys", QueryKeysRequest) routerGroup.POST("/keys", AddNewKeyRequest) routerGroup.GET("/keys/seed", SeedRequest) diff --git a/client/lcd/root.go b/client/lcd/root.go index 26a3f49383c6..84dc0687ebfb 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -179,18 +179,18 @@ func createSwaggerHandler(server *gin.Engine, ctx context.CLIContext, cdc *wire. } if moduleEnabled(moduleArray,"key") { - keys.RegisterAll(server.Group("/")) + keys.RegisterSwaggerRoutes(server.Group("/")) } if moduleEnabled(moduleArray,"token") { - auth.RegisterLCDRoutes(server.Group("/"), ctx, cdc, "acc") - bank.RegisterLCDRoutes(server.Group("/"), ctx, cdc, kb) + auth.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, "acc") + bank.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, kb) } -/* + if moduleEnabled(moduleArray,"stake") { - stake.RegisterQueryLCDRoutes(server.Group("/"), ctx, cdc) + stake.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc) } -*/ + } func moduleEnabled(modules []string, name string) bool { diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 6d3f47eb0ff7..66e3c4faf9f5 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -74,7 +74,7 @@ func QueryAccountRequestHandlerFn( } // register to Cosmos-LCD swagger routes -func RegisterLCDRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, storeName string) { +func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, storeName string) { routerGroup.GET("accounts/:address",QueryKeysRequestHandlerFn(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index a5f787f8816e..bfd36fa9f863 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -141,7 +141,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo } } -func RegisterLCDRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { +func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { routerGroup.POST("/accounts/:address/send", SendRequestFn(cdc, ctx, kb)) routerGroup.POST("/create_transfer", CreateTransferTxForSignFn(cdc, ctx)) routerGroup.POST("/signed_transfer", ComposeAndBroadcastSignedTransferTxFn(cdc, ctx)) diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 4dfaa0f7faa4..bae6af63bfd3 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -14,6 +14,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/gorilla/mux" + "github.com/gin-gonic/gin" + "errors" + "github.com/cosmos/cosmos-sdk/client/httputils" ) const storeName = "stake" @@ -554,3 +557,184 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.Handler w.Write(output) } } + +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)) +} + +func delegationHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + + // read parameters + bech32delegator := gtx.Param("delegator") + bech32validator := gtx.Param("validator") + + 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 + } + + key := stake.GetDelegationKey(delegatorAddr, validatorAddr) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, errors.New(fmt.Sprintf("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.Response(gtx,nil) + return + } + + delegation, err := types.UnmarshalDelegation(cdc, key, res) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + httputils.Response(gtx,delegation) + } +} + +// http request handler to query an unbonding-delegation +func ubdHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + + // read parameters + bech32delegator := gtx.Param("delegator") + bech32validator := gtx.Param("validator") + + 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 + } + + key := stake.GetUBDKey(delegatorAddr, validatorAddr) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("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.Response(gtx,nil) + return + } + + ubd, err := types.UnmarshalUBD(cdc, key, res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + httputils.Response(gtx,ubd) + } +} + +// http request handler to query an redelegation +func redHandlerFun(cdc *wire.Codec, ctx 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") + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + validatorSrcAddr, err := sdk.AccAddressFromBech32(bech32validatorSrc) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + validatorDstAddr, err := sdk.AccAddressFromBech32(bech32validatorDst) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + + key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query redelegation. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + httputils.Response(gtx,nil) + return + } + + red, err := types.UnmarshalRED(cdc, key, res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + httputils.Response(gtx,red) + } +} + +func validatorsHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + kvs, err := ctx.QuerySubspace(stake.ValidatorsKey, storeName) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query validators. Error: %s", err.Error()))) + return + } + + // the query will return empty if there are no validators + if len(kvs) == 0 { + httputils.Response(gtx,nil) + return + } + + // parse out the validators + validators := make([]types.BechValidator, len(kvs)) + for i, kv := range kvs { + + addr := kv.Key[1:] + validator, err := types.UnmarshalValidator(cdc, addr, kv.Value) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + bech32Validator, err := validator.Bech32Validator() + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + validators[i] = bech32Validator + } + + httputils.Response(gtx,validators) + } +} \ No newline at end of file diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index 7c2fdf905207..70426867776b 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" "github.com/gorilla/mux" + "github.com/gin-gonic/gin" ) // RegisterRoutes registers staking-related REST handlers to a router @@ -13,3 +14,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, k registerQueryRoutes(cliCtx, r, cdc) registerTxRoutes(cliCtx, r, cdc, kb) } + +func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { + RegisterSwaggerQueryRoutes(routerGroup, ctx, cdc) +} \ No newline at end of file From 26af9361abc27f0b09b1b12b09110d05b858292e Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Tue, 21 Aug 2018 11:20:09 +0800 Subject: [PATCH 05/25] Refactor code according to lint result --- client/context/context.go | 12 +++--- client/context/loadbalancing.go | 9 ++-- client/httputils/httputils.go | 20 +++++---- client/keys/add.go | 19 ++++++--- client/keys/delete.go | 3 +- client/keys/list.go | 5 ++- client/keys/show.go | 3 +- client/keys/update.go | 3 +- client/lcd/docs/docs.go | 29 ++++++------- client/lcd/root.go | 16 ++++--- client/lcd/version.go | 9 ++-- cmd/gaia/cmd/gaiacli/main.go | 1 + store/multistoreproof.go | 69 ++++++++++++++++-------------- store/multistoreproof_test.go | 76 ++++++++++++++++----------------- store/transientstore.go | 2 +- x/auth/client/rest/query.go | 14 +++--- x/bank/client/rest/sendtx.go | 22 ++++++---- x/stake/client/rest/query.go | 33 +++++++------- x/stake/client/rest/rest.go | 3 +- 19 files changed, 187 insertions(+), 161 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index c7818f836b33..08f9bc3554ea 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -118,13 +118,13 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { } // WithCert - return a copy of the context with an updated Cert -func (c CLIContext) WithCert(cert tendermintLite.Certifier) CLIContext { - c.Cert = cert - return c +func (ctx CLIContext) WithCert(cert tendermintLite.Certifier) CLIContext { + ctx.Cert = cert + return ctx } // WithCert - return a copy of the context with an updated ClientMgr -func (c CLIContext) WithClientMgr(clientMgr *ClientManager) CLIContext { - c.ClientMgr = clientMgr - return c +func (ctx CLIContext) WithClientMgr(clientMgr *ClientManager) CLIContext { + ctx.ClientMgr = clientMgr + return ctx } \ No newline at end of file diff --git a/client/context/loadbalancing.go b/client/context/loadbalancing.go index 9a8c6e4b49ab..46cd2d5accd6 100644 --- a/client/context/loadbalancing.go +++ b/client/context/loadbalancing.go @@ -7,6 +7,8 @@ import ( "github.com/pkg/errors" ) +// This 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 currentIndex int @@ -15,9 +17,9 @@ type ClientManager struct { func NewClientManager(nodeURIs string) (*ClientManager,error) { if nodeURIs != "" { - nodeUrlArray := strings.Split(nodeURIs, ",") + nodeURLArray := strings.Split(nodeURIs, ",") var clients []rpcclient.Client - for _, url := range nodeUrlArray { + for _, url := range nodeURLArray { client := rpcclient.NewHTTP(url, "/websocket") clients = append(clients, client) } @@ -26,9 +28,8 @@ func NewClientManager(nodeURIs string) (*ClientManager,error) { clients: clients, } return mgr, nil - } else { - return nil, errors.New("missing node URIs") } + return nil, errors.New("missing node URIs") } func (mgr *ClientManager) getClient() rpcclient.Client { diff --git a/client/httputils/httputils.go b/client/httputils/httputils.go index 39f9a1558585..435afa23cc29 100644 --- a/client/httputils/httputils.go +++ b/client/httputils/httputils.go @@ -5,32 +5,34 @@ import ( "net/http" ) +// Create error http response func NewError(ctx *gin.Context, errCode int, err error) { - errorResponse := HTTPError{ - Api: "2.0", + errorResponse := httpError{ + API: "2.0", Code: errCode, ErrMsg: err.Error(), } ctx.JSON(errCode, errorResponse) } -func Response(ctx *gin.Context, data interface{}) { - response := HTTPResponse{ - Api: "2.0", +// Create normal http response +func NormalResponse(ctx *gin.Context, data interface{}) { + response := httpResponse{ + API: "2.0", Code: 0, Result: data, } ctx.JSON(http.StatusOK, response) } -type HTTPResponse struct { - Api string `json:"rest api" example:"2.0"` +type httpResponse struct { + API string `json:"rest api" example:"2.0"` Code int `json:"code" example:"0"` Result interface{} `json:"result"` } -type HTTPError struct { - Api string `json:"rest api" example:"2.0"` +type httpError struct { + API string `json:"rest api" example:"2.0"` Code int `json:"code" example:"500"` ErrMsg string `json:"error message"` } \ No newline at end of file diff --git a/client/keys/add.go b/client/keys/add.go index ba185171c1f4..0c67fd4e6dc7 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -239,6 +239,7 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(bz) } +// Handler of adding new key in swagger rest server func AddNewKeyRequest(gtx *gin.Context) { var kb keys.Keybase var m NewKeyBody @@ -258,25 +259,30 @@ func AddNewKeyRequest(gtx *gin.Context) { return } if len(m.Name) < 1 || len(m.Name) > 16 { - httputils.NewError(gtx, http.StatusBadRequest, errors.New("Account name length should not be longer than 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, errors.New("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, errors.New("Account password length should be between 8 and 16")) + httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account password length should be between 8 and 16")) return } // check if already exists infos, err := kb.List() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + for _, i := range infos { if i.GetName() == m.Name { - httputils.NewError(gtx, http.StatusConflict, errors.New(fmt.Sprintf("Account with name %s already exists.", m.Name))) + httputils.NewError(gtx, http.StatusConflict, fmt.Errorf("Account with name %s already exists.", m.Name)) return } } @@ -300,7 +306,7 @@ func AddNewKeyRequest(gtx *gin.Context) { keyOutput.Seed = seed - httputils.Response(gtx, keyOutput) + httputils.NormalResponse(gtx, keyOutput) } @@ -327,11 +333,12 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(seed)) } +// Handler of creating seed in swagger rest server func SeedRequest(gtx *gin.Context) { algo := keys.SigningAlgo("secp256k1") seed := getSeed(algo) - httputils.Response(gtx, seed) + httputils.NormalResponse(gtx, seed) } \ No newline at end of file diff --git a/client/keys/delete.go b/client/keys/delete.go index e0bb9348c43b..30db0b93b390 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -93,6 +93,7 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } +// Handler of deleting specified key in swagger rest server func DeleteKeyRequest(gtx *gin.Context) { name := gtx.Param("name") var kb keys.Keybase @@ -116,5 +117,5 @@ func DeleteKeyRequest(gtx *gin.Context) { return } - httputils.Response(gtx, "success") + httputils.NormalResponse(gtx, "success") } \ No newline at end of file diff --git a/client/keys/list.go b/client/keys/list.go index 6ed5c0c598ee..9ae74bda7fc8 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -70,6 +70,7 @@ func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(output) } +// Handler of listing all keys in swagger rest server func QueryKeysRequest(gtx *gin.Context) { kb, err := GetKeyBase() if err != nil { @@ -83,7 +84,7 @@ func QueryKeysRequest(gtx *gin.Context) { } // an empty list will be JSONized as null, but we want to keep the empty list if len(infos) == 0 { - httputils.Response(gtx, nil) + httputils.NormalResponse(gtx, nil) return } keysOutput, err := Bech32KeysOutput(infos) @@ -91,5 +92,5 @@ func QueryKeysRequest(gtx *gin.Context) { httputils.NewError(gtx, http.StatusInternalServerError, err) return } - httputils.Response(gtx, keysOutput) + httputils.NormalResponse(gtx, keysOutput) } \ No newline at end of file diff --git a/client/keys/show.go b/client/keys/show.go index 51c67c4828da..87d2c181b368 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -68,6 +68,7 @@ func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(output) } +// Handler of getting specified key in swagger rest server func GetKeyRequest(gtx *gin.Context) { name := gtx.Param("name") @@ -83,5 +84,5 @@ func GetKeyRequest(gtx *gin.Context) { httputils.NewError(gtx, http.StatusInternalServerError, err) return } - httputils.Response(gtx, keyOutput) + httputils.NormalResponse(gtx, keyOutput) } \ No newline at end of file diff --git a/client/keys/update.go b/client/keys/update.go index e9a3118c91f2..edd66dd35c1b 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -97,6 +97,7 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } +// Handler of updating specified key in swagger rest server func UpdateKeyRequest(gtx *gin.Context) { name := gtx.Param("name") var kb keys.Keybase @@ -127,5 +128,5 @@ func UpdateKeyRequest(gtx *gin.Context) { return } - httputils.Response(gtx, "success") + httputils.NormalResponse(gtx, "success") } \ No newline at end of file diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go index 8e525dc91034..5bda80b26352 100644 --- a/client/lcd/docs/docs.go +++ b/client/lcd/docs/docs.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/viper" "encoding/json" "strings" - "errors" "github.com/cosmos/cosmos-sdk/client" "bytes" "fmt" @@ -1563,7 +1562,7 @@ var doc = `{ } }` -var TagToModuleDesc = ` +var tagToModuleDesc = ` { "General":"general", "Key Management":"key", @@ -1641,31 +1640,31 @@ func modularizeAPIs(modules string, paths map[string]interface{}) map[string]int filteredAPIs := make(map[string]interface{}) var moduleToTag map[string]string - if err := json.Unmarshal([]byte(TagToModuleDesc), &moduleToTag); err != nil { + if err := json.Unmarshal([]byte(tagToModuleDesc), &moduleToTag); err != nil { panic(err) } moduleArray := strings.Split(modules,",") for path,operations := range paths { if reflect.TypeOf(operations).String() != "map[string]interface {}" { - panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", - reflect.TypeOf(operations).String()))) + panic(fmt.Errorf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(operations).String())) } operationAPIs := operations.(map[string]interface{}) for operation,API := range operationAPIs { if reflect.TypeOf(API).String() != "map[string]interface {}" { - panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", - reflect.TypeOf(API).String()))) + panic(fmt.Errorf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(API).String())) } APIInfo := API.(map[string]interface{}) tags := APIInfo["tags"].([]interface{}) if len(tags) != 1 { - panic(errors.New(fmt.Sprintf("only support one tag, got %d tags",len(tags)))) + panic(fmt.Errorf("only support one tag, got %d tags",len(tags))) } if reflect.TypeOf(tags[0]).String() != "string" { - panic(errors.New(fmt.Sprintf("unexpected data type, expected: string, got: %s", - reflect.TypeOf(tags[0]).String()))) + panic(fmt.Errorf("unexpected data type, expected: string, got: %s", + reflect.TypeOf(tags[0]).String())) } moduleName := moduleToTag[tags[0].(string)] enable := moduleEnabled(moduleArray,moduleName) @@ -1698,14 +1697,14 @@ func (s *s) ReadDoc() string { addrInfo := strings.Split(listenAddr,":") if len(addrInfo) != 2{ - panic(errors.New("invalid listen address")) + panic(fmt.Errorf("invalid listen address")) } listenPort := addrInfo[1] docs["host"] = swaggerHost + ":" + listenPort if reflect.TypeOf(docs["info"]).String() != "map[string]interface {}" { - panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", - reflect.TypeOf(docs["info"]).String()))) + panic(fmt.Errorf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(docs["info"]).String())) } infos := docs["info"].(map[string]interface{}) description := infos["description"].(string) @@ -1714,8 +1713,8 @@ func (s *s) ReadDoc() string { docs["info"] = infos if reflect.TypeOf(docs["paths"]).String() != "map[string]interface {}" { - panic(errors.New(fmt.Sprintf("unexpected data type, expected: map[string]interface {}, got: %s", - reflect.TypeOf(docs["paths"]).String()))) + panic(fmt.Errorf("unexpected data type, expected: map[string]interface {}, got: %s", + reflect.TypeOf(docs["paths"]).String())) } paths := docs["paths"].(map[string]interface{}) docs["paths"] = modularizeAPIs(modules,paths) diff --git a/client/lcd/root.go b/client/lcd/root.go index 84dc0687ebfb..06a0b4a18185 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -4,11 +4,11 @@ import ( "net/http" "os" - client "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - keys "github.com/cosmos/cosmos-sdk/client/keys" - rpc "github.com/cosmos/cosmos-sdk/client/rpc" - tx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/wire" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" @@ -26,11 +26,10 @@ import ( "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" "strings" - "errors" - _ "github.com/cosmos/cosmos-sdk/client/lcd/docs" "github.com/tendermint/tendermint/libs/cli" tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" keyTypes "github.com/cosmos/cosmos-sdk/crypto/keys" + "fmt" ) // ServeCommand will generate a long-running rest server @@ -106,6 +105,9 @@ func createHandler(cdc *wire.Codec) http.Handler { return r } +// ServeSwaggerCommand will generate a long-running rest server +// that exposes functionality similar to the ServeCommand, but it provide swagger-ui +// Which is much friendly for further development func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "rest-server-swagger", @@ -126,7 +128,7 @@ func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { //Split the node list string into multi full node URIs nodeAddrArray := strings.Split(nodeAddrs,",") if len(nodeAddrArray) < 1 { - panic(errors.New("missing node URIs")) + 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]) diff --git a/client/lcd/version.go b/client/lcd/version.go index 64beeb2d06d7..e7dd6ee8f961 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -8,7 +8,6 @@ import ( "github.com/cosmos/cosmos-sdk/version" "github.com/gin-gonic/gin" "github.com/swaggo/swag/example/celler/httputil" - "errors" "github.com/cosmos/cosmos-sdk/client/httputils" ) @@ -32,18 +31,20 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { } } +// handler of getting rest server version func CLIVersionRequest(gtx *gin.Context) { v := version.GetVersion() - httputils.Response(gtx,v) + httputils.NormalResponse(gtx,v) } +// handler of getting connected node version func NodeVersionRequest(cliCtx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { appVersion, err := cliCtx.Query("/app/version") if err != nil { - httputil.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) + httputil.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("could't query version. error: %s", err.Error())) return } - httputils.Response(gtx,string(appVersion)) + httputils.NormalResponse(gtx,string(appVersion)) } } \ No newline at end of file diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 241f7eeedfb9..8c0fe10569a9 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -19,6 +19,7 @@ import ( stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + _ "github.com/cosmos/cosmos-sdk/client/lcd/docs" ) // rootCmd is the entry point for this binary diff --git a/store/multistoreproof.go b/store/multistoreproof.go index abf5ce07a9e4..82cc48df215d 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -1,47 +1,50 @@ package store import ( - "github.com/tendermint/tendermint/crypto/merkle" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/iavl" "bytes" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" + "github.com/tendermint/iavl" + "github.com/tendermint/tendermint/crypto/merkle" + cmn "github.com/tendermint/tendermint/libs/common" ) +// commitID of substores, such as acc store, gov store type SubstoreCommitID struct { - Name string `json:"name"` - Version int64 `json:"version"` - CommitHash cmn.HexBytes `json:"commit_hash"` + Name string `json:"name"` + Version int64 `json:"version"` + CommitHash cmn.HexBytes `json:"commit_hash"` } +// proof of store which have multi substores type MultiStoreProof struct { CommitIDList []SubstoreCommitID `json:"commit_id_list"` - StoreName string `json:"store_name"` - RangeProof iavl.RangeProof `json:"range_proof"` + StoreName string `json:"store_name"` + RangeProof iavl.RangeProof `json:"range_proof"` } +// build MultiStoreProof based on iavl proof and storeInfos func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) ([]byte, error) { var rangeProof iavl.RangeProof - err := cdc.UnmarshalBinary(iavlProof,&rangeProof) + err := cdc.UnmarshalBinary(iavlProof, &rangeProof) if err != nil { return nil, err } var multiStoreProof MultiStoreProof - for _,storeInfo := range storeInfos { + for _, storeInfo := range storeInfos { - commitId := SubstoreCommitID{ - Name: storeInfo.Name, - Version:storeInfo.Core.CommitID.Version, - CommitHash:storeInfo.Core.CommitID.Hash, + commitID := SubstoreCommitID{ + Name: storeInfo.Name, + Version: storeInfo.Core.CommitID.Version, + CommitHash: storeInfo.Core.CommitID.Hash, } - multiStoreProof.CommitIDList = append(multiStoreProof.CommitIDList,commitId) + multiStoreProof.CommitIDList = append(multiStoreProof.CommitIDList, commitID) } multiStoreProof.StoreName = storeName multiStoreProof.RangeProof = rangeProof - proof,err := cdc.MarshalBinary(multiStoreProof) + proof, err := cdc.MarshalBinary(multiStoreProof) if err != nil { return nil, err } @@ -49,27 +52,28 @@ func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []store return proof, nil } +// verify multiStoreCommitInfo against appHash func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []SubstoreCommitID, appHash []byte) ([]byte, error) { var substoreCommitHash []byte var kvPairs cmn.KVPairs - for _,multiStoreCommitID := range multiStoreCommitInfo { + for _, multiStoreCommitID := range multiStoreCommitInfo { if multiStoreCommitID.Name == storeName { - substoreCommitHash = multiStoreCommitID.CommitHash; + substoreCommitHash = multiStoreCommitID.CommitHash } - kHash := []byte(multiStoreCommitID.Name) + keyHash := []byte(multiStoreCommitID.Name) storeInfo := storeInfo{ - Core:storeCore{ - CommitID:sdk.CommitID{ + Core: storeCore{ + CommitID: sdk.CommitID{ Version: multiStoreCommitID.Version, - Hash: multiStoreCommitID.CommitHash, + Hash: multiStoreCommitID.CommitHash, }, }, } kvPairs = append(kvPairs, cmn.KVPair{ - Key: kHash, + Key: keyHash, Value: storeInfo.Hash(), }) } @@ -85,35 +89,36 @@ func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []Substor //Rebuild simple merkle hash tree var hashList [][]byte for _, kvPair := range kvPairs { - hashResult := merkle.SimpleHashFromTwoHashes(kvPair.Key,kvPair.Value) - hashList=append(hashList,hashResult) + hashResult := merkle.SimpleHashFromTwoHashes(kvPair.Key, kvPair.Value) + hashList = append(hashList, hashResult) } - if !bytes.Equal(appHash,simpleHashFromHashes(hashList)){ + if !bytes.Equal(appHash, simpleHashFromHashes(hashList)) { return nil, cmn.NewError("The merkle root of multiStoreCommitInfo doesn't equal to appHash") } return substoreCommitHash, nil } -func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) (error){ +// verify iavl proof +func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error { // Validate the proof to ensure data integrity. err := rangeProof.Verify(substoreCommitHash) if err != nil { - return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") + return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") } if len(value) != 0 { // Validate existence proof err = rangeProof.VerifyItem(key, value) if err != nil { - return errors.Wrap(err, "failed in existence verification") + return errors.Wrap(err, "failed in existence verification") } } else { // Validate absence proof err = rangeProof.VerifyAbsence(key) if err != nil { - return errors.Wrap(err, "failed in absence verification") + return errors.Wrap(err, "failed in absence verification") } } @@ -130,6 +135,6 @@ func simpleHashFromHashes(hashes [][]byte) []byte { default: left := simpleHashFromHashes(hashes[:(len(hashes)+1)/2]) right := simpleHashFromHashes(hashes[(len(hashes)+1)/2:]) - return merkle.SimpleHashFromTwoHashes(left,right) + return merkle.SimpleHashFromTwoHashes(left, right) } -} \ No newline at end of file +} diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 9c4b0721aed5..1538915677a1 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -1,69 +1,69 @@ package store import ( - "testing" - "github.com/tendermint/iavl" - cmn "github.com/tendermint/tendermint/libs/common" "encoding/hex" "github.com/stretchr/testify/assert" + "github.com/tendermint/iavl" + cmn "github.com/tendermint/tendermint/libs/common" + "testing" ) func TestVerifyMultiStoreCommitInfo(t *testing.T) { - appHash,_ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84") + appHash, _ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84") - substoreRootHash,_ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") + substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") storeName := "acc" var multiStoreCommitInfo []SubstoreCommitID - gocRootHash,_ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78") - multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ - Name:"gov", - Version:689, - CommitHash:gocRootHash, + gocRootHash, _ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "gov", + Version: 689, + CommitHash: gocRootHash, }) - multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ - Name:"main", - Version:689, - CommitHash:nil, + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "main", + Version: 689, + CommitHash: nil, }) - accRootHash,_ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") - multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ - Name:"acc", - Version:689, - CommitHash:accRootHash, + accRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "acc", + Version: 689, + CommitHash: accRootHash, }) - multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ - Name:"ibc", - Version:689, - CommitHash:nil, + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "ibc", + Version: 689, + CommitHash: nil, }) - stakeRootHash,_ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4") - multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ - Name:"stake", - Version:689, - CommitHash:stakeRootHash, + stakeRootHash, _ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "stake", + Version: 689, + CommitHash: stakeRootHash, }) - slashingRootHash,_ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e") - multiStoreCommitInfo=append(multiStoreCommitInfo,SubstoreCommitID{ - Name:"slashing", - Version:689, - CommitHash:slashingRootHash, + slashingRootHash, _ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "slashing", + Version: 689, + CommitHash: slashingRootHash, }) - commitHash, err := VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + commitHash, err := VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) assert.Nil(t, err) assert.Equal(t, commitHash, substoreRootHash) - appHash,_ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") + appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") - _,err = VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) - assert.Error(t,err,"appHash doesn't match to the merkle root of multiStoreCommitInfo") + _, err = VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + assert.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") } func TestVerifyRangeProof(t *testing.T) { @@ -93,4 +93,4 @@ func TestVerifyRangeProof(t *testing.T) { assert.NotEmpty(t, proof) err = VerifyRangeProof(key, val, root, proof) assert.Nil(t, err) -} \ No newline at end of file +} diff --git a/store/transientstore.go b/store/transientstore.go index e3e0b6bf4b58..f04dfe255677 100644 --- a/store/transientstore.go +++ b/store/transientstore.go @@ -44,6 +44,6 @@ func (ts *transientStore) Gas(meter GasMeter, config GasConfig) KVStore { } // Implements Store. -func (st *transientStore) GetStoreType() StoreType { +func (ts *transientStore) GetStoreType() StoreType { return sdk.StoreTypeTransient } \ No newline at end of file diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 66e3c4faf9f5..6a9a40a60a71 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -13,7 +13,6 @@ import ( "github.com/gorilla/mux" "github.com/gin-gonic/gin" "github.com/cosmos/cosmos-sdk/client/httputils" - "errors" ) // register REST routes @@ -75,10 +74,11 @@ func QueryAccountRequestHandlerFn( // register to Cosmos-LCD swagger routes func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, storeName string) { - routerGroup.GET("accounts/:address",QueryKeysRequestHandlerFn(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) + routerGroup.GET("accounts/:address",QueryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) } -func QueryKeysRequestHandlerFn(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { +// handler of query account in swagger rest server +func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { bech32addr := gtx.Param("address") @@ -91,23 +91,23 @@ func QueryKeysRequestHandlerFn(storeName string, cdc *wire.Codec, decoder auth.A res, err := ctx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query account. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query account. Error: %s", err.Error())) return } // the query will return empty if there is no data for this account if len(res) == 0 { - httputils.Response(gtx,nil) + httputils.NormalResponse(gtx,nil) return } // decode the value account, err := decoder(res) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) return } - httputils.Response(gtx,account) + httputils.NormalResponse(gtx,account) } } \ No newline at end of file diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index bfd36fa9f863..4534302a1cd8 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -141,10 +141,11 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo } } +// RegisterSwaggerRoutes - Central function to define routes that get registered by the main application func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { - routerGroup.POST("/accounts/:address/send", SendRequestFn(cdc, ctx, kb)) - routerGroup.POST("/create_transfer", CreateTransferTxForSignFn(cdc, ctx)) - routerGroup.POST("/signed_transfer", ComposeAndBroadcastSignedTransferTxFn(cdc, ctx)) + routerGroup.POST("/accounts/:address/send", sendRequestFn(cdc, ctx, kb)) + routerGroup.POST("/create_transfer", createTransferTxForSignFn(cdc, ctx)) + routerGroup.POST("/signed_transfer", composeAndBroadcastSignedTransferTxFn(cdc, ctx)) } func composeTx(cdc *wire.Codec, ctx context.CLIContext, transferBody transferBody) (auth.StdSignMsg, authctx.TxContext, sdk.Error) { @@ -206,7 +207,8 @@ func composeTx(cdc *wire.Codec, ctx context.CLIContext, transferBody transferBod return txForSign, txCtx, nil } -func CreateTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { +// handler of creating transfer transaction +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 { @@ -227,11 +229,12 @@ 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.Response(gtx,string(base64TxData)) + httputils.NormalResponse(gtx,string(base64TxData)) } } -func ComposeAndBroadcastSignedTransferTxFn(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { +// 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 { @@ -276,11 +279,12 @@ func ComposeAndBroadcastSignedTransferTxFn(cdc *wire.Codec, ctx context.CLIConte return } - httputils.Response(gtx,res) + httputils.NormalResponse(gtx,res) } } -func SendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin.HandlerFunc { +// handler of sending tokens in swagger rest server +func sendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin.HandlerFunc { return func(gtx *gin.Context) { bech32addr := gtx.Param("address") @@ -339,6 +343,6 @@ func SendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin return } - httputils.Response(gtx,res) + httputils.NormalResponse(gtx,res) } } \ No newline at end of file diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 1a56ce043a57..8e729486bb5c 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -15,7 +15,6 @@ import ( "github.com/gorilla/mux" "github.com/gin-gonic/gin" - "errors" "github.com/cosmos/cosmos-sdk/client/httputils" ) @@ -558,7 +557,7 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.Handler } } -func RegisterSwaggerQueryRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { +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)) @@ -588,13 +587,13 @@ func delegationHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFu res, err := ctx.QueryStore(key, storeName) if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, errors.New(fmt.Sprintf("couldn't query delegation. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusBadRequest, 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.Response(gtx,nil) + httputils.NormalResponse(gtx,nil) return } @@ -604,7 +603,7 @@ func delegationHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFu return } - httputils.Response(gtx,delegation) + httputils.NormalResponse(gtx,delegation) } } @@ -632,23 +631,23 @@ func ubdHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { res, err := ctx.QueryStore(key, storeName) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + 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.Response(gtx,nil) + httputils.NormalResponse(gtx,nil) return } ubd, err := types.UnmarshalUBD(cdc, key, res) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) return } - httputils.Response(gtx,ubd) + httputils.NormalResponse(gtx,ubd) } } @@ -682,23 +681,23 @@ func redHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { res, err := ctx.QueryStore(key, storeName) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query redelegation. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query redelegation. Error: %s", err.Error())) return } // the query will return empty if there is no data for this record if len(res) == 0 { - httputils.Response(gtx,nil) + httputils.NormalResponse(gtx,nil) return } red, err := types.UnmarshalRED(cdc, key, res) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) return } - httputils.Response(gtx,red) + httputils.NormalResponse(gtx,red) } } @@ -706,13 +705,13 @@ func validatorsHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFu return func(gtx *gin.Context) { kvs, err := ctx.QuerySubspace(stake.ValidatorsKey, storeName) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query validators. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query validators. Error: %s", err.Error())) return } // the query will return empty if there are no validators if len(kvs) == 0 { - httputils.Response(gtx,nil) + httputils.NormalResponse(gtx,nil) return } @@ -723,7 +722,7 @@ func validatorsHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFu addr := kv.Key[1:] validator, err := types.UnmarshalValidator(cdc, addr, kv.Value) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, errors.New(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query unbonding-delegation. Error: %s", err.Error())) return } @@ -735,6 +734,6 @@ func validatorsHandlerFun(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFu validators[i] = bech32Validator } - httputils.Response(gtx,validators) + httputils.NormalResponse(gtx,validators) } } \ No newline at end of file diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index 70426867776b..521ff9fe9ef9 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -15,6 +15,7 @@ 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) { - RegisterSwaggerQueryRoutes(routerGroup, ctx, cdc) + registerSwaggerQueryRoutes(routerGroup, ctx, cdc) } \ No newline at end of file From 4607ca98e8394b83a98ab12dff77d64cd88f2e6f Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Tue, 21 Aug 2018 13:38:33 +0800 Subject: [PATCH 06/25] Refactor comment and fix test failure --- client/context/context.go | 2 +- client/context/loadbalancing.go | 3 ++- client/context/query.go | 6 +++++- client/httputils/httputils.go | 4 ++-- client/keys/add.go | 27 ++++++++++++--------------- client/keys/delete.go | 2 +- client/keys/list.go | 2 +- client/keys/root.go | 2 +- client/keys/show.go | 2 +- client/keys/update.go | 2 +- client/lcd/version.go | 4 ++-- 11 files changed, 29 insertions(+), 27 deletions(-) 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..cacd0af8c7bc 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -312,8 +312,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..3c0675dd603f 100644 --- a/client/httputils/httputils.go +++ b/client/httputils/httputils.go @@ -5,7 +5,7 @@ import ( "net/http" ) -// Create error http response +// NewError create error http response func NewError(ctx *gin.Context, errCode int, err error) { errorResponse := httpError{ API: "2.0", @@ -15,7 +15,7 @@ func NewError(ctx *gin.Context, errCode int, err error) { ctx.JSON(errCode, errorResponse) } -// Create normal http response +// NormalResponse create normal http response func NormalResponse(ctx *gin.Context, data interface{}) { response := httpResponse{ API: "2.0", diff --git a/client/keys/add.go b/client/keys/add.go index 0c67fd4e6dc7..d69d584d9458 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -239,37 +239,34 @@ 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 func AddNewKeyRequest(gtx *gin.Context) { var kb keys.Keybase var m NewKeyBody - kb, err := GetKeyBase() - if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } - if err := gtx.BindJSON(&m); err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } - 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 +279,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 } } @@ -333,7 +330,7 @@ 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") diff --git a/client/keys/delete.go b/client/keys/delete.go index 30db0b93b390..163712a396b0 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 diff --git a/client/keys/list.go b/client/keys/list.go index 9ae74bda7fc8..5ba720df228d 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 { diff --git a/client/keys/root.go b/client/keys/root.go index d7b32c81a9bb..a444803d0290 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 register swagger REST routes 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..25e6c525dfee 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") diff --git a/client/keys/update.go b/client/keys/update.go index edd66dd35c1b..5da194f790a5 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 diff --git a/client/lcd/version.go b/client/lcd/version.go index e7dd6ee8f961..dcfa1c70b141 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) } -// 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") From 75285341857c5bddc8e3c5c4bbc0d58297a58bec Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Tue, 21 Aug 2018 19:36:05 +0800 Subject: [PATCH 07/25] Add lcd test for swagger lcd --- client/context/query.go | 1 + client/httputils/httputils.go | 26 +- client/keys/add.go | 21 +- client/keys/delete.go | 2 +- client/keys/list.go | 7 +- client/keys/show.go | 7 +- client/keys/update.go | 2 +- client/lcd/docs/docs.go | 677 ++++++++++++++++++++++++---------- client/lcd/lcd_test.go | 185 ++++++++++ client/lcd/root.go | 60 ++- client/lcd/test_helpers.go | 110 ++++++ client/lcd/version.go | 4 +- x/auth/client/rest/query.go | 11 +- x/bank/client/rest/sendtx.go | 46 ++- x/stake/client/rest/query.go | 376 +++++++++++++++---- x/stake/client/rest/rest.go | 3 +- x/stake/client/rest/tx.go | 243 ++++++++++++ 17 files changed, 1451 insertions(+), 330 deletions(-) diff --git a/client/context/query.go b/client/context/query.go index cacd0af8c7bc..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) { diff --git a/client/httputils/httputils.go b/client/httputils/httputils.go index 3c0675dd603f..1f49ba995a0f 100644 --- a/client/httputils/httputils.go +++ b/client/httputils/httputils.go @@ -7,31 +7,25 @@ import ( // 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(), } + if err != nil { + errorResponse.ErrMsg = err.Error() + } + ctx.JSON(errCode, errorResponse) } // NormalResponse create normal http response -func NormalResponse(ctx *gin.Context, data interface{}) { - response := httpResponse{ - API: "2.0", - Code: 0, - Result: data, - } - ctx.JSON(http.StatusOK, response) -} - -type httpResponse struct { - API string `json:"rest api" example:"2.0"` - Code int `json:"code" example:"0"` - Result interface{} `json:"result"` +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 d69d584d9458..1357059407d1 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -239,12 +239,17 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(bz) } +// nolint: gocyclo // AddNewKeyRequest is the handler of adding new key in swagger rest server func AddNewKeyRequest(gtx *gin.Context) { - var kb keys.Keybase var m NewKeyBody - - 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 = json.Unmarshal(body, &m) + if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } @@ -303,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) } @@ -337,5 +348,5 @@ func SeedRequest(gtx *gin.Context) { 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 163712a396b0..62cef116c7a4 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -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 5ba720df228d..934c0743f007 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -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/show.go b/client/keys/show.go index 25e6c525dfee..caff452c95ee 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -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 5da194f790a5..3f7ab7c0944e 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -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..574bb4cbfbb3 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,49 @@ 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) + //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 +519,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 := getValidatorsFromSwaggerLCD(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 +546,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)) @@ -963,6 +1139,15 @@ func getValidators(t *testing.T, port string) []stake.BechValidator { return validators } +func getValidatorsFromSwaggerLCD(t *testing.T, port string) []stake.BechValidator { + res, body := Request(t, port, "GET", "/stake_validators", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var validators []stake.BechValidator + err := cdc.UnmarshalJSON([]byte(body), &validators) + require.Nil(t, err) + return validators +} + func getValidator(t *testing.T, port string, validatorAddr sdk.AccAddress) stake.BechValidator { res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s", validatorAddr.String()), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/client/lcd/root.go b/client/lcd/root.go index 06a0b4a18185..c1258817f9f1 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,7 +141,33 @@ 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) + //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) @@ -190,7 +188,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..d8a73b0def2a 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 } +// InitializeTestLCD starts Tendermint and the LCD 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 dcfa1c70b141..925fd419e926 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -34,7 +34,7 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { // 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)) } // NodeVersionRequest is the handler of getting connected node 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..5114aae9e28d 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -97,7 +97,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 +108,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..f3f9cec00a93 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,7 +235,7 @@ 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) } } @@ -237,7 +243,13 @@ func createTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.Hand 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 +291,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 +314,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 +367,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..912bf419657b 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,136 @@ 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) + } +} + +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 +719,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 +804,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 +909,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..5b1bba7b9d73 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -16,6 +16,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, k } // RegisterSwaggerRoutes registers staking status query REST API handlers to a router -func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { +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 From 473fe21c6e9c7b10b0dbcfc91f078fc3e1c3ff03 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Tue, 21 Aug 2018 19:48:40 +0800 Subject: [PATCH 08/25] Fix for test lint warnning --- client/httputils/httputils.go | 6 +++--- client/keys/add.go | 2 +- client/lcd/test_helpers.go | 2 +- x/bank/client/rest/sendtx.go | 1 + x/stake/client/rest/query.go | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/httputils/httputils.go b/client/httputils/httputils.go index 1f49ba995a0f..45747c22a947 100644 --- a/client/httputils/httputils.go +++ b/client/httputils/httputils.go @@ -7,7 +7,7 @@ import ( // NewError create error http response func NewError(ctx *gin.Context, errCode int, err error) { - errorResponse := HttpError{ + errorResponse := HTTPError{ API: "2.0", Code: errCode, } @@ -24,8 +24,8 @@ func NormalResponse(ctx *gin.Context, data []byte) { ctx.Writer.Write(data) } -// HttpError is http response with error -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 1357059407d1..904e17af0567 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -239,8 +239,8 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(bz) } -// nolint: gocyclo // AddNewKeyRequest is the handler of adding new key in swagger rest server +// nolint: gocyclo func AddNewKeyRequest(gtx *gin.Context) { var m NewKeyBody body, err := ioutil.ReadAll(gtx.Request.Body) diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index d8a73b0def2a..e711fbea9b01 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -208,7 +208,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress return cleanup, validatorsPKs, port } -// InitializeTestLCD starts Tendermint and the LCD in process, listening on +// 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. diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index f3f9cec00a93..554155f3daac 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -239,6 +239,7 @@ func createTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.Hand } } +// 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) { diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 912bf419657b..dcfbc1350a94 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -635,6 +635,7 @@ func delegatorHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.Ha } } +// nolint: gocyclo func delegatorTxsHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { var output []byte From 95e367a7fc50a59af370c5519b3f94cec54ce3bb Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 22 Aug 2018 00:03:54 +0800 Subject: [PATCH 09/25] Refactor comment --- client/keys/root.go | 2 +- client/lcd/root.go | 3 +-- x/auth/client/rest/query.go | 7 +++---- x/stake/client/rest/rest.go | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/client/keys/root.go b/client/keys/root.go index a444803d0290..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") } -// RegisterSwaggerRoutes register 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/lcd/root.go b/client/lcd/root.go index c1258817f9f1..23488c05aee0 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -145,6 +145,7 @@ 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 { @@ -170,9 +171,7 @@ func createSwaggerHandler(server *gin.Engine, cdc *wire.Codec) { 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)) diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 5114aae9e28d..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") diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index 5b1bba7b9d73..aa2cfce5935c 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -15,7 +15,7 @@ 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 +// 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) From 9d944b0b11f33d450101a53e95b1007c3cad59a1 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 22 Aug 2018 10:18:45 +0800 Subject: [PATCH 10/25] Add new test for lcd with swagger --- client/lcd/lcd_test.go | 101 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 574bb4cbfbb3..f0b8c8695b1e 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -390,6 +390,7 @@ func TestCoinSendSwaggerLCD(t *testing.T) { // 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) @@ -524,7 +525,7 @@ func TestValidatorsQueryFromSwaggerLCD(t *testing.T) { defer cleanup() require.Equal(t, 1, len(pks)) - validators := getValidatorsFromSwaggerLCD(t, port) + validators := getValidators(t, port) require.Equal(t, len(validators), 1) // make sure all the validators were found (order unknown because sorted by operator addr) @@ -641,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)) @@ -1139,15 +1229,6 @@ func getValidators(t *testing.T, port string) []stake.BechValidator { return validators } -func getValidatorsFromSwaggerLCD(t *testing.T, port string) []stake.BechValidator { - res, body := Request(t, port, "GET", "/stake_validators", nil) - require.Equal(t, http.StatusOK, res.StatusCode, body) - var validators []stake.BechValidator - err := cdc.UnmarshalJSON([]byte(body), &validators) - require.Nil(t, err) - return validators -} - func getValidator(t *testing.T, port string, validatorAddr sdk.AccAddress) stake.BechValidator { res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s", validatorAddr.String()), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) From f2664726823aa320be25c6ee5287f64242f291bc Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 23 Aug 2018 19:25:15 +0800 Subject: [PATCH 11/25] Refactor lcd according to code review --- client/context/context.go | 16 ++++++++-------- client/context/query.go | 8 ++++---- client/keys/add.go | 4 ++-- client/keys/delete.go | 10 ++++++++-- client/keys/list.go | 5 ++--- client/keys/update.go | 10 ++++++++-- client/lcd/root.go | 6 +++--- x/auth/client/rest/query.go | 2 +- x/bank/client/rest/sendtx.go | 7 +++---- 9 files changed, 39 insertions(+), 29 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index 3e00abced534..76366a0a37c8 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -31,8 +31,8 @@ type CLIContext struct { Async bool JSON bool PrintResponse bool - Cert tendermintLite.Certifier - ClientMgr *ClientManager + Certifier tendermintLite.Certifier + ClientManager *ClientManager } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -117,14 +117,14 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { return ctx } -// WithCert - return a copy of the context with an updated Cert -func (ctx CLIContext) WithCert(cert tendermintLite.Certifier) CLIContext { - ctx.Cert = cert +// WithCertifier - return a copy of the context with an updated Certifier +func (ctx CLIContext) WithCertifier(certifier tendermintLite.Certifier) CLIContext { + ctx.Certifier = certifier return ctx } -// WithClientMgr - return a copy of the context with an updated ClientMgr -func (ctx CLIContext) WithClientMgr(clientMgr *ClientManager) CLIContext { - ctx.ClientMgr = clientMgr +// WithClientManager - return a copy of the context with an updated ClientManager +func (ctx CLIContext) WithClientManager(clientManager *ClientManager) CLIContext { + ctx.ClientManager = clientManager return ctx } \ No newline at end of file diff --git a/client/context/query.go b/client/context/query.go index 77cdf5b3d88d..018462c9a93f 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -23,8 +23,8 @@ import ( // GetNode returns an RPC client. If the context's client is not defined, an // error is returned. func (ctx CLIContext) GetNode() (rpcclient.Client, error) { - if ctx.ClientMgr != nil { - return ctx.ClientMgr.getClient(), nil + if ctx.ClientManager != nil { + return ctx.ClientManager.getClient(), nil } if ctx.Client == nil { return nil, errors.New("no RPC client defined") @@ -314,7 +314,7 @@ func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err e } // TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node - if ctx.Cert == nil { + if ctx.Certifier == nil { if ctx.Logger != nil { io.WriteString(ctx.Logger, fmt.Sprintf("Missing valid certifier to verify data from untrusted node\n")) } @@ -322,7 +322,7 @@ func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err e } // AppHash for height H is in header H+1 - commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Cert) + commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier) if err != nil { return nil, err } diff --git a/client/keys/add.go b/client/keys/add.go index 904e17af0567..a0ba8cce86a0 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -180,7 +180,7 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { } body, err := ioutil.ReadAll(r.Body) - err = json.Unmarshal(body, &m) + err = cdc.UnmarshalJSON(body, &m) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -248,7 +248,7 @@ func AddNewKeyRequest(gtx *gin.Context) { httputils.NewError(gtx, http.StatusBadRequest, err) return } - err = json.Unmarshal(body, &m) + err = cdc.UnmarshalJSON(body, &m) if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return diff --git a/client/keys/delete.go b/client/keys/delete.go index 62cef116c7a4..722460f94726 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/gin-gonic/gin" "github.com/cosmos/cosmos-sdk/client/httputils" + "io/ioutil" ) func deleteKeyCommand() *cobra.Command { @@ -96,10 +97,15 @@ func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) { // 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 var m DeleteKeyBody - 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 = cdc.UnmarshalJSON(body, &m) + if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } diff --git a/client/keys/list.go b/client/keys/list.go index 934c0743f007..7cb4b2f65534 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -1,7 +1,6 @@ package keys import ( - "encoding/json" "net/http" "github.com/spf13/cobra" @@ -61,7 +60,7 @@ func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(err.Error())) return } - output, err := json.MarshalIndent(keysOutput, "", " ") + output, err := cdc.MarshalJSONIndent(keysOutput, "", " ") if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -92,7 +91,7 @@ func QueryKeysRequest(gtx *gin.Context) { httputils.NewError(gtx, http.StatusInternalServerError, err) return } - output, err := json.MarshalIndent(keysOutput, "", " ") + output, err := cdc.MarshalJSONIndent(keysOutput, "", " ") if err != nil { httputils.NewError(gtx, http.StatusInternalServerError, err) return diff --git a/client/keys/update.go b/client/keys/update.go index 3f7ab7c0944e..f731bc2216d3 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -13,6 +13,7 @@ import ( "github.com/gin-gonic/gin" "errors" "github.com/cosmos/cosmos-sdk/client/httputils" + "io/ioutil" ) func updateKeyCommand() *cobra.Command { @@ -100,10 +101,15 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { // 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 var m UpdateKeyBody - 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 = cdc.UnmarshalJSON(body, &m) + if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) return } diff --git a/client/lcd/root.go b/client/lcd/root.go index 23488c05aee0..da0e14c8dac6 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -157,17 +157,17 @@ func createSwaggerHandler(server *gin.Engine, cdc *wire.Codec) { 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]) + certifier, err := tendermintLiteProxy.GetCertifier(chainID, rootDir, nodeAddrArray[0]) if err != nil { panic(err) } //Create load balancing engine - clientMgr,err := context.NewClientManager(nodeAddrs) + clientManager, 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) + ctx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout).WithCertifier(certifier).WithClientManager(clientManager) server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index aae2a1da2a41..f350358b6cfc 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -74,7 +74,7 @@ func QueryAccountRequestHandlerFn( // 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("bank/balance/:address",queryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) } func queryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 554155f3daac..cfc286f4cf77 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -27,11 +27,9 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, k } type sendBody struct { - // fees is not used currently - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` + Name string `json:"name"` Password string `json:"password"` + Amount sdk.Coins `json:"amount"` ChainID string `json:"chain_id"` AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` @@ -146,6 +144,7 @@ func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, routerGroup.POST("/accounts/:address/send", sendRequestFn(cdc, ctx, kb)) routerGroup.POST("/create_transfer", createTransferTxForSignFn(cdc, ctx)) routerGroup.POST("/signed_transfer", composeAndBroadcastSignedTransferTxFn(cdc, ctx)) + routerGroup.POST("/bank/transfers", composeAndBroadcastSignedTransferTxFn(cdc, ctx)) } func composeTx(cdc *wire.Codec, ctx context.CLIContext, transferBody transferBody) (auth.StdSignMsg, authctx.TxContext, sdk.Error) { From 6cdf21ddc5a9ab5fcd8bbbac89b6a12ceacf7c04 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 24 Aug 2018 10:56:43 +0800 Subject: [PATCH 12/25] Refactor code according to code review --- client/context/query.go | 72 ++++---- client/keys/add.go | 42 ++--- client/keys/list.go | 2 +- client/lcd/docs/docs.go | 175 ++------------------ client/lcd/root.go | 6 +- x/bank/client/rest/sendtx.go | 310 +++++++++++++---------------------- 6 files changed, 195 insertions(+), 412 deletions(-) diff --git a/client/context/query.go b/client/context/query.go index 018462c9a93f..71b16f710200 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" "strings" tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + abci "github.com/tendermint/tendermint/abci/types" ) // GetNode returns an RPC client. If the context's client is not defined, an @@ -284,33 +285,11 @@ 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) { - node, err := ctx.GetNode() - if err != nil { - return res, err - } - - opts := rpcclient.ABCIQueryOptions{ - Height: ctx.Height, - Trusted: ctx.TrustNode, - } - - result, err := node.ABCIQueryWithOptions(path, key, opts) - if err != nil { - return res, err - } - - resp := result.Response - if resp.Code != uint32(0) { - return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) - } - - // Data from trusted node or subspace doesn't need verification +// proofVerify perform response proof verification +func (ctx CLIContext) proofVerify(path string, resp abci.ResponseQuery) error { + // Data from trusted node or subspace query doesn't need verification if ctx.TrustNode || !isQueryStoreWithProof(path) { - return resp.Value,nil + return nil } // TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node @@ -318,30 +297,61 @@ func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err e if ctx.Logger != nil { io.WriteString(ctx.Logger, fmt.Sprintf("Missing valid certifier to verify data from untrusted node\n")) } - return resp.Value, nil + return nil } + node, err := ctx.GetNode() // AppHash for height H is in header H+1 commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier) if err != nil { - return nil, err + return err } var multiStoreProof store.MultiStoreProof cdc := wire.NewCodec() err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) if err != nil { - return res, errors.Wrap(err, "failed to unmarshalBinary rangeProof") + return errors.Wrap(err, "failed to unmarshalBinary rangeProof") } // Validate the substore commit hash against trusted appHash substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, multiStoreProof.CommitIDList, commit.Header.AppHash) if err != nil { - return nil, errors.Wrap(err, "failed in verifying the proof against appHash") + return errors.Wrap(err, "failed in verifying the proof against appHash") } err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) if err != nil { - return nil, errors.Wrap(err, "failed in the range proof verification") + return errors.Wrap(err, "failed in the range proof verification") + } + return nil +} + +// 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) { + node, err := ctx.GetNode() + if err != nil { + return res, err + } + + opts := rpcclient.ABCIQueryOptions{ + Height: ctx.Height, + Trusted: ctx.TrustNode, + } + + result, err := node.ABCIQueryWithOptions(path, key, opts) + if err != nil { + return res, err + } + + resp := result.Response + if resp.Code != uint32(0) { + return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) + } + + err = ctx.proofVerify(path, resp) + if err != nil { + return nil, err } return resp.Value, nil diff --git a/client/keys/add.go b/client/keys/add.go index a0ba8cce86a0..f211150a0127 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -187,14 +187,10 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(err.Error())) return } - if m.Name == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("You have to specify a name for the locally stored account.")) - return - } - if m.Password == "" { + + if paramCheck(m) != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("You have to specify a password for the locally stored account.")) + w.Write([]byte(err.Error())) return } @@ -239,8 +235,23 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(bz) } +// paramCheck performs add new key parameters checking +func paramCheck(m NewKeyBody) error { + if len(m.Name) < 1 || len(m.Name) > 16 { + return fmt.Errorf("account name length should not be longer than 16") + } + for _, char := range []rune(m.Name) { + if !syntax.IsWordChar(char) { + return fmt.Errorf("account name should not contains any char beyond [_0-9A-Za-z]") + } + } + if len(m.Password) < 8 || len(m.Password) > 16 { + return fmt.Errorf("account password length should be no less than 8 and no greater than 16") + } + return nil +} + // AddNewKeyRequest is the handler of adding new key in swagger rest server -// nolint: gocyclo func AddNewKeyRequest(gtx *gin.Context) { var m NewKeyBody body, err := ioutil.ReadAll(gtx.Request.Body) @@ -254,19 +265,8 @@ func AddNewKeyRequest(gtx *gin.Context) { 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]")) - return - } - } - if len(m.Password) < 8 || len(m.Password) > 16 { - httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account password length should be no less than 8 and no greater than 16")) - return + if paramCheck(m) != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) } kb, err := GetKeyBase() diff --git a/client/keys/list.go b/client/keys/list.go index 7cb4b2f65534..e54d95d70f01 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -69,7 +69,7 @@ func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write(output) } -// DeleteKeyRequest is the handler of listing all keys in swagger rest server +// QueryKeysRequest is the handler of listing all keys in swagger rest server func QueryKeysRequest(gtx *gin.Context) { kb, err := GetKeyBase() if err != nil { diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go index 0af26374a551..8e7876ed1d7a 100644 --- a/client/lcd/docs/docs.go +++ b/client/lcd/docs/docs.go @@ -533,7 +533,7 @@ var doc = `{ } } }, - "/accounts/{address}": { + "/bank/balance/{address}": { "get": { "description": "Get the detailed information for specific address", "consumes": [ @@ -586,120 +586,7 @@ var doc = `{ } } }, - "/create_transfer": { - "post": { - "description": "Build transaction", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Token Operation" - ], - "summary": "Build transaction", - "parameters": [ - { - "description": "create transaction parameters", - "name": "transferBody", - "in": "body", - "required": true, - "schema": { - "type": "object", - "$ref": "#/definitions/bank.transferBody" - } - } - ], - "responses": { - "200": { - "description": "OK. The returned string is base64 encoding", - "schema": { - "type": "string" - } - }, - "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" - } - } - } - } - }, - "/signed_transfer": { - "post": { - "description": "Broadcast signed transaction.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Token Operation" - ], - "summary": "Broadcast signed transaction", - "parameters": [ - { - "description": "Signed transaction. Transaction data, signatures and public keys should be base64 encoding", - "name": "signedTransaction", - "in": "body", - "required": true, - "schema": { - "type": "object", - "$ref": "#/definitions/bank.signedBody" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "object", - "$ref": "#/definitions/bank.ResultBroadcastTxCommit" - } - }, - "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" - } - } - } - } - }, - "/accounts/{address}/send": { + "/bank/transfers": { "post": { "description": "This API require the Cosmos-LCD have keystore module. It will ask keystore module for transaction signature", "consumes": [ @@ -713,12 +600,6 @@ var doc = `{ ], "summary": "Send coins to a address", "parameters": [ - { - "type": "string", - "description": "address to send asset", - "name": "address", - "in": "path" - }, { "description": "transfer asset", "name": "sendAsset", @@ -726,7 +607,7 @@ var doc = `{ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/bank.sendBody" + "$ref": "#/definitions/bank.transferBody" } } ], @@ -1293,45 +1174,15 @@ var doc = `{ "bank.transferBody": { "type": "object", "properties": { - "account_number": { - "type": "integer" - }, - "amount": { - "type": "integer" - }, - "denomination": { + "name": { "type": "string" }, - "ensure_account_sequence": { - "type": "boolean" - }, - "chain_id": { + "to_address": { "type": "string" }, "from_address": { "type": "string" }, - "gas": { - "type": "integer" - }, - "fee": { - "type": "string", - "example": "10 monikerToken" - }, - "sequence": { - "type": "integer" - }, - "to_address": { - "type": "string" - } - } - }, - "bank.sendBody": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, "amount": { "type": "object", "$ref": "#/definitions/sdk.Coins" @@ -1343,17 +1194,25 @@ var doc = `{ "type": "string" }, "account_number": { - "type": "integer" + "type": "string" }, "gas": { - "type": "integer" + "type": "string" }, "fee": { "type": "string", - "example": "10 monikerToken" + "example": "1steak" }, "sequence": { - "type": "integer" + "type": "string" + }, + "signed": { + "type": "boolean", + "example": true + }, + "ensure_account_sequence": { + "type": "boolean", + "example": false } } }, diff --git a/client/lcd/root.go b/client/lcd/root.go index da0e14c8dac6..4a5cfb5bec29 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -110,7 +110,7 @@ func createHandler(cdc *wire.Codec) http.Handler { func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "rest-server-swagger", - Short: "Start LCD (light-client daemon), a local REST server with swagger-ui, default uri: http://localhost:1317/swagger/index.html", + Short: "Start Gaia-lite (gaia light client daemon), a local REST server with swagger-ui, default url: http://localhost:1317/swagger/index.html", RunE: func(cmd *cobra.Command, args []string) error { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). With("module", "rest-server-swagger") @@ -134,7 +134,7 @@ func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { cmd.Flags().String(client.FlagListenAddr, "localhost:1317", "Address for server to listen on.") cmd.Flags().String(client.FlagNodeList, "tcp://localhost:26657", "Node list to connect to, example: \"tcp://10.10.10.10:26657,tcp://20.20.20.20:26657\".") cmd.Flags().String(client.FlagChainID, "", "ID of chain we connect to, must be specified.") - cmd.Flags().String(client.FlagSwaggerHostIP, "localhost", "The host IP of the Cosmos-LCD server, swagger will send request to this host.") + cmd.Flags().String(client.FlagSwaggerHostIP, "localhost", "The host IP of the Gaia-lite server, swagger-ui will send request to this host.") cmd.Flags().String(client.FlagModules, "general,key,token", "Enabled modules.") cmd.Flags().Bool(client.FlagTrustNode, false, "Trust full nodes or not.") @@ -154,7 +154,7 @@ func createSwaggerHandler(server *gin.Engine, cdc *wire.Codec) { //Split the node list string into multi full node URIs nodeAddrArray := strings.Split(nodeAddrs,",") if len(nodeAddrArray) < 1 { - panic(fmt.Errorf("missing node URIs")) + panic(fmt.Errorf("missing node URLs")) } //Tendermint certifier can only connect to one full node. Here we assign the first full node to it certifier, err := tendermintLiteProxy.GetCertifier(chainID, rootDir, nodeAddrArray[0]) diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index cfc286f4cf77..0ca01d34de35 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -15,10 +15,10 @@ import ( "github.com/gorilla/mux" "github.com/gin-gonic/gin" "github.com/cosmos/cosmos-sdk/x/auth" - "encoding/base64" "github.com/cosmos/cosmos-sdk/client/httputils" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" - "errors" + "bytes" + "fmt" ) // RegisterRoutes - Central function to define routes that get registered by the main application @@ -27,9 +27,11 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, k } type sendBody struct { - Name string `json:"name"` - Password string `json:"password"` + // fees is not used currently + // Fees sdk.Coin `json="fees"` Amount sdk.Coins `json:"amount"` + LocalAccountName string `json:"name"` + Password string `json:"password"` ChainID string `json:"chain_id"` AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` @@ -38,22 +40,18 @@ type sendBody struct { } type transferBody struct { - ChainID string `json:"chain_id"` - FromAddress string `json:"from_address"` - ToAddress string `json:"to_address"` - Amount sdk.Int `json:"amount"` - Denomination string `json:"denomination"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - EnsureAccAndSeq bool `json:"ensure_account_sequence"` - Gas int64 `json:"gas"` - Fee string `json:"fee"` -} - -type signedBody struct { - TransferBody transferBody `json:"transfer_body"` - Signature []byte `json:"signature_list"` - PublicKey []byte `json:"public_key_list"` + Name string `json:"name"` + Password string `json:"password"` + FromAddress string `json:"from_address"` + ToAddress string `json:"to_address"` + Amount sdk.Coins `json:"amount"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` + Fee string `json:"fee"` + Signed bool `json:"signed"` // true by default + EnsureAccAndSeq bool `json:"ensure_account_sequence"` } var msgCdc = wire.NewCodec() @@ -141,73 +139,11 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo // RegisterSwaggerRoutes - Central function to define routes that get registered by the main application func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { - routerGroup.POST("/accounts/:address/send", sendRequestFn(cdc, ctx, kb)) - routerGroup.POST("/create_transfer", createTransferTxForSignFn(cdc, ctx)) - routerGroup.POST("/signed_transfer", composeAndBroadcastSignedTransferTxFn(cdc, ctx)) - routerGroup.POST("/bank/transfers", composeAndBroadcastSignedTransferTxFn(cdc, ctx)) -} - -func composeTx(cdc *wire.Codec, ctx context.CLIContext, transferBody transferBody) (auth.StdSignMsg, authctx.TxContext, sdk.Error) { - - emptyMsg := auth.StdSignMsg{} - emptyTxContext := authctx.TxContext{} - - amount := sdk.NewCoin(transferBody.Denomination, transferBody.Amount) - var amounts sdk.Coins - amounts = append(amounts, amount) - - fromAddress, err := sdk.AccAddressFromBech32(transferBody.FromAddress) - if err != nil { - return emptyMsg, emptyTxContext, sdk.ErrInvalidAddress(err.Error()) - } - - toAddress, err := sdk.AccAddressFromBech32(transferBody.ToAddress) - if err != nil { - return emptyMsg, emptyTxContext, sdk.ErrInvalidAddress(err.Error()) - } - - // build message - msg := client.BuildMsg(fromAddress, toAddress, amounts) - - accountNumber := transferBody.AccountNumber - sequence := transferBody.Sequence - gas := transferBody.Gas - fee := transferBody.Fee - - if transferBody.EnsureAccAndSeq { - if ctx.AccDecoder == nil { - ctx = ctx.WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - } - accountNumber, err = ctx.GetAccountNumber(fromAddress) - if err != nil { - return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) - } - - sequence, err = ctx.GetAccountSequence(fromAddress) - if err != nil { - return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) - } - } - - txCtx := authctx.TxContext{ - Codec: cdc, - Gas: gas, - Fee: fee, - ChainID: transferBody.ChainID, - AccountNumber: accountNumber, - Sequence: sequence, - } - - txForSign, err := txCtx.Build([]sdk.Msg{msg}) - if err != nil { - return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) - } - - return txForSign, txCtx, nil + routerGroup.POST("/bank/transfers", transferRequestFn(cdc, ctx, kb)) } // handler of creating transfer transaction -func createTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { +func transferRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin.HandlerFunc { return func(gtx *gin.Context) { var transferBody transferBody body, err := ioutil.ReadAll(gtx.Request.Body) @@ -231,148 +167,126 @@ func createTransferTxForSignFn(cdc *wire.Codec, ctx context.CLIContext) gin.Hand return } - base64TxData := make([]byte, base64.StdEncoding.EncodedLen(len(txForSign.Bytes()))) - base64.StdEncoding.Encode(base64TxData,txForSign.Bytes()) + if !transferBody.Signed { + httputils.NormalResponse(gtx, txForSign.Bytes()) + return + } - httputils.NormalResponse(gtx,base64TxData) + signAndBroadcase(gtx, cdc, ctx, txForSign, transferBody, kb) } } -// 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 - 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 - } +// signAndBroadcase perform transaction sign and broadcast operation +func signAndBroadcase(gtx *gin.Context, cdc *wire.Codec, ctx context.CLIContext, txForSign auth.StdSignMsg, transferBody transferBody, kb keys.Keybase) { - if signedTransaction.Signature == nil || signedTransaction.PublicKey == nil { - httputils.NewError(gtx, http.StatusBadRequest, errors.New("signature or public key is empty")) - return - } + if transferBody.Name == "" || transferBody.Password == "" { + httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("missing key name or password in signning transaction")) + return + } - signature, err := base64.StdEncoding.DecodeString(string(signedTransaction.Signature)) - if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - return - } - publicKey, err := base64.StdEncoding.DecodeString(string(signedTransaction.PublicKey)) - if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - return - } + info, err := kb.Get(transferBody.Name) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } - txForSign, txCtx, errMsg := composeTx(cdc, ctx, signedTransaction.TransferBody) - if errMsg != nil { - if errMsg.Code() == sdk.CodeInternal { - httputils.NewError(gtx, http.StatusInternalServerError, errMsg) - } else { - httputils.NewError(gtx, http.StatusBadRequest, errMsg) - } - } + fromAddress, err := sdk.AccAddressFromBech32(transferBody.FromAddress) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } - txDataForBroadcast, err := txCtx.BuildTxWithSignature(cdc, txForSign, signature, publicKey) - if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } + if !bytes.Equal(info.GetPubKey().Address(), fromAddress) { + httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("the fromAddress doesn't equal to the address of sign key")) + return + } - res, err := ctx.BroadcastTx(txDataForBroadcast) - if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } + sig, pubkey, err := kb.Sign(transferBody.Name, transferBody.Password, txForSign.Bytes()) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } - output, err := wire.MarshalJSONIndent(cdc, res) - if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } + sigs := []auth.StdSignature{{ + AccountNumber: txForSign.AccountNumber, + Sequence: txForSign.Sequence, + PubKey: pubkey, + Signature: sig, + }} - httputils.NormalResponse(gtx, output) + txBytes, err := ctx.Codec.MarshalBinary(auth.NewStdTx(txForSign.Msgs, txForSign.Fee, sigs, txForSign.Memo)) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return } -} -// handler of sending tokens in swagger rest server -func sendRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) gin.HandlerFunc { - return func(gtx *gin.Context) { + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } - bech32addr := gtx.Param("address") + output, err := wire.MarshalJSONIndent(cdc, res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } - address, err := sdk.AccAddressFromBech32(bech32addr) - if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - return - } + httputils.NormalResponse(gtx, output) +} - var m sendBody - 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 - } +// composeTx perform StdSignMsg building operation +func composeTx(cdc *wire.Codec, ctx context.CLIContext, transferBody transferBody) (auth.StdSignMsg, authctx.TxContext, sdk.Error) { - info, err := kb.Get(m.LocalAccountName) - if err != nil { - httputils.NewError(gtx, http.StatusUnauthorized, err) - return - } + emptyMsg := auth.StdSignMsg{} + emptyTxContext := authctx.TxContext{} - from := sdk.AccAddress(info.GetPubKey().Address()) + fromAddress, err := sdk.AccAddressFromBech32(transferBody.FromAddress) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInvalidAddress(err.Error()) + } - to, err := sdk.AccAddressFromBech32(address.String()) - if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - return - } + toAddress, err := sdk.AccAddressFromBech32(transferBody.ToAddress) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInvalidAddress(err.Error()) + } - // build message - msg := client.BuildMsg(from, to, m.Amount) - if err != nil { // XXX rechecking same error ? - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } + // build message + msg := client.BuildMsg(fromAddress, toAddress, transferBody.Amount) - txCtx := authctx.TxContext{ - Codec: cdc, - Gas: m.Gas, - Fee: m.Fee, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - } + accountNumber := transferBody.AccountNumber + sequence := transferBody.Sequence + gas := transferBody.Gas + fee := transferBody.Fee - txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - httputils.NewError(gtx, http.StatusUnauthorized, err) - return + if transferBody.EnsureAccAndSeq { + if ctx.AccDecoder == nil { + ctx = ctx.WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) } - - res, err := ctx.BroadcastTx(txBytes) + accountNumber, err = ctx.GetAccountNumber(fromAddress) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return + return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) } - output, err := wire.MarshalJSONIndent(cdc, res) + sequence, err = ctx.GetAccountSequence(fromAddress) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return + return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) } + } - httputils.NormalResponse(gtx, output) + txCtx := authctx.TxContext{ + Codec: cdc, + Gas: gas, + Fee: fee, + ChainID: transferBody.ChainID, + AccountNumber: accountNumber, + Sequence: sequence, + } + + txForSign, err := txCtx.Build([]sdk.Msg{msg}) + if err != nil { + return emptyMsg, emptyTxContext, sdk.ErrInternal(err.Error()) } + + return txForSign, txCtx, nil } \ No newline at end of file From 9409e73ecf23744ca62ec4be43eae4426c88a460 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 24 Aug 2018 14:27:45 +0800 Subject: [PATCH 13/25] Fix errors in test_cover and test_lint --- client/context/query.go | 3 + client/keys/add.go | 55 +++++------- client/lcd/lcd_test.go | 14 ++-- x/auth/client/rest/query.go | 2 +- x/bank/client/rest/sendtx.go | 157 ++++++++++++++++------------------- 5 files changed, 106 insertions(+), 125 deletions(-) diff --git a/client/context/query.go b/client/context/query.go index 71b16f710200..b7337031129b 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -301,6 +301,9 @@ func (ctx CLIContext) proofVerify(path string, resp abci.ResponseQuery) error { } node, err := ctx.GetNode() + if err != nil { + return err + } // AppHash for height H is in header H+1 commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier) if err != nil { diff --git a/client/keys/add.go b/client/keys/add.go index f211150a0127..26302e067fb5 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -188,22 +188,13 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - if paramCheck(m) != nil { - w.WriteHeader(http.StatusBadRequest) + errCode, err := paramCheck(kb, m) + if err != nil { + w.WriteHeader(errCode) w.Write([]byte(err.Error())) return } - // check if already exists - infos, err := kb.List() - for _, i := range infos { - if i.GetName() == m.Name { - w.WriteHeader(http.StatusConflict) - w.Write([]byte(fmt.Sprintf("Account with name %s already exists.", m.Name))) - return - } - } - // create account seed := m.Seed if seed == "" { @@ -236,19 +227,32 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { } // paramCheck performs add new key parameters checking -func paramCheck(m NewKeyBody) error { +func paramCheck(kb keys.Keybase, m NewKeyBody) (int, error) { if len(m.Name) < 1 || len(m.Name) > 16 { - return fmt.Errorf("account name length should not be longer than 16") + return http.StatusBadRequest, fmt.Errorf("account name length should not be longer than 16") } for _, char := range []rune(m.Name) { if !syntax.IsWordChar(char) { - return fmt.Errorf("account name should not contains any char beyond [_0-9A-Za-z]") + return http.StatusBadRequest, fmt.Errorf("account name should not contains any char beyond [_0-9A-Za-z]") } } if len(m.Password) < 8 || len(m.Password) > 16 { - return fmt.Errorf("account password length should be no less than 8 and no greater than 16") + return http.StatusBadRequest, fmt.Errorf("account password length should be no less than 8 and no greater than 16") } - return nil + + // check if already exists + infos, err := kb.List() + if err != nil { + return http.StatusInternalServerError, err + } + + for _, i := range infos { + if i.GetName() == m.Name { + return http.StatusConflict, fmt.Errorf("account with name %s already exists", m.Name) + } + } + + return 0, nil } // AddNewKeyRequest is the handler of adding new key in swagger rest server @@ -265,28 +269,15 @@ func AddNewKeyRequest(gtx *gin.Context) { return } - if paramCheck(m) != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - } - kb, err := GetKeyBase() if err != nil { httputils.NewError(gtx, http.StatusInternalServerError, err) return } - // check if already exists - infos, err := kb.List() + errCode, err := paramCheck(kb, m) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } - - for _, i := range infos { - if i.GetName() == m.Name { - httputils.NewError(gtx, http.StatusConflict, fmt.Errorf("account with name %s already exists", m.Name)) - return - } + httputils.NewError(gtx, errCode, err) } // create account diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index f0b8c8695b1e..81b7eaef8f7f 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -340,7 +340,7 @@ func TestCoinSend(t *testing.T) { someFakeAddr := sdk.AccAddress(bz) // query empty - res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", someFakeAddr), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/bank/balance/%s", someFakeAddr), nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) acc := getAccount(t, port, addr) @@ -382,7 +382,7 @@ func TestCoinSendSwaggerLCD(t *testing.T) { someFakeAddr := sdk.AccAddress(bz) // query empty - res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", someFakeAddr), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/bank/balance/%s", someFakeAddr), nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) acc := getAccount(t, port, addr) @@ -934,7 +934,7 @@ func TestProposalsQuery(t *testing.T) { //_____________________________________________________________________________ // get the account to get the sequence func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { - res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", addr), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/bank/balance/%s", addr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var acc auth.Account err := cdc.UnmarshalJSON([]byte(body), &acc) @@ -967,9 +967,11 @@ func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress "sequence":"%d", "gas": "10000", "amount":[%s], - "chain_id":"%s" - }`, name, password, accnum, sequence, coinbz, chainID)) - res, body := Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), jsonStr) + "chain_id":"%s", + "signed": true, + "to_address":"%s" + }`, name, password, accnum, sequence, coinbz, chainID, receiveAddr)) + res, body := Request(t, port, "POST", "/bank/transfers", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultTx) diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index f350358b6cfc..88e1ba1fbe6a 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -18,7 +18,7 @@ import ( // register REST routes func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, storeName string) { r.HandleFunc( - "/accounts/{address}", + "/bank/balance/{address}", QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx), ).Methods("GET") } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 0ca01d34de35..e8f1e64e9dd1 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -19,24 +19,12 @@ import ( authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "bytes" "fmt" + "github.com/tendermint/tendermint/libs/bech32" ) // RegisterRoutes - Central function to define routes that get registered by the main application func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") -} - -type sendBody struct { - // fees is not used currently - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - Fee string `json:"fee"` + r.HandleFunc("/bank/transfers", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") } type transferBody struct { @@ -50,7 +38,7 @@ type transferBody struct { Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` Fee string `json:"fee"` - Signed bool `json:"signed"` // true by default + Signed bool `json:"signed"` EnsureAccAndSeq bool `json:"ensure_account_sequence"` } @@ -63,76 +51,49 @@ func init() { // SendRequestHandlerFn - http request handler to send coins to a address func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // collect data - vars := mux.Vars(r) - bech32addr := vars["address"] - - to, err := sdk.AccAddressFromBech32(bech32addr) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - var m sendBody + var transferBody transferBody body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - err = msgCdc.UnmarshalJSON(body, &m) + err = msgCdc.UnmarshalJSON(body, &transferBody) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - - info, err := kb.Get(m.LocalAccountName) + transferBody, errCode, err := paramPreprocess(transferBody, kb) if err != nil { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(err.Error())) - return - } - - // build message - msg := client.BuildMsg(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount) - if err != nil { // XXX rechecking same error ? - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(errCode) w.Write([]byte(err.Error())) return } - txCtx := authctx.TxContext{ - Codec: cdc, - Gas: m.Gas, - Fee: m.Fee, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - } - - txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) + txForSign, _, errMsg := composeTx(cdc, cliCtx, transferBody) if err != nil { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(err.Error())) + if errMsg.Code() == sdk.CodeInternal { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + } else { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + } return } - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + if !transferBody.Signed { + w.Write(txForSign.Bytes()) return } - output, err := wire.MarshalJSONIndent(cdc, res) + output, errCode, err := signAndBroadcase(cdc, cliCtx, txForSign, transferBody, kb) if err != nil { - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(errCode) w.Write([]byte(err.Error())) return } - w.Write(output) } } @@ -156,6 +117,11 @@ func transferRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) httputils.NewError(gtx, http.StatusBadRequest, err) return } + transferBody, errCode, err := paramPreprocess(transferBody, kb) + if err != nil { + httputils.NewError(gtx, errCode, err) + return + } txForSign, _, errMsg := composeTx(cdc, ctx, transferBody) if err != nil { @@ -172,39 +138,61 @@ func transferRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) return } - signAndBroadcase(gtx, cdc, ctx, txForSign, transferBody, kb) + output, errCode, err := signAndBroadcase(cdc, ctx, txForSign, transferBody, kb) + if err != nil { + httputils.NewError(gtx, errCode, err) + return + } + httputils.NormalResponse(gtx, output) } } -// signAndBroadcase perform transaction sign and broadcast operation -func signAndBroadcase(gtx *gin.Context, cdc *wire.Codec, ctx context.CLIContext, txForSign auth.StdSignMsg, transferBody transferBody, kb keys.Keybase) { - - if transferBody.Name == "" || transferBody.Password == "" { - httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("missing key name or password in signning transaction")) - return +// paramPreprocess performs transferBody preprocess +func paramPreprocess(body transferBody, kb keys.Keybase) (transferBody, int, error) { + if body.Name == "" { + if body.Signed { + return transferBody{}, http.StatusBadRequest, fmt.Errorf("missing key name, can't sign transaction") + } + if body.FromAddress == "" { + return transferBody{}, http.StatusBadRequest, fmt.Errorf("both the key name and fromAddreass are missed") + } } - info, err := kb.Get(transferBody.Name) - if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - return - } + if body.Name != "" { + info, err := kb.Get(body.Name) + if err != nil { + return transferBody{}, http.StatusBadRequest, err + } + if body.FromAddress == "" { + addressFromPubKey, err := bech32.ConvertAndEncode(sdk.Bech32PrefixAccAddr, info.GetPubKey().Address().Bytes()) + if err != nil { + return transferBody{}, http.StatusInternalServerError, err + } + body.FromAddress = addressFromPubKey + } else { + fromAddress, err := sdk.AccAddressFromBech32(body.FromAddress) + if err != nil { + return transferBody{}, http.StatusBadRequest, err + } - fromAddress, err := sdk.AccAddressFromBech32(transferBody.FromAddress) - if err != nil { - httputils.NewError(gtx, http.StatusBadRequest, err) - return + if !bytes.Equal(info.GetPubKey().Address(), fromAddress) { + return transferBody{}, http.StatusBadRequest, fmt.Errorf("the fromAddress doesn't equal to the address of sign key") + } + } } + return body, 0, nil +} - if !bytes.Equal(info.GetPubKey().Address(), fromAddress) { - httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("the fromAddress doesn't equal to the address of sign key")) - return +// signAndBroadcase perform transaction sign and broadcast operation +func signAndBroadcase(cdc *wire.Codec, ctx context.CLIContext, txForSign auth.StdSignMsg, transferBody transferBody, kb keys.Keybase) ([]byte, int, error) { + + if transferBody.Name == "" || transferBody.Password == "" { + return nil, http.StatusBadRequest, fmt.Errorf("missing key name or password in signning transaction") } sig, pubkey, err := kb.Sign(transferBody.Name, transferBody.Password, txForSign.Bytes()) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return + return nil, http.StatusInternalServerError, err } sigs := []auth.StdSignature{{ @@ -216,23 +204,20 @@ func signAndBroadcase(gtx *gin.Context, cdc *wire.Codec, ctx context.CLIContext, txBytes, err := ctx.Codec.MarshalBinary(auth.NewStdTx(txForSign.Msgs, txForSign.Fee, sigs, txForSign.Memo)) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return + return nil, http.StatusInternalServerError, err } res, err := ctx.BroadcastTx(txBytes) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return + return nil, http.StatusInternalServerError, err } output, err := wire.MarshalJSONIndent(cdc, res) if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return + return nil, http.StatusInternalServerError, err } - httputils.NormalResponse(gtx, output) + return output, 0, nil } // composeTx perform StdSignMsg building operation From 70aa433ea95ef7e7d56181d560b1e99326c50d90 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Sat, 25 Aug 2018 10:27:45 +0800 Subject: [PATCH 14/25] Add new stake api and remove advanced command --- client/lcd/docs/docs.go | 139 +++++++++++++++++++++++++++++++++++ client/lcd/root.go | 4 +- x/stake/client/rest/query.go | 95 +++++++++++++++++++----- 3 files changed, 217 insertions(+), 21 deletions(-) diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go index 8e7876ed1d7a..e22fe381fbdf 100644 --- a/client/lcd/docs/docs.go +++ b/client/lcd/docs/docs.go @@ -470,6 +470,96 @@ var doc = `{ } } }, + "/stake/pool": { + "get": { + "description": "Query the staking pool information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query the staking pool information", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/stake.pool" + } + }, + "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/parameters": { + "get": { + "description": "Query the staking params values", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Stake Operation" + ], + "summary": "Query the staking params values", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/stake.params" + } + }, + "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", @@ -1706,6 +1796,55 @@ var doc = `{ "type": "object", "$ref": "#/definitions/stake.txInfo" } + }, + "stake.pool": { + "type": "object", + "properties": { + "loose_tokens": { + "type": "string" + }, + "bonded_tokens": { + "type": "integer" + }, + "inflation_last_time": { + "type": "string" + }, + "inflation": { + "type": "string" + }, + "date_last_commission_reset": { + "type": "integer" + }, + "prev_bonded_shares": { + "type": "string" + } + } + }, + "stake.params": { + "type": "object", + "properties": { + "inflation_rate_change": { + "type": "string" + }, + "inflation_max": { + "type": "string" + }, + "inflation_min": { + "type": "string" + }, + "goal_bonded": { + "type": "string" + }, + "unbonding_time": { + "type": "string" + }, + "max_validators": { + "type": "integer" + }, + "bond_denom": { + "type": "string" + } + } } } }` diff --git a/client/lcd/root.go b/client/lcd/root.go index 4a5cfb5bec29..9e904097aef5 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -109,11 +109,11 @@ func createHandler(cdc *wire.Codec) http.Handler { // Which is much friendly for further development func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "rest-server-swagger", + Use: "lite-server", Short: "Start Gaia-lite (gaia light client daemon), a local REST server with swagger-ui, default url: http://localhost:1317/swagger/index.html", RunE: func(cmd *cobra.Command, args []string) error { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). - With("module", "rest-server-swagger") + With("module", "lite-server") listenAddr := viper.GetString(client.FlagListenAddr) //Create rest server server := gin.New() diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 43324a975d4e..bb409f3d04d2 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -569,6 +569,67 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.Handler } } + +// HTTP request handler to query the pool information +func poolHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + key := stake.PoolKey + + res, err := cliCtx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query pool. Error: %s", err.Error()))) + return + } + + pool, err := types.UnmarshalPool(cdc, res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + output, err := cdc.MarshalJSON(pool) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + +// HTTP request handler to query the staking params values +func paramsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + key := stake.ParamKey + + res, err := cliCtx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query parameters. Error: %s", err.Error()))) + return + } + + params, err := types.UnmarshalParams(cdc, res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + output, err := cdc.MarshalJSON(params) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + func registerSwaggerQueryRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { routerGroup.GET("/stake/delegators/:delegatorAddr", delegatorHandlerCreation(cdc, ctx)) routerGroup.GET("/stake/delegators/:delegatorAddr/txs", delegatorTxsHandlerCreation(cdc, ctx)) @@ -578,6 +639,8 @@ func registerSwaggerQueryRoutes(routerGroup *gin.RouterGroup, ctx context.CLICon 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)) + routerGroup.GET("/stake/pool", poolHandlerCreation(cdc, ctx)) + routerGroup.GET("/stake/parameters", paramsHandlerCreation(cdc, ctx)) } func delegatorHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { @@ -992,63 +1055,57 @@ func validatorHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.Ha httputils.NormalResponse(gtx, output) } } -======= + // HTTP request handler to query the pool information -func poolHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func poolHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { key := stake.PoolKey res, err := cliCtx.QueryStore(key, storeName) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query pool. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query pool. Error: %s", err.Error())) return } pool, err := types.UnmarshalPool(cdc, res) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } output, err := cdc.MarshalJSON(pool) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } - w.Write(output) + httputils.NormalResponse(gtx, output) } } // HTTP request handler to query the staking params values -func paramsHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func paramsHandlerCreation(cdc *wire.Codec, cliCtx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { key := stake.ParamKey res, err := cliCtx.QueryStore(key, storeName) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query parameters. Error: %s", err.Error()))) + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query parameters. Error: %s", err.Error())) return } params, err := types.UnmarshalParams(cdc, res) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } output, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + httputils.NewError(gtx, http.StatusInternalServerError, err) return } - w.Write(output) + httputils.NormalResponse(gtx, output) } } From 7a4b019f9f2b1081824ddf3c69366b1fea1dc4d7 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Mon, 27 Aug 2018 11:12:11 +0800 Subject: [PATCH 15/25] Add spec to client.md and add this subcommand to basecli --- docs/light/getting_started.md | 60 +++++++++++++-------------- docs/sdk/clients.md | 13 ++++-- examples/basecoin/cmd/basecli/main.go | 1 + 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/docs/light/getting_started.md b/docs/light/getting_started.md index 5f11956c0540..227027663884 100644 --- a/docs/light/getting_started.md +++ b/docs/light/getting_started.md @@ -1,40 +1,38 @@ # Getting Started To start a rest server, we need to specify the following parameters: -| Parameter | Type | Default | Required | Description | -| ----------- | --------- | ----------------------- | -------- | ---------------------------------------------------- | -| chain-id | string | null | true | chain id of the full node to connect | -| node | URL | "tcp://localhost:46657" | true | address of the full node to connect | -| laddr | URL | "tcp://localhost:1317" | true | address to run the rest server on | -| trust-node | bool | "false" | true | Whether this LCD is connected to a trusted full node | -| trust-store | DIRECTORY | "$HOME/.lcd" | false | directory for save checkpoints and validator sets | -Sample command: - -```bash -gaiacli light-client --chain-id=test --laddr=tcp://localhost:1317 --node tcp://localhost:46657 --trust-node=false +| Parameter | Type | Default | Required | Description | +| --------------- | --------- | ----------------------- | -------- | ---------------------------------------------------- | +| chain-id | string | null | true | ID of chain we connect to, must be specified | +| home | string | "$HOME/.gaiacli" | false | directory for config and data, such as key and checkpoint | +| node-list | string | "tcp://localhost:26657" | false | Full node list to connect to, example: "tcp://10.10.10.10:26657,tcp://20.20.20.20:26657" | +| laddr | string | "tcp://localhost:1317" | false | Address for server to listen on | +| trust-node | bool | false | false | Trust full nodes or not | +| swagger-host-ip | string | "localhost" | false | The host IP of the Gaia-lite server, swagger-ui will send request to this host | +| modules | string | "general,key,token" | false | Enabled modules | + +Sample command to start gaia-lite node: +``` +gaiacli lite-server --chain-id {your chain id} ``` -## Gaia Light Use Cases - -LCD could be very helpful for related service providers. For a wallet service provider, LCD could -make transaction faster and more reliable in the following cases. - -### Create an account - -![deposit](pics/create-account.png) - -First you need to get a new seed phrase :[get-seed](api.md#keysseed---get) - -After having new seed, you could generate a new account with it : [keys](api.md#keys---post) - -### Transfer a token +When the connected full node is trusted, then the proof is not necessary, so you can run gaia-lite node with trust-node option: +``` +gaiacli lite-server --chain-id {your chain id} --trust-node +``` -![transfer](pics/transfer-tokens.png) +If you have want to run gaia-lite node in a remote server, then you must specify the public ip to swagger-host-ip +``` +gaiacli lite-server --chain-id {your chain id} --swagger-host-ip {remove server public ip} +``` -The first step is to build an asset transfer transaction. Here we can post all necessary parameters -to /create_transfer to get the unsigned transaction byte array. Refer to this link for detailed -operation: [build transaction](api.md#create_transfer---post) +The gaia-lite node can connect to multiple full nodes. Then gaia-lite will do load balancing for full nodes which is helpful to improve reliability and TPS. You can do this by this command: +``` +gaiacli lite-server --chain-id {your chain id} --node-list tcp://10.10.10.10:26657,tcp://20.20.20.20:26657 +``` -Then sign the returned transaction byte array with users' private key. Finally broadcast the signed -transaction. Refer to this link for how to broadcast the signed transaction: [broadcast transaction](api.md#create_transfer---post) +The gaia-lite support modular rest APIs. Now it supports four modules: general, key, token and stake. If you need all of them, just start it with this command: + ``` + gaiacli lite-server --chain-id {your chain id} --modules general,key,token,stake + ``` \ No newline at end of file diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index be42a46dbaa9..99cbd3f55937 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -378,6 +378,13 @@ With the `pool` command you will get the values for: ## Gaia-Lite -::: tip Note -🚧 We are actively working on documentation for Gaia-lite. -::: +The Gaia-Lite is a light gaiad node. Unlike gaiad full node, it doesn't participate consensus and execute transactions, so it only require very little storage and calculation resource. However, it can provide the same security as a gaia full node. + +It provides modular rest APIs. Through these APIs, you can send transactions and query blockchain information. Besides, all query results with proof will be verified against the validator set and merkle hash algorithm. + +You can start a gaia-lite node with the following command: +``` +gaiacli lite-server --chain-id {your chain id} +``` + +Please refer to this [getting start](../light/getting_started.md) for detailed description about how to run gaia-lite. \ No newline at end of file diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 23183c87a4d3..24714f503882 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -79,6 +79,7 @@ func main() { rootCmd.AddCommand( client.LineBreak, lcd.ServeCommand(cdc), + lcd.ServeSwaggerCommand(cdc), keys.Commands(), client.LineBreak, version.VersionCmd, From 00c43fa52a8a554b858c94fef05a76e2bd187a33 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Mon, 27 Aug 2018 11:15:23 +0800 Subject: [PATCH 16/25] change key apis to accommodate to doc/light/api.md --- client/flags.go | 1 + client/keys/add.go | 69 ++++++++++++++++++++++++++++++++++------- client/keys/root.go | 4 +-- client/lcd/docs/docs.go | 54 ++++++++++++++++++++++++-------- 4 files changed, 101 insertions(+), 27 deletions(-) diff --git a/client/flags.go b/client/flags.go index c5ca17f58b68..5e4512f7cced 100644 --- a/client/flags.go +++ b/client/flags.go @@ -23,6 +23,7 @@ const ( FlagSwaggerHostIP = "swagger-host-ip" FlagModules = "modules" FlagNodeList = "node-list" + FlagTrustStore = "trust-store" ) // LineBreak can be included in a command list to provide a blank line diff --git a/client/keys/add.go b/client/keys/add.go index 26302e067fb5..45b7735fdf5b 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -188,7 +188,7 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - errCode, err := paramCheck(kb, m) + errCode, err := paramCheck(kb, m.Name, m.Password) if err != nil { w.WriteHeader(errCode) w.Write([]byte(err.Error())) @@ -227,16 +227,16 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { } // paramCheck performs add new key parameters checking -func paramCheck(kb keys.Keybase, m NewKeyBody) (int, error) { - if len(m.Name) < 1 || len(m.Name) > 16 { +func paramCheck(kb keys.Keybase, name, password string) (int, error) { + if len(name) < 1 || len(name) > 16 { return http.StatusBadRequest, fmt.Errorf("account name length should not be longer than 16") } - for _, char := range []rune(m.Name) { + for _, char := range []rune(name) { if !syntax.IsWordChar(char) { return http.StatusBadRequest, fmt.Errorf("account name should not contains any char beyond [_0-9A-Za-z]") } } - if len(m.Password) < 8 || len(m.Password) > 16 { + if len(password) < 8 || len(password) > 16 { return http.StatusBadRequest, fmt.Errorf("account password length should be no less than 8 and no greater than 16") } @@ -247,8 +247,8 @@ func paramCheck(kb keys.Keybase, m NewKeyBody) (int, error) { } for _, i := range infos { - if i.GetName() == m.Name { - return http.StatusConflict, fmt.Errorf("account with name %s already exists", m.Name) + if i.GetName() == name { + return http.StatusConflict, fmt.Errorf("account with name %s already exists", name) } } @@ -275,9 +275,10 @@ func AddNewKeyRequest(gtx *gin.Context) { return } - errCode, err := paramCheck(kb, m) + errCode, err := paramCheck(kb, m.Name, m.Password) if err != nil { httputils.NewError(gtx, errCode, err) + return } // create account @@ -332,12 +333,56 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(seed)) } +// RecoverKeyBody is recover key request REST body +type RecoverKeyBody struct { + Password string `json:"password"` + Seed string `json:"seed"` +} + // SeedRequest is the handler of creating seed in swagger rest server -func SeedRequest(gtx *gin.Context) { +func RecoverResuest(gtx *gin.Context) { + name := gtx.Param("name") + var m RecoverKeyBody + 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 + } - algo := keys.SigningAlgo("secp256k1") + kb, err := GetKeyBase() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } - seed := getSeed(algo) + errCode, err := paramCheck(kb, name, m.Password) + if err != nil { + httputils.NewError(gtx, errCode, err) + return + } - httputils.NormalResponse(gtx, []byte(seed)) + info, err := kb.CreateKey(name, m.Seed, m.Password) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + keyOutput, err := Bech32KeyOutput(info) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + bz, err := json.Marshal(keyOutput) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.NormalResponse(gtx, bz) } \ No newline at end of file diff --git a/client/keys/root.go b/client/keys/root.go index 1a6edc40d21f..6d65e97d1cfb 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -44,8 +44,8 @@ func RegisterRoutes(r *mux.Router) { func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/keys", QueryKeysRequest) routerGroup.POST("/keys", AddNewKeyRequest) - routerGroup.GET("/keys/seed", SeedRequest) - routerGroup.GET("/keys/get/:name", GetKeyRequest) + routerGroup.POST("/keys/:name/recover", RecoverResuest) + routerGroup.GET("/keys/:name", GetKeyRequest) routerGroup.PUT("/keys/:name", UpdateKeyRequest) routerGroup.DELETE("/keys/:name", DeleteKeyRequest) } \ No newline at end of file diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go index e22fe381fbdf..e93d816b0747 100644 --- a/client/lcd/docs/docs.go +++ b/client/lcd/docs/docs.go @@ -834,9 +834,9 @@ var doc = `{ } } }, - "/keys/get/{name}": { - "get": { - "description": "Get detailed information for specific key name", + "/keys/{name}/recover": { + "post": { + "description": "Recover a key from seed", "consumes": [ "application/json" ], @@ -846,15 +846,25 @@ var doc = `{ "tags": [ "Key Management" ], - "summary": "Get key information", - "parameters": [ + "parameters": [ { "type": "string", "description": "key name", "name": "name", "in": "path" + }, + { + "description": "seed and password for a new key", + "name": "seedAndPwd", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/keys.RecoverKeyBody" + } } ], + "summary": "Recover a key from seed", "responses": { "200": { "description": "OK", @@ -887,9 +897,9 @@ var doc = `{ } } }, - "/keys/seed": { - "get": { - "description": "Get a seed for creating key", + "/keys/{name}": { + "get": { + "description": "Get detailed information for specific key name", "consumes": [ "application/json" ], @@ -899,12 +909,21 @@ var doc = `{ "tags": [ "Key Management" ], - "summary": "Get a seed", + "summary": "Get key information", + "parameters": [ + { + "type": "string", + "description": "key name", + "name": "name", + "in": "path" + } + ], "responses": { "200": { "description": "OK", "schema": { - "type": "string" + "type": "object", + "$ref": "#/definitions/keys.KeyOutput" } }, "400": { @@ -929,9 +948,7 @@ var doc = `{ } } } - } - }, - "/keys/{name}": { + }, "put": { "description": "The keys are protected by the password, here this API provides a way to change the password", "consumes": [ @@ -1419,6 +1436,17 @@ var doc = `{ } } }, + "keys.RecoverKeyBody": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "seed": { + "type": "string" + } + } + }, "keys.UpdateKeyBody": { "type": "object", "properties": { From 2455604719f282c515bb38e32f8a4919ae22c18e Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Mon, 27 Aug 2018 11:18:33 +0800 Subject: [PATCH 17/25] Refactor multi store proof verification, reuse commitInfo hash calculation --- store/multistoreproof.go | 48 ++++++++++------------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/store/multistoreproof.go b/store/multistoreproof.go index 82cc48df215d..e98a198e935f 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -5,7 +5,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" "github.com/tendermint/iavl" - "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -55,15 +54,16 @@ func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []store // verify multiStoreCommitInfo against appHash func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []SubstoreCommitID, appHash []byte) ([]byte, error) { var substoreCommitHash []byte - var kvPairs cmn.KVPairs + var storeInfos []storeInfo + var height int64 for _, multiStoreCommitID := range multiStoreCommitInfo { if multiStoreCommitID.Name == storeName { substoreCommitHash = multiStoreCommitID.CommitHash + height = multiStoreCommitID.Version } - - keyHash := []byte(multiStoreCommitID.Name) storeInfo := storeInfo{ + Name: multiStoreCommitID.Name, Core: storeCore{ CommitID: sdk.CommitID{ Version: multiStoreCommitID.Version, @@ -72,29 +72,19 @@ func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []Substor }, } - kvPairs = append(kvPairs, cmn.KVPair{ - Key: keyHash, - Value: storeInfo.Hash(), - }) + storeInfos = append(storeInfos, storeInfo) } if len(substoreCommitHash) == 0 { return nil, cmn.NewError("failed to get substore root commit hash by store name") } - if kvPairs == nil { - return nil, cmn.NewError("failed to extract information from multiStoreCommitInfo") - } - //sort the kvPair list - kvPairs.Sort() - - //Rebuild simple merkle hash tree - var hashList [][]byte - for _, kvPair := range kvPairs { - hashResult := merkle.SimpleHashFromTwoHashes(kvPair.Key, kvPair.Value) - hashList = append(hashList, hashResult) + + ci := commitInfo{ + Version: height, + StoreInfos: storeInfos, } - if !bytes.Equal(appHash, simpleHashFromHashes(hashList)) { - return nil, cmn.NewError("The merkle root of multiStoreCommitInfo doesn't equal to appHash") + if !bytes.Equal(appHash, ci.Hash()) { + return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash") } return substoreCommitHash, nil } @@ -123,18 +113,4 @@ func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof * } return nil -} - -func simpleHashFromHashes(hashes [][]byte) []byte { - // Recursive impl. - switch len(hashes) { - case 0: - return nil - case 1: - return hashes[0] - default: - left := simpleHashFromHashes(hashes[:(len(hashes)+1)/2]) - right := simpleHashFromHashes(hashes[(len(hashes)+1)/2:]) - return merkle.SimpleHashFromTwoHashes(left, right) - } -} +} \ No newline at end of file From 776029652f42adca98c4d6379fe87971c5b16fcd Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Mon, 27 Aug 2018 12:39:56 +0800 Subject: [PATCH 18/25] Fix key management apis test error --- client/lcd/lcd_test.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 07a37609384e..12e6df085061 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -124,7 +124,11 @@ func TestKeysSwaggerLCD(t *testing.T) { // get seed // TODO Do we really need this endpoint? - res, body := Request(t, port, "GET", "/keys/seed", nil) + recoverKeyURL := fmt.Sprintf("/keys/%s/recover", "test_recover") + seedRecover := "divorce meat banana embody near until uncover wait uniform capital crawl test praise cloud foil monster garbage hedgehog wrong skate there bonus box odor" + passwordRecover := "1234567890" + jsonStrRecover := []byte(fmt.Sprintf(`{"seed":"%s", "password":"%s"}`, seedRecover, passwordRecover)) + res, body := Request(t, port, "POST", recoverKeyURL, jsonStrRecover) require.Equal(t, http.StatusOK, res.StatusCode, body) reg, err := regexp.Compile(`([a-z]+ ){12}`) require.Nil(t, err) @@ -156,7 +160,7 @@ func TestKeysSwaggerLCD(t *testing.T) { res, body = Request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var m [2]keys.KeyOutput + var m [3]keys.KeyOutput err = cdc.UnmarshalJSON([]byte(body), &m) require.Nil(t, err) @@ -168,7 +172,7 @@ func TestKeysSwaggerLCD(t *testing.T) { require.Equal(t, addr2Bech32, m[1].Address.String(), "Did not serve keys Address correctly") // select key - keyEndpoint := fmt.Sprintf("/keys/get/%s", newName) + keyEndpoint := fmt.Sprintf("/keys/%s", newName) res, body = Request(t, port, "GET", keyEndpoint, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput From f6ebccf32c66a34f247d0e0d2e1c72433f891830 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Mon, 27 Aug 2018 17:59:02 +0800 Subject: [PATCH 19/25] Refactor lcd related spec --- docs/light/getting_started.md | 10 +++++----- docs/sdk/clients.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/light/getting_started.md b/docs/light/getting_started.md index 227027663884..f032adafdd7a 100644 --- a/docs/light/getting_started.md +++ b/docs/light/getting_started.md @@ -14,25 +14,25 @@ To start a rest server, we need to specify the following parameters: Sample command to start gaia-lite node: ``` -gaiacli lite-server --chain-id {your chain id} +gaiacli lite-server --chain-id= ``` When the connected full node is trusted, then the proof is not necessary, so you can run gaia-lite node with trust-node option: ``` -gaiacli lite-server --chain-id {your chain id} --trust-node +gaiacli lite-server --chain-id= --trust-node ``` If you have want to run gaia-lite node in a remote server, then you must specify the public ip to swagger-host-ip ``` -gaiacli lite-server --chain-id {your chain id} --swagger-host-ip {remove server public ip} +gaiacli lite-server --chain-id= --swagger-host-ip= ``` The gaia-lite node can connect to multiple full nodes. Then gaia-lite will do load balancing for full nodes which is helpful to improve reliability and TPS. You can do this by this command: ``` -gaiacli lite-server --chain-id {your chain id} --node-list tcp://10.10.10.10:26657,tcp://20.20.20.20:26657 +gaiacli lite-server --chain-id= --node-list=tcp://10.10.10.10:26657,tcp://20.20.20.20:26657 ``` The gaia-lite support modular rest APIs. Now it supports four modules: general, key, token and stake. If you need all of them, just start it with this command: ``` - gaiacli lite-server --chain-id {your chain id} --modules general,key,token,stake + gaiacli lite-server --chain-id= --modules=general,key,token,stake ``` \ No newline at end of file diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 99cbd3f55937..5a8278c4a375 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -384,7 +384,7 @@ It provides modular rest APIs. Through these APIs, you can send transactions and You can start a gaia-lite node with the following command: ``` -gaiacli lite-server --chain-id {your chain id} +gaiacli lite-server --chain-id= ``` Please refer to this [getting start](../light/getting_started.md) for detailed description about how to run gaia-lite. \ No newline at end of file From 986504fcd4c4417910de735291fbb053e8ae9eba Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Tue, 28 Aug 2018 15:30:48 +0800 Subject: [PATCH 20/25] Add missed api in ICS0 and ICS1, remove the response wrapper in api.md --- client/context/query.go | 16 + client/keys/add.go | 6 +- client/keys/root.go | 1 + client/keys/sign.go | 83 +++++ client/lcd/docs/docs.go | 222 +++++++++++++- client/lcd/root.go | 20 +- client/tx/broadcast.go | 72 +++++ client/tx/root.go | 6 + docs/light/api.md | 555 ++++++++++++++++++++++------------ docs/light/getting_started.md | 11 +- x/auth/client/rest/query.go | 4 +- x/bank/client/rest/query.go | 61 ++++ x/bank/client/rest/rest.go | 15 + x/bank/client/rest/sendtx.go | 12 +- 14 files changed, 852 insertions(+), 232 deletions(-) create mode 100644 client/keys/sign.go create mode 100644 x/bank/client/rest/query.go create mode 100644 x/bank/client/rest/rest.go diff --git a/client/context/query.go b/client/context/query.go index 9a2c3f4b3ef8..b6f5b61765ff 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -168,6 +168,22 @@ func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, er return res, err } +// BroadcastTxSync broadcasts transaction bytes to a Tendermint node +// synchronously. +func (ctx CLIContext) BroadcastTxSync(tx []byte) (*ctypes.ResultBroadcastTx, error) { + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxSync(tx) + if err != nil { + return res, err + } + + return res, err +} + // EnsureAccountExists ensures that an account exists for a given context. An // error is returned if it does not. func (ctx CLIContext) EnsureAccountExists() error { diff --git a/client/keys/add.go b/client/keys/add.go index 45b7735fdf5b..33670d1782d7 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -216,7 +216,7 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { keyOutput.Seed = seed - bz, err := json.Marshal(keyOutput) + bz, err := cdc.MarshalJSON(keyOutput) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) @@ -339,7 +339,7 @@ type RecoverKeyBody struct { Seed string `json:"seed"` } -// SeedRequest is the handler of creating seed in swagger rest server +// RecoverResuest is the handler of creating seed in swagger rest server func RecoverResuest(gtx *gin.Context) { name := gtx.Param("name") var m RecoverKeyBody @@ -378,7 +378,7 @@ func RecoverResuest(gtx *gin.Context) { return } - bz, err := json.Marshal(keyOutput) + bz, err := cdc.MarshalJSON(keyOutput) if err != nil { httputils.NewError(gtx, http.StatusInternalServerError, err) return diff --git a/client/keys/root.go b/client/keys/root.go index 6d65e97d1cfb..e6ad00cf0207 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -48,4 +48,5 @@ func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/keys/:name", GetKeyRequest) routerGroup.PUT("/keys/:name", UpdateKeyRequest) routerGroup.DELETE("/keys/:name", DeleteKeyRequest) + routerGroup.POST("/keys/:name/sign", SignResuest) } \ No newline at end of file diff --git a/client/keys/sign.go b/client/keys/sign.go new file mode 100644 index 000000000000..57a0511293db --- /dev/null +++ b/client/keys/sign.go @@ -0,0 +1,83 @@ +package keys + +import ( + "github.com/gin-gonic/gin" + "io/ioutil" + "github.com/cosmos/cosmos-sdk/client/httputils" + "net/http" +) + +type SignBody struct { + Tx []byte `json:"tx_bytes"` + Password string `json:"password"` +} +/* +var showKeysCmd = &cobra.Command{ + Use: "sign ", + Short: "Show key info for the given name", + Long: `Return public details of one local key.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + name := args[0] + info, err := getKey(name) + if err != nil { + return err + } + + showAddress := viper.GetBool(FlagAddress) + showPublicKey := viper.GetBool(FlagPublicKey) + outputSet := cmd.Flag(cli.OutputFlag).Changed + if showAddress && showPublicKey { + return errors.New("cannot use both --address and --pubkey at once") + } + if outputSet && (showAddress || showPublicKey) { + return errors.New("cannot use --output with --address or --pubkey") + } + if showAddress { + printKeyAddress(info) + return nil + } + if showPublicKey { + printPubKey(info) + return nil + } + + printInfo(info) + return nil + }, +} +*/ +// SignResuest is the handler of creating seed in swagger rest server +func SignResuest(gtx *gin.Context) { + name := gtx.Param("name") + var m SignBody + 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 + } + kb, err := GetKeyBase() + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + sig, _, err := kb.Sign(name, m.Password, m.Tx) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + output, err := cdc.MarshalJSON(sig) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + + httputils.NormalResponse(gtx, output) +} \ No newline at end of file diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go index e93d816b0747..3d9ad954bc9c 100644 --- a/client/lcd/docs/docs.go +++ b/client/lcd/docs/docs.go @@ -19,8 +19,8 @@ import ( var doc = `{ "swagger": "2.0", "info": { - "description": "All cosmos-lcd supported APIs will be shown by this swagger-ui page. You can access these APIs through this page.", - "title": "Swagger Cosmos-LCD API", + "description": "All Gaia-lite supported APIs will be shown by this swagger-ui page. You can access these APIs through this page.", + "title": "Gaia-lite Swagger-UI", "termsOfService": "http://swagger.io/terms/", "contact": { "name": "API Support", @@ -560,7 +560,7 @@ var doc = `{ } } }, - "/stake/delegators/:delegatorAddr/delegations": { + "/stake/delegators/{delegatorAddr}/delegations": { "post": { "description": "Send stake related transactions", "consumes": [ @@ -623,7 +623,7 @@ var doc = `{ } } }, - "/bank/balance/{address}": { + "/bank/balances/{address}": { "get": { "description": "Get the detailed information for specific address", "consumes": [ @@ -649,7 +649,7 @@ var doc = `{ "description": "OK", "schema": { "type": "object", - "$ref": "#/definitions/auth.BaseAccount" + "$ref": "#/definitions/sdk.Coins" } }, "400": { @@ -678,7 +678,7 @@ var doc = `{ }, "/bank/transfers": { "post": { - "description": "This API require the Cosmos-LCD have keystore module. It will ask keystore module for transaction signature", + "description": "This API require the Gaia-lite has keystore module. It will ask keystore module for transaction signature", "consumes": [ "application/json" ], @@ -897,6 +897,68 @@ var doc = `{ } } }, + "/keys/{name}/sign": { + "post": { + "description": "Sign user specified bytes array", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "parameters": [ + { + "type": "string", + "description": "key name", + "name": "name", + "in": "path" + }, + { + "description": "seed and password for a new key", + "name": "txAndPwd", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/keys.SignBody" + } + } + ], + "summary": "Sign user specified bytes array", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "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" + } + } + } + } + }, "/keys/{name}": { "get": { "description": "Get detailed information for specific key name", @@ -1068,6 +1130,116 @@ var doc = `{ } } }, + "/auth/accounts/{address}": { + "get": { + "description": "Get the detailed information for specific address", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Key Management" + ], + "summary": "Query account information", + "parameters": [ + { + "type": "string", + "description": "address", + "name": "address", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/auth.BaseAccount" + } + }, + "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" + } + } + } + } + }, + "/txs": { + "post": { + "description": "Broadcast transaction", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Transaction" + ], + "parameters": [ + { + "description": "seed and password for a new key", + "name": "txAndReturn", + "in": "body", + "required": true, + "schema": { + "type": "object", + "$ref": "#/definitions/tx.TxBody" + } + } + ], + "summary": "Broadcast transaction", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/bank.ResultBroadcastTxCommit" + } + }, + "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" + } + } + } + } + }, "/node_version": { "get": { "description": "Get connected full node version", @@ -1114,7 +1286,7 @@ var doc = `{ }, "/version": { "get": { - "description": "Get Cosmos-LCD version", + "description": "Get Gaia-lite version", "consumes": [ "application/json" ], @@ -1124,7 +1296,7 @@ var doc = `{ "tags": [ "General" ], - "summary": "Get Cosmos-LCD version", + "summary": "Get Gaia-lite version", "responses": { "200": { "description": "OK", @@ -1313,9 +1485,9 @@ var doc = `{ "sequence": { "type": "string" }, - "signed": { + "generate": { "type": "boolean", - "example": true + "example": false }, "ensure_account_sequence": { "type": "boolean", @@ -1447,6 +1619,17 @@ var doc = `{ } } }, + "keys.SignBody": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "tx_bytes": { + "type": "string" + } + } + }, "keys.UpdateKeyBody": { "type": "object", "properties": { @@ -1458,6 +1641,18 @@ var doc = `{ } } }, + "tx.TxBody": { + "type": "object", + "properties": { + "transaction": { + "type": "string" + }, + "return": { + "type": "string", + "example": "block" + } + } + }, "httputil.HTTPError": { "type": "object", "properties": { @@ -1881,8 +2076,9 @@ var tagToModuleDesc = ` { "General":"general", "Key Management":"key", - "Token Operation":"token", - "Stake Operation":"stake" + "Token Operation":"bank", + "Stake Operation":"staking", + "Transaction":"transaction" } ` @@ -1901,7 +2097,7 @@ func addOptionsToDesc (desc string) string { buffer.WriteString(desc) buffer.WriteString("\n") - buffer.WriteString("Cosmos-LCD starting options:") + buffer.WriteString("Gaid-lite starting options:") buffer.WriteString("\n") buffer.WriteString(cli.HomeFlag) diff --git a/client/lcd/root.go b/client/lcd/root.go index 9e904097aef5..c92231b3b4ba 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -135,7 +135,7 @@ func ServeSwaggerCommand(cdc *wire.Codec) *cobra.Command { cmd.Flags().String(client.FlagNodeList, "tcp://localhost:26657", "Node list to connect to, example: \"tcp://10.10.10.10:26657,tcp://20.20.20.20:26657\".") cmd.Flags().String(client.FlagChainID, "", "ID of chain we connect to, must be specified.") cmd.Flags().String(client.FlagSwaggerHostIP, "localhost", "The host IP of the Gaia-lite server, swagger-ui will send request to this host.") - cmd.Flags().String(client.FlagModules, "general,key,token", "Enabled modules.") + cmd.Flags().String(client.FlagModules, "general,key,bank", "Enabled modules.") cmd.Flags().Bool(client.FlagTrustNode, false, "Trust full nodes or not.") return cmd @@ -177,19 +177,31 @@ func createSwaggerHandler(server *gin.Engine, cdc *wire.Codec) { server.GET("/node_version", NodeVersionRequest(ctx)) } + if moduleEnabled(moduleArray,"transaction") { + tx.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc) + } + if moduleEnabled(moduleArray,"key") { keys.RegisterSwaggerRoutes(server.Group("/")) + auth.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, "acc") } - if moduleEnabled(moduleArray,"token") { - auth.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, "acc") + if moduleEnabled(moduleArray,"bank") { bank.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, kb) } - if moduleEnabled(moduleArray,"stake") { + if moduleEnabled(moduleArray,"staking") { stake.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, kb) } +/* + if moduleEnabled(moduleArray,"governance") { + gov.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, kb) + } + if moduleEnabled(moduleArray,"slashing") { + slashing.RegisterSwaggerRoutes(server.Group("/"), ctx, cdc, kb) + } +*/ } func moduleEnabled(modules []string, name string) bool { diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 89ad48f43f81..1aa2875349b2 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -5,8 +5,18 @@ import ( "net/http" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/httputils" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/gin-gonic/gin" + "io/ioutil" + "fmt" ) +const ( + FlagSync = "sync" + FlagAsync = "async" + FlagBlock = "block" +) // Tx Broadcast Body type BroadcastTxBody struct { TxBytes string `json:"tx"` @@ -35,3 +45,65 @@ func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { w.Write([]byte(string(res.Height))) } } + +type TxBody struct { + Transaction string `json:"transaction"` + Return string `json:"return"` +} + +// BroadcastTxRequest REST Handler +func BroadcastTxRequest(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + var txBody TxBody + body, err := ioutil.ReadAll(gtx.Request.Body) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + err = cdc.UnmarshalJSON(body, &txBody) + if err != nil { + httputils.NewError(gtx, http.StatusBadRequest, err) + return + } + var output []byte + switch txBody.Return { + case FlagBlock: + res, err := ctx.BroadcastTx([]byte(txBody.Transaction)) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + output, err = cdc.MarshalJSON(res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + case FlagSync: + res, err := ctx.BroadcastTxSync([]byte(txBody.Transaction)) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + output, err = cdc.MarshalJSON(res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + case FlagAsync: + res, err := ctx.BroadcastTxAsync([]byte(txBody.Transaction)) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + output, err = cdc.MarshalJSON(res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, err) + return + } + default: + httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("unsupported return type. supported types: block, sync, async")) + return + } + httputils.NormalResponse(gtx,output) + } +} \ No newline at end of file diff --git a/client/tx/root.go b/client/tx/root.go index 7e18d5aae126..dad5603ae30a 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/wire" + "github.com/gin-gonic/gin" ) // AddCommands adds a number of tx-query related subcommands @@ -23,3 +24,8 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { // r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") // r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") } + + +func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { + routerGroup.POST("/txs", BroadcastTxRequest(cdc, ctx)) +} \ No newline at end of file diff --git a/docs/light/api.md b/docs/light/api.md index 6c5e8aa5f02a..7bbfedc63607 100644 --- a/docs/light/api.md +++ b/docs/light/api.md @@ -32,7 +32,7 @@ Exposes the same functionality as the Tendermint RPC from a full node. It aims t ```json { "transaction": "string", - "return": "string", + "return": "async" } ``` @@ -40,15 +40,11 @@ Exposes the same functionality as the Tendermint RPC from a full node. It aims t ```json { - "rest api":"2.0", - "code":200, - "error":"", - "result":{ + "code":0, "hash":"0D33F2F03A5234F38706E43004489E061AC40A2E", "data":"", "log":"" - } } ``` @@ -63,26 +59,18 @@ This API exposes all functionality needed for key creation, signing and manageme - Returns on success: ```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "account":[ - { - "name":"monkey", - "address":"cosmosaccaddr1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "pub_key":"cosmosaccpub1zcjduc3q8s8ha96ry4xc5xvjp9tr9w9p0e5lk5y0rpjs5epsfxs4wmf72x3shvus0t" - }, - { - "name":"test", - "address":"cosmosaccaddr1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2", - "pub_key":"cosmosaccpub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak" - } - ], - "block_height":5241 - } -} +[ + { + "name":"monkey", + "address":"cosmosaccaddr1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", + "pub_key":"cosmosaccpub1zcjduc3q8s8ha96ry4xc5xvjp9tr9w9p0e5lk5y0rpjs5epsfxs4wmf72x3shvus0t" + }, + { + "name":"test", + "address":"cosmosaccaddr1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2", + "pub_key":"cosmosaccpub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak" + } +] ``` ### POST /keys @@ -93,9 +81,9 @@ This API exposes all functionality needed for key creation, signing and manageme ```json { - "name": "string", - "password": "string", - "seed": "string", + "name": "string", + "password": "string", + "seed": "string" } ``` @@ -103,12 +91,11 @@ Returns on success: ```json { - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "seed":"crime carpet recycle erase simple prepare moral dentist fee cause pitch trigger when velvet animal abandon" - } + "name":"test2", + "type":"local", + "address":"cosmosaccaddr17pjnvcae9pktplx0jz5r0vn8ugsqmfuzwsvzzj", + "pub_key":"cosmosaccpub1addwnpepq0gxp200rysljv9v645llj3y3d3t4vjrdcnl8dnk540fnmu25h6tgustsyt", + "seed":"muffin novel usual evoke camp canal decade asthma creek lend record media adapt fresh brisk govern plate debris come mother behave coil process next" } ``` @@ -120,14 +107,10 @@ Returns on success: ```json { - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "name":"test", - "address":"cosmosaccaddr1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2", - "pub_key":"cosmosaccpub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak" - } + "name": "test1", + "type": "local", + "address": "cosmosaccaddr1dla9p5dycmwndmqfehlymwvvjjtfkfw4he576q", + "pub_key": "cosmosaccpub1addwnpepqgdykrxehg3k9vttjref3dhndtnsy5r3k5f30kekv8elqdluj5ktgl86nwd" } ``` @@ -140,19 +123,14 @@ Returns on success: ```json { "old_password": "string", - "new_password": "string", + "new_password": "string" } ``` - Returns on success: -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result":{} -} +```string +success ``` ### DELETE /keys/{name} @@ -163,19 +141,14 @@ Returns on success: ```json { - "password": "string", + "password": "string" } ``` - Returns on success: -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{} -} +```string +success ``` ### POST /keys/{name}/recover @@ -187,7 +160,7 @@ Returns on success: ```json { "password": "string", - "seed": "string", + "seed": "string" } ``` @@ -195,12 +168,10 @@ Returns on success: ```json { - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "address":"BD607C37147656A507A5A521AA9446EB72B2C907" - } + "name":"test2", + "type":"local", + "address":"cosmosaccaddr17pjnvcae9pktplx0jz5r0vn8ugsqmfuzwsvzzj", + "pub_key":"cosmosaccpub1addwnpepq0gxp200rysljv9v645llj3y3d3t4vjrdcnl8dnk540fnmu25h6tgustsyt" } ``` @@ -212,21 +183,27 @@ Returns on success: ```json { - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "address": "82A57F8575BDFA22F5164C75361A21D2B0E11089", - "public_key": "PubKeyEd25519{A0EEEED3C9CE1A6988DEBFE347635834A1C0EBA0B4BB1125896A7072D22E650D}", + "type": "auth/Account", + "value": { + "address": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", "coins": [ - {"atom": 300}, - {"photon": 15} + { + "denom": "monikerToken", + "amount": "990" + }, + { + "denom": "steak", + "amount": "49" + } ], - "account_number": 1, - "sequence": 7 + "public_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "Aje2CWOpo0mcrfZy0Q+zSabeHjvT7oEuXuKljLU9agE/" + }, + "account_number": "0", + "sequence": "1" } } -} ``` ## ICS20 - TokenAPI @@ -240,38 +217,42 @@ The TokenAPI exposes all functionality needed to query account balances and send - Returns on success: ```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result": { - "atom":1000, - "photon":500, - "ether":20 +[ + { + "denom":"monikerToken", + "amount":"990" + }, + { + "denom":"steak", + "amount":"49" } -} +] ``` ### POST /bank/transfers - **URL**: `/bank/transfers` - **Functionality**: Create a transfer in the bank module. -- POST Body: +- POST Body, generate is false: ```json { + "account_number": "0", "amount": [ { - "denom": "string", - "amount": 64, + "amount": "10", + "denom": "monikerToken" } ], - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64, + "chain_id": "test-chain-KfHYRV", + "ensure_account_sequence": false, + "fee": "", + "gas": "10000", + "generate": false, + "name": "moniker", + "password": "12345678", + "sequence": "1", + "to_address": "cosmosaccaddr17pjnvcae9pktplx0jz5r0vn8ugsqmfuzwsvzzj" } ``` @@ -279,12 +260,97 @@ The TokenAPI exposes all functionality needed to query account balances and send ```json { - "rest api":"2.0", - "code":200, - "error":"", - "result":{ - "transaction":"TODO:" - } + "check_tx": { + "log": "Msg 0: ", + "gasWanted": "10000", + "gasUsed": "1242" + }, + "deliver_tx": { + "log": "Msg 0: ", + "gasWanted": "10000", + "gasUsed": "3288", + "tags": [ + { + "key": "c2VuZGVy", + "value": "Y29zbW9zYWNjYWRkcjF6dmw4cDJ3NnY5MGswZ2VlcnFzbnozZTZqeGEycnpnM3d2dXBtbg==" + }, + { + "key": "cmVjaXBpZW50", + "value": "Y29zbW9zYWNjYWRkcjE3cGpudmNhZTlwa3RwbHgwano1cjB2bjh1Z3NxbWZ1endzdnp6ag==" + } + ] + }, + "hash": "3474141FF827BDB85F39EF94D3B6B93E3D3C2A7A", + "height": "1574" +} +``` + +- POST Body, generate is true: + +```json +{ + "account_number": "0", + "amount": [ + { + "amount": "10", + "denom": "monikerToken" + } + ], + "chain_id": "test-chain-KfHYRV", + "ensure_account_sequence": false, + "fee": "1steak", + "gas": "10000", + "generate": true, + "name": "moniker", + "password": "12345678", + "sequence": "2", + "to_address": "cosmosaccaddr17pjnvcae9pktplx0jz5r0vn8ugsqmfuzwsvzzj" +} +``` + +- Returns on success: + +```json +{ + "account_number": "0", + "chain_id": "test-chain-KfHYRV", + "fee": { + "amount": [ + { + "amount": "1", + "denom": "steak" + } + ], + "gas": "10000" + }, + "memo": "", + "msgs": [ + { + "inputs": [ + { + "address": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "coins": [ + { + "amount": "10", + "denom": "monikerToken" + } + ] + } + ], + "outputs": [ + { + "address": "cosmosaccaddr17pjnvcae9pktplx0jz5r0vn8ugsqmfuzwsvzzj", + "coins": [ + { + "amount": "10", + "denom": "monikerToken" + } + ] + } + ] + } + ], + "sequence": "2" } ``` @@ -300,14 +366,16 @@ The StakingAPI exposes all functionality needed for validation and delegation in ```json { - "rest api":"2.1", - "code":200, - "error":"", - "result": { - "atom":1000, - "photon":500, - "ether":20 - } + "delegations": [ + { + "delegator_addr": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "validator_addr": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "shares": "100.0000000000", + "height": "0" + } + ], + "unbonding_delegations": null, + "redelegations": null } ``` @@ -318,12 +386,30 @@ The StakingAPI exposes all functionality needed for validation and delegation in - Returns on success: ```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{} -} +[ + { + "operator": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "pub_key": "cosmosvalpub1zcjduepq6lql7kxcsrss2wmaxmsv5afkprdqshhupafqc7h37h0wmlhvkn5qrcky3x", + "jailed": false, + "status": 2, + "tokens": "1000000000000", + "delegator_shares": "1000000000000", + "description": { + "moniker": "moniker", + "identity": "", + "website": "", + "details": "" + }, + "bond_height": "0", + "bond_intra_tx_counter": 0, + "proposer_reward_pool": null, + "commission": "0", + "commission_max": "0", + "commission_change_rate": "0", + "commission_change_today": "0", + "prev_bonded_shares": "0" + } +] ``` ### GET /stake/delegators/{delegatorAddr}/validators/{validatorAddr} @@ -334,10 +420,26 @@ The StakingAPI exposes all functionality needed for validation and delegation in ```json { - "rest api":"2.1", - "code":200, - "error":"", - "result":{} + "operator": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "pub_key": "cosmosvalpub1zcjduepq6lql7kxcsrss2wmaxmsv5afkprdqshhupafqc7h37h0wmlhvkn5qrcky3x", + "jailed": false, + "status": 2, + "tokens": "1000000000000", + "delegator_shares": "1000000000000", + "description": { + "moniker": "moniker", + "identity": "", + "website": "", + "details": "" + }, + "bond_height": "0", + "bond_intra_tx_counter": 0, + "proposer_reward_pool": null, + "commission": "0", + "commission_max": "0", + "commission_change_rate": "0", + "commission_change_today": "0", + "prev_bonded_shares": "0" } ``` @@ -348,14 +450,29 @@ The StakingAPI exposes all functionality needed for validation and delegation in - Returns on success: ```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} +[ + { + "hash": "string", + "height": 0, + "result": { + "code": 0, + "data": "string", + "gas_used": 0, + "gas_wanted": 0, + "info": "string", + "log": "string", + "tags": [ + [ + { + "key": "string", + "value": 0 + } + ] + ] + }, + "tx": "string" + } +] ``` ### POST /stake/delegators/{delegatorAddr}/delegations @@ -387,13 +504,13 @@ The StakingAPI exposes all functionality needed for validation and delegation in { "delegator_addr": "string", "validator_addr": "string", - "shares": "string", + "shares": "string" } ], "complete_unbondings": [ { "delegator_addr": "string", - "validator_addr": "string", + "validator_addr": "string" } ], "begin_redelegates": [ @@ -401,14 +518,14 @@ The StakingAPI exposes all functionality needed for validation and delegation in "delegator_addr": "string", "validator_src_addr": "string", "validator_dst_addr": "string", - "shares": "string", + "shares": "string" } ], "complete_redelegates": [ { "delegator_addr": "string", "validator_src_addr": "string", - "validator_dst_addr": "string", + "validator_dst_addr": "string" } ] } @@ -419,12 +536,28 @@ The StakingAPI exposes all functionality needed for validation and delegation in ```json { - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } + "check_tx": { + "log": "Msg 0: ", + "gasWanted": "10000", + "gasUsed": "1242" + }, + "deliver_tx": { + "log": "Msg 0: ", + "gasWanted": "10000", + "gasUsed": "3288", + "tags": [ + { + "key": "c2VuZGVy", + "value": "Y29zbW9zYWNjYWRkcjF6dmw4cDJ3NnY5MGswZ2VlcnFzbnozZTZqeGEycnpnM3d2dXBtbg==" + }, + { + "key": "cmVjaXBpZW50", + "value": "Y29zbW9zYWNjYWRkcjE3cGpudmNhZTlwa3RwbHgwano1cjB2bjh1Z3NxbWZ1endzdnp6ag==" + } + ] + }, + "hash": "3474141FF827BDB85F39EF94D3B6B93E3D3C2A7A", + "height": "1574" } ``` @@ -436,12 +569,10 @@ The StakingAPI exposes all functionality needed for validation and delegation in ```json { - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } + "delegator_addr": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "validator_addr": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "shares": "100.0000000000", + "height": "0" } ``` @@ -452,14 +583,22 @@ The StakingAPI exposes all functionality needed for validation and delegation in - Returns on success: ```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} +[ + { + "balance": { + "amount": "string", + "denom": "string" + }, + "creation_height": 0, + "delegator_addr": "string", + "initial_balance": { + "amount": "string", + "denom": "string" + }, + "min_time": 0, + "validator_addr": "string" + } +] ``` ### GET /stake/validators @@ -469,14 +608,30 @@ The StakingAPI exposes all functionality needed for validation and delegation in - Returns on success: ```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" +[ + { + "operator": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "pub_key": "cosmosvalpub1zcjduepq6lql7kxcsrss2wmaxmsv5afkprdqshhupafqc7h37h0wmlhvkn5qrcky3x", + "jailed": false, + "status": 2, + "tokens": "1000000000000", + "delegator_shares": "1000000000000", + "description": { + "moniker": "moniker", + "identity": "", + "website": "", + "details": "" + }, + "bond_height": "0", + "bond_intra_tx_counter": 0, + "proposer_reward_pool": null, + "commission": "0", + "commission_max": "0", + "commission_change_rate": "0", + "commission_change_today": "0", + "prev_bonded_shares": "0" } -} +] ``` ### GET /stake/validators/{validatorAddr} @@ -487,12 +642,26 @@ The StakingAPI exposes all functionality needed for validation and delegation in ```json { - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } + "operator": "cosmosaccaddr1zvl8p2w6v90k0geerqsnz3e6jxa2rzg3wvupmn", + "pub_key": "cosmosvalpub1zcjduepq6lql7kxcsrss2wmaxmsv5afkprdqshhupafqc7h37h0wmlhvkn5qrcky3x", + "jailed": false, + "status": 2, + "tokens": "1000000000000", + "delegator_shares": "1000000000000", + "description": { + "moniker": "moniker", + "identity": "", + "website": "", + "details": "" + }, + "bond_height": "0", + "bond_intra_tx_counter": 0, + "proposer_reward_pool": null, + "commission": "0", + "commission_max": "0", + "commission_change_rate": "0", + "commission_change_today": "0", + "prev_bonded_shares": "0" } ``` @@ -504,18 +673,13 @@ The StakingAPI exposes all functionality needed for validation and delegation in ```json { - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "inflation_rate_change": 1300000000, - "inflation_max": 2000000000, - "inflation_min": 700000000, - "goal_bonded": 6700000000, - "unbonding_time": "72h0m0s", - "max_validators": 100, - "bond_denom": "atom" - } + "inflation_rate_change": "1300000000", + "inflation_max": "2000000000", + "inflation_min": "700000000", + "goal_bonded": "6700000000", + "unbonding_time": "259200000000000", + "max_validators": 100, + "bond_denom": "steak" } ``` @@ -527,17 +691,12 @@ The StakingAPI exposes all functionality needed for validation and delegation in ```json { - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "loose_tokens": 0, - "bonded_tokens": 0, - "inflation_last_time": "1970-01-01 01:00:00 +0100 CET", - "inflation": 700000000, - "date_last_commission_reset": 0, - "prev_bonded_shares": 0, - } + "loose_tokens": "500035934654", + "bonded_tokens": "1000000000000", + "inflation_last_time": "2018-08-28T06:40:39.617950067Z", + "inflation": "700002217", + "date_last_commission_reset": "0", + "prev_bonded_shares": "0" } ``` @@ -570,7 +729,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te - **Functionality**: Submit a proposal - POST Body: -```js +```json { "base_req": { // Name of key to use @@ -593,7 +752,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "initial_deposit": [ { "denom": "string", - "amount": 64, + "amount": 64 } ] } @@ -607,7 +766,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "code":200, "error":"", "result":{ - "TODO": "TODO", + "TODO": "TODO" } } ``` @@ -659,7 +818,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "gas": 0 }, "depositer": "string", - "amount": 0, + "amount": 0 } ``` @@ -671,7 +830,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "code":200, "error":"", "result":{ - "TODO": "TODO", + "TODO": "TODO" } } ``` @@ -738,7 +897,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "proposal-id": 1, "voter": "cosmosaccaddr1849m9wncrqp6v4tkss6a3j8uzvuv0cp7f75lrq", "option": "yes" - }, + } ] } ``` @@ -751,7 +910,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te - **Functionality**: Vote for a specific proposal - POST Body: -```js +```json { "base_req": { "name": "string", @@ -764,7 +923,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te // A cosmosaccaddr address "voter": "string", // Value of the vote option `Yes`, `No` `Abstain`, `NoWithVeto` - "option": "string", + "option": "string" } ``` @@ -776,7 +935,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te "code":200, "error":"", "result":{ - "TODO": "TODO", + "TODO": "TODO" } } ``` @@ -827,7 +986,7 @@ The SlashingAPI exposes all functionalities needed to slash (*i.e* penalize) val - **Functionality**: Submit a message to unjail a validator after it has been penalized. - POST Body: -```js +```json { // Name of key to use "name": "string", @@ -836,7 +995,7 @@ The SlashingAPI exposes all functionalities needed to slash (*i.e* penalize) val "chain_id": "string", "account_number": 64, "sequence": 64, - "gas": 64, + "gas": 64 } ``` diff --git a/docs/light/getting_started.md b/docs/light/getting_started.md index f032adafdd7a..a07c62f26d13 100644 --- a/docs/light/getting_started.md +++ b/docs/light/getting_started.md @@ -10,7 +10,7 @@ To start a rest server, we need to specify the following parameters: | laddr | string | "tcp://localhost:1317" | false | Address for server to listen on | | trust-node | bool | false | false | Trust full nodes or not | | swagger-host-ip | string | "localhost" | false | The host IP of the Gaia-lite server, swagger-ui will send request to this host | -| modules | string | "general,key,token" | false | Enabled modules | +| modules | string | "general,key,bank" | false | Enabled modules | Sample command to start gaia-lite node: ``` @@ -31,8 +31,7 @@ The gaia-lite node can connect to multiple full nodes. Then gaia-lite will do lo ``` gaiacli lite-server --chain-id= --node-list=tcp://10.10.10.10:26657,tcp://20.20.20.20:26657 ``` - -The gaia-lite support modular rest APIs. Now it supports four modules: general, key, token and stake. If you need all of them, just start it with this command: - ``` - gaiacli lite-server --chain-id= --modules=general,key,token,stake - ``` \ No newline at end of file +Gaia-lite is built in a modular format. Each Cosmos module defines it's own RPC API. Currently the following modules are supported: general, transaction, key, bank, staking, governance, and slashing. +``` +gaiacli lite-server --chain-id= --modules=general,key,bank,staking +``` \ No newline at end of file diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 88e1ba1fbe6a..714b89bf8a13 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -18,7 +18,7 @@ import ( // register REST routes func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, storeName string) { r.HandleFunc( - "/bank/balance/{address}", + "/auth/accounts/{address}", QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx), ).Methods("GET") } @@ -74,7 +74,7 @@ func QueryAccountRequestHandlerFn( // 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("bank/balance/:address",queryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) + routerGroup.GET("/auth/accounts/:address",queryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) } func queryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { diff --git a/x/bank/client/rest/query.go b/x/bank/client/rest/query.go new file mode 100644 index 000000000000..640a09326e6e --- /dev/null +++ b/x/bank/client/rest/query.go @@ -0,0 +1,61 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/client/httputils" +) + +// registerSwaggerQueryRoutes - Central function to define account query related routes that get registered by the main application +func registerSwaggerQueryRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, storeName string) { + routerGroup.GET("/bank/balances/:account",queryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) +} + +func queryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CLIContext) gin.HandlerFunc { + return func(gtx *gin.Context) { + + bech32addr := gtx.Param("account") + + addr, err := sdk.AccAddressFromBech32(bech32addr) + if err != nil { + httputils.NewError(gtx, http.StatusConflict, err) + return + } + + res, err := ctx.QueryStore(auth.AddressStoreKey(addr), storeName) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't query account. Error: %s", err.Error())) + return + } + + // the query will return empty if there is no data for this account + if len(res) == 0 { + httputils.NewError(gtx, http.StatusNoContent, fmt.Errorf("this account info is nil+")) + return + } + + // decode the value + account, err := decoder(res) + if err != nil { + httputils.NewError(gtx, http.StatusInternalServerError, fmt.Errorf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) + return + } + + // print out whole account + output, err := cdc.MarshalJSON(account.GetCoins()) + 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/rest.go b/x/bank/client/rest/rest.go new file mode 100644 index 000000000000..260b28f68ecf --- /dev/null +++ b/x/bank/client/rest/rest.go @@ -0,0 +1,15 @@ +package rest + + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/gin-gonic/gin" + "github.com/cosmos/cosmos-sdk/crypto/keys" +) + +// RegisterRoutes registers staking-related REST handlers to a router +func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { + registerSwaggerQueryRoutes(routerGroup, ctx, cdc, "acc") + registerSwaggerTxRoutes(routerGroup, ctx, cdc, kb) +} \ No newline at end of file diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index e8f1e64e9dd1..5c01acfdc1fc 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -38,7 +38,7 @@ type transferBody struct { Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` Fee string `json:"fee"` - Signed bool `json:"signed"` + Generate bool `json:"generate"` EnsureAccAndSeq bool `json:"ensure_account_sequence"` } @@ -83,7 +83,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo return } - if !transferBody.Signed { + if transferBody.Generate { w.Write(txForSign.Bytes()) return } @@ -98,8 +98,8 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo } } -// RegisterSwaggerRoutes - Central function to define routes that get registered by the main application -func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { +// registerSwaggerTxRoutes - Central function to define routes that get registered by the main application +func registerSwaggerTxRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { routerGroup.POST("/bank/transfers", transferRequestFn(cdc, ctx, kb)) } @@ -133,7 +133,7 @@ func transferRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) return } - if !transferBody.Signed { + if transferBody.Generate { httputils.NormalResponse(gtx, txForSign.Bytes()) return } @@ -150,7 +150,7 @@ func transferRequestFn(cdc *wire.Codec, ctx context.CLIContext, kb keys.Keybase) // paramPreprocess performs transferBody preprocess func paramPreprocess(body transferBody, kb keys.Keybase) (transferBody, int, error) { if body.Name == "" { - if body.Signed { + if !body.Generate { return transferBody{}, http.StatusBadRequest, fmt.Errorf("missing key name, can't sign transaction") } if body.FromAddress == "" { From 9427eb5d0fc2f4d1db02482af82d59fcaf9402bc Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Tue, 28 Aug 2018 15:56:47 +0800 Subject: [PATCH 21/25] Fix failures in test_cover and test_lint --- client/keys/root.go | 2 +- client/lcd/docs/docs.go | 30 +++++++++++++++--------------- client/lcd/lcd_test.go | 6 +++--- client/lcd/test_helpers.go | 2 +- client/tx/broadcast.go | 18 ++++++++++-------- client/tx/root.go | 2 +- x/auth/client/rest/query.go | 2 +- x/bank/client/rest/rest.go | 2 +- x/stake/client/rest/rest.go | 2 +- 9 files changed, 34 insertions(+), 32 deletions(-) diff --git a/client/keys/root.go b/client/keys/root.go index e6ad00cf0207..b79f23d17e6f 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") } -// RegisterSwaggerRoutes - Central function to define key management related routes that get registered by the main application +// RegisterSwaggerRoutes registers key management related routes to Gaia-lite server func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/keys", QueryKeysRequest) routerGroup.POST("/keys", AddNewKeyRequest) diff --git a/client/lcd/docs/docs.go b/client/lcd/docs/docs.go index 3d9ad954bc9c..490968c9df9f 100644 --- a/client/lcd/docs/docs.go +++ b/client/lcd/docs/docs.go @@ -46,7 +46,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Get all delegations from a delegator", "parameters": [ @@ -99,7 +99,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Get all staking txs from a delegator", "parameters": [ @@ -152,7 +152,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Query all validators that a delegator is bonded to", "parameters": [ @@ -205,7 +205,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Query a validator that a delegator is bonded to", "parameters": [ @@ -264,7 +264,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Query a delegation between a delegator and a validator", "parameters": [ @@ -323,7 +323,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Query all unbonding_delegations between a delegator and a validator", "parameters": [ @@ -382,7 +382,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Get all validators", "responses": { @@ -427,7 +427,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Get a single validator info", "parameters": [ @@ -480,7 +480,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Query the staking pool information", "responses": { @@ -525,7 +525,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Query the staking params values", "responses": { @@ -570,7 +570,7 @@ var doc = `{ "application/json" ], "tags": [ - "Stake Operation" + "Staking" ], "summary": "Send stake related transaction", "parameters": [ @@ -633,7 +633,7 @@ var doc = `{ "application/json" ], "tags": [ - "Token Operation" + "Bank" ], "summary": "Query account information", "parameters": [ @@ -686,7 +686,7 @@ var doc = `{ "application/json" ], "tags": [ - "Token Operation" + "Bank" ], "summary": "Send coins to a address", "parameters": [ @@ -2076,8 +2076,8 @@ var tagToModuleDesc = ` { "General":"general", "Key Management":"key", - "Token Operation":"bank", - "Stake Operation":"staking", + "Bank":"bank", + "Staking":"staking", "Transaction":"transaction" } ` diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 12e6df085061..ad2bbce7f7be 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -344,7 +344,7 @@ func TestCoinSend(t *testing.T) { someFakeAddr := sdk.AccAddress(bz) // query empty - res, body := Request(t, port, "GET", fmt.Sprintf("/bank/balance/%s", someFakeAddr), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", someFakeAddr), nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) acc := getAccount(t, port, addr) @@ -386,7 +386,7 @@ func TestCoinSendSwaggerLCD(t *testing.T) { someFakeAddr := sdk.AccAddress(bz) // query empty - res, body := Request(t, port, "GET", fmt.Sprintf("/bank/balance/%s", someFakeAddr), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", someFakeAddr), nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) acc := getAccount(t, port, addr) @@ -974,7 +974,7 @@ func TestProposalsQuery(t *testing.T) { //_____________________________________________________________________________ // get the account to get the sequence func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { - res, body := Request(t, port, "GET", fmt.Sprintf("/bank/balance/%s", addr), nil) + res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var acc auth.Account err := cdc.UnmarshalJSON([]byte(body), &acc) diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index e711fbea9b01..06c93b0169fc 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -286,7 +286,7 @@ func InitializeTestSwaggerLCD(t *testing.T, nValidators int, initAddrs []sdk.Acc 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.FlagModules, "general,key,bank,staking") viper.Set(client.FlagTrustNode, false) node, err := startTM(config, logger, genDoc, privVal, app) diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 1aa2875349b2..cbed52d12660 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -13,9 +13,9 @@ import ( ) const ( - FlagSync = "sync" - FlagAsync = "async" - FlagBlock = "block" + flagSync = "sync" + flagAsync = "async" + flagBlock = "block" ) // Tx Broadcast Body type BroadcastTxBody struct { @@ -46,15 +46,17 @@ func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } } -type TxBody struct { +// BroadcastBody contains the data of tx and specify how to broadcast tx +type BroadcastBody struct { Transaction string `json:"transaction"` Return string `json:"return"` } // BroadcastTxRequest REST Handler +// nolint: gocyclo func BroadcastTxRequest(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc { return func(gtx *gin.Context) { - var txBody TxBody + var txBody BroadcastBody body, err := ioutil.ReadAll(gtx.Request.Body) if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) @@ -67,7 +69,7 @@ func BroadcastTxRequest(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc } var output []byte switch txBody.Return { - case FlagBlock: + case flagBlock: res, err := ctx.BroadcastTx([]byte(txBody.Transaction)) if err != nil { httputils.NewError(gtx, http.StatusInternalServerError, err) @@ -78,7 +80,7 @@ func BroadcastTxRequest(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc httputils.NewError(gtx, http.StatusInternalServerError, err) return } - case FlagSync: + case flagSync: res, err := ctx.BroadcastTxSync([]byte(txBody.Transaction)) if err != nil { httputils.NewError(gtx, http.StatusInternalServerError, err) @@ -89,7 +91,7 @@ func BroadcastTxRequest(cdc *wire.Codec, ctx context.CLIContext) gin.HandlerFunc httputils.NewError(gtx, http.StatusInternalServerError, err) return } - case FlagAsync: + case flagAsync: res, err := ctx.BroadcastTxAsync([]byte(txBody.Transaction)) if err != nil { httputils.NewError(gtx, http.StatusInternalServerError, err) diff --git a/client/tx/root.go b/client/tx/root.go index dad5603ae30a..17341f830599 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -25,7 +25,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) { // r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") } - +// RegisterSwaggerRoutes registers transaction related REST routes to Gaia-lite server func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec) { routerGroup.POST("/txs", BroadcastTxRequest(cdc, ctx)) } \ No newline at end of file diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 714b89bf8a13..e5f2bba6c2b7 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -72,7 +72,7 @@ func QueryAccountRequestHandlerFn( } } -// RegisterSwaggerRoutes - Central function to define account query related routes that get registered by the main application +// RegisterSwaggerRoutes registers account query related routes to Gaia-lite server func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, storeName string) { routerGroup.GET("/auth/accounts/:address",queryAccountRequestHandler(storeName,cdc,authcmd.GetAccountDecoder(cdc),ctx)) } diff --git a/x/bank/client/rest/rest.go b/x/bank/client/rest/rest.go index 260b28f68ecf..ee1866f91e6f 100644 --- a/x/bank/client/rest/rest.go +++ b/x/bank/client/rest/rest.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys" ) -// RegisterRoutes registers staking-related REST handlers to a router +// RegisterRoutes registers bank-related REST handlers to Gaia-lite server func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { registerSwaggerQueryRoutes(routerGroup, ctx, cdc, "acc") registerSwaggerTxRoutes(routerGroup, ctx, cdc, kb) diff --git a/x/stake/client/rest/rest.go b/x/stake/client/rest/rest.go index aa2cfce5935c..036685122413 100644 --- a/x/stake/client/rest/rest.go +++ b/x/stake/client/rest/rest.go @@ -15,7 +15,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, k registerTxRoutes(cliCtx, r, cdc, kb) } -// RegisterSwaggerRoutes - Central function to define stake related routes that get registered by the main application +// RegisterSwaggerRoutes registers staking related routes to Gaia-lite server func RegisterSwaggerRoutes(routerGroup *gin.RouterGroup, ctx context.CLIContext, cdc *wire.Codec, kb keys.Keybase) { registerSwaggerQueryRoutes(routerGroup, ctx, cdc) registerSwaggerTxRoutes(routerGroup, ctx, cdc, kb) From 81f294bd51802378130f53f0b9ffb912a4937b02 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Tue, 28 Aug 2018 16:34:32 +0800 Subject: [PATCH 22/25] Add key sign command and fix test_lint failure in key sign --- client/keys/root.go | 1 + client/keys/sign.go | 78 ++++++++++++++++++++++++--------------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/client/keys/root.go b/client/keys/root.go index b79f23d17e6f..a8a1b685ac41 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -23,6 +23,7 @@ func Commands() *cobra.Command { addKeyCommand(), listKeysCmd, showKeysCmd, + keySignCmd, client.LineBreak, deleteKeyCommand(), updateKeyCommand(), diff --git a/client/keys/sign.go b/client/keys/sign.go index 57a0511293db..ce311a41ae7e 100644 --- a/client/keys/sign.go +++ b/client/keys/sign.go @@ -5,52 +5,62 @@ import ( "io/ioutil" "github.com/cosmos/cosmos-sdk/client/httputils" "net/http" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "encoding/base64" + "fmt" ) -type SignBody struct { - Tx []byte `json:"tx_bytes"` - Password string `json:"password"` +const ( + flagFrom = "from" + flagPassword = "password" + flagTx = "tx" +) + +func init() { + keySignCmd.Flags().String(flagFrom, "", "Name of private key with which to sign") + keySignCmd.Flags().String(flagPassword, "", "Password of private key") + keySignCmd.Flags().String(flagTx, "", "Base64 encoded tx data for sign") } -/* -var showKeysCmd = &cobra.Command{ - Use: "sign ", - Short: "Show key info for the given name", - Long: `Return public details of one local key.`, - Args: cobra.ExactArgs(1), + +var keySignCmd = &cobra.Command{ + Use: "sign", + Short: "Sign user specified data", + Long: `Sign user data with specified key and password`, RunE: func(cmd *cobra.Command, args []string) error { - name := args[0] - info, err := getKey(name) + name := viper.GetString(flagFrom) + password := viper.GetString(flagPassword) + tx := viper.GetString(flagTx) + + decodedTx, err := base64.StdEncoding.DecodeString(tx) if err != nil { return err } - showAddress := viper.GetBool(FlagAddress) - showPublicKey := viper.GetBool(FlagPublicKey) - outputSet := cmd.Flag(cli.OutputFlag).Changed - if showAddress && showPublicKey { - return errors.New("cannot use both --address and --pubkey at once") - } - if outputSet && (showAddress || showPublicKey) { - return errors.New("cannot use --output with --address or --pubkey") - } - if showAddress { - printKeyAddress(info) - return nil - } - if showPublicKey { - printPubKey(info) - return nil + kb, err := GetKeyBase() + if err != nil { + return err } - printInfo(info) + sig, _, err := kb.Sign(name, password, decodedTx) + if err != nil { + return err + } + encoded := base64.StdEncoding.EncodeToString(sig) + fmt.Println(string(encoded)) return nil }, } -*/ + +type keySignBody struct { + Tx []byte `json:"tx_bytes"` + Password string `json:"password"` +} + // SignResuest is the handler of creating seed in swagger rest server func SignResuest(gtx *gin.Context) { name := gtx.Param("name") - var m SignBody + var m keySignBody body, err := ioutil.ReadAll(gtx.Request.Body) if err != nil { httputils.NewError(gtx, http.StatusBadRequest, err) @@ -73,11 +83,7 @@ func SignResuest(gtx *gin.Context) { return } - output, err := cdc.MarshalJSON(sig) - if err != nil { - httputils.NewError(gtx, http.StatusInternalServerError, err) - return - } + encoded := base64.StdEncoding.EncodeToString(sig) - httputils.NormalResponse(gtx, output) + httputils.NormalResponse(gtx, []byte(encoded)) } \ No newline at end of file From ff15759ce1eef78aa298279a5a527e5193a99022 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 29 Aug 2018 02:03:38 +0800 Subject: [PATCH 23/25] Fix failure in test_cover and test_lint --- client/lcd/lcd_test.go | 2 +- x/bank/client/rest/sendtx.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index fba2d6c75665..5941a4e1800a 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -1022,7 +1022,7 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc "sequence":"%d", "amount":[%s], "chain_id":"%s", - "generate": true, + "generate": false, "to_address":"%s" }`, gasStr, name, password, accnum, sequence, coinbz, chainID, receiveAddr)) res, body = Request(t, port, "POST", "/bank/transfers", jsonStr) diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index c1f56bd558d8..b821d58e1308 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -65,7 +65,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo } transferBody, errCode, err := paramPreprocess(transferBody, kb) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(&w, errCode, err.Error()) return } From 5b250cc776f71fb3211c20cd31b22546980d4177 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 29 Aug 2018 09:50:31 +0800 Subject: [PATCH 24/25] Refactor code according to code review feedback --- .../{loadbalancing.go => client_manager.go} | 14 +++++------ ...lancing_test.go => client_manager_test.go} | 12 ++++----- client/context/context.go | 4 +-- client/context/query.go | 25 +++++++++++-------- 4 files changed, 29 insertions(+), 26 deletions(-) rename client/context/{loadbalancing.go => client_manager.go} (82%) rename client/context/{loadbalancing_test.go => client_manager_test.go} (58%) diff --git a/client/context/loadbalancing.go b/client/context/client_manager.go similarity index 82% rename from client/context/loadbalancing.go rename to client/context/client_manager.go index 664ca3385747..8fffb9d65185 100644 --- a/client/context/loadbalancing.go +++ b/client/context/client_manager.go @@ -1,22 +1,22 @@ package context import ( + "github.com/pkg/errors" rpcclient "github.com/tendermint/tendermint/rpc/client" "strings" "sync" - "github.com/pkg/errors" ) // 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 + clients []rpcclient.Client currentIndex int - mutex sync.RWMutex + mutex sync.Mutex } // NewClientManager create a new ClientManager -func NewClientManager(nodeURIs string) (*ClientManager,error) { +func NewClientManager(nodeURIs string) (*ClientManager, error) { if nodeURIs != "" { nodeURLArray := strings.Split(nodeURIs, ",") var clients []rpcclient.Client @@ -26,7 +26,7 @@ func NewClientManager(nodeURIs string) (*ClientManager,error) { } mgr := &ClientManager{ currentIndex: 0, - clients: clients, + clients: clients, } return mgr, nil } @@ -39,8 +39,8 @@ func (mgr *ClientManager) getClient() rpcclient.Client { client := mgr.clients[mgr.currentIndex] mgr.currentIndex++ - if mgr.currentIndex >= len(mgr.clients){ + if mgr.currentIndex >= len(mgr.clients) { mgr.currentIndex = 0 } return client -} \ No newline at end of file +} diff --git a/client/context/loadbalancing_test.go b/client/context/client_manager_test.go similarity index 58% rename from client/context/loadbalancing_test.go rename to client/context/client_manager_test.go index 893048854850..09b8542dcf85 100644 --- a/client/context/loadbalancing_test.go +++ b/client/context/client_manager_test.go @@ -1,16 +1,16 @@ package context import ( - "testing" "github.com/stretchr/testify/assert" + "testing" ) func TestLoadBalancing(t *testing.T) { nodeURIs := "10.10.10.10:26657,20.20.20.20:26657,30.30.30.30:26657" - clientMgr,err := NewClientManager(nodeURIs) - assert.Empty(t,err) + clientMgr, err := NewClientManager(nodeURIs) + assert.Empty(t, err) endpoint := clientMgr.getClient() - assert.NotEqual(t,endpoint,clientMgr.getClient()) + assert.NotEqual(t, endpoint, clientMgr.getClient()) clientMgr.getClient() - assert.Equal(t,endpoint,clientMgr.getClient()) -} \ No newline at end of file + assert.Equal(t, endpoint, clientMgr.getClient()) +} diff --git a/client/context/context.go b/client/context/context.go index 39a338979257..b57e336657c2 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/viper" rpcclient "github.com/tendermint/tendermint/rpc/client" - tendermintLite"github.com/tendermint/tendermint/lite" + tendermintLite "github.com/tendermint/tendermint/lite" ) const ctxAccStoreName = "acc" @@ -131,4 +131,4 @@ func (ctx CLIContext) WithCertifier(certifier tendermintLite.Certifier) CLIConte func (ctx CLIContext) WithClientManager(clientManager *ClientManager) CLIContext { ctx.ClientManager = clientManager return ctx -} \ No newline at end of file +} diff --git a/client/context/query.go b/client/context/query.go index 56baa2ae9d9f..cf8862a765bf 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -305,12 +305,8 @@ func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error { return nil } -// proofVerify perform response proof verification -func (ctx CLIContext) proofVerify(path string, resp abci.ResponseQuery) error { - // Data from trusted node or subspace query doesn't need verification - if ctx.TrustNode || !isQueryStoreWithProof(path) { - return nil - } +// verifyProof perform response proof verification +func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { // TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node if ctx.Certifier == nil { @@ -338,7 +334,8 @@ func (ctx CLIContext) proofVerify(path string, resp abci.ResponseQuery) error { } // Validate the substore commit hash against trusted appHash - substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, multiStoreProof.CommitIDList, commit.Header.AppHash) + substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, + multiStoreProof.CommitIDList, commit.Header.AppHash) if err != nil { return errors.Wrap(err, "failed in verifying the proof against appHash") } @@ -368,11 +365,16 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro } resp := result.Response - if resp.Code != uint32(0) { + if resp.IsOK() { return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) } - err = ctx.proofVerify(path, resp) + // Data from trusted node or subspace query doesn't need verification + if ctx.TrustNode || !isQueryStoreWithProof(path) { + return resp.Value, nil + } + + err = ctx.verifyProof(path, resp) if err != nil { return nil, err } @@ -398,9 +400,10 @@ func isQueryStoreWithProof(path string) (bool) { if len(paths) != 3 { return false } - // WARNING This should be consistent with query method in iavlstore.go + // Currently, only when query path[2] is store or key, will proof be included in response. + // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go if paths[2] == "store" || paths[2] == "key" { return true } return false -} \ No newline at end of file +} From 135473ce17e0f8ddb418f43b12c4f284cf47374e Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Wed, 29 Aug 2018 09:57:59 +0800 Subject: [PATCH 25/25] Fix a mistake in previous commit --- client/context/client_manager_test.go | 2 +- client/context/query.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/context/client_manager_test.go b/client/context/client_manager_test.go index 09b8542dcf85..1960f74ced25 100644 --- a/client/context/client_manager_test.go +++ b/client/context/client_manager_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestLoadBalancing(t *testing.T) { +func TestClientManager(t *testing.T) { nodeURIs := "10.10.10.10:26657,20.20.20.20:26657,30.30.30.30:26657" clientMgr, err := NewClientManager(nodeURIs) assert.Empty(t, err) diff --git a/client/context/query.go b/client/context/query.go index cf8862a765bf..0cc845306fff 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -365,7 +365,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro } resp := result.Response - if resp.IsOK() { + if !resp.IsOK() { return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) }