cli: working txs and account fetching

This commit is contained in:
Ethan Buchman 2017-01-29 11:41:21 -08:00
parent 665b39e330
commit 8262d0cc71
4 changed files with 237 additions and 19 deletions

69
cmd/basecoin/account.go Normal file
View File

@ -0,0 +1,69 @@
package main
import (
"encoding/hex"
"errors"
"fmt"
"github.com/urfave/cli"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
client "github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
func cmdAccount(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.New("account command requires an argument ([address])")
}
addrHex := c.Args()[0]
// convert destination address to bytes
addr, err := hex.DecodeString(addrHex)
if err != nil {
return errors.New("Account address is invalid hex: " + err.Error())
}
acc, err := getAcc(c, addr)
if err != nil {
return err
}
fmt.Println(string(wire.JSONBytes(acc)))
return nil
}
// fetch the account by querying the app
func getAcc(c *cli.Context, address []byte) (*types.Account, error) {
tmAddr := c.String("tendermint")
clientURI := client.NewClientURI(tmAddr)
tmResult := new(ctypes.TMResult)
params := map[string]interface{}{
"path": "/key",
"data": append([]byte("base/a/"), address...),
"prove": false,
}
_, err := clientURI.Call("abci_query", params, tmResult)
if err != nil {
return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err))
}
res := (*tmResult).(*ctypes.ResultABCIQuery)
if !res.Response.Code.IsOK() {
return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log))
}
accountBytes := res.Response.Value
if len(accountBytes) == 0 {
return nil, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address))
}
var acc *types.Account
err = wire.ReadBinaryBytes(accountBytes, &acc)
if err != nil {
return nil, errors.New(cmn.Fmt("Error reading account %X error: %v",
accountBytes, err.Error()))
}
return acc, nil
}

View File

@ -44,6 +44,12 @@ var (
// tx flags // tx flags
var ( var (
tmAddrFlag = cli.StringFlag{
Name: "tendermint",
Value: "tcp://localhost:46657",
Usage: "Tendermint RPC address",
}
toFlag = cli.StringFlag{ toFlag = cli.StringFlag{
Name: "to", Name: "to",
Value: "", Value: "",
@ -97,6 +103,12 @@ var (
Value: "", Value: "",
Usage: "Plugin to send the transaction to", Usage: "Plugin to send the transaction to",
} }
chainIDFlag = cli.StringFlag{
Name: "chain_id",
Value: "test_chain_id",
Usage: "ID of the chain for replay protection",
}
) )
func main() { func main() {
@ -118,6 +130,7 @@ func main() {
eyesDBFlag, eyesDBFlag,
genesisFlag, genesisFlag,
inProcTMFlag, inProcTMFlag,
chainIDFlag,
}, },
}, },
@ -129,12 +142,15 @@ func main() {
return cmdSendTx(c) return cmdSendTx(c)
}, },
Flags: []cli.Flag{ Flags: []cli.Flag{
tmAddrFlag,
toFlag, toFlag,
fromFlag, fromFlag,
amountFlag, amountFlag,
coinFlag, coinFlag,
gasFlag, gasFlag,
feeFlag, feeFlag,
chainIDFlag,
seqFlag,
}, },
}, },
@ -146,6 +162,7 @@ func main() {
return cmdAppTx(c) return cmdAppTx(c)
}, },
Flags: []cli.Flag{ Flags: []cli.Flag{
tmAddrFlag,
nameFlag, nameFlag,
fromFlag, fromFlag,
amountFlag, amountFlag,
@ -153,6 +170,19 @@ func main() {
gasFlag, gasFlag,
feeFlag, feeFlag,
dataFlag, dataFlag,
seqFlag,
},
},
{
Name: "account",
Usage: "Get details of an account",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdAccount(c)
},
Flags: []cli.Flag{
tmAddrFlag,
}, },
}, },
} }

View File

@ -6,11 +6,21 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/tendermint/abci/server" "github.com/tendermint/abci/server"
"github.com/tendermint/basecoin/app"
cmn "github.com/tendermint/go-common" cmn "github.com/tendermint/go-common"
cfg "github.com/tendermint/go-config"
//logger "github.com/tendermint/go-logger"
eyes "github.com/tendermint/merkleeyes/client" eyes "github.com/tendermint/merkleeyes/client"
tmcfg "github.com/tendermint/tendermint/config/tendermint"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/proxy"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/app"
) )
var config cfg.Config
const EyesCacheSize = 10000 const EyesCacheSize = 10000
func cmdStart(c *cli.Context) error { func cmdStart(c *cli.Context) error {
@ -28,27 +38,57 @@ func cmdStart(c *cli.Context) error {
} }
// Create Basecoin app // Create Basecoin app
app := app.NewBasecoin(eyesCli) basecoinApp := app.NewBasecoin(eyesCli)
// If genesis file was specified, set key-value options // If genesis file was specified, set key-value options
if c.String("genesis") != "" { if c.String("genesis") != "" {
err := app.LoadGenesis(c.String("genesis")) err := basecoinApp.LoadGenesis(c.String("genesis"))
if err != nil { if err != nil {
return errors.New(cmn.Fmt("%+v", err)) return errors.New(cmn.Fmt("%+v", err))
} }
} }
// Start the listener if c.Bool("in-proc") {
svr, err := server.NewServer(c.String("address"), "socket", app) startTendermint(c, basecoinApp)
} else {
startBasecoinABCI(c, basecoinApp)
}
return nil
}
func startBasecoinABCI(c *cli.Context, basecoinApp *app.Basecoin) error {
// Start the ABCI listener
svr, err := server.NewServer(c.String("address"), "socket", basecoinApp)
if err != nil { if err != nil {
return errors.New("create listener: " + err.Error()) return errors.New("create listener: " + err.Error())
} }
// Wait forever // Wait forever
cmn.TrapSignal(func() { cmn.TrapSignal(func() {
// Cleanup // Cleanup
svr.Stop() svr.Stop()
}) })
return nil return nil
}
func startTendermint(c *cli.Context, basecoinApp *app.Basecoin) {
// Get configuration
config = tmcfg.GetConfig("")
// logger.SetLogLevel("notice") //config.GetString("log_level"))
// parseFlags(config, args[1:]) // Command line overrides
// Create & start tendermint node
privValidatorFile := config.GetString("priv_validator_file")
privValidator := tmtypes.LoadOrGenPrivValidator(privValidatorFile)
n := node.NewNode(config, privValidator, proxy.NewLocalClientCreator(basecoinApp))
n.Start()
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
n.Stop()
})
} }

View File

@ -9,7 +9,9 @@ import (
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common" cmn "github.com/tendermint/go-common"
client "github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
) )
@ -21,28 +23,44 @@ func cmdSendTx(c *cli.Context) error {
gas, fee := c.Int("gas"), int64(c.Int("fee")) gas, fee := c.Int("gas"), int64(c.Int("fee"))
chainID := c.String("chain_id") chainID := c.String("chain_id")
// convert destination address to bytes
to, err := hex.DecodeString(toHex) to, err := hex.DecodeString(toHex)
if err != nil { if err != nil {
return errors.New("To address is invalid hex: " + err.Error()) return errors.New("To address is invalid hex: " + err.Error())
} }
// load the priv validator
// XXX: this is overkill for now, we need a keys solution
privVal := tmtypes.LoadPrivValidator(fromFile) privVal := tmtypes.LoadPrivValidator(fromFile)
sequence := getSeq(c) // get the sequence number for the tx
sequence, err := getSeq(c, privVal.Address)
if err != nil {
return err
}
// craft the tx
input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
output := newOutput(to, coin, amount) output := newOutput(to, coin, amount)
tx := &types.SendTx{
tx := types.SendTx{
Gas: int64(gas), Gas: int64(gas),
Fee: types.Coin{coin, fee}, Fee: types.Coin{coin, fee},
Inputs: []types.TxInput{input}, Inputs: []types.TxInput{input},
Outputs: []types.TxOutput{output}, Outputs: []types.TxOutput{output},
} }
tx.Inputs[0].Signature = privVal.Sign(tx.SignBytes(chainID)) // sign that puppy
signBytes := tx.SignBytes(chainID)
tx.Inputs[0].Signature = privVal.Sign(signBytes)
fmt.Println("Signed SendTx:")
fmt.Println(string(wire.JSONBytes(tx))) fmt.Println(string(wire.JSONBytes(tx)))
// broadcast the transaction to tendermint
if err := broadcastTx(c, tx); err != nil {
return err
}
return nil return nil
} }
@ -55,6 +73,7 @@ func cmdAppTx(c *cli.Context) error {
chainID := c.String("chain_id") chainID := c.String("chain_id")
dataString := c.String("data") dataString := c.String("data")
// convert data to bytes
data := []byte(dataString) data := []byte(dataString)
if cmn.IsHex(dataString) { if cmn.IsHex(dataString) {
data, _ = hex.DecodeString(dataString) data, _ = hex.DecodeString(dataString)
@ -62,11 +81,13 @@ func cmdAppTx(c *cli.Context) error {
privVal := tmtypes.LoadPrivValidator(fromFile) privVal := tmtypes.LoadPrivValidator(fromFile)
sequence := getSeq(c) sequence, err := getSeq(c, privVal.Address)
if err != nil {
return err
}
input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
tx := &types.AppTx{
tx := types.AppTx{
Gas: int64(gas), Gas: int64(gas),
Fee: types.Coin{coin, fee}, Fee: types.Coin{coin, fee},
Name: name, Name: name,
@ -75,16 +96,74 @@ func cmdAppTx(c *cli.Context) error {
} }
tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID))
fmt.Println("Signed AppTx:")
fmt.Println(string(wire.JSONBytes(tx))) fmt.Println(string(wire.JSONBytes(tx)))
if err := broadcastTx(c, tx); err != nil {
return err
}
return nil return nil
} }
func getSeq(c *cli.Context) int { // broadcast the transaction to tendermint
if c.IsSet("sequence") { func broadcastTx(c *cli.Context, tx types.Tx) error {
return c.Int("sequence") tmResult := new(ctypes.TMResult)
tmAddr := c.String("tendermint")
clientURI := client.NewClientURI(tmAddr)
/*txBytes := []byte(wire.JSONBytes(struct {
types.Tx `json:"unwrap"`
}{tx}))*/
txBytes := wire.BinaryBytes(tx)
_, err := clientURI.Call("broadcast_tx_sync", map[string]interface{}{"tx": txBytes}, tmResult)
if err != nil {
return errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
} }
// TODO: get from query res := (*tmResult).(*ctypes.ResultBroadcastTx)
return 0 if !res.Code.IsOK() {
return errors.New(cmn.Fmt("BroadcastTxSync got non-zero exit code: %v. %X; %s", res.Code, res.Data, res.Log))
}
return nil
}
// 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) {
if c.IsSet("sequence") {
return c.Int("sequence"), nil
}
tmAddr := c.String("tendermint")
clientURI := client.NewClientURI(tmAddr)
tmResult := new(ctypes.TMResult)
params := map[string]interface{}{
"path": "/key",
"data": append([]byte("base/a/"), address...),
"prove": false,
}
_, err := clientURI.Call("abci_query", params, tmResult)
if err != nil {
return 0, errors.New(cmn.Fmt("Error calling /abci_query: %v", err))
}
res := (*tmResult).(*ctypes.ResultABCIQuery)
if !res.Response.Code.IsOK() {
return 0, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log))
}
accountBytes := res.Response.Value
if len(accountBytes) == 0 {
return 0, errors.New(cmn.Fmt("Account bytes are empty from query for address %X", address))
}
var acc *types.Account
err = wire.ReadBinaryBytes(accountBytes, &acc)
if err != nil {
return 0, errors.New(cmn.Fmt("Error reading account %X error: %v",
accountBytes, err.Error()))
}
return acc.Sequence + 1, nil
} }
func newOutput(to []byte, coin string, amount int64) types.TxOutput { func newOutput(to []byte, coin string, amount int64) types.TxOutput {