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
- 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

View File

@ -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"

View File

@ -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)
}

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) {
// 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 {

View File

@ -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}
}