diff --git a/.circleci/config.yml b/.circleci/config.yml index c194aa84f3..d866bd637e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -178,7 +178,7 @@ jobs: name: Test multi-seed Gaia simulation command: | export PATH="$GOBIN:$PATH" - make test_sim_gaia_multi_seed + scripts/multisim.sh 25 TestFullGaiaSimulation test_cover: <<: *defaults diff --git a/PENDING.md b/PENDING.md index 3c56e92344..64961a634a 100644 --- a/PENDING.md +++ b/PENDING.md @@ -3,7 +3,8 @@ BREAKING CHANGES * Gaia REST API (`gaiacli advanced rest-server`) - * [\#3056](https://github.com/cosmos/cosmos-sdk/pull/3056) `generate_only` and `simulate` have moved from query arguments to POST requests body. + * [gaia-lite] [\#2191](https://github.com/cosmos/cosmos-sdk/issues/2191) Split `POST /stake/delegators/{delegatorAddr}/delegations` into `POST /stake/delegators/{delegatorAddr}/delegations`, `POST /stake/delegators/{delegatorAddr}/unbonding_delegations` and `POST /stake/delegators/{delegatorAddr}/redelegations` + * [gaia-lite] [\#3056](https://github.com/cosmos/cosmos-sdk/pull/3056) `generate_only` and `simulate` have moved from query arguments to POST requests body. * Gaia CLI (`gaiacli`) @@ -34,6 +35,8 @@ IMPROVEMENTS * Gaia CLI (`gaiacli`) * Gaia + * [\#3021](https://github.com/cosmos/cosmos-sdk/pull/3021) Add `--gentx-dir` to `gaiad collect-gentxs` to specify a directory from which collect and load gentxs. + Add `--output-document` to `gaiad init` to allow one to redirect output to file. * SDK diff --git a/client/context/context.go b/client/context/context.go index df30374de9..9fac7c27a8 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -47,7 +47,7 @@ type CLIContext struct { JSON bool PrintResponse bool Verifier tmlite.Verifier - DryRun bool + Simulate bool GenerateOnly bool fromAddress types.AccAddress fromName string @@ -85,7 +85,7 @@ func NewCLIContext() CLIContext { JSON: viper.GetBool(client.FlagJson), PrintResponse: viper.GetBool(client.FlagPrintResponse), Verifier: verifier, - DryRun: viper.GetBool(client.FlagDryRun), + Simulate: viper.GetBool(client.FlagDryRun), GenerateOnly: viper.GetBool(client.FlagGenerateOnly), fromAddress: fromAddress, fromName: fromName, @@ -244,3 +244,15 @@ func (ctx CLIContext) WithVerifier(verifier tmlite.Verifier) CLIContext { ctx.Verifier = verifier return ctx } + +// WithGenerateOnly returns a copy of the context with updated GenerateOnly value +func (ctx CLIContext) WithGenerateOnly(generateOnly bool) CLIContext { + ctx.GenerateOnly = generateOnly + return ctx +} + +// WithSimulation returns a copy of the context with updated Simulate value +func (ctx CLIContext) WithSimulation(simulate bool) CLIContext { + ctx.Simulate = simulate + return ctx +} diff --git a/client/flags.go b/client/flags.go index 6a3ccff060..cf7be359e4 100644 --- a/client/flags.go +++ b/client/flags.go @@ -43,6 +43,7 @@ const ( FlagSSLHosts = "ssl-hosts" FlagSSLCertFile = "ssl-certfile" FlagSSLKeyFile = "ssl-keyfile" + FlagOutputDocument = "output-document" // inspired by wget -O ) // LineBreak can be included in a command list to provide a blank line diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 0c88d45c68..8d7c2c93b7 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -814,4 +814,4 @@ func TestProposalsQuery(t *testing.T) { require.Len(t, votes, 2) require.True(t, addrs[0].String() == votes[0].Voter.String() || addrs[0].String() == votes[1].Voter.String()) require.True(t, addrs[1].String() == votes[0].Voter.String() || addrs[1].String() == votes[1].Voter.String()) -} +} \ No newline at end of file diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 0ecbe505fe..a76bdddd68 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -625,71 +625,6 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string - post: - summary: Submit delegation - parameters: - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - "$ref": "#/definitions/BaseReq" - delegations: - type: array - items: - type: object - properties: - delegator_addr: - $ref: "#/definitions/Address" - validator_addr: - $ref: "#/definitions/ValidatorAddress" - delegation: - $ref: "#/definitions/Coin" - begin_unbondings: - type: array - items: - type: object - properties: - delegator_addr: - $ref: "#/definitions/Address" - validator_addr: - $ref: "#/definitions/ValidatorAddress" - shares: - type: string - example: "100" - begin_redelegates: - type: array - items: - type: object - properties: - delegator_addr: - $ref: "#/definitions/Address" - validator_src_addr: - $ref: "#/definitions/ValidatorAddress" - validator_dst_addr: - $ref: "#/definitions/ValidatorAddress" - shares: - type: string - example: "100" - tags: - - ICS21 - consumes: - - application/json - produces: - - application/json - responses: - 200: - description: OK - schema: - $ref: "#/definitions/BroadcastTxCommitResult" - 400: - description: Invalid delegator address or delegation body - 401: - description: Key password is wrong - 500: - description: Internal Server Error get: summary: Get all delegations from a delegator tags: @@ -702,12 +637,72 @@ paths: schema: type: array items: - type: object - "$ref": "#/definitions/Delegation" + $ref: "#/definitions/Delegation" 400: description: Invalid delegator address 500: description: Internal Server Error + post: + summary: Submit delegation + parameters: + - in: body + name: delegation + description: The password of the account to remove from the KMS + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + delegator_addr: + $ref: "#/definitions/Address" + validator_addr: + $ref: "#/definitions/ValidatorAddress" + delegation: + $ref: "#/definitions/Coin" + tags: + - ICS21 + consumes: + - application/json + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid delegator address or delegation request body + 401: + description: Key password is wrong + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/delegations/{validatorAddr}: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Query the current delegation between a delegator and a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/Delegation" + 400: + description: Invalid delegator address or validator address + 500: + description: Internal Server Error /stake/delegators/{delegatorAddr}/unbonding_delegations: parameters: - in: path @@ -727,12 +722,85 @@ paths: schema: type: array items: - type: object - "$ref": "#/definitions/UnbondingDelegation" + $ref: "#/definitions/UnbondingDelegation" 400: description: Invalid delegator address 500: description: Internal Server Error + post: + summary: Submit an unbonding delegation + parameters: + - in: query + name: simulate + description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it + required: false + type: boolean + - in: query + name: generate_only + description: if true, build an unsigned transaction and write it back + required: false + type: boolean + - in: body + name: delegation + description: The password of the account to remove from the KMS + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + delegator_addr: + $ref: "#/definitions/Address" + validator_addr: + $ref: "#/definitions/ValidatorAddress" + shares: + type: string + example: "100" + tags: + - ICS21 + consumes: + - application/json + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid delegator address or unbonding delegation request body + 401: + description: Key password is wrong + 500: + description: Internal Server Error + /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: + parameters: + - in: path + name: delegatorAddr + description: Bech32 AccAddress of Delegator + required: true + type: string + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Query all unbonding delegations between a delegator and a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/UnbondingDelegation" + 400: + description: Invalid delegator address or validator address + 500: + description: Internal Server Error /stake/delegators/{delegatorAddr}/redelegations: parameters: - in: path @@ -752,12 +820,58 @@ paths: schema: type: array items: - type: object - "$ref": "#/definitions/Redelegation" + $ref: "#/definitions/Redelegation" 400: description: Invalid delegator address 500: description: Internal Server Error + post: + summary: Submit a redelegation + parameters: + - in: query + name: simulate + description: if true, ignore the gas field and perform a simulation of a transaction, but don't broadcast it + required: false + type: boolean + - in: query + name: generate_only + description: if true, build an unsigned transaction and write it back + required: false + type: boolean + - in: body + name: delegation + description: The password of the account to remove from the KMS + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + delegator_addr: + $ref: "#/definitions/Address" + validator_src_addr: + $ref: "#/definitions/ValidatorAddress" + validator_dst_addr: + $ref: "#/definitions/ValidatorAddress" + shares: + type: string + example: "100" + tags: + - ICS21 + consumes: + - application/json + produces: + - application/json + responses: + 200: + description: OK + schema: + $ref: "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid delegator address or redelegation request body + 401: + description: Key password is wrong + 500: + description: Internal Server Error /stake/delegators/{delegatorAddr}/validators: parameters: - in: path @@ -835,63 +949,6 @@ paths: description: Invalid delegator address 500: description: Internal Server Error - /stake/delegators/{delegatorAddr}/delegations/{validatorAddr}: - parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string - get: - summary: Query the current delegation between a delegator and a validator - tags: - - ICS21 - produces: - - application/json - responses: - 200: - description: OK - schema: - $ref: "#/definitions/Delegation" - 400: - description: Invalid delegator address or validator address - 500: - description: Internal Server Error - /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: - parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - - in: path - name: validatorAddr - description: Bech32 OperatorAddress of validator - required: true - type: string - get: - summary: Query all unbonding delegations between a delegator and a validator - tags: - - ICS21 - produces: - - application/json - responses: - 200: - description: OK - schema: - type: array - items: - type: object - "$ref": "#/definitions/UnbondingDelegation" - 400: - description: Invalid delegator address or validator address - 500: - description: Internal Server Error /stake/validators: get: summary: Get all validator candidates @@ -1116,7 +1173,7 @@ paths: type: object properties: base_req: - "$ref": "#/definitions/BaseReq" + $ref: "#/definitions/BaseReq" responses: 200: description: OK @@ -1147,7 +1204,7 @@ paths: type: object properties: base_req: - "$ref": "#/definitions/BaseReq" + $ref: "#/definitions/BaseReq" title: type: string description: @@ -1156,7 +1213,7 @@ paths: type: string example: "text" proposer: - "$ref": "#/definitions/Address" + $ref: "#/definitions/Address" initial_deposit: type: array items: @@ -1165,7 +1222,7 @@ paths: 200: description: OK schema: - "$ref": "#/definitions/BroadcastTxCommitResult" + $ref: "#/definitions/BroadcastTxCommitResult" 400: description: Invalid proposal body 401: @@ -1201,12 +1258,57 @@ paths: schema: type: array items: - "$ref": "#/definitions/TextProposal" + $ref: "#/definitions/TextProposal" 400: description: Invalid query parameters 500: description: Internal Server Error + /gov/proposals/{proposalId}: + get: + summary: Query a proposal + description: Query a proposal by id + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + $ref: "#/definitions/TextProposal" + 400: + description: Invalid proposal id + 500: + description: Internal Server Error /gov/proposals/{proposalId}/deposits: + get: + summary: Query deposits + description: Query deposits by proposalId + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Deposit" + 400: + description: Invalid proposal id + 500: + description: Internal Server Error post: summary: Deposit tokens to a proposal description: Send transaction to deposit tokens to a proposal @@ -1230,9 +1332,9 @@ paths: type: object properties: base_req: - "$ref": "#/definitions/BaseReq" + $ref: "#/definitions/BaseReq" depositor: - "$ref": "#/definitions/Address" + $ref: "#/definitions/Address" amount: type: array items: @@ -1241,146 +1343,13 @@ paths: 200: description: OK schema: - "$ref": "#/definitions/BroadcastTxCommitResult" + $ref: "#/definitions/BroadcastTxCommitResult" 400: description: Invalid proposal id or deposit body 401: description: Key password is wrong 500: description: Internal Server Error - get: - summary: Query deposits - description: Query deposits by proposalId - produces: - - application/json - tags: - - ICS22 - parameters: - - type: string - name: proposalId - required: true - in: path - responses: - 200: - description: OK - schema: - type: array - items: - "$ref": "#/definitions/Deposit" - 400: - description: Invalid proposal id - 500: - description: Internal Server Error - /gov/proposals/{proposalId}/tally: - get: - summary: Get a proposal's tally result at the current time - description: Gets a proposal's tally result at the current time. If the proposal is pending deposits (i.e status 'DepositPeriod') it returns an empty tally result. - produces: - - application/json - tags: - - ICS22 - parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path - responses: - 200: - description: OK - schema: - $ref: "#/definitions/TallyResult" - 400: - description: Invalid proposal id - 500: - description: Internal Server Error - /gov/proposals/{proposalId}/votes: - post: - summary: Vote a proposal - description: Send transaction to vote a proposal - consumes: - - application/json - produces: - - application/json - tags: - - ICS22 - parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path - - description: valid value of `"option"` field can be `"yes"`, `"no"`, `"no_with_veto"` and `"abstain"` - name: post_vote_body - in: body - required: true - schema: - type: object - properties: - base_req: - "$ref": "#/definitions/BaseReq" - voter: - "$ref": "#/definitions/Address" - option: - type: string - example: "yes" - responses: - 200: - description: OK - schema: - "$ref": "#/definitions/BroadcastTxCommitResult" - 400: - description: Invalid proposal id or vote body - 401: - description: Key password is wrong - 500: - description: Internal Server Error - get: - summary: Query voters - description: Query voters information by proposalId - produces: - - application/json - tags: - - ICS22 - parameters: - - type: string - description: proposal id - name: proposalId - required: true - in: path - responses: - 200: - description: OK - schema: - type: array - items: - "$ref": "#/definitions/Vote" - 400: - description: Invalid proposal id - 500: - description: Internal Server Error - /gov/proposals/{proposalId}: - get: - summary: Query a proposal - description: Query a proposal by id - produces: - - application/json - tags: - - ICS22 - parameters: - - type: string - name: proposalId - required: true - in: path - responses: - 200: - description: OK - schema: - "$ref": "#/definitions/TextProposal" - 400: - description: Invalid proposal id - 500: - description: Internal Server Error /gov/proposals/{proposalId}/deposits/{depositor}: get: summary: Query deposit @@ -1411,6 +1380,71 @@ paths: description: Found no deposit 500: description: Internal Server Error + /gov/proposals/{proposalId}/votes: + get: + summary: Query voters + description: Query voters information by proposalId + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + description: proposal id + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Vote" + 400: + description: Invalid proposal id + 500: + description: Internal Server Error + post: + summary: Vote a proposal + description: Send transaction to vote a proposal + consumes: + - application/json + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + description: proposal id + name: proposalId + required: true + in: path + - description: valid value of `"option"` field can be `"yes"`, `"no"`, `"no_with_veto"` and `"abstain"` + name: post_vote_body + in: body + required: true + schema: + type: object + properties: + base_req: + $ref: "#/definitions/BaseReq" + voter: + $ref: "#/definitions/Address" + option: + type: string + example: "yes" + responses: + 200: + description: OK + schema: + $ref: "#/definitions/BroadcastTxCommitResult" + 400: + description: Invalid proposal id or vote body + 401: + description: Key password is wrong + 500: + description: Internal Server Error /gov/proposals/{proposalId}/votes/{voter}: get: summary: Query vote @@ -1441,6 +1475,29 @@ paths: description: Found no vote 500: description: Internal Server Error + /gov/proposals/{proposalId}/tally: + get: + summary: Get a proposal's tally result at the current time + description: Gets a proposal's tally result at the current time. If the proposal is pending deposits (i.e status 'DepositPeriod') it returns an empty tally result. + produces: + - application/json + tags: + - ICS22 + parameters: + - type: string + description: proposal id + name: proposalId + required: true + in: path + responses: + 200: + description: OK + schema: + $ref: "#/definitions/TallyResult" + 400: + description: Invalid proposal id + 500: + description: Internal Server Error /gov/parameters/deposit: get: summary: Query governance deposit parameters @@ -1538,7 +1595,7 @@ definitions: tags: type: array items: - "$ref": "#/definitions/KVPair" + $ref: "#/definitions/KVPair" example: code: 0 data: data @@ -1567,7 +1624,7 @@ definitions: tags: type: array items: - "$ref": "#/definitions/KVPair" + $ref: "#/definitions/KVPair" example: code: 5 data: data @@ -1868,7 +1925,7 @@ definitions: total_deposit: type: array items: - "$ref": "#/definitions/Coin" + $ref: "#/definitions/Coin" voting_start_time: type: string Deposit: @@ -1877,11 +1934,11 @@ definitions: amount: type: array items: - "$ref": "#/definitions/Coin" + $ref: "#/definitions/Coin" proposal_id: type: integer depositor: - "$ref": "#/definitions/Address" + $ref: "#/definitions/Address" TallyResult: type: object properties: diff --git a/client/utils/rest.go b/client/utils/rest.go index 36ff05af8b..5917455ee3 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -159,21 +159,21 @@ func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req i // ValidateBasic performs basic validation of a BaseReq. If custom validation // logic is needed, the implementing request handler should perform those // checks manually. -func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { - switch { - case len(br.Name) == 0: +func (br BaseReq) ValidateBasic(w http.ResponseWriter, cliCtx context.CLIContext) bool { + if !cliCtx.GenerateOnly && !cliCtx.Simulate { + switch { + case len(br.Password) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") + return false + case len(br.ChainID) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "chain-id required but not specified") + return false + } + } + if len(br.Name) == 0 { WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified") return false - - case len(br.Password) == 0: - WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") - return false - - case len(br.ChainID) == 0: - WriteErrorResponse(w, http.StatusUnauthorized, "chainID required but not specified") - return false } - return true } diff --git a/client/utils/utils.go b/client/utils/utils.go index 08a538eec6..6fde3ce6b5 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -34,14 +34,14 @@ func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIConte return err } - if txBldr.SimulateGas || cliCtx.DryRun { + if txBldr.SimulateGas || cliCtx.Simulate { txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) if err != nil { return err } fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas) } - if cliCtx.DryRun { + if cliCtx.Simulate { return nil } diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index f7d3b2d831..845edc757b 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -645,6 +645,37 @@ trust_node = true cleanupDirs(gaiadHome, gaiacliHome) } +func TestGaiadCollectGentxs(t *testing.T) { + t.Parallel() + // Initialise temporary directories + gaiadHome, gaiacliHome := getTestingHomeDirs(t.Name()) + gentxDir, err := ioutil.TempDir("", "") + gentxDoc := filepath.Join(gentxDir, "gentx.json") + require.NoError(t, err) + + tests.ExecuteT(t, fmt.Sprintf("gaiad --home=%s unsafe-reset-all", gaiadHome), "") + os.RemoveAll(filepath.Join(gaiadHome, "config", "gentx")) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s foo", gaiacliHome), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli keys delete --home=%s bar", gaiacliHome), app.DefaultKeyPass) + executeWriteCheckErr(t, fmt.Sprintf("gaiacli keys add --home=%s foo", gaiacliHome), app.DefaultKeyPass) + executeWriteCheckErr(t, fmt.Sprintf("gaiacli keys add --home=%s bar", gaiacliHome), app.DefaultKeyPass) + executeWriteCheckErr(t, fmt.Sprintf("gaiacli config --home=%s output json", gaiacliHome)) + fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --home=%s", gaiacliHome)) + + // Run init + _ = executeInit(t, fmt.Sprintf("gaiad init -o --moniker=foo --home=%s", gaiadHome)) + // Add account to genesis.json + executeWriteCheckErr(t, fmt.Sprintf( + "gaiad add-genesis-account %s 150%s,1000fooToken --home=%s", fooAddr, stakeTypes.DefaultBondDenom, gaiadHome)) + executeWrite(t, fmt.Sprintf("cat %s%sconfig%sgenesis.json", gaiadHome, string(os.PathSeparator), string(os.PathSeparator))) + // Write gentx file + executeWriteCheckErr(t, fmt.Sprintf( + "gaiad gentx --name=foo --home=%s --home-client=%s --output-document=%s", gaiadHome, gaiacliHome, gentxDoc), app.DefaultKeyPass) + // Collect gentxs from a custom directory + executeWriteCheckErr(t, fmt.Sprintf("gaiad collect-gentxs --home=%s --gentx-dir=%s", gaiadHome, gentxDir), app.DefaultKeyPass) + cleanupDirs(gaiadHome, gaiacliHome, gentxDir) +} + //___________________________________________________________________________________ // helper methods diff --git a/cmd/gaia/init/collect.go b/cmd/gaia/init/collect.go index da97e46263..e630d494cc 100644 --- a/cmd/gaia/init/collect.go +++ b/cmd/gaia/init/collect.go @@ -18,6 +18,10 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" ) +const ( + flagGenTxDir = "gentx-dir" +) + type initConfig struct { ChainID string GenTxsDir string @@ -35,7 +39,6 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { config := ctx.Config config.SetRoot(viper.GetString(cli.HomeFlag)) name := viper.GetString(client.FlagName) - nodeID, valPubKey, err := InitializeNodeValidatorFiles(config) if err != nil { return err @@ -46,19 +49,13 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { return err } - toPrint := printInfo{ - Moniker: config.Moniker, - ChainID: genDoc.ChainID, - NodeID: nodeID, + genTxsDir := viper.GetString(flagGenTxDir) + if genTxsDir == "" { + genTxsDir = filepath.Join(config.RootDir, "config", "gentx") } - initCfg := initConfig{ - ChainID: genDoc.ChainID, - GenTxsDir: filepath.Join(config.RootDir, "config", "gentx"), - Name: name, - NodeID: nodeID, - ValPubKey: valPubKey, - } + toPrint := newPrintInfo(config.Moniker, genDoc.ChainID, nodeID, genTxsDir, json.RawMessage("")) + initCfg := newInitConfig(genDoc.ChainID, genTxsDir, name, nodeID, valPubKey) appMessage, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) if err != nil { @@ -73,6 +70,9 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { } cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagGenTxDir, "", + "override default \"gentx\" directory from which collect and execute "+ + "genesis transactions; default [--home]/config/gentx/") return cmd } @@ -117,3 +117,27 @@ func genAppStateFromConfig( err = ExportGenesisFile(genFile, initCfg.ChainID, nil, appState) return } + +func newInitConfig(chainID, genTxsDir, name, nodeID string, + valPubKey crypto.PubKey) initConfig { + + return initConfig{ + ChainID: chainID, + GenTxsDir: genTxsDir, + Name: name, + NodeID: nodeID, + ValPubKey: valPubKey, + } +} + +func newPrintInfo(moniker, chainID, nodeID, genTxsDir string, + appMessage json.RawMessage) printInfo { + + return printInfo{ + Moniker: moniker, + ChainID: chainID, + NodeID: nodeID, + GenTxsDir: genTxsDir, + AppMessage: appMessage, + } +} diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go index c8a7c05d2e..9f985762a7 100644 --- a/cmd/gaia/init/gentx.go +++ b/cmd/gaia/init/gentx.go @@ -137,9 +137,12 @@ following delegation and commission default parameters: } // Fetch output file name - outputDocument, err := makeOutputFilepath(config.RootDir, nodeID) - if err != nil { - return err + outputDocument := viper.GetString(client.FlagOutputDocument) + if outputDocument == "" { + outputDocument, err = makeOutputFilepath(config.RootDir, nodeID) + if err != nil { + return err + } } if err := writeSignedGenTx(cdc, outputDocument, signedTx); err != nil { @@ -154,6 +157,8 @@ following delegation and commission default parameters: cmd.Flags().String(tmcli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") + cmd.Flags().String(client.FlagOutputDocument, "", + "write the genesis transaction JSON document to the given file instead of the default location") cmd.Flags().AddFlagSet(cli.FsCommissionCreate) cmd.Flags().AddFlagSet(cli.FsAmount) cmd.Flags().AddFlagSet(cli.FsPk) diff --git a/cmd/gaia/init/init.go b/cmd/gaia/init/init.go index 7690d53364..922cfbafa5 100644 --- a/cmd/gaia/init/init.go +++ b/cmd/gaia/init/init.go @@ -28,6 +28,7 @@ type printInfo struct { Moniker string `json:"moniker"` ChainID string `json:"chain_id"` NodeID string `json:"node_id"` + GenTxsDir string `json:"gentxs_dir"` AppMessage json.RawMessage `json:"app_message"` } @@ -77,12 +78,7 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { return err } - toPrint := printInfo{ - ChainID: chainID, - Moniker: config.Moniker, - NodeID: nodeID, - AppMessage: appState, - } + toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState) cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) diff --git a/cmd/gaia/init/testnet.go b/cmd/gaia/init/testnet.go index cfa3e6396a..10b0b8265f 100644 --- a/cmd/gaia/init/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -279,13 +279,7 @@ func collectGenFiles( config.SetRoot(nodeDir) nodeID, valPubKey := nodeIDs[i], valPubKeys[i] - initCfg := initConfig{ - ChainID: chainID, - GenTxsDir: gentxsDir, - Name: moniker, - NodeID: nodeID, - ValPubKey: valPubKey, - } + initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey) genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) if err != nil { diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 7bb2640fdd..27cbc043c7 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -49,8 +49,11 @@ func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIC return } + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { + if !baseReq.ValidateBasic(w, cliCtx) { return } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 53deffbbe5..821da0cc7d 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -78,8 +78,11 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { + if !baseReq.ValidateBasic(w, cliCtx) { return } @@ -123,8 +126,11 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { + if !baseReq.ValidateBasic(w, cliCtx) { return } @@ -162,8 +168,11 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { + if !baseReq.ValidateBasic(w, cliCtx) { return } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 19c9219711..704b663e62 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -43,8 +43,11 @@ func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { + if !baseReq.ValidateBasic(w, cliCtx) { return } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 5f33a1210c..ec28f48ed6 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -38,8 +38,11 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL return } + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { + if !baseReq.ValidateBasic(w, cliCtx) { return } diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index d571bef9eb..97a62ebd99 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -29,8 +29,6 @@ const ( FlagGenesisFormat = "genesis-format" FlagNodeID = "node-id" FlagIP = "ip" - - FlagOutputDocument = "output-document" // inspired by wget -O ) // common flagsets to add to various functions diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 5deb5b53c2..bb2bc0e7fa 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -2,81 +2,72 @@ package rest import ( "bytes" - "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/gorilla/mux" - - ctypes "github.com/tendermint/tendermint/rpc/core/types" ) func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, kb keys.Keybase) { r.HandleFunc( "/stake/delegators/{delegatorAddr}/delegations", - delegationsRequestHandlerFn(cdc, kb, cliCtx), + postDelegationsHandlerFn(cdc, kb, cliCtx), + ).Methods("POST") + r.HandleFunc( + "/stake/delegators/{delegatorAddr}/unbonding_delegations", + postUnbondingDelegationsHandlerFn(cdc, kb, cliCtx), + ).Methods("POST") + r.HandleFunc( + "/stake/delegators/{delegatorAddr}/redelegations", + postRedelegationsHandlerFn(cdc, kb, cliCtx), ).Methods("POST") } type ( msgDelegationsInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - Delegation sdk.Coin `json:"delegation"` + BaseReq utils.BaseReq `json:"base_req"` + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 + ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 + Delegation sdk.Coin `json:"delegation"` } msgBeginRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 - SharesAmount string `json:"shares"` + BaseReq utils.BaseReq `json:"base_req"` + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr sdk.ValAddress `json:"validator_dst_addr"` // in bech32 + SharesAmount sdk.Dec `json:"shares"` } msgBeginUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - SharesAmount string `json:"shares"` - } - - // the request body for edit delegations - EditDelegationsReq struct { - BaseReq utils.BaseReq `json:"base_req"` - Delegations []msgDelegationsInput `json:"delegations"` - BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` - BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` + BaseReq utils.BaseReq `json:"base_req"` + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` // in bech32 + ValidatorAddr sdk.ValAddress `json:"validator_addr"` // in bech32 + SharesAmount sdk.Dec `json:"shares"` } ) -// 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 -// TODO: Seriously consider how to refactor...do we need to make it multiple txs? -// If not, we can just use CompleteAndBroadcastTxREST. -func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { +func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req EditDelegationsReq + var req msgDelegationsInput - body, err := ioutil.ReadAll(r.Body) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - err = cdc.UnmarshalJSON(body, &req) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { + if !baseReq.ValidateBasic(w, cliCtx) { return } @@ -86,182 +77,98 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte return } - // build messages - messages := make([]sdk.Msg, len(req.Delegations)+ - len(req.BeginRedelegates)+ - len(req.BeginUnbondings)) - - i := 0 - for _, msg := range req.Delegations { - delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") - return - } - - messages[i] = stake.MsgDelegate{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Delegation: msg.Delegation, - } - - i++ + if !bytes.Equal(info.GetPubKey().Address(), req.DelegatorAddr) { + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") + return } - for _, msg := range req.BeginRedelegates { - delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") - return - } - - valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - shares, err := sdk.NewDecFromStr(msg.SharesAmount) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - messages[i] = stake.MsgBeginRedelegate{ - DelegatorAddr: delAddr, - ValidatorSrcAddr: valSrcAddr, - ValidatorDstAddr: valDstAddr, - SharesAmount: shares, - } - - i++ - } - - for _, msg := range req.BeginUnbondings { - delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") - return - } - - valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - shares, err := sdk.NewDecFromStr(msg.SharesAmount) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - messages[i] = stake.MsgBeginUnbonding{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - SharesAmount: shares, - } - - i++ - } - - simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) + msg := stake.NewMsgDelegate(req.DelegatorAddr, req.ValidatorAddr, req.Delegation) + err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) - if !ok { + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + } +} + +func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req msgBeginRedelegateInput + + err := utils.ReadRESTReq(w, r, cdc, &req) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: baseReq.ChainID, + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w, cliCtx) { + return } - // sign messages - signedTxs := make([][]byte, len(messages[:])) - for i, msg := range messages { - // increment sequence for each message - txBldr = txBldr.WithAccountNumber(baseReq.AccountNumber) - txBldr = txBldr.WithSequence(baseReq.Sequence) - - baseReq.Sequence++ - - if baseReq.Simulate || txBldr.SimulateGas { - newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - if baseReq.Simulate { - utils.WriteSimulationResponse(w, newBldr.Gas) - return - } - - txBldr = newBldr - } - - if baseReq.GenerateOnly { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - signedTxs[i] = txBytes + info, err := kb.Get(baseReq.Name) + if err != nil { + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + return } - // 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 { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - results[i] = res + if !bytes.Equal(info.GetPubKey().Address(), req.DelegatorAddr) { + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") + return } - utils.PostProcessResponse(w, cdc, results, cliCtx.Indent) + msg := stake.NewMsgBeginRedelegate(req.DelegatorAddr, req.ValidatorSrcAddr, req.ValidatorDstAddr, req.SharesAmount) + err = msg.ValidateBasic() + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) + } +} + +func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req msgBeginUnbondingInput + + err := utils.ReadRESTReq(w, r, cdc, &req) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + cliCtx = cliCtx.WithGenerateOnly(req.BaseReq.GenerateOnly) + cliCtx = cliCtx.WithSimulation(req.BaseReq.Simulate) + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w, cliCtx) { + return + } + + info, err := kb.Get(baseReq.Name) + if err != nil { + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + return + } + + if !bytes.Equal(info.GetPubKey().Address(), req.DelegatorAddr) { + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") + return + } + + msg := stake.NewMsgBeginUnbonding(req.DelegatorAddr, req.ValidatorAddr, req.SharesAmount) + err = msg.ValidateBasic() + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } }