cosmos-sdk/cmd/commands/tx.go

220 lines
5.2 KiB
Go
Raw Normal View History

package commands
2017-01-28 18:12:58 -08:00
import (
"encoding/hex"
"errors"
"fmt"
"github.com/urfave/cli"
"github.com/tendermint/basecoin/types"
2017-01-29 15:32:38 -08:00
2017-01-28 18:12:58 -08:00
cmn "github.com/tendermint/go-common"
2017-01-29 11:41:21 -08:00
client "github.com/tendermint/go-rpc/client"
2017-01-28 18:12:58 -08:00
"github.com/tendermint/go-wire"
2017-01-29 11:41:21 -08:00
ctypes "github.com/tendermint/tendermint/rpc/core/types"
2017-01-28 18:12:58 -08:00
tmtypes "github.com/tendermint/tendermint/types"
)
var TxFlags = []cli.Flag{
NodeFlag,
ChainIDFlag,
FromFlag,
AmountFlag,
CoinFlag,
GasFlag,
FeeFlag,
SeqFlag,
}
var (
TxCmd = cli.Command{
Name: "tx",
Usage: "Create, sign, and broadcast a transaction",
ArgsUsage: "",
Subcommands: []cli.Command{
SendTxCmd,
AppTxCmd,
},
}
SendTxCmd = cli.Command{
Name: "send",
Usage: "Create, sign, and broadcast a SendTx transaction",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdSendTx(c)
},
Flags: append(TxFlags, ToFlag),
}
AppTxCmd = cli.Command{
Name: "app",
Usage: "Create, sign, and broadcast a raw AppTx transaction",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdAppTx(c)
},
Flags: append(TxFlags, NameFlag, DataFlag),
// Subcommands are dynamically registered with plugins as needed
Subcommands: []cli.Command{},
}
)
// Register a subcommand of TxCmd to craft transactions for plugins
func RegisterTxSubcommand(cmd cli.Command) {
TxCmd.Subcommands = append(TxCmd.Subcommands, cmd)
}
2017-01-28 18:12:58 -08:00
func cmdSendTx(c *cli.Context) error {
toHex := c.String("to")
fromFile := c.String("from")
amount := int64(c.Int("amount"))
coin := c.String("coin")
gas, fee := c.Int("gas"), int64(c.Int("fee"))
chainID := c.String("chain_id")
2017-01-29 11:41:21 -08:00
// convert destination address to bytes
2017-01-30 08:16:00 -08:00
to, err := hex.DecodeString(StripHex(toHex))
2017-01-28 18:12:58 -08:00
if err != nil {
return errors.New("To address is invalid hex: " + err.Error())
}
2017-02-07 13:10:17 -08:00
// load the priv key
privKey := LoadKey(fromFile)
2017-01-28 18:12:58 -08:00
2017-01-29 11:41:21 -08:00
// get the sequence number for the tx
2017-02-07 13:10:17 -08:00
sequence, err := getSeq(c, privKey.Address)
2017-01-29 11:41:21 -08:00
if err != nil {
return err
}
2017-01-28 18:12:58 -08:00
2017-01-29 11:41:21 -08:00
// craft the tx
2017-02-07 13:10:17 -08:00
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
2017-01-28 18:12:58 -08:00
output := newOutput(to, coin, amount)
2017-01-29 11:41:21 -08:00
tx := &types.SendTx{
2017-01-28 18:12:58 -08:00
Gas: int64(gas),
Fee: types.Coin{coin, fee},
Inputs: []types.TxInput{input},
Outputs: []types.TxOutput{output},
}
2017-01-29 11:41:21 -08:00
// sign that puppy
signBytes := tx.SignBytes(chainID)
2017-02-07 13:10:17 -08:00
tx.Inputs[0].Signature = privKey.Sign(signBytes)
2017-01-29 11:41:21 -08:00
fmt.Println("Signed SendTx:")
2017-01-28 18:12:58 -08:00
fmt.Println(string(wire.JSONBytes(tx)))
2017-01-29 11:41:21 -08:00
// broadcast the transaction to tendermint
2017-01-29 21:27:57 -08:00
if _, err := broadcastTx(c, tx); err != nil {
2017-01-29 11:41:21 -08:00
return err
}
2017-01-28 18:12:58 -08:00
return nil
}
func cmdAppTx(c *cli.Context) error {
2017-01-29 15:32:38 -08:00
// convert data to bytes
dataString := c.String("data")
data := []byte(dataString)
if isHex(dataString) {
data, _ = hex.DecodeString(dataString)
}
2017-01-28 18:12:58 -08:00
name := c.String("name")
return AppTx(c, name, data)
2017-01-29 15:32:38 -08:00
}
func AppTx(c *cli.Context, name string, data []byte) error {
2017-01-28 18:12:58 -08:00
fromFile := c.String("from")
amount := int64(c.Int("amount"))
coin := c.String("coin")
gas, fee := c.Int("gas"), int64(c.Int("fee"))
chainID := c.String("chain_id")
2017-02-07 13:10:17 -08:00
privKey := tmtypes.LoadPrivValidator(fromFile)
2017-01-28 18:12:58 -08:00
2017-02-07 13:10:17 -08:00
sequence, err := getSeq(c, privKey.Address)
2017-01-29 11:41:21 -08:00
if err != nil {
return err
}
2017-01-28 18:12:58 -08:00
2017-02-07 13:10:17 -08:00
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
2017-01-29 11:41:21 -08:00
tx := &types.AppTx{
2017-01-28 18:12:58 -08:00
Gas: int64(gas),
Fee: types.Coin{coin, fee},
Name: name,
Input: input,
Data: data,
}
2017-02-07 13:10:17 -08:00
tx.Input.Signature = privKey.Sign(tx.SignBytes(chainID))
2017-01-29 11:41:21 -08:00
fmt.Println("Signed AppTx:")
2017-01-28 18:12:58 -08:00
fmt.Println(string(wire.JSONBytes(tx)))
2017-01-29 11:41:21 -08:00
2017-01-31 08:00:23 -08:00
res, err := broadcastTx(c, tx)
if err != nil {
2017-01-29 11:41:21 -08:00
return err
}
2017-01-31 08:00:23 -08:00
fmt.Printf("Response: %X\n", res)
2017-01-29 11:41:21 -08:00
return nil
}
// broadcast the transaction to tendermint
2017-01-29 21:27:57 -08:00
func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, error) {
2017-01-29 11:41:21 -08:00
tmResult := new(ctypes.TMResult)
2017-01-29 18:41:37 -08:00
tmAddr := c.String("node")
2017-01-29 11:41:21 -08:00
clientURI := client.NewClientURI(tmAddr)
2017-01-29 15:32:38 -08:00
// Don't you hate having to do this?
// How many times have I lost an hour over this trick?!
txBytes := []byte(wire.BinaryBytes(struct {
2017-01-29 11:41:21 -08:00
types.Tx `json:"unwrap"`
2017-01-29 15:32:38 -08:00
}{tx}))
2017-01-29 21:27:57 -08:00
_, err := clientURI.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult)
2017-01-29 11:41:21 -08:00
if err != nil {
2017-01-29 21:27:57 -08:00
return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
2017-01-29 11:41:21 -08:00
}
2017-01-29 21:27:57 -08:00
res := (*tmResult).(*ctypes.ResultBroadcastTxCommit)
2017-01-30 10:11:44 -08:00
// if it fails check, we don't even get a delivertx back!
if !res.CheckTx.Code.IsOK() {
r := res.CheckTx
return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
}
2017-01-29 21:27:57 -08:00
if !res.DeliverTx.Code.IsOK() {
r := res.DeliverTx
return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
2017-01-29 11:41:21 -08:00
}
2017-01-29 21:27:57 -08:00
return res.DeliverTx.Data, nil
2017-01-28 18:12:58 -08:00
}
2017-01-29 11:41:21 -08:00
// if the sequence flag is set, return it;
// else, fetch the account by querying the app and return the sequence number
func getSeq(c *cli.Context, address []byte) (int, error) {
2017-01-28 18:12:58 -08:00
if c.IsSet("sequence") {
2017-01-29 11:41:21 -08:00
return c.Int("sequence"), nil
}
2017-01-29 18:41:37 -08:00
tmAddr := c.String("node")
2017-01-29 13:34:48 -08:00
acc, err := getAcc(tmAddr, address)
2017-01-29 11:41:21 -08:00
if err != nil {
2017-01-29 13:34:48 -08:00
return 0, err
2017-01-29 11:41:21 -08:00
}
return acc.Sequence + 1, nil
2017-01-28 18:12:58 -08:00
}
func newOutput(to []byte, coin string, amount int64) types.TxOutput {
return types.TxOutput{
Address: to,
Coins: types.Coins{
types.Coin{
Denom: coin,
Amount: amount,
},
},
}
}