diff --git a/internal/x/paychan/README.md b/internal/x/paychan/README.md index 1d9a312c..6adb635c 100644 --- a/internal/x/paychan/README.md +++ b/internal/x/paychan/README.md @@ -8,7 +8,6 @@ Simplifications: TODO - in code TODOs - - write basic cmds - Tidy up - method descriptions, heading comments, remove uneccessary comments, README/docs - chnge module name to "channel"? - Find a better name for Queue - clarify distinction between int slice and abstract queue concept @@ -16,6 +15,7 @@ Simplifications: - find nicer name for payout - add Gas usage - add tags (return channel id on creation) + - refactor cmds to be able to test them, then test them - use custom errors instead of using sdk.ErrInternal - split off signatures from update as with txs/msgs - testing easier, code easier to use, doesn't store sigs unecessarily on chain - consider removing pubKey from UpdateSignature - instead let channel module access accountMapper diff --git a/internal/x/paychan/client/cmd/cmd.go b/internal/x/paychan/client/cmd/cmd.go index ccc1fb2c..bed4c1c4 100644 --- a/internal/x/paychan/client/cmd/cmd.go +++ b/internal/x/paychan/client/cmd/cmd.go @@ -3,6 +3,7 @@ package cli import ( "encoding/base64" "fmt" + "io/ioutil" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -21,62 +22,54 @@ import ( // list of functions that return pointers to cobra commands // No local storage needed for cli acting as a sender -// Current minimal set of cli commands: -// create paychan - create and fund (sender signs tx) -// generate new update - print a signed update (from sender) -// submit update - send update to chain (either can sign tx) -// Future cli commands: -// create paychan -// close paychan -// get paychan(s) -// send paychan payment -// get balance from receiver - -/* -func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command { +func CreateChannelCmd(cdc *wire.Codec) *cobra.Command { flagTo := "to" - flagAmount := "amount" + flagCoins := "amount" cmd := &cobra.Command{ Use: "create", Short: "Create a new 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.", + 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 "client context" stuct populated with info from common flags ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + // ctx.PrintResponse = true TODO is this needed for channelID // Get sender adress - senderAddress, err := ctx.GetFromAddress() + sender, err := ctx.GetFromAddress() if err != nil { return err } // Get receiver address toStr := viper.GetString(flagTo) - receiverAddress, err := sdk.GetAccAddressBech32(toStr) + receiver, err := sdk.GetAccAddressBech32(toStr) if err != nil { return err } // Get channel funding amount - amountString := viper.GetString(flagAmount) - amount, err := sdk.ParseCoins(amountString) + coinsString := viper.GetString(flagCoins) + coins, err := sdk.ParseCoins(coinsString) if err != nil { return err } // Create the create channel msg to send - // TODO write NewMsgCreate func? msg := paychan.MsgCreate{ - Sender: senderAddress, - Receiver: receiverAddress, - Amount: amount, + Participants: []sdk.AccAddress{sender, receiver}, + Coins: coins, } + err = msg.ValidateBasic() + if err != nil { + return err + } + // Build and sign the transaction, then broadcast to the blockchain - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) if err != nil { return err } @@ -84,77 +77,180 @@ func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command { return nil }, } - cmd.Flags().String(flagTo, "", "Recipient address of the payment channel") - cmd.Flags().String(flagAmount, "", "Amount of coins to fund the paymetn channel with") + cmd.Flags().String(flagTo, "", "Recipient address of the payment channel.") + cmd.Flags().String(flagAmount, "", "Amount of coins to fund the payment channel with.") return cmd } -func GenerateNewStateCmd(cdc *wire.Codec) *cobra.Command { - flagId := "id" - flagTo := "to" - flagAmount := "amount" +func GeneratePaymentCmd(cdc *wire.Codec) *cobra.Command { + flagId := "id" // ChannelID + flagReceiverAmount := "r-amount" // amount the receiver should received on closing the channel + flagSenderAmount := "s-amount" // cmd := &cobra.Command{ - Use: "new-state", - Short: "Generate a new payment channel state.", - 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, signed by the sender.", + Use: "pay", + Short: "Generate a .", // TODO descriptions + Long: "Generate a new ", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { // Create a "client context" stuct populated with info from common flags ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + // ctx.PrintResponse = false TODO is this needed to stop any other output messing up json? // Get sender adress - senderAddress, err := ctx.GetFromAddress() - if err != nil { - return err - } + // senderAddress, err := ctx.GetFromAddress() + // if err != nil { + // return err + // } // Get the paychan id - id := viper.GetInt64(flagId) + id := viper.GetInt64(flagId) // TODO make this default to pulling id from chain - // Get receiver address - toStr := viper.GetString(flagTo) - receiverAddress, err := sdk.GetAccAddressBech32(toStr) + // Get channel receiver amount + senderCoins, err := sdk.ParseCoins(viper.GetString(flagSenderAmount)) if err != nil { return err } - // Get channel receiver amount - amountString := viper.GetString(flagAmount) - amount, err := sdk.ParseCoins(amountString) + receiverCoins, err := sdk.ParseCoins(viper.GetString(flagReceiverAmount)) if err != nil { return err } // create close paychan msg - msg := paychan.MsgClose{ - Sender: senderAddress, - Receiver: receiverAddress, - Id: id, - ReceiverAmount: amount, + update := paychan.Update{ + ChannelID: id, + Payout: paychan.Payout{senderCoins, receiverCoins}, + // empty sigs } - // Sign the msg as the sender - txBytes, err := EnsureSignBuild(ctx, ctx.FromAddressName, msg, cdc) + // Sign the update as the sender + keybase, err := keys.GetKeyBase() if err != nil { return err } + name := ctx.FromAddressName + passphrase, err := ctx.GetPassphraseFromStdin(name) + 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, + } + + // Print out the update + jsonUpdate := cdc.MarshalJSONIndent(update) + fmt.Println(string(jsonUpdate)) - // Print out the signed msg - fmt.Println("txBytes:", txBytes) - //encodedTxBytes := make([]byte, base64.StdEncoding.EncodedLen(len(txBytes))) - encodedTxBytes := base64.StdEncoding.EncodeToString(txBytes) - fmt.Println("base64TxBytes:", encodedTxBytes) return nil }, } cmd.Flags().Int(flagId, 0, "ID of the payment channel.") - cmd.Flags().String(flagTo, "", "Recipient address of the payment channel") - cmd.Flags().String(flagAmount, "", "Amount of coins to fund the paymetn channel with") + cmd.Flags().String(flagSenderAmount, "", "") + cmd.Flags().String(flagReceiverAmount, "", "") return cmd } +func VerifyPaymentCmd(cdc *wire.Codec, paychanStoreName, string) *cobra.Command { + + cmd := &cobra.Command{ + Use: "verify", + Short: "", // TODO + Long: "", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + + // read in update + bz, err := ioutil.ReadAll(os.Stdin) + 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 := ctx.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 + updateIsOK := paychan.Keeper.VerifyUpdate(channel ,update) + + // print result + fmt.Println(updateIsOK) + + return nil + }, + } + + return cmd +} + +func SubmitPaymentChannelCmd(cdc *wire.Codec) *cobra.Command { + + cmd := &cobra.Command{ + Use: "submit", + Short: "", + Long: "", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + + // Create a "client context" stuct populated with info from common flags + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + // ctx.PrintResponse = true TODO is this needed for channelID + + // Get sender adress + submitter, err := ctx.GetFromAddress() + if err != nil { + return err + } + + // read in update + bz, err := ioutil.ReadAll(os.Stdin) + 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 + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + + return cmd +} + +/* func ClosePaychanCmd(cdc *wire.Codec) *cobra.Command { flagState := "state" diff --git a/internal/x/paychan/handler.go b/internal/x/paychan/handler.go index c4d949a8..4bd93fd2 100644 --- a/internal/x/paychan/handler.go +++ b/internal/x/paychan/handler.go @@ -45,10 +45,10 @@ func handleMsgSubmitUpdate(ctx sdk.Context, k Keeper, msg MsgSubmitUpdate) sdk.R participants := channel.Participants // if only sender signed - if reflect.DeepEqual(msg.submitter, participants[0]) { + if reflect.DeepEqual(msg.Submitter, participants[0]) { tags, err = k.InitCloseChannelBySender(ctx, msg.Update) // else if receiver signed - } else if reflect.DeepEqual(msg.submitter, participants[len(participants)-1]) { + } else if reflect.DeepEqual(msg.Submitter, participants[len(participants)-1]) { tags, err = k.CloseChannelByReceiver(ctx, msg.Update) } diff --git a/internal/x/paychan/keeper.go b/internal/x/paychan/keeper.go index 318a3435..4c1dc0ca 100644 --- a/internal/x/paychan/keeper.go +++ b/internal/x/paychan/keeper.go @@ -74,7 +74,12 @@ func (k Keeper) CreateChannel(ctx sdk.Context, sender sdk.AccAddress, receiver s func (k Keeper) InitCloseChannelBySender(ctx sdk.Context, update Update) (sdk.Tags, sdk.Error) { // This is roughly the default path for non unidirectional channels - err := k.validateUpdate(ctx, update) + // get the channel + channel, found := k.getChannel(ctx, update.ChannelID) + if !found { + return nil, sdk.ErrInternal("Channel doesn't exist") + } + err := k.VerifyUpdate(channel, update) if err != nil { return nil, err } @@ -113,7 +118,12 @@ func (k Keeper) InitCloseChannelBySender(ctx sdk.Context, update Update) (sdk.Ta func (k Keeper) CloseChannelByReceiver(ctx sdk.Context, update Update) (sdk.Tags, sdk.Error) { - err := k.validateUpdate(ctx, update) + // get the channel + channel, found := k.getChannel(ctx, update.ChannelID) + if !found { + return nil, sdk.ErrInternal("Channel doesn't exist") + } + err := k.VerifyUpdate(channel, update) if err != nil { return nil, err } @@ -152,12 +162,8 @@ func (k Keeper) CloseChannelByReceiver(ctx sdk.Context, update Update) (sdk.Tags // return returnUpdate // } -func (k Keeper) validateUpdate(ctx sdk.Context, update Update) sdk.Error { - // Check that channel exists - channel, found := k.getChannel(ctx, update.ChannelID) - if !found { - return sdk.ErrInternal("Channel doesn't exist") - } +func (k Keeper) VerifyUpdate(channel Channel, update Update) sdk.Error { + // Check the num of payout participants match channel participants if len(update.Payout) != len(channel.Participants) { return sdk.ErrInternal("Payout doesn't match number of channel participants") @@ -177,7 +183,7 @@ func (k Keeper) validateUpdate(ctx sdk.Context, update Update) sdk.Error { return sdk.ErrInternal("Payout amount doesn't match channel amount") } // Check sender signature is OK - if !k.verifySignatures(ctx, channel, update) { + if !k.verifySignatures(channel, update) { return sdk.ErrInternal("Signature on update not valid") } return nil @@ -206,7 +212,7 @@ func (k Keeper) closeChannel(ctx sdk.Context, update Update) (sdk.Tags, sdk.Erro return tags, nil } -func (k Keeper) verifySignatures(ctx sdk.Context, channel Channel, update Update) bool { +func (k Keeper) verifySignatures(channel Channel, update Update) bool { // In non unidirectional channels there will be more than one signature to check signBytes := update.GetSignBytes() @@ -326,7 +332,7 @@ func (k Keeper) getSubmittedUpdateKey(channelID ChannelID) []byte { func (k Keeper) getChannel(ctx sdk.Context, channelID ChannelID) (Channel, bool) { // load from DB store := ctx.KVStore(k.storeKey) - bz := store.Get(k.getChannelKey(channelID)) + bz := store.Get(k.GetChannelKey(channelID)) var channel Channel if bz == nil { @@ -344,13 +350,13 @@ func (k Keeper) setChannel(ctx sdk.Context, channel Channel) { // marshal bz := k.cdc.MustMarshalBinary(channel) // panics if something goes wrong // write to db - key := k.getChannelKey(channel.ID) + key := k.GetChannelKey(channel.ID) store.Set(key, bz) // panics if something goes wrong } func (k Keeper) deleteChannel(ctx sdk.Context, channelID ChannelID) { store := ctx.KVStore(k.storeKey) - store.Delete(k.getChannelKey(channelID)) + store.Delete(k.GetChannelKey(channelID)) // TODO does this have return values? What happens when key doesn't exist? } @@ -373,7 +379,7 @@ func (k Keeper) getNewChannelID(ctx sdk.Context) ChannelID { return newID } -func (k Keeper) getChannelKey(channelID ChannelID) []byte { +func (k Keeper) GetChannelKey(channelID ChannelID) []byte { return []byte(fmt.Sprintf("channel:%d", channelID)) } func (k Keeper) getLastChannelIDKey() []byte { diff --git a/internal/x/paychan/types.go b/internal/x/paychan/types.go index 7c4c0e38..3324a231 100644 --- a/internal/x/paychan/types.go +++ b/internal/x/paychan/types.go @@ -45,7 +45,7 @@ type UpdateSignature struct { CryptoSignature crypto.Signature } -type Payout []sdk.Coins // a list of coins to be paid to each of Channel.Participants +type Payout [2]sdk.Coins // a list of coins to be paid to each of Channel.Participants func (p Payout) IsNotNegative() bool { result := true for _, coins := range p { @@ -195,7 +195,7 @@ func (msg MsgCreate) GetSigners() []sdk.AccAddress { // A message to close a payment channel. type MsgSubmitUpdate struct { Update - submitter sdk.AccAddress + Submitter sdk.AccAddress } // func (msg MsgSubmitUpdate) NewMsgSubmitUpdate(update Update) MsgSubmitUpdate { @@ -247,5 +247,5 @@ func (msg MsgSubmitUpdate) ValidateBasic() sdk.Error { func (msg MsgSubmitUpdate) GetSigners() []sdk.AccAddress { // Signing not strictly necessary as signatures contained within the channel update. // TODO add signature by submitting address - return []sdk.AccAddress{msg.submitter} + return []sdk.AccAddress{msg.Submitter} }