package cli import ( "fmt" "io/ioutil" "os" "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/keys" "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" "github.com/kava-labs/kava/internal/x/paychan" ) // list of functions that return pointers to cobra commands // No local storage needed for cli acting as a sender func CreateChannelCmd(cdc *wire.Codec) *cobra.Command { flagTo := "to" flagCoins := "amount" cmd := &cobra.Command{ Use: "create", Short: "Create a new payment channel", Long: "Create a new unidirectional 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 channel.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { // Create a tx and cli "contexts": structs populated with info from common flags. txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). WithCodec(cdc). WithLogger(os.Stdout). WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) // Get sender address sender, err := cliCtx.GetFromAddress() if err != nil { return err } // Get receiver address toStr := viper.GetString(flagTo) receiver, err := sdk.AccAddressFromBech32(toStr) if err != nil { return err } // Get channel funding amount coinsString := viper.GetString(flagCoins) coins, err := sdk.ParseCoins(coinsString) if err != nil { return err } // Create the create channel msg to send msg := paychan.MsgCreate{ Participants: [2]sdk.AccAddress{sender, receiver}, Coins: coins, } err = msg.ValidateBasic() if err != nil { return err } // Build and sign the transaction, then broadcast to the blockchain return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().String(flagTo, "", "Recipient address of the payment channel.") cmd.Flags().String(flagCoins, "", "Amount of coins to fund the payment channel with.") return cmd } func GeneratePaymentCmd(cdc *wire.Codec) *cobra.Command { flagId := "chan-id" flagReceiverAmount := "rec-amt" // amount the receiver should received on closing the channel flagSenderAmount := "sen-amt" flagPaymentFile := "filename" cmd := &cobra.Command{ Use: "pay", Short: "Generate a new payment.", // TODO descriptions Long: "Generate a payment file (json) to send to the receiver as a payment.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { // Create a cli "context": struct populated with info from common flags. cliCtx := context.NewCLIContext(). WithCodec(cdc). WithLogger(os.Stdout). WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) // Get the paychan id id := paychan.ChannelID(viper.GetInt64(flagId)) // TODO make this default to pulling id from chain // Get channel receiver amount senderCoins, err := sdk.ParseCoins(viper.GetString(flagSenderAmount)) if err != nil { return err } // Get channel receiver amount receiverCoins, err := sdk.ParseCoins(viper.GetString(flagReceiverAmount)) if err != nil { return err } // create close paychan msg update := paychan.Update{ ChannelID: id, Payout: paychan.Payout{senderCoins, receiverCoins}, // empty sigs } // Sign the update as the sender keybase, err := keys.GetKeyBase() if err != nil { return err } name := cliCtx.FromAddressName passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) if err != nil { return err } bz := update.GetSignBytes() sig, pubKey, err := keybase.Sign(name, passphrase, bz) if err != nil { return err } update.Sigs = [1]paychan.UpdateSignature{{ PubKey: pubKey, CryptoSignature: sig, }} // Write out the update jsonUpdate, err := wire.MarshalJSONIndent(cdc, update) if err != nil { return err } paymentFile := viper.GetString(flagPaymentFile) err = ioutil.WriteFile(paymentFile, jsonUpdate, 0644) if err != nil { return err } fmt.Printf("Written payment out to %v.\n", paymentFile) return nil }, } cmd.Flags().Int(flagId, 0, "ID of the payment channel.") cmd.Flags().String(flagSenderAmount, "", "Total coins to payout to sender on channel close.") cmd.Flags().String(flagReceiverAmount, "", "Total coins to payout to sender on channel close.") cmd.Flags().String(flagPaymentFile, "payment.json", "File name to write the payment into.") return cmd } func VerifyPaymentCmd(cdc *wire.Codec, paychanStoreName string) *cobra.Command { flagPaymentFile := "payment" cmd := &cobra.Command{ Use: "verify", Short: "Verify a payment file.", Long: "Verify that a received payment can be used to close a channel.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { // Create a cli "context": struct populated with info from common flags. cliCtx := context.NewCLIContext(). WithCodec(cdc). WithLogger(os.Stdout). WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) // read in update bz, err := ioutil.ReadFile(viper.GetString(flagPaymentFile)) if err != nil { // TODO add nice message about how to feed in stdin return err } // decode json var update paychan.Update cdc.UnmarshalJSON(bz, &update) // get the channel from the node res, err := cliCtx.QueryStore(paychan.GetChannelKey(update.ChannelID), paychanStoreName) if len(res) == 0 || err != nil { return errors.Errorf("channel with ID '%d' does not exist", update.ChannelID) } var channel paychan.Channel cdc.MustUnmarshalBinary(res, &channel) //verify verificationError := paychan.VerifyUpdate(channel, update) // print result if verificationError == nil { fmt.Printf("Payment is valid for channel '%d'.\n", update.ChannelID) } else { fmt.Printf("Payment is NOT valid for channel '%d'.\n", update.ChannelID) fmt.Println(verificationError) } return nil }, } cmd.Flags().String(flagPaymentFile, "payment.json", "File name to read the payment from.") return cmd } func SubmitPaymentCmd(cdc *wire.Codec) *cobra.Command { flagPaymentFile := "payment" cmd := &cobra.Command{ Use: "submit", Short: "Submit a payment to the blockchain to close the channel.", Long: fmt.Sprintf("Submit a payment to the blockchain to either close a channel immediately (if you are the receiver) or after a dispute period of %d blocks (if you are the sender).", paychan.ChannelDisputeTime), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { // Create a tx and cli "contexts": structs populated with info from common flags. txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). WithCodec(cdc). WithLogger(os.Stdout). WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) // Get sender address submitter, err := cliCtx.GetFromAddress() if err != nil { return err } // read in update bz, err := ioutil.ReadFile(viper.GetString(flagPaymentFile)) if err != nil { return err } // decode json var update paychan.Update cdc.UnmarshalJSON(bz, &update) // Create the create channel msg to send msg := paychan.MsgSubmitUpdate{ Update: update, Submitter: submitter, } err = msg.ValidateBasic() if err != nil { return err } // Build and sign the transaction, then broadcast to the blockchain return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().String(flagPaymentFile, "payment.json", "File to read the payment from.") return cmd } func GetChannelCmd(cdc *wire.Codec, paychanStoreName string) *cobra.Command { flagId := "chan-id" cmd := &cobra.Command{ Use: "get", Short: "Get info on a channel.", Long: "Get the details of a non closed channel plus any submitted update waiting to be executed.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { // Create a cli "context": struct populated with info from common flags. cliCtx := context.NewCLIContext(). WithCodec(cdc). WithLogger(os.Stdout). WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) // Get channel ID id := paychan.ChannelID(viper.GetInt64(flagId)) // Get the channel from the node res, err := cliCtx.QueryStore(paychan.GetChannelKey(id), paychanStoreName) if len(res) == 0 || err != nil { return errors.Errorf("channel with ID '%d' does not exist", id) } var channel paychan.Channel cdc.MustUnmarshalBinary(res, &channel) // Convert the channel to a json object for pretty printing jsonChannel, err := wire.MarshalJSONIndent(cdc, channel) if err != nil { return err } // print out json channel fmt.Println(string(jsonChannel)) // Get any submitted updates from the node res, err = cliCtx.QueryStore(paychan.GetSubmittedUpdateKey(id), paychanStoreName) if err != nil { return err } // Print out the submitted update if it exists if len(res) != 0 { var submittedUpdate paychan.SubmittedUpdate cdc.MustUnmarshalBinary(res, &submittedUpdate) // Convert the submitted update to a json object for pretty printing jsonSU, err := wire.MarshalJSONIndent(cdc, submittedUpdate) if err != nil { return err } // print out json submitted update fmt.Println(string(jsonSU)) } return nil }, } cmd.Flags().Int(flagId, 0, "ID of the payment channel.") return cmd }