tidy cli commands

This commit is contained in:
rhuairahrighairigh 2018-07-14 23:27:56 +01:00
parent db7d440ca1
commit e595382288
4 changed files with 231 additions and 121 deletions

View File

@ -17,6 +17,7 @@ import (
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
//ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" //ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli"
//stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" //stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli"
paychancmd "github.com/kava-labs/kava/internal/x/paychan/client/cli"
"github.com/kava-labs/kava/internal/app" "github.com/kava-labs/kava/internal/app"
"github.com/kava-labs/kava/internal/lcd" "github.com/kava-labs/kava/internal/lcd"
@ -57,7 +58,6 @@ func main() {
rootCmd.AddCommand( rootCmd.AddCommand(
client.PostCommands( // this just wraps the input cmds with common flags client.PostCommands( // this just wraps the input cmds with common flags
bankcmd.SendTxCmd(cdc), bankcmd.SendTxCmd(cdc),
// paychan commands...
//ibccmd.IBCTransferCmd(cdc), //ibccmd.IBCTransferCmd(cdc),
//ibccmd.IBCRelayCmd(cdc), //ibccmd.IBCRelayCmd(cdc),
//stakecmd.GetCmdCreateValidator(cdc), //stakecmd.GetCmdCreateValidator(cdc),
@ -66,6 +66,19 @@ func main() {
//stakecmd.GetCmdUnbond(cdc), //stakecmd.GetCmdUnbond(cdc),
)...) )...)
paychanCmd := &cobra.Command{
Use: "paychan",
Short: "Payment channel subcommands",
}
stakeCmd.AddCommand(
client.PostCommands(
paychancmd.CreatePaychanCmd(cdc),
paychancmd.GenerateNewStateCmd(cdc),
paychancmd.ClosePaychanCmd(cdc),
)...)
rootCmd.AddCommand(
paychanCmd,
)
// add proxy, version and key info // add proxy, version and key info
rootCmd.AddCommand( rootCmd.AddCommand(
client.LineBreak, client.LineBreak,

View File

@ -1,30 +1,37 @@
package cli package cli
import ( import (
"encoding/base64"
"fmt" "fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/keys"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/wire"
"github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/x/auth"
//"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/kava-labs/kava/internal/x/paychan" "github.com/kava-labs/kava/internal/x/paychan"
) )
// list of functions that return pointers to cobra commands // list of functions that return pointers to cobra commands
// No local storage needed for cli acting as a sender // No local storage needed for cli acting as a sender
// Currently minimum set of cli commands are implemented:
// create paychan - create and fund
// generate new paychan state - print a half signed close tx (sender signs)
// close paychan - close using state (receiver signs)
// Future cli commands:
// create paychan // create paychan
// close paychan // close paychan
// get paychan(s) // get paychan(s)
// send paychan payment // send paychan payment
// get balance from receiver // get balance from receiver
// minimum
// create paychan (sender signs)
// create state update (sender signs) (just a half signed close tx, (json encoded?))
// close paychan (receiver signs) (provide state update as arg)
func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command { func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command {
flagTo := "to" flagTo := "to"
flagAmount := "amount" flagAmount := "amount"
@ -35,20 +42,9 @@ func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command {
Long: "Create a new payment channel from a local address to a remote address, funded with some amount of coins. These coins are removed from the sender account and put into the payment channel.", Long: "Create a new payment channel from a local address to a remote address, funded with some amount of coins. These coins are removed from the sender account and put into the payment channel.",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// get args: from, to, amount
// create a "client context" stuct populated with info from common flags // Create a "client context" stuct populated with info from common flags
ctx := context.NewCoreContextFromViper() ctx := context.NewCoreContextFromViper()
// ChainID: chainID,
// Height: viper.GetInt64(client.FlagHeight),
// Gas: viper.GetInt64(client.FlagGas),
// TrustNode: viper.GetBool(client.FlagTrustNode),
// FromAddressName: viper.GetString(client.FlagName),
// NodeURI: nodeURI,
// AccountNumber: viper.GetInt64(client.FlagAccountNumber),
// Sequence: viper.GetInt64(client.FlagSequence),
// Client: rpc,
// Decoder: nil,
// AccountStore: "acc",
// Get sender adress // Get sender adress
senderAddress, err := ctx.GetFromAddress() senderAddress, err := ctx.GetFromAddress()
@ -73,16 +69,17 @@ func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command {
// Create the create channel msg to send // Create the create channel msg to send
// TODO write NewMsgCreate func? // TODO write NewMsgCreate func?
msg := paychan.MsgCreate{ msg := paychan.MsgCreate{
sender: senderAddress, Sender: senderAddress,
receiver: receiverAddress, Receiver: receiverAddress,
amount: amount, Amount: amount,
} }
// Build and sign the transaction, then broadcast to Tendermint // Build and sign the transaction, then broadcast to the blockchain
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc)
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
}, },
} }
cmd.Flags().String(flagTo, "", "Recipient address of the payment channel") cmd.Flags().String(flagTo, "", "Recipient address of the payment channel")
@ -90,34 +87,37 @@ func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command {
return cmd return cmd
} }
func CreateNewStateCmd(cdc *wire.Codec) *cobra.Command { func GenerateNewStateCmd(cdc *wire.Codec) *cobra.Command {
flagId := "id" flagId := "id"
flagTo := "to" flagTo := "to"
flagAmount := "amount" flagAmount := "amount"
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "localstate", Use: "new-state",
Short: "Create a payment channel claim", Short: "Generate a new payment channel state.",
Long: "", Long: "Generate a new state for an existing payment channel and print it out. The new state is represented as a half signed close transaction.",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// sender(name) receiver id
// create a "client context" stuct populated with info from common flags // Create a "client context" stuct populated with info from common flags
ctx := context.NewCoreContextFromViper() ctx := context.NewCoreContextFromViper()
// Get sender adress // Get sender adress
senderAddress, err := ctx.GetFromAddress() senderAddress, err := ctx.GetFromAddress()
if err != nil { if err != nil {
return err return err
} }
// Get id
id := viper.GetInt(flagId) // Get the paychan id
id := viper.GetInt64(flagId)
// Get receiver address // Get receiver address
toStr := viper.GetString(flagTo) toStr := viper.GetString(flagTo)
receiverAddress, err := sdk.GetAccAddressBech32(toStr) receiverAddress, err := sdk.GetAccAddressBech32(toStr)
if err != nil { if err != nil {
return err return err
} }
// Get channel receiver amount // Get channel receiver amount
amountString := viper.GetString(flagAmount) amountString := viper.GetString(flagAmount)
amount, err := sdk.ParseCoins(amountString) amount, err := sdk.ParseCoins(amountString)
@ -125,26 +125,26 @@ func CreateNewStateCmd(cdc *wire.Codec) *cobra.Command {
return err return err
} }
// create MsgClose // create close paychan msg
msg := paychan.MsgClose{ msg := paychan.MsgClose{
sender: senderAddress, Sender: senderAddress,
receiver: receiverAddress, Receiver: receiverAddress,
id: id, Id: id,
receiverAmount: amount, ReceiverAmount: amount,
} }
// half sign it // Sign the msg as the sender
txBytes, err := EnsureSignBuild(ctx, ctx.FromAddressName, msg, cdc) txBytes, err := EnsureSignBuild(ctx, ctx.FromAddressName, msg, cdc)
if err != nil { if err != nil {
return err return err
} }
// print it out // Print out the signed msg
fmt.Println("txBytes:", txBytes)
fmt.Println(txBytes) encodedTxBytes := make([]byte, base64.StdEncoding.EncodedLen(len(txBytes)))
base64.StdEncoding.Encode(encodedTxBytes, txBytes)
fmt.Println("base64TxBytes:", encodedTxBytes)
return nil
}, },
} }
cmd.Flags().Int(flagId, 0, "ID of the payment channel.") cmd.Flags().Int(flagId, 0, "ID of the payment channel.")
@ -153,50 +153,147 @@ func CreateNewStateCmd(cdc *wire.Codec) *cobra.Command {
return cmd return cmd
} }
// sign and build the transaction from the msg
func EnsureSignBuild(ctx context.CoreContext, name string, msg sdk.Msg, cdc *wire.Codec) ([]byte, error) {
ctx, err = EnsureAccountNumber(ctx)
if err != nil {
return nil, err
}
// default to next sequence number if none provided
ctx, err = EnsureSequence(ctx)
if err != nil {
return nil, err
}
passphrase, err := ctx.GetPassphraseFromStdin(name)
if err != nil {
return nil, err
}
txBytes, err := ctx.SignAndBuild(name, passphrase, msg, cdc)
if err != nil {
return nil, err
}
return txBytes
}
func ClosePaychanCmd(cdc *wire.Codec) *cobra.Command { func ClosePaychanCmd(cdc *wire.Codec) *cobra.Command {
flagId := "id"
flagTo := "to"
flagState := "state" flagState := "state"
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "close", Use: "close",
Short: "Close a payment channel", Short: "Close a payment channel, given a state",
Long: "", Long: "Close an existing payment channel with a state received from a sender.",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// get sender, reciver, id ctx := context.NewCoreContextFromViper()
// get state
// sign the state tx with receiver // Get the sender-signed close tx
// broadcast to tendermint state := viper.GetString(flagState)
txBytes, err := base64.StdEncoding.DecodeString(state)
if err != nil {
return err
}
stdTx := auth.StdTx{}
cdc.UnmarshalBinary(txBytes, stdTx)
// Sign close tx
// ensure contxt has up to date account and sequence numbers
ctx, err = Ensure(ctx)
if err != nil {
return err
}
// Sign message (asks user for password)
_, sig, err := UserSignMsg(ctx, ctx.FromAddressName, stdTx.Msg)
if err != nil {
return err
}
// Append signature to close tx
stdTx.Signatures = append(stdTx.Signatures, sig)
// encode close tx
txBytes, err = cdc.MarshalBinary(stdTx)
if err != nil {
return err
}
// Broadcast close tx to the blockchain
res, err := ctx.BroadcastTx(txBytes)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
}, },
} }
//cmd.Flags().String(flagTo, "", "Recipient address of the payment channel") cmd.Flags().String(flagState, "", "State received from sender.")
//cmd.Flags().String(flagAmount, "", "Amount of coins to fund the paymetn channel with")
return cmd return cmd
} }
// HELPER FUNCTIONS
// This is a partial refactor of cosmos-sdk/client/context.
// Existing API was awkard to use for paychans.
func Ensure(ctx context.CoreContext) (context.CoreContext, error) {
ctx, err := context.EnsureAccountNumber(ctx)
if err != nil {
return ctx, err
}
// default to next sequence number if none provided
ctx, err = context.EnsureSequence(ctx)
if err != nil {
return ctx, err
}
return ctx, nil
}
func UserSignMsg(ctx context.CoreContext, name string, msg sdk.Msg) (signMsg auth.StdSignMsg, stdSig auth.StdSignature, err error) {
// TODO check how to handle non error return values on error. Returning empty versions doesn't seem right.
passphrase, err := ctx.GetPassphraseFromStdin(name)
if err != nil {
return signMsg, stdSig, err
}
// build the Sign Messsage from the Standard Message
chainID := ctx.ChainID
if chainID == "" {
return signMsg, stdSig, errors.Errorf("Chain ID required but not specified")
}
accnum := ctx.AccountNumber
sequence := ctx.Sequence
signMsg = auth.StdSignMsg{
ChainID: chainID,
AccountNumbers: []int64{accnum},
Sequences: []int64{sequence},
Msg: msg,
Fee: auth.NewStdFee(ctx.Gas, sdk.Coin{}), // TODO run simulate to estimate gas?
}
keybase, err := keys.GetKeyBase()
if err != nil {
return signMsg, stdSig, err
}
// sign and build
bz := signMsg.Bytes()
sig, pubkey, err := keybase.Sign(name, passphrase, bz)
if err != nil {
return signMsg, stdSig, err
}
stdSig = auth.StdSignature{
PubKey: pubkey,
Signature: sig,
AccountNumber: accnum,
Sequence: sequence,
}
return signMsg, stdSig, nil
}
func Build(cdc *wire.Codec, signMsg auth.StdSignMsg, sig auth.StdSignature) ([]byte, error) {
tx := auth.NewStdTx(signMsg.Msg, signMsg.Fee, []auth.StdSignature{sig})
return cdc.MarshalBinary(tx)
}
func EnsureSignBuild(ctx context.CoreContext, name string, msg sdk.Msg, cdc *wire.Codec) ([]byte, error) {
//Ensure context has up to date account and sequence numbers
ctx, err := Ensure(ctx)
if err != nil {
return nil, err
}
// Sign message (asks user for password)
signMsg, sig, err := UserSignMsg(ctx, name, msg)
if err != nil {
return nil, err
}
// Create tx and marshal
txBytes, err := Build(cdc, signMsg, sig)
if err != nil {
return nil, err
}
return txBytes, nil
}

