mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-30 18:25:19 +00:00
400 lines
10 KiB
Go
400 lines
10 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"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/wire"
|
|
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
|
|
|
"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 "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
|
|
sender, err := ctx.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
|
|
err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
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 := "id" // ChannelID
|
|
flagReceiverAmount := "r-amount" // amount the receiver should received on closing the channel
|
|
flagSenderAmount := "s-amount" //
|
|
|
|
cmd := &cobra.Command{
|
|
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
|
|
// }
|
|
|
|
// 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 := 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, err := wire.MarshalJSONIndent(cdc, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(jsonUpdate))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().Int(flagId, 0, "ID of the payment channel.")
|
|
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 {
|
|
|
|
// Create a "client context" stuct populated with info from common flags
|
|
ctx := context.NewCoreContextFromViper()
|
|
|
|
// 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.VerifyUpdate(channel, update)
|
|
|
|
// print result
|
|
fmt.Println(updateIsOK)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
func SubmitPaymentCmd(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
|
|
err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
/*
|
|
func ClosePaychanCmd(cdc *wire.Codec) *cobra.Command {
|
|
flagState := "state"
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "close",
|
|
Short: "Close a payment channel, given a state",
|
|
Long: "Close an existing payment channel with a state received from a sender. This signs it as the receiver before submitting to the blockchain.",
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
|
|
|
|
// Get the sender-signed close tx
|
|
state := viper.GetString(flagState)
|
|
txBytes, err := base64.StdEncoding.DecodeString(state)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stdTx := auth.StdTx{}
|
|
cdc.UnmarshalBinary(txBytes, &stdTx)
|
|
|
|
// 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)
|
|
if err != nil {
|
|
return ctx, err
|
|
}
|
|
// default to next sequence number if none provided
|
|
ctx, err = context.EnsureSequence(ctx)
|
|
if err != nil {
|
|
return ctx, err
|
|
}
|
|
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.
|
|
|
|
passphrase, err := ctx.GetPassphraseFromStdin(name)
|
|
if err != nil {
|
|
return signMsg, stdSig, err
|
|
}
|
|
|
|
// 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()
|
|
if err != nil {
|
|
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,
|
|
}
|
|
|
|
return signMsg, stdSig, nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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
|
|
}
|
|
// 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
|
|
}
|
|
*/
|