2018-07-08 22:09:07 +00:00
package cli
import (
2018-07-14 22:27:56 +00:00
"encoding/base64"
2018-07-08 22:09:07 +00:00
"fmt"
2018-07-14 22:27:56 +00:00
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
2018-07-14 00:09:02 +00:00
"github.com/cosmos/cosmos-sdk/client/context"
2018-07-14 22:27:56 +00:00
"github.com/cosmos/cosmos-sdk/client/keys"
2018-07-14 00:09:02 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
2018-07-14 22:27:56 +00:00
"github.com/cosmos/cosmos-sdk/x/auth"
2018-07-15 14:08:08 +00:00
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
2018-07-14 22:27:56 +00:00
2018-07-14 00:09:02 +00:00
"github.com/kava-labs/kava/internal/x/paychan"
2018-07-08 22:09:07 +00:00
)
// list of functions that return pointers to cobra commands
// No local storage needed for cli acting as a sender
2018-07-14 22:27:56 +00:00
// Currently minimum set of cli commands are implemented:
// create paychan - create and fund
// generate new paychan state - print a half signed close tx (sender signs)
// close paychan - close using state (receiver signs)
// Future cli commands:
2018-07-08 22:09:07 +00:00
// create paychan
// close paychan
// get paychan(s)
// send paychan payment
// get balance from receiver
2018-07-14 00:09:02 +00:00
func CreatePaychanCmd ( cdc * wire . Codec ) * cobra . Command {
flagTo := "to"
flagAmount := "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." ,
Args : cobra . NoArgs ,
2018-07-08 22:09:07 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2018-07-14 22:27:56 +00:00
// Create a "client context" stuct populated with info from common flags
2018-07-15 14:08:08 +00:00
ctx := context . NewCoreContextFromViper ( ) . WithDecoder ( authcmd . GetAccountDecoder ( cdc ) )
2018-07-08 22:09:07 +00:00
2018-07-14 00:09:02 +00:00
// Get sender adress
senderAddress , err := ctx . GetFromAddress ( )
if err != nil {
return err
}
2018-07-08 22:09:07 +00:00
2018-07-14 00:09:02 +00:00
// Get receiver address
toStr := viper . GetString ( flagTo )
receiverAddress , err := sdk . GetAccAddressBech32 ( toStr )
2018-07-08 22:09:07 +00:00
if err != nil {
return err
}
2018-07-14 00:09:02 +00:00
// Get channel funding amount
amountString := viper . GetString ( flagAmount )
amount , err := sdk . ParseCoins ( amountString )
2018-07-08 22:09:07 +00:00
if err != nil {
return err
}
2018-07-14 00:09:02 +00:00
// Create the create channel msg to send
// TODO write NewMsgCreate func?
msg := paychan . MsgCreate {
2018-07-14 22:27:56 +00:00
Sender : senderAddress ,
Receiver : receiverAddress ,
Amount : amount ,
2018-07-08 22:09:07 +00:00
}
2018-07-14 22:27:56 +00:00
// Build and sign the transaction, then broadcast to the blockchain
2018-07-14 00:09:02 +00:00
res , err := ctx . EnsureSignBuildBroadcast ( ctx . FromAddressName , msg , cdc )
if err != nil {
return err
}
fmt . Printf ( "Committed at block %d. Hash: %s\n" , res . Height , res . Hash . String ( ) )
2018-07-14 22:27:56 +00:00
return nil
2018-07-14 00:09:02 +00:00
} ,
}
cmd . Flags ( ) . String ( flagTo , "" , "Recipient address of the payment channel" )
cmd . Flags ( ) . String ( flagAmount , "" , "Amount of coins to fund the paymetn channel with" )
return cmd
}
2018-07-08 22:09:07 +00:00
2018-07-14 22:27:56 +00:00
func GenerateNewStateCmd ( cdc * wire . Codec ) * cobra . Command {
2018-07-14 00:09:02 +00:00
flagId := "id"
flagTo := "to"
flagAmount := "amount"
cmd := & cobra . Command {
2018-07-14 22:27:56 +00:00
Use : "new-state" ,
Short : "Generate a new payment channel state." ,
2018-07-15 14:08:08 +00:00
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." ,
2018-07-14 00:09:02 +00:00
Args : cobra . NoArgs ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2018-07-14 22:27:56 +00:00
// Create a "client context" stuct populated with info from common flags
2018-07-15 14:08:08 +00:00
ctx := context . NewCoreContextFromViper ( ) . WithDecoder ( authcmd . GetAccountDecoder ( cdc ) )
2018-07-14 22:27:56 +00:00
2018-07-14 00:09:02 +00:00
// Get sender adress
senderAddress , err := ctx . GetFromAddress ( )
2018-07-08 22:09:07 +00:00
if err != nil {
return err
}
2018-07-14 22:27:56 +00:00
// Get the paychan id
id := viper . GetInt64 ( flagId )
2018-07-14 00:09:02 +00:00
// Get receiver address
toStr := viper . GetString ( flagTo )
receiverAddress , err := sdk . GetAccAddressBech32 ( toStr )
if err != nil {
return err
}
2018-07-14 22:27:56 +00:00
2018-07-14 00:09:02 +00:00
// Get channel receiver amount
amountString := viper . GetString ( flagAmount )
amount , err := sdk . ParseCoins ( amountString )
if err != nil {
return err
}
2018-07-14 22:27:56 +00:00
// create close paychan msg
2018-07-14 00:09:02 +00:00
msg := paychan . MsgClose {
2018-07-14 22:27:56 +00:00
Sender : senderAddress ,
Receiver : receiverAddress ,
Id : id ,
ReceiverAmount : amount ,
2018-07-14 00:09:02 +00:00
}
2018-07-14 22:27:56 +00:00
// Sign the msg as the sender
2018-07-14 00:09:02 +00:00
txBytes , err := EnsureSignBuild ( ctx , ctx . FromAddressName , msg , cdc )
2018-07-08 22:09:07 +00:00
if err != nil {
return err
}
2018-07-14 00:09:02 +00:00
2018-07-14 22:27:56 +00:00
// Print out the signed msg
fmt . Println ( "txBytes:" , txBytes )
2018-07-15 14:08:08 +00:00
//encodedTxBytes := make([]byte, base64.StdEncoding.EncodedLen(len(txBytes)))
encodedTxBytes := base64 . StdEncoding . EncodeToString ( txBytes )
2018-07-14 22:27:56 +00:00
fmt . Println ( "base64TxBytes:" , encodedTxBytes )
return nil
2018-07-14 00:09:02 +00:00
} ,
}
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" )
return cmd
}
2018-07-14 22:27:56 +00:00
func ClosePaychanCmd ( cdc * wire . Codec ) * cobra . Command {
flagState := "state"
2018-07-14 00:09:02 +00:00
2018-07-14 22:27:56 +00:00
cmd := & cobra . Command {
Use : "close" ,
Short : "Close a payment channel, given a state" ,
2018-07-15 14:08:08 +00:00
Long : "Close an existing payment channel with a state received from a sender. This signs it as the receiver before submitting to the blockchain." ,
2018-07-14 22:27:56 +00:00
Args : cobra . NoArgs ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2018-07-15 14:08:08 +00:00
ctx := context . NewCoreContextFromViper ( ) . WithDecoder ( authcmd . GetAccountDecoder ( cdc ) )
2018-07-14 22:27:56 +00:00
// Get the sender-signed close tx
state := viper . GetString ( flagState )
txBytes , err := base64 . StdEncoding . DecodeString ( state )
if err != nil {
return err
}
stdTx := auth . StdTx { }
2018-07-15 14:08:08 +00:00
cdc . UnmarshalBinary ( txBytes , & stdTx )
2018-07-14 22:27:56 +00:00
// Sign close tx
// ensure contxt has up to date account and sequence numbers
ctx , err = Ensure ( ctx )
if err != nil {
return err
}
// Sign message (asks user for password)
_ , sig , err := UserSignMsg ( ctx , ctx . FromAddressName , stdTx . Msg )
if err != nil {
return err
}
// Append signature to close tx
stdTx . Signatures = append ( stdTx . Signatures , sig )
// encode close tx
txBytes , err = cdc . MarshalBinary ( stdTx )
if err != nil {
return err
}
// Broadcast close tx to the blockchain
res , err := ctx . BroadcastTx ( txBytes )
if err != nil {
return err
}
fmt . Printf ( "Committed at block %d. Hash: %s\n" , res . Height , res . Hash . String ( ) )
return nil
} ,
}
cmd . Flags ( ) . String ( flagState , "" , "State received from sender." )
return cmd
}
// HELPER FUNCTIONS
// This is a partial refactor of cosmos-sdk/client/context.
// Existing API was awkard to use for paychans.
func Ensure ( ctx context . CoreContext ) ( context . CoreContext , error ) {
ctx , err := context . EnsureAccountNumber ( ctx )
2018-07-14 00:09:02 +00:00
if err != nil {
2018-07-14 22:27:56 +00:00
return ctx , err
2018-07-14 00:09:02 +00:00
}
// default to next sequence number if none provided
2018-07-14 22:27:56 +00:00
ctx , err = context . EnsureSequence ( ctx )
2018-07-14 00:09:02 +00:00
if err != nil {
2018-07-14 22:27:56 +00:00
return ctx , err
2018-07-14 00:09:02 +00:00
}
2018-07-14 22:27:56 +00:00
return ctx , nil
}
func UserSignMsg ( ctx context . CoreContext , name string , msg sdk . Msg ) ( signMsg auth . StdSignMsg , stdSig auth . StdSignature , err error ) {
// TODO check how to handle non error return values on error. Returning empty versions doesn't seem right.
2018-07-14 00:09:02 +00:00
passphrase , err := ctx . GetPassphraseFromStdin ( name )
if err != nil {
2018-07-14 22:27:56 +00:00
return signMsg , stdSig , err
2018-07-14 00:09:02 +00:00
}
2018-07-14 22:27:56 +00:00
// build the Sign Messsage from the Standard Message
chainID := ctx . ChainID
if chainID == "" {
return signMsg , stdSig , errors . Errorf ( "Chain ID required but not specified" )
}
accnum := ctx . AccountNumber
sequence := ctx . Sequence
signMsg = auth . StdSignMsg {
ChainID : chainID ,
AccountNumbers : [ ] int64 { accnum } ,
Sequences : [ ] int64 { sequence } ,
Msg : msg ,
Fee : auth . NewStdFee ( ctx . Gas , sdk . Coin { } ) , // TODO run simulate to estimate gas?
}
keybase , err := keys . GetKeyBase ( )
2018-07-14 00:09:02 +00:00
if err != nil {
2018-07-14 22:27:56 +00:00
return signMsg , stdSig , err
}
// sign and build
bz := signMsg . Bytes ( )
sig , pubkey , err := keybase . Sign ( name , passphrase , bz )
if err != nil {
return signMsg , stdSig , err
}
stdSig = auth . StdSignature {
PubKey : pubkey ,
Signature : sig ,
AccountNumber : accnum ,
Sequence : sequence ,
2018-07-14 00:09:02 +00:00
}
2018-07-14 22:27:56 +00:00
return signMsg , stdSig , nil
2018-07-14 00:09:02 +00:00
}
2018-07-14 22:27:56 +00:00
func Build ( cdc * wire . Codec , signMsg auth . StdSignMsg , sig auth . StdSignature ) ( [ ] byte , error ) {
tx := auth . NewStdTx ( signMsg . Msg , signMsg . Fee , [ ] auth . StdSignature { sig } )
return cdc . MarshalBinary ( tx )
}
2018-07-14 00:09:02 +00:00
2018-07-14 22:27:56 +00:00
func EnsureSignBuild ( ctx context . CoreContext , name string , msg sdk . Msg , cdc * wire . Codec ) ( [ ] byte , error ) {
//Ensure context has up to date account and sequence numbers
ctx , err := Ensure ( ctx )
if err != nil {
return nil , err
2018-07-08 22:09:07 +00:00
}
2018-07-14 22:27:56 +00:00
// Sign message (asks user for password)
signMsg , sig , err := UserSignMsg ( ctx , name , msg )
if err != nil {
return nil , err
}
// Create tx and marshal
txBytes , err := Build ( cdc , signMsg , sig )
if err != nil {
return nil , err
}
return txBytes , nil
2018-07-08 22:09:07 +00:00
}