diff --git a/app/app.go b/app/app.go index bdcdbbbd80..c750a7a3a5 100644 --- a/app/app.go +++ b/app/app.go @@ -16,6 +16,7 @@ import ( "github.com/tendermint/basecoin/modules/coin" "github.com/tendermint/basecoin/modules/fee" "github.com/tendermint/basecoin/modules/nonce" + "github.com/tendermint/basecoin/modules/roles" "github.com/tendermint/basecoin/stack" sm "github.com/tendermint/basecoin/state" "github.com/tendermint/basecoin/version" @@ -56,14 +57,19 @@ func NewBasecoin(handler basecoin.Handler, eyesCli *eyes.Client, logger log.Logg // DefaultHandler - placeholder to just handle sendtx func DefaultHandler(feeDenom string) basecoin.Handler { // use the default stack - h := coin.NewHandler() - d := stack.NewDispatcher(stack.WrapHandler(h)) + c := coin.NewHandler() + r := roles.NewHandler() + d := stack.NewDispatcher( + stack.WrapHandler(c), + stack.WrapHandler(r), + ) return stack.New( base.Logger{}, stack.Recovery{}, auth.Signatures{}, base.Chain{}, nonce.ReplayCheck{}, + roles.NewMiddleware(), fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank), ).Use(d) } diff --git a/client/commands/common.go b/client/commands/common.go index f14a11f384..b6781bc3fe 100644 --- a/client/commands/common.go +++ b/client/commands/common.go @@ -83,11 +83,11 @@ func GetCertifier() (*certifiers.InquiringCertifier, error) { return cert, nil } -// ParseAddress parses an address of form: +// ParseActor parses an address of form: // [:][:] // into a basecoin.Actor. // If app is not specified or "", then assume auth.NameSigs -func ParseAddress(input string) (res basecoin.Actor, err error) { +func ParseActor(input string) (res basecoin.Actor, err error) { chain, app := "", auth.NameSigs input = strings.TrimSpace(input) spl := strings.SplitN(input, ":", 3) @@ -114,3 +114,17 @@ func ParseAddress(input string) (res basecoin.Actor, err error) { } return } + +// ParseActors takes a comma-separated list of actors and parses them into +// a slice +func ParseActors(key string) (signers []basecoin.Actor, err error) { + var act basecoin.Actor + for _, k := range strings.Split(key, ",") { + act, err = ParseActor(k) + if err != nil { + return + } + signers = append(signers, act) + } + return +} diff --git a/client/commands/txs/helpers.go b/client/commands/txs/helpers.go index 3ac1617cdb..68e53915ff 100644 --- a/client/commands/txs/helpers.go +++ b/client/commands/txs/helpers.go @@ -52,6 +52,37 @@ func GetSignerAct() (res basecoin.Actor) { return res } +// DoTx is a helper function for the lazy :) +// +// It uses only public functions and goes through the standard sequence of +// wrapping the tx with middleware layers, signing it, either preparing it, +// or posting it and displaying the result. +// +// If you want a non-standard flow, just call the various functions directly. +// eg. if you already set the middleware layers in your code, or want to +// output in another format. +func DoTx(tx basecoin.Tx) (err error) { + tx, err = Middleware.Wrap(tx) + if err != nil { + return err + } + + err = SignTx(tx) + if err != nil { + return err + } + + bres, err := PrepareOrPostTx(tx) + if err != nil { + return err + } + if bres == nil { + return nil // successful prep, nothing left to do + } + return OutputTx(bres) // print response of the post + +} + // SignTx will validate the tx, and signs it if it is wrapping a Signable. // Modifies tx in place, and returns an error if it should sign but couldn't func SignTx(tx basecoin.Tx) error { diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index cf9e95f9f1..0a3e8e997c 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -22,6 +22,7 @@ import ( coincmd "github.com/tendermint/basecoin/modules/coin/commands" feecmd "github.com/tendermint/basecoin/modules/fee/commands" noncecmd "github.com/tendermint/basecoin/modules/nonce/commands" + rolecmd "github.com/tendermint/basecoin/modules/roles/commands" ) // BaseCli - main basecoin client command @@ -61,6 +62,7 @@ func main() { // set up the middleware txcmd.Middleware = txcmd.Wrappers{ feecmd.FeeWrapper{}, + rolecmd.RoleWrapper{}, noncecmd.NonceWrapper{}, basecmd.ChainWrapper{}, authcmd.SigWrapper{}, diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index a3770c5aaf..cf4dbfe28d 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -33,32 +33,12 @@ func init() { fs.Bool(FlagValid, false, "Is count valid?") } -// TODO: counterTx is very similar to the sendtx one, -// maybe we can pull out some common patterns? func counterTx(cmd *cobra.Command, args []string) error { tx, err := readCounterTxFlags() if err != nil { return err } - - tx, err = txcmd.Middleware.Wrap(tx) - if err != nil { - return err - } - - err = txcmd.SignTx(tx) - if err != nil { - return err - } - - bres, err := txcmd.PrepareOrPostTx(tx) - if err != nil { - return err - } - if bres == nil { - return nil // successful prep, nothing left to do - } - return txcmd.OutputTx(bres) // print response of the post + return txcmd.DoTx(tx) } func readCounterTxFlags() (tx basecoin.Tx, err error) { diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index b651b29e0b..cff4a51acb 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -37,31 +37,12 @@ func sendTxCmd(cmd *cobra.Command, args []string) error { if err != nil { return err } - - tx, err = txcmd.Middleware.Wrap(tx) - if err != nil { - return err - } - - err = txcmd.SignTx(tx) - if err != nil { - return err - } - - // otherwise, post it and display response - bres, err := txcmd.PrepareOrPostTx(tx) - if err != nil { - return err - } - if bres == nil { - return nil // successful prep, nothing left to do - } - return txcmd.OutputTx(bres) // print response of the post + return txcmd.DoTx(tx) } func readSendTxFlags() (tx basecoin.Tx, err error) { // parse to address - toAddr, err := commands.ParseAddress(viper.GetString(FlagTo)) + toAddr, err := commands.ParseActor(viper.GetString(FlagTo)) if err != nil { return tx, err } @@ -94,5 +75,5 @@ func readFromAddr() (basecoin.Actor, error) { if from == "" { return txcmd.GetSignerAct(), nil } - return commands.ParseAddress(from) + return commands.ParseActor(from) } diff --git a/modules/fee/commands/wrap.go b/modules/fee/commands/wrap.go index 6f8c082f80..32fb984830 100644 --- a/modules/fee/commands/wrap.go +++ b/modules/fee/commands/wrap.go @@ -55,5 +55,5 @@ func readPayer() (basecoin.Actor, error) { if payer == "" { return txcmd.GetSignerAct(), nil } - return commands.ParseAddress(payer) + return commands.ParseActor(payer) } diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index e92a8d596a..405b0ea94a 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -9,7 +9,7 @@ import ( lc "github.com/tendermint/light-client" "github.com/tendermint/basecoin" - lcmd "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/client/commands" proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/modules/nonce" "github.com/tendermint/basecoin/stack" @@ -19,7 +19,7 @@ import ( var NonceQueryCmd = &cobra.Command{ Use: "nonce [address]", Short: "Get details of a nonce sequence number, with proof", - RunE: lcmd.RequireInit(nonceQueryCmd), + RunE: commands.RequireInit(nonceQueryCmd), } func nonceQueryCmd(cmd *cobra.Command, args []string) error { @@ -28,7 +28,7 @@ func nonceQueryCmd(cmd *cobra.Command, args []string) error { } addr := strings.Join(args, ",") - signers, err := parseActors(addr) + signers, err := commands.ParseActors(addr) if err != nil { return err } diff --git a/modules/nonce/commands/wrap.go b/modules/nonce/commands/wrap.go index ea42fe4834..5082946654 100644 --- a/modules/nonce/commands/wrap.go +++ b/modules/nonce/commands/wrap.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "strings" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -54,19 +53,7 @@ func readNonceKey() ([]basecoin.Actor, error) { if nonce == "" { return []basecoin.Actor{txcmd.GetSignerAct()}, nil } - return parseActors(nonce) -} - -func parseActors(key string) (signers []basecoin.Actor, err error) { - var act basecoin.Actor - for _, k := range strings.Split(key, ",") { - act, err = commands.ParseAddress(k) - if err != nil { - return - } - signers = append(signers, act) - } - return + return commands.ParseActors(nonce) } // read the sequence from the flag or query for it if flag is -1 diff --git a/modules/roles/commands/query.go b/modules/roles/commands/query.go new file mode 100644 index 0000000000..d7c97a8e24 --- /dev/null +++ b/modules/roles/commands/query.go @@ -0,0 +1,40 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + lcmd "github.com/tendermint/basecoin/client/commands" + proofcmd "github.com/tendermint/basecoin/client/commands/proofs" + "github.com/tendermint/basecoin/modules/roles" + "github.com/tendermint/basecoin/stack" +) + +// RoleQueryCmd - command to query a role +var RoleQueryCmd = &cobra.Command{ + Use: "role [name]", + Short: "Get details of a role, with proof", + RunE: lcmd.RequireInit(roleQueryCmd), +} + +func roleQueryCmd(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("Missing required argument [name]") + } else if len(args) > 1 { + return errors.New("Command only supports one name") + } + + role, err := parseRole(args[0]) + if err != nil { + return err + } + + var res roles.Role + key := stack.PrefixedKey(roles.NameRole, role) + proof, err := proofcmd.GetAndParseAppProof(key, &res) + if err != nil { + return err + } + + return proofcmd.OutputProof(res, proof.BlockHeight()) +} diff --git a/modules/roles/commands/tx.go b/modules/roles/commands/tx.go new file mode 100644 index 0000000000..b9c39043ce --- /dev/null +++ b/modules/roles/commands/tx.go @@ -0,0 +1,65 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/client/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" + "github.com/tendermint/basecoin/modules/roles" +) + +// CreateRoleTxCmd is CLI command to send tokens between basecoin accounts +var CreateRoleTxCmd = &cobra.Command{ + Use: "send", + Short: "send tokens from one account to another", + RunE: commands.RequireInit(createRoleTxCmd), +} + +//nolint +const ( + FlagRole = "role" + FlagMembers = "members" + FlagMinSigs = "min-sigs" +) + +func init() { + flags := CreateRoleTxCmd.Flags() + flags.String(FlagRole, "", "Name of the role to create") + flags.String(FlagMembers, "", "Set of comma-separated addresses for this role") + flags.Int(FlagMinSigs, 0, "Minimum number of signatures needed to assume this role") +} + +// createRoleTxCmd is an example of how to make a tx +func createRoleTxCmd(cmd *cobra.Command, args []string) error { + tx, err := readCreateRoleTxFlags() + if err != nil { + return err + } + return txcmd.DoTx(tx) +} + +func readCreateRoleTxFlags() (tx basecoin.Tx, err error) { + role, err := parseRole(viper.GetString(FlagRole)) + if err != nil { + return tx, err + } + + sigs := viper.GetInt(FlagMinSigs) + if sigs < 1 { + return tx, errors.Errorf("--%s must be at least 1", FlagMinSigs) + } + + signers, err := commands.ParseActors(viper.GetString(FlagMembers)) + if err != nil { + return tx, err + } + if len(signers) == 0 { + return tx, errors.New("must specify at least one member") + } + + tx = roles.NewCreateRoleTx(role, uint32(sigs), signers) + return tx, nil +} diff --git a/modules/roles/commands/wrap.go b/modules/roles/commands/wrap.go new file mode 100644 index 0000000000..48d85a2e77 --- /dev/null +++ b/modules/roles/commands/wrap.go @@ -0,0 +1,48 @@ +package commands + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin" + txcmd "github.com/tendermint/basecoin/client/commands/txs" + "github.com/tendermint/basecoin/modules/roles" +) + +// nolint +const ( + FlagAssumeRole = "assume-role" +) + +// RoleWrapper wraps a tx with 0, 1, or more roles +type RoleWrapper struct{} + +var _ txcmd.Wrapper = RoleWrapper{} + +// Wrap grabs the sequence number from the flag and wraps +// the tx with this nonce. Grabs the permission from the signer, +// as we still only support single sig on the cli +func (RoleWrapper) Wrap(tx basecoin.Tx) (basecoin.Tx, error) { + assume := viper.GetStringSlice(FlagAssumeRole) + + // we wrap from inside-out, so we must wrap them in the reverse order, + // so they are applied in the order the user intended + for i := len(assume) - 1; i >= 0; i-- { + r, err := parseRole(assume[i]) + if err != nil { + return tx, err + } + tx = roles.NewAssumeRoleTx(r, tx) + } + return tx, nil +} + +// Register adds the sequence flags to the cli +func (RoleWrapper) Register(fs *pflag.FlagSet) { + fs.StringSlice(FlagAssumeRole, nil, "Roles to assume (can use multiple times)") +} + +// parse role turns the string->byte... todo: support hex? +func parseRole(role string) ([]byte, error) { + return []byte(role), nil +}