View File

@ -25,7 +25,7 @@ func NewHandler(k Keeper) sdk.Handler {
// Leaves validation to the keeper methods. // Leaves validation to the keeper methods.
func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result { func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result {
// TODO maybe remove tags for first version // TODO maybe remove tags for first version
tags, err := k.CreatePaychan(ctx, msg.sender, msg.receiver, msg.amount) tags, err := k.CreatePaychan(ctx, msg.Sender, msg.Receiver, msg.Amount)
if err != nil { if err != nil {
return err.Result() return err.Result()
} }
@ -39,7 +39,7 @@ func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result {
// Leaves validation to the keeper methods. // Leaves validation to the keeper methods.
func handleMsgClose(ctx sdk.Context, k Keeper, msg MsgClose) sdk.Result { func handleMsgClose(ctx sdk.Context, k Keeper, msg MsgClose) sdk.Result {
// TODO maybe remove tags for first version // TODO maybe remove tags for first version
tags, err := k.ClosePaychan(ctx, msg.sender, msg.receiver, msg.id, msg.receiverAmount) tags, err := k.ClosePaychan(ctx, msg.Sender, msg.Receiver, msg.Id, msg.ReceiverAmount)
if err != nil { if err != nil {
return err.Result() return err.Result()
} }

View File

@ -41,9 +41,9 @@ type Paychan struct {
// A message to create a payment channel. // A message to create a payment channel.
type MsgCreate struct { type MsgCreate struct {
// maybe just wrap a paychan struct // maybe just wrap a paychan struct
sender sdk.Address Sender sdk.Address
receiver sdk.Address Receiver sdk.Address
amount sdk.Coins Amount sdk.Coins
} }
// Create a new message. // Create a new message.
@ -66,9 +66,9 @@ func (msg MsgCreate) GetSignBytes() []byte {
ReceiverAddr string `json:"receiver_addr"` ReceiverAddr string `json:"receiver_addr"`
Amount sdk.Coins `json:"amount"` Amount sdk.Coins `json:"amount"`
}{ }{
SenderAddr: sdk.MustBech32ifyAcc(msg.sender), SenderAddr: sdk.MustBech32ifyAcc(msg.Sender),
ReceiverAddr: sdk.MustBech32ifyAcc(msg.receiver), ReceiverAddr: sdk.MustBech32ifyAcc(msg.Receiver),
Amount: msg.amount, Amount: msg.Amount,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -81,21 +81,21 @@ func (msg MsgCreate) ValidateBasic() sdk.Error {
// Validate without external information (such as account balance) // Validate without external information (such as account balance)
// check if all fields present / not 0 valued // check if all fields present / not 0 valued
if len(msg.sender) == 0 { if len(msg.Sender) == 0 {
return sdk.ErrInvalidAddress(msg.sender.String()) return sdk.ErrInvalidAddress(msg.Sender.String())
} }
if len(msg.receiver) == 0 { if len(msg.Receiver) == 0 {
return sdk.ErrInvalidAddress(msg.receiver.String()) return sdk.ErrInvalidAddress(msg.Receiver.String())
} }
if len(msg.amount) == 0 { if len(msg.Amount) == 0 {
return sdk.ErrInvalidCoins(msg.amount.String()) return sdk.ErrInvalidCoins(msg.Amount.String())
} }
// Check if coins are sorted, non zero, non negative // Check if coins are sorted, non zero, non negative
if !msg.amount.IsValid() { if !msg.Amount.IsValid() {
return sdk.ErrInvalidCoins(msg.amount.String()) return sdk.ErrInvalidCoins(msg.Amount.String())
} }
if !msg.amount.IsPositive() { if !msg.Amount.IsPositive() {
return sdk.ErrInvalidCoins(msg.amount.String()) return sdk.ErrInvalidCoins(msg.Amount.String())
} }
// TODO check if Address valid? // TODO check if Address valid?
return nil return nil
@ -103,17 +103,17 @@ func (msg MsgCreate) ValidateBasic() sdk.Error {
func (msg MsgCreate) GetSigners() []sdk.Address { func (msg MsgCreate) GetSigners() []sdk.Address {
// Only sender must sign to create a paychan // Only sender must sign to create a paychan
return []sdk.Address{msg.sender} return []sdk.Address{msg.Sender}
} }
// A message to close a payment channel. // A message to close a payment channel.
type MsgClose struct { type MsgClose struct {
// have to include sender and receiver in msg explicitly (rather than just universal paychanID) // have to include sender and receiver in msg explicitly (rather than just universal paychanID)
// this gives ability to verify signatures with no external information // this gives ability to verify signatures with no external information
sender sdk.Address Sender sdk.Address
receiver sdk.Address Receiver sdk.Address
id int64 // TODO is another int type better? Id int64 // TODO is another int type better?
receiverAmount sdk.Coins // amount the receiver should get - sender amount implicit with paychan balance ReceiverAmount sdk.Coins // amount the receiver should get - sender amount implicit with paychan balance
} }
// func (msg MsgClose) NewMsgClose(sender sdk.Address, receiver sdk.Address, id integer, receiverAmount sdk.Coins) MsgClose { // func (msg MsgClose) NewMsgClose(sender sdk.Address, receiver sdk.Address, id integer, receiverAmount sdk.Coins) MsgClose {
@ -135,10 +135,10 @@ func (msg MsgClose) GetSignBytes() []byte {
Id int64 `json:"id"` Id int64 `json:"id"`
ReceiverAmount sdk.Coins `json:"receiver_amount"` ReceiverAmount sdk.Coins `json:"receiver_amount"`
}{ }{
SenderAddr: sdk.MustBech32ifyAcc(msg.sender), SenderAddr: sdk.MustBech32ifyAcc(msg.Sender),
ReceiverAddr: sdk.MustBech32ifyAcc(msg.receiver), ReceiverAddr: sdk.MustBech32ifyAcc(msg.Receiver),
Id: msg.id, Id: msg.Id,
ReceiverAmount: msg.receiverAmount, ReceiverAmount: msg.ReceiverAmount,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -148,25 +148,25 @@ func (msg MsgClose) GetSignBytes() []byte {
func (msg MsgClose) ValidateBasic() sdk.Error { func (msg MsgClose) ValidateBasic() sdk.Error {
// check if all fields present / not 0 valued // check if all fields present / not 0 valued
if len(msg.sender) == 0 { if len(msg.Sender) == 0 {
return sdk.ErrInvalidAddress(msg.sender.String()) return sdk.ErrInvalidAddress(msg.Sender.String())
} }
if len(msg.receiver) == 0 { if len(msg.Receiver) == 0 {
return sdk.ErrInvalidAddress(msg.receiver.String()) return sdk.ErrInvalidAddress(msg.Receiver.String())
} }
if len(msg.receiverAmount) == 0 { if len(msg.ReceiverAmount) == 0 {
return sdk.ErrInvalidCoins(msg.receiverAmount.String()) return sdk.ErrInvalidCoins(msg.ReceiverAmount.String())
} }
// check id ≥ 0 // check id ≥ 0
if msg.id < 0 { if msg.Id < 0 {
return sdk.ErrInvalidAddress(strconv.Itoa(int(msg.id))) // TODO implement custom errors return sdk.ErrInvalidAddress(strconv.Itoa(int(msg.Id))) // TODO implement custom errors
} }
// Check if coins are sorted, non zero, non negative // Check if coins are sorted, non zero, non negative
if !msg.receiverAmount.IsValid() { if !msg.ReceiverAmount.IsValid() {
return sdk.ErrInvalidCoins(msg.receiverAmount.String()) return sdk.ErrInvalidCoins(msg.ReceiverAmount.String())
} }
if !msg.receiverAmount.IsPositive() { if !msg.ReceiverAmount.IsPositive() {
return sdk.ErrInvalidCoins(msg.receiverAmount.String()) return sdk.ErrInvalidCoins(msg.ReceiverAmount.String())
} }
// TODO check if Address valid? // TODO check if Address valid?
return nil return nil
@ -174,5 +174,5 @@ func (msg MsgClose) ValidateBasic() sdk.Error {
func (msg MsgClose) GetSigners() []sdk.Address { func (msg MsgClose) GetSigners() []sdk.Address {
// Both sender and receiver must sign in order to close a channel // Both sender and receiver must sign in order to close a channel
return []sdk.Address{msg.sender, msg.receiver} return []sdk.Address{msg.Sender, msg.Receiver}
} }