Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

x/gov: Proto Tx CLI command #5942

Merged
merged 42 commits into from
Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2b8afe5
WIP on x/gov protobuf cli tx cmd's
aaronc Apr 6, 2020
40143b4
Implement gov.Codec in codec/std
aaronc Apr 6, 2020
ecf248a
Add protobuf x/gov REST tx handlers
aaronc Apr 7, 2020
23b7c90
Add deprecation notice and RegisterHandlers
aaronc Apr 7, 2020
6801eeb
Optimize imports
aaronc Apr 8, 2020
03c594a
Reorder imports
aaronc Apr 8, 2020
aa27dd1
Fix imports
aaronc Apr 8, 2020
a4dab80
Switch to non-codec based handling of NewMsgSubmitProposal
aaronc Apr 8, 2020
9437697
Tweak gov.Codec interface
aaronc Apr 9, 2020
43ce8b3
Fix errors and update tests
aaronc Apr 9, 2020
e54dd31
Merge branch 'master' into aaronc/5864-x-gov
aaronc Apr 9, 2020
22ea39c
Fix tests
aaronc Apr 9, 2020
7f6fbc1
Merge branch 'master' into aaronc/5864-x-gov
aaronc Apr 9, 2020
dd4d834
Fix simapp
aaronc Apr 9, 2020
267383b
Update ADR and docs
aaronc Apr 9, 2020
c0f9965
Resolve cyclic import issues
aaronc Apr 9, 2020
b2d2a22
Handle pointer proposal types because of how protobuf oneof's are gen…
aaronc Apr 9, 2020
6183dc7
Fix errors
aaronc Apr 9, 2020
978bca5
Fix tests
aaronc Apr 10, 2020
63354d0
Spelling
aaronc Apr 10, 2020
83ff6cc
Merge branch 'master' into aaronc/5864-x-gov
aaronc Apr 10, 2020
b85ae8c
Merge branch 'aaronc/5864-x-gov' of github.com:cosmos/cosmos-sdk into…
aaronc Apr 10, 2020
0a65118
Make MsgSubmitProposal implement MsgSubmitProposalI again so that ami…
aaronc Apr 10, 2020
5dd7c66
Merge branch 'master' into aaronc/5864-x-gov
aaronc Apr 10, 2020
57961df
Always use pointer types for gov.Content instances
aaronc Apr 10, 2020
63580d3
Fix test
aaronc Apr 10, 2020
71e9e6e
Fix test
aaronc Apr 10, 2020
4366886
Remove unused constructor
aaronc Apr 10, 2020
96feec9
Add invalid test case to fix coverage gap
aaronc Apr 10, 2020
1cad064
Merge branch 'master' into aaronc/5864-x-gov
aaronc Apr 10, 2020
35f3662
Update ADR 020 Changelog
aaronc Apr 10, 2020
218de47
Fix golangci issue
aaronc Apr 10, 2020
06ca9fa
Regenerate proto files
aaronc Apr 13, 2020
4a91774
Merge branch 'master' into aaronc/5864-x-gov
aaronc Apr 13, 2020
7a99bd7
Refactor MsgSubmitProposalI constructor out of gov.Codec and into sta…
aaronc Apr 13, 2020
64e194c
Fix lint error
aaronc Apr 13, 2020
609f99f
Update ADR
aaronc Apr 13, 2020
620f436
Update ADR
aaronc Apr 13, 2020
029a5e6
Update docs/architecture/adr-020-protobuf-transaction-encoding.md
aaronc Apr 14, 2020
7a374d5
Address PR feedback
aaronc Apr 14, 2020
9d182fc
Merge branch 'master' into aaronc/5864-x-gov
aaronc Apr 14, 2020
cac9b12
Rename ctr to newMsgFn
aaronc Apr 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions codec/std/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/evidence"
eviexported "github.com/cosmos/cosmos-sdk/x/evidence/exported"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/supply"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
Expand Down Expand Up @@ -180,6 +181,22 @@ func (c *Codec) UnmarshalProposal(bz []byte) (gov.Proposal, error) {
}, nil
}

func (c *Codec) NewMsgSubmitProposal(content types.Content, initialDeposit sdk.Coins, proposer sdk.AccAddress) (types.MsgSubmitProposalI, error) {
stdContent := &Content{}
err := stdContent.SetContent(content)
if err != nil {
return nil, err
}
msg := MsgSubmitProposal{
MsgSubmitProposalBase: gov.MsgSubmitProposalBase{
InitialDeposit: initialDeposit,
Proposer: proposer,
},
Content: stdContent,
}
return msg, nil
}

