2017-01-30 06:16:51 -08:00
|
|
|
package commands
|
2017-01-28 18:12:58 -08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/spf13/cobra"
|
2017-01-28 18:12:58 -08:00
|
|
|
|
|
|
|
"github.com/tendermint/basecoin/types"
|
2017-02-23 08:04:03 -08:00
|
|
|
crypto "github.com/tendermint/go-crypto"
|
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-03-16 01:01:29 -07:00
|
|
|
wire "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
|
|
|
)
|
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
//commands
|
|
|
|
var (
|
|
|
|
TxCmd = &cobra.Command{
|
|
|
|
Use: "tx",
|
|
|
|
Short: "Create, sign, and broadcast a transaction",
|
|
|
|
}
|
2017-02-07 10:16:41 -08:00
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
SendTxCmd = &cobra.Command{
|
|
|
|
Use: "send",
|
|
|
|
Short: "A SendTx transaction, for sending tokens around",
|
|
|
|
Run: sendTxCmd,
|
|
|
|
}
|
2017-02-07 10:16:41 -08:00
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
AppTxCmd = &cobra.Command{
|
|
|
|
Use: "app",
|
|
|
|
Short: "An AppTx transaction, for sending raw data to plugins",
|
|
|
|
Run: appTxCmd,
|
|
|
|
}
|
|
|
|
)
|
2017-02-07 10:16:41 -08:00
|
|
|
|
2017-01-30 06:16:51 -08:00
|
|
|
var (
|
2017-04-01 12:49:56 -07:00
|
|
|
//persistent flags
|
2017-03-08 23:19:07 -08:00
|
|
|
txNodeFlag string
|
|
|
|
amountFlag string
|
|
|
|
fromFlag string
|
|
|
|
seqFlag int
|
|
|
|
gasFlag int
|
|
|
|
feeFlag string
|
|
|
|
chainIDFlag string
|
2017-04-01 12:49:56 -07:00
|
|
|
|
|
|
|
//non-persistent flags
|
|
|
|
toFlag string
|
|
|
|
dataFlag string
|
|
|
|
nameFlag string
|
2017-01-30 06:16:51 -08:00
|
|
|
)
|
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
func init() {
|
|
|
|
|
|
|
|
// register flags
|
|
|
|
cmdTxFlags := []Flag2Register{
|
|
|
|
{&txNodeFlag, "node", "tcp://localhost:46657", "Tendermint RPC address"},
|
|
|
|
{&chainIDFlag, "chain_id", "test_chain_id", "ID of the chain for replay protection"},
|
|
|
|
{&fromFlag, "from", "key.json", "Path to a private key to sign the transaction"},
|
|
|
|
{&amountFlag, "amount", "", "Coins to send in transaction of the format <amt><coin>,<amt2><coin2>,... (eg: 1btc,2gold,5silver},"},
|
|
|
|
{&gasFlag, "gas", 0, "The amount of gas for the transaction"},
|
|
|
|
{&feeFlag, "fee", "", "Coins for the transaction fee of the format <amt><coin>"},
|
|
|
|
{&seqFlag, "sequence", -1, "Sequence number for the account (-1 to autocalculate},"},
|
|
|
|
}
|
|
|
|
|
|
|
|
sendTxFlags := []Flag2Register{
|
|
|
|
{&toFlag, "to", "", "Destination address for the transaction"},
|
|
|
|
}
|
|
|
|
|
|
|
|
appTxFlags := []Flag2Register{
|
|
|
|
{&nameFlag, "name", "", "Plugin to send the transaction to"},
|
|
|
|
{&dataFlag, "data", "", "Data to send with the transaction"},
|
|
|
|
}
|
|
|
|
|
|
|
|
RegisterPersistentFlags(TxCmd, cmdTxFlags)
|
|
|
|
RegisterFlags(SendTxCmd, sendTxFlags)
|
|
|
|
RegisterFlags(AppTxCmd, appTxFlags)
|
|
|
|
|
|
|
|
//register commands
|
|
|
|
TxCmd.AddCommand(SendTxCmd, AppTxCmd)
|
2017-01-30 06:56:47 -08:00
|
|
|
}
|
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
func sendTxCmd(cmd *cobra.Command, args []string) {
|
2017-01-28 18:12:58 -08:00
|
|
|
|
2017-01-29 11:41:21 -08:00
|
|
|
// convert destination address to bytes
|
2017-03-08 23:19:07 -08:00
|
|
|
to, err := hex.DecodeString(StripHex(toFlag))
|
2017-01-28 18:12:58 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("To address is invalid hex: %+v\n", err))
|
2017-01-28 18:12:58 -08:00
|
|
|
}
|
|
|
|
|
2017-02-07 13:10:17 -08:00
|
|
|
// load the priv key
|
2017-03-08 23:19:07 -08:00
|
|
|
privKey := LoadKey(fromFlag)
|
2017-01-28 18:12:58 -08:00
|
|
|
|
2017-01-29 11:41:21 -08:00
|
|
|
// get the sequence number for the tx
|
2017-03-08 23:19:07 -08:00
|
|
|
sequence, err := getSeq(privKey.Address[:])
|
2017-01-29 11:41:21 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-01-29 11:41:21 -08:00
|
|
|
}
|
2017-01-28 18:12:58 -08:00
|
|
|
|
2017-02-09 18:48:42 -08:00
|
|
|
//parse the fee and amounts into coin types
|
2017-03-08 23:19:07 -08:00
|
|
|
feeCoin, err := types.ParseCoin(feeFlag)
|
2017-02-09 18:48:42 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-02-09 18:48:42 -08:00
|
|
|
}
|
2017-03-08 23:19:07 -08:00
|
|
|
amountCoins, err := types.ParseCoins(amountFlag)
|
2017-02-09 18:48:42 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-02-09 18:48:42 -08:00
|
|
|
}
|
|
|
|
|
2017-01-29 11:41:21 -08:00
|
|
|
// craft the tx
|
2017-02-09 18:48:42 -08:00
|
|
|
input := types.NewTxInput(privKey.PubKey, amountCoins, sequence)
|
|
|
|
output := newOutput(to, amountCoins)
|
2017-01-29 11:41:21 -08:00
|
|
|
tx := &types.SendTx{
|
2017-03-08 23:19:07 -08:00
|
|
|
Gas: int64(gasFlag),
|
2017-02-09 18:48:42 -08:00
|
|
|
Fee: feeCoin,
|
2017-01-28 18:12:58 -08:00
|
|
|
Inputs: []types.TxInput{input},
|
|
|
|
Outputs: []types.TxOutput{output},
|
|
|
|
}
|
|
|
|
|
2017-01-29 11:41:21 -08:00
|
|
|
// sign that puppy
|
2017-03-08 23:19:07 -08:00
|
|
|
signBytes := tx.SignBytes(chainIDFlag)
|
2017-02-23 08:04:03 -08:00
|
|
|
tx.Inputs[0].Signature = crypto.SignatureS{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-03-08 23:19:07 -08:00
|
|
|
data, log, err := broadcastTx(tx)
|
2017-03-23 15:51:15 -07:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-01-29 11:41:21 -08:00
|
|
|
}
|
2017-03-23 15:51:15 -07:00
|
|
|
fmt.Printf("Response: %X ; %s\n", data, log)
|
2017-01-28 18:12:58 -08:00
|
|
|
}
|
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
func appTxCmd(cmd *cobra.Command, args []string) {
|
2017-01-29 15:32:38 -08:00
|
|
|
// convert data to bytes
|
2017-03-08 23:19:07 -08:00
|
|
|
data := []byte(dataFlag)
|
|
|
|
if isHex(dataFlag) {
|
|
|
|
data, _ = hex.DecodeString(dataFlag)
|
2017-01-29 15:32:38 -08:00
|
|
|
}
|
2017-03-08 23:19:07 -08:00
|
|
|
name := nameFlag
|
|
|
|
AppTx(name, data)
|
2017-01-29 15:32:38 -08:00
|
|
|
}
|
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
func AppTx(name string, data []byte) {
|
2017-01-28 18:12:58 -08:00
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
privKey := LoadKey(fromFlag)
|
2017-01-28 18:12:58 -08:00
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
sequence, err := getSeq(privKey.Address[:])
|
2017-01-29 11:41:21 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-01-29 11:41:21 -08:00
|
|
|
}
|
2017-01-28 18:12:58 -08:00
|
|
|
|
2017-02-09 18:48:42 -08:00
|
|
|
//parse the fee and amounts into coin types
|
2017-03-08 23:19:07 -08:00
|
|
|
feeCoin, err := types.ParseCoin(feeFlag)
|
2017-02-09 18:48:42 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-02-09 18:48:42 -08:00
|
|
|
}
|
2017-03-08 23:19:07 -08:00
|
|
|
|
|
|
|
amountCoins, err := types.ParseCoins(amountFlag)
|
2017-02-09 18:48:42 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-02-09 18:48:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
input := types.NewTxInput(privKey.PubKey, amountCoins, sequence)
|
2017-01-29 11:41:21 -08:00
|
|
|
tx := &types.AppTx{
|
2017-03-08 23:19:07 -08:00
|
|
|
Gas: int64(gasFlag),
|
2017-02-09 18:48:42 -08:00
|
|
|
Fee: feeCoin,
|
2017-01-28 18:12:58 -08:00
|
|
|
Name: name,
|
|
|
|
Input: input,
|
|
|
|
Data: data,
|
|
|
|
}
|
|
|
|
|
2017-03-08 23:19:07 -08:00
|
|
|
tx.Input.Signature = crypto.SignatureS{privKey.Sign(tx.SignBytes(chainIDFlag))}
|
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-03-08 23:19:07 -08:00
|
|
|
data, log, err := broadcastTx(tx)
|
2017-01-31 08:00:23 -08:00
|
|
|
if err != nil {
|
2017-03-08 23:19:07 -08:00
|
|
|
cmn.Exit(fmt.Sprintf("%+v\n", err))
|
2017-01-29 11:41:21 -08:00
|
|
|
}
|
2017-02-23 15:55:20 -08:00
|
|
|
fmt.Printf("Response: %X ; %s\n", data, log)
|
2017-01-29 11:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// broadcast the transaction to tendermint
|
2017-03-08 23:19:07 -08:00
|
|
|
func broadcastTx(tx types.Tx) ([]byte, string, error) {
|
|
|
|
|
2017-01-29 11:41:21 -08:00
|
|
|
tmResult := new(ctypes.TMResult)
|
2017-03-08 23:19:07 -08:00
|
|
|
uriClient := client.NewURIClient(txNodeFlag)
|
2017-01-29 11:41:21 -08:00
|
|
|
|
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-03-08 23:19:07 -08:00
|
|
|
|
2017-03-16 01:01:29 -07:00
|
|
|
_, err := uriClient.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult)
|
2017-01-29 11:41:21 -08:00
|
|
|
if err != nil {
|
2017-02-23 15:55:20 -08:00
|
|
|
return nil, "", errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
|
2017-01-29 11:41:21 -08:00
|
|
|
}
|
2017-03-08 23:19:07 -08:00
|
|
|
|
2017-01-29 21:27:57 -08:00
|
|
|
res := (*tmResult).(*ctypes.ResultBroadcastTxCommit)
|
2017-03-08 23:19:07 -08:00
|
|
|
|
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
|
2017-02-23 15:55:20 -08:00
|
|
|
return nil, "", errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
|
2017-01-30 10:11:44 -08:00
|
|
|
}
|
2017-03-08 23:19:07 -08:00
|
|
|
|
2017-01-29 21:27:57 -08:00
|
|
|
if !res.DeliverTx.Code.IsOK() {
|
|
|
|
r := res.DeliverTx
|
2017-02-23 15:55:20 -08:00
|
|
|
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-03-08 23:19:07 -08:00
|
|
|
|
2017-02-23 15:55:20 -08:00
|
|
|
return res.DeliverTx.Data, res.DeliverTx.Log, 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
|
2017-03-08 23:19:07 -08:00
|
|
|
func getSeq(address []byte) (int, error) {
|
|
|
|
|
|
|
|
if seqFlag >= 0 {
|
|
|
|
return seqFlag, nil
|
2017-01-29 11:41:21 -08:00
|
|
|
}
|
2017-03-08 23:19:07 -08:00
|
|
|
|
|
|
|
acc, err := getAcc(txNodeFlag, 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
|
|
|
}
|
|
|
|
|
2017-02-09 18:48:42 -08:00
|
|
|
func newOutput(to []byte, amount types.Coins) types.TxOutput {
|
2017-01-28 18:12:58 -08:00
|
|
|
return types.TxOutput{
|
|
|
|
Address: to,
|
2017-02-09 18:48:42 -08:00
|
|
|
Coins: amount,
|
2017-01-28 18:12:58 -08:00
|
|
|
}
|
|
|
|
}
|