0g-chain/internal/x/paychan/client/cli/cmd.go
2018-09-19 16:36:57 -04:00

326 lines
9.6 KiB
Go

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
}