// ----------------------------------------------------------------------------
// necessary types and interfaces registered. This codec is provided to all the
// modules the application depends on.
Expand Down
192 changes: 192 additions & 0 deletions x/gov/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"bufio"
"fmt"
gov "github.com/cosmos/cosmos-sdk/x/gov/types"
aaronc marked this conversation as resolved.
Show resolved Hide resolved
"strconv"
"strings"

Expand All @@ -11,6 +12,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
Expand Down Expand Up @@ -49,6 +51,196 @@ var ProposalFlags = []string{
FlagDeposit,
}

// NewTxCmd returns the transaction commands for this module
// governance ModuleClient is slightly different from other ModuleClients in that
// it contains a slice of "proposal" child commands. These commands are respective
// to proposal type handlers that are implemented in other modules but are mounted
// under the governance CLI (eg. parameter change proposals).
func NewTxCmd(cdc gov.Codec, txg tx.Generator, ar tx.AccountRetriever, pcmds []*cobra.Command) *cobra.Command {
govTxCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Governance transactions subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

cmdSubmitProp := NewCmdSubmitProposal(cdc, txg, ar)
for _, pcmd := range pcmds {
cmdSubmitProp.AddCommand(flags.PostCommands(pcmd)[0])
}

govTxCmd.AddCommand(flags.PostCommands(
NewCmdDeposit(cdc, txg, ar),
NewCmdVote(cdc, txg, ar),
cmdSubmitProp,
)...)

return govTxCmd
}

// NewCmdSubmitProposal implements submitting a proposal transaction command.
func NewCmdSubmitProposal(cdc gov.Codec, txg tx.Generator, ar tx.AccountRetriever) *cobra.Command {
cmd := &cobra.Command{
Use: "submit-proposal",
Short: "Submit a proposal along with an initial deposit",
Long: strings.TrimSpace(
fmt.Sprintf(`Submit a proposal along with an initial deposit.
Proposal title, description, type and deposit can be given directly or through a proposal JSON file.

Example:
$ %s tx gov submit-proposal --proposal="path/to/proposal.json" --from mykey

Where proposal.json contains:

{
"title": "Test Proposal",
"description": "My awesome proposal",
"type": "Text",
"deposit": "10test"
}

Which is equivalent to:

$ %s tx gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey
`,
version.ClientName, version.ClientName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithMarshaler(cdc)
txf := tx.NewFactoryFromCLI(inBuf).WithTxGenerator(txg).WithAccountRetriever(ar)

proposal, err := parseSubmitProposalFlags()
if err != nil {
return err
}

amount, err := sdk.ParseCoins(proposal.Deposit)
if err != nil {
return err
}

content := types.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type)

msg, err := cdc.NewMsgSubmitProposal(content, amount, cliCtx.GetFromAddress())
if err != nil {
return err
}
if err = msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTx(cliCtx, txf, msg)
},
}

cmd.Flags().String(FlagTitle, "", "title of proposal")
cmd.Flags().String(FlagDescription, "", "description of proposal")
cmd.Flags().String(flagProposalType, "", "proposalType of proposal, types: text/parameter_change/software_upgrade")
cmd.Flags().String(FlagDeposit, "", "deposit of proposal")
cmd.Flags().String(FlagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)")

return cmd
}

// NewCmdDeposit implements depositing tokens for an active proposal.
func NewCmdDeposit(cdc codec.Marshaler, txg tx.Generator, ar tx.AccountRetriever) *cobra.Command {
return &cobra.Command{
Use: "deposit [proposal-id] [deposit]",
Args: cobra.ExactArgs(2),
Short: "Deposit tokens for an active proposal",
Long: strings.TrimSpace(
fmt.Sprintf(`Submit a deposit for an active proposal. You can
find the proposal-id by running "%s query gov proposals".

Example:
$ %s tx gov deposit 1 10stake --from mykey
`,
version.ClientName, version.ClientName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithMarshaler(cdc)
aaronc marked this conversation as resolved.
Show resolved Hide resolved
txf := tx.NewFactoryFromCLI(inBuf).WithTxGenerator(txg).WithAccountRetriever(ar)

// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0])
}

// Get depositor address
from := cliCtx.GetFromAddress()

// Get amount of coins
amount, err := sdk.ParseCoins(args[1])
if err != nil {
return err
}

msg := types.NewMsgDeposit(from, proposalID, amount)
err = msg.ValidateBasic()
if err != nil {
return err
}

return tx.GenerateOrBroadcastTx(cliCtx, txf, msg)
},
}
}

// NewCmdVote implements creating a new vote command.
func NewCmdVote(cdc codec.Marshaler, txg tx.Generator, ar tx.AccountRetriever) *cobra.Command {
return &cobra.Command{
Use: "vote [proposal-id] [option]",
Args: cobra.ExactArgs(2),
Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain",
Long: strings.TrimSpace(
fmt.Sprintf(`Submit a vote for an active proposal. You can
find the proposal-id by running "%s query gov proposals".


Example:
$ %s tx gov vote 1 yes --from mykey
`,
version.ClientName, version.ClientName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithMarshaler(cdc)
aaronc marked this conversation as resolved.
Show resolved Hide resolved
txf := tx.NewFactoryFromCLI(inBuf).WithTxGenerator(txg).WithAccountRetriever(ar)

// Get voting address
from := cliCtx.GetFromAddress()

// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0])
}

// Find out which vote option user chose
byteVoteOption, err := types.VoteOptionFromString(govutils.NormalizeVoteOption(args[1]))
if err != nil {
return err
}

// Build vote message and run basic validation
msg := types.NewMsgVote(from, proposalID, byteVoteOption)
err = msg.ValidateBasic()
if err != nil {
return err
}

return tx.GenerateOrBroadcastTx(cliCtx, txf, msg)
},
}
}

// GetTxCmd returns the transaction commands for this module
// governance ModuleClient is slightly different from other ModuleClients in that
// it contains a slice of "proposal" child commands. These commands are respective
Expand Down
7 changes: 7 additions & 0 deletions x/gov/client/rest/rest.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package rest

import (
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/x/gov/types"
"net/http"

"github.com/gorilla/mux"
Expand Down Expand Up @@ -28,6 +30,11 @@ type ProposalRESTHandler struct {
Handler func(http.ResponseWriter, *http.Request)
}

func RegisterHandlers(cliCtx context.CLIContext, m types.Codec, txg tx.Generator, r *mux.Router, phs []ProposalRESTHandler) {
registerQueryRoutes(cliCtx, r)
registerTxHandlers(cliCtx, m, txg, r, phs)
}

// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, phs []ProposalRESTHandler) {
registerQueryRoutes(cliCtx, r)
Expand Down
120 changes: 120 additions & 0 deletions x/gov/client/rest/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,133 @@ import (
"github.com/gorilla/mux"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)

func registerTxHandlers(cliCtx context.CLIContext, m types.Codec, txg tx.Generator, r *mux.Router, phs []ProposalRESTHandler) {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
propSubRtr := r.PathPrefix("/gov/proposals").Subrouter()
for _, ph := range phs {
propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST")
}

cliCtx = cliCtx.WithMarshaler(m)
r.HandleFunc("/gov/proposals", newPostProposalHandlerFn(cliCtx, m, txg)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), newDepositHandlerFn(cliCtx, txg)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), newVoteHandlerFn(cliCtx, txg)).Methods("POST")
}

func newPostProposalHandlerFn(cliCtx context.CLIContext, m types.Codec, txg tx.Generator) http.HandlerFunc {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
return func(w http.ResponseWriter, r *http.Request) {
var req PostProposalReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}

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

proposalType := gcutils.NormalizeProposalType(req.ProposalType)
content := types.ContentFromProposalType(req.Title, req.Description, proposalType)

msg, err := m.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer)
if rest.CheckBadRequestError(w, err) {
return
}
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
return
}

tx.WriteGeneratedTxResponse(cliCtx, w, txg, req.BaseReq, msg)
}
}

func newDepositHandlerFn(cliCtx context.CLIContext, txg tx.Generator) http.HandlerFunc {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]

if len(strProposalID) == 0 {
rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified")
return
}

proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}

var req DepositReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}

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

// create the message
msg := types.NewMsgDeposit(req.Depositor, proposalID, req.Amount)
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
return
}

tx.WriteGeneratedTxResponse(cliCtx, w, txg, req.BaseReq, msg)
}
}

func newVoteHandlerFn(cliCtx context.CLIContext, txg tx.Generator) http.HandlerFunc {
aaronc marked this conversation as resolved.
Show resolved Hide resolved
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]

if len(strProposalID) == 0 {
rest.WriteErrorResponse(w, http.StatusBadRequest, "proposalId required but not specified")
return
}

proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}

var req VoteReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}

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

voteOption, err := types.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option))
if rest.CheckBadRequestError(w, err) {
return
}

// create the message
msg := types.NewMsgVote(req.Voter, proposalID, voteOption)
if rest.CheckBadRequestError(w, msg.ValidateBasic()) {
return
}

tx.WriteGeneratedTxResponse(cliCtx, w, txg, req.BaseReq, msg)
}
}

// ---------------------------------------------------------------------------
// Deprecated
//
// TODO: Remove once client-side Protobuf migration has been completed.
// ---------------------------------------------------------------------------
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, phs []ProposalRESTHandler) {
propSubRtr := r.PathPrefix("/gov/proposals").Subrouter()
for _, ph := range phs {
Expand Down
Loading