add commands

This commit is contained in:
rhuairahrighairigh 2018-09-01 16:41:40 -04:00
parent 3ae217e927
commit 3f7bcca487
5 changed files with 180 additions and 78 deletions

View File

@ -8,7 +8,6 @@ Simplifications:
TODO TODO
- in code TODOs - in code TODOs
- write basic cmds
- Tidy up - method descriptions, heading comments, remove uneccessary comments, README/docs - Tidy up - method descriptions, heading comments, remove uneccessary comments, README/docs
- chnge module name to "channel"? - chnge module name to "channel"?
- Find a better name for Queue - clarify distinction between int slice and abstract queue concept - 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 - find nicer name for payout
- add Gas usage - add Gas usage
- add tags (return channel id on creation) - 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 - 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 - 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 - consider removing pubKey from UpdateSignature - instead let channel module access accountMapper

View File

@ -3,6 +3,7 @@ package cli
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -21,62 +22,54 @@ import (
// 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
// 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: func CreateChannelCmd(cdc *wire.Codec) *cobra.Command {
// create paychan
// close paychan
// get paychan(s)
// send paychan payment
// get balance from receiver
/*
func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command {
flagTo := "to" flagTo := "to"
flagAmount := "amount" flagCoins := "amount"
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create", Use: "create",
Short: "Create a new payment channel", 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, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// 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().WithDecoder(authcmd.GetAccountDecoder(cdc)) ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
// ctx.PrintResponse = true TODO is this needed for channelID
// Get sender adress // Get sender adress
senderAddress, err := ctx.GetFromAddress() sender, err := ctx.GetFromAddress()
if err != nil { if err != nil {
return err return err
} }
// Get receiver address // Get receiver address
toStr := viper.GetString(flagTo) toStr := viper.GetString(flagTo)
receiverAddress, err := sdk.GetAccAddressBech32(toStr) receiver, err := sdk.GetAccAddressBech32(toStr)
if err != nil { if err != nil {
return err return err
} }
// Get channel funding amount // Get channel funding amount
amountString := viper.GetString(flagAmount) coinsString := viper.GetString(flagCoins)
amount, err := sdk.ParseCoins(amountString) coins, err := sdk.ParseCoins(coinsString)
if err != nil { if err != nil {
return err return err
} }
// Create the create channel msg to send // Create the create channel msg to send
// TODO write NewMsgCreate func?
msg := paychan.MsgCreate{ msg := paychan.MsgCreate{
Sender: senderAddress, Participants: []sdk.AccAddress{sender, receiver},
Receiver: receiverAddress, Coins: coins,
Amount: amount,
} }
err = msg.ValidateBasic()
if err != nil {
return err
}
// Build and sign the transaction, then broadcast to the blockchain // 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 { if err != nil {
return err return err
} }
@ -84,77 +77,180 @@ func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command {
return nil return nil
}, },
} }
cmd.Flags().String(flagTo, "", "Recipient address 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(flagAmount, "", "Amount of coins to fund the payment channel with.")
return cmd return cmd
} }
func GenerateNewStateCmd(cdc *wire.Codec) *cobra.Command { func GeneratePaymentCmd(cdc *wire.Codec) *cobra.Command {
flagId := "id" flagId := "id" // ChannelID
flagTo := "to" flagReceiverAmount := "r-amount" // amount the receiver should received on closing the channel
flagAmount := "amount" flagSenderAmount := "s-amount" //
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "new-state", Use: "pay",
Short: "Generate a new payment channel state.", Short: "Generate a .", // TODO descriptions
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.", Long: "Generate a new ",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
// 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().WithDecoder(authcmd.GetAccountDecoder(cdc)) 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 // Get sender adress
senderAddress, err := ctx.GetFromAddress() // senderAddress, err := ctx.GetFromAddress()
if err != nil { // if err != nil {
return err // return err
} // }
// Get the paychan id // Get the paychan id
id := viper.GetInt64(flagId) id := viper.GetInt64(flagId) // TODO make this default to pulling id from chain
// Get receiver address // Get channel receiver amount
toStr := viper.GetString(flagTo) senderCoins, err := sdk.ParseCoins(viper.GetString(flagSenderAmount))
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) receiverCoins, err := sdk.ParseCoins(viper.GetString(flagReceiverAmount))
amount, err := sdk.ParseCoins(amountString)
if err != nil { if err != nil {
return err return err
} }
// create close paychan msg // create close paychan msg
msg := paychan.MsgClose{ update := paychan.Update{
Sender: senderAddress, ChannelID: id,
Receiver: receiverAddress, Payout: paychan.Payout{senderCoins, receiverCoins},
Id: id, // empty sigs
ReceiverAmount: amount,
} }
// Sign the msg as the sender // Sign the update as the sender
txBytes, err := EnsureSignBuild(ctx, ctx.FromAddressName, msg, cdc) keybase, err := keys.GetKeyBase()
if err != nil { if err != nil {
return err 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 return nil
}, },
} }
cmd.Flags().Int(flagId, 0, "ID of the payment channel.") cmd.Flags().Int(flagId, 0, "ID of the payment channel.")
cmd.Flags().String(flagTo, "", "Recipient address of the payment channel") cmd.Flags().String(flagSenderAmount, "", "")
cmd.Flags().String(flagAmount, "", "Amount of coins to fund the paymetn channel with") cmd.Flags().String(flagReceiverAmount, "", "")
return cmd 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 { func ClosePaychanCmd(cdc *wire.Codec) *cobra.Command {
flagState := "state" flagState := "state"

View File

@ -45,10 +45,10 @@ func handleMsgSubmitUpdate(ctx sdk.Context, k Keeper, msg MsgSubmitUpdate) sdk.R
participants := channel.Participants participants := channel.Participants
// if only sender signed // 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) tags, err = k.InitCloseChannelBySender(ctx, msg.Update)
// else if receiver signed // 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) tags, err = k.CloseChannelByReceiver(ctx, msg.Update)
} }

View File

@ -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) { func (k Keeper) InitCloseChannelBySender(ctx sdk.Context, update Update) (sdk.Tags, sdk.Error) {
// This is roughly the default path for non unidirectional channels // 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -152,12 +162,8 @@ func (k Keeper) CloseChannelByReceiver(ctx sdk.Context, update Update) (sdk.Tags
// return returnUpdate // return returnUpdate
// } // }
func (k Keeper) validateUpdate(ctx sdk.Context, update Update) sdk.Error { func (k Keeper) VerifyUpdate(channel Channel, update Update) sdk.Error {
// Check that channel exists
channel, found := k.getChannel(ctx, update.ChannelID)
if !found {
return sdk.ErrInternal("Channel doesn't exist")
}
// Check the num of payout participants match channel participants // Check the num of payout participants match channel participants
if len(update.Payout) != len(channel.Participants) { if len(update.Payout) != len(channel.Participants) {
return sdk.ErrInternal("Payout doesn't match number of 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") return sdk.ErrInternal("Payout amount doesn't match channel amount")
} }
// Check sender signature is OK // 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 sdk.ErrInternal("Signature on update not valid")
} }
return nil return nil
@ -206,7 +212,7 @@ func (k Keeper) closeChannel(ctx sdk.Context, update Update) (sdk.Tags, sdk.Erro
return tags, nil 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 // In non unidirectional channels there will be more than one signature to check
signBytes := update.GetSignBytes() 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) { func (k Keeper) getChannel(ctx sdk.Context, channelID ChannelID) (Channel, bool) {
// load from DB // load from DB
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
bz := store.Get(k.getChannelKey(channelID)) bz := store.Get(k.GetChannelKey(channelID))
var channel Channel var channel Channel
if bz == nil { if bz == nil {
@ -344,13 +350,13 @@ func (k Keeper) setChannel(ctx sdk.Context, channel Channel) {
// marshal // marshal
bz := k.cdc.MustMarshalBinary(channel) // panics if something goes wrong bz := k.cdc.MustMarshalBinary(channel) // panics if something goes wrong
// write to db // write to db
key := k.getChannelKey(channel.ID) key := k.GetChannelKey(channel.ID)
store.Set(key, bz) // panics if something goes wrong store.Set(key, bz) // panics if something goes wrong
} }
func (k Keeper) deleteChannel(ctx sdk.Context, channelID ChannelID) { func (k Keeper) deleteChannel(ctx sdk.Context, channelID ChannelID) {
store := ctx.KVStore(k.storeKey) 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? // 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 return newID
} }
func (k Keeper) getChannelKey(channelID ChannelID) []byte { func (k Keeper) GetChannelKey(channelID ChannelID) []byte {
return []byte(fmt.Sprintf("channel:%d", channelID)) return []byte(fmt.Sprintf("channel:%d", channelID))
} }
func (k Keeper) getLastChannelIDKey() []byte { func (k Keeper) getLastChannelIDKey() []byte {

View File

@ -45,7 +45,7 @@ type UpdateSignature struct {
CryptoSignature crypto.Signature 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 { func (p Payout) IsNotNegative() bool {
result := true result := true
for _, coins := range p { for _, coins := range p {
@ -195,7 +195,7 @@ func (msg MsgCreate) GetSigners() []sdk.AccAddress {
// A message to close a payment channel. // A message to close a payment channel.
type MsgSubmitUpdate struct { type MsgSubmitUpdate struct {
Update Update
submitter sdk.AccAddress Submitter sdk.AccAddress
} }
// func (msg MsgSubmitUpdate) NewMsgSubmitUpdate(update Update) MsgSubmitUpdate { // func (msg MsgSubmitUpdate) NewMsgSubmitUpdate(update Update) MsgSubmitUpdate {
@ -247,5 +247,5 @@ func (msg MsgSubmitUpdate) ValidateBasic() sdk.Error {
func (msg MsgSubmitUpdate) GetSigners() []sdk.AccAddress { func (msg MsgSubmitUpdate) GetSigners() []sdk.AccAddress {
// Signing not strictly necessary as signatures contained within the channel update. // Signing not strictly necessary as signatures contained within the channel update.
// TODO add signature by submitting address // TODO add signature by submitting address
return []sdk.AccAddress{msg.submitter} return []sdk.AccAddress{msg.Submitter}
} }