diff --git a/cmd/basecoin/account.go b/cmd/basecoin/account.go new file mode 100644 index 000000000..f3d3a4a8d --- /dev/null +++ b/cmd/basecoin/account.go @@ -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 +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index dc940a370..6c4707f81 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -44,6 +44,12 @@ var ( // tx flags var ( + tmAddrFlag = cli.StringFlag{ + Name: "tendermint", + Value: "tcp://localhost:46657", + Usage: "Tendermint RPC address", + } + toFlag = cli.StringFlag{ Name: "to", Value: "", @@ -97,6 +103,12 @@ var ( Value: "", 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() { @@ -118,6 +130,7 @@ func main() { eyesDBFlag, genesisFlag, inProcTMFlag, + chainIDFlag, }, }, @@ -129,12 +142,15 @@ func main() { return cmdSendTx(c) }, Flags: []cli.Flag{ + tmAddrFlag, toFlag, fromFlag, amountFlag, coinFlag, gasFlag, feeFlag, + chainIDFlag, + seqFlag, }, }, @@ -146,6 +162,7 @@ func main() { return cmdAppTx(c) }, Flags: []cli.Flag{ + tmAddrFlag, nameFlag, fromFlag, amountFlag, @@ -153,6 +170,19 @@ func main() { gasFlag, feeFlag, dataFlag, + seqFlag, + }, + }, + + { + Name: "account", + Usage: "Get details of an account", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdAccount(c) + }, + Flags: []cli.Flag{ + tmAddrFlag, }, }, } diff --git a/cmd/basecoin/start.go b/cmd/basecoin/start.go index fde62e3d6..5d5c2de54 100644 --- a/cmd/basecoin/start.go +++ b/cmd/basecoin/start.go @@ -6,11 +6,21 @@ import ( "github.com/urfave/cli" "github.com/tendermint/abci/server" - "github.com/tendermint/basecoin/app" cmn "github.com/tendermint/go-common" + cfg "github.com/tendermint/go-config" + //logger "github.com/tendermint/go-logger" 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 func cmdStart(c *cli.Context) error { @@ -28,27 +38,57 @@ func cmdStart(c *cli.Context) error { } // Create Basecoin app - app := app.NewBasecoin(eyesCli) + basecoinApp := app.NewBasecoin(eyesCli) // If genesis file was specified, set key-value options if c.String("genesis") != "" { - err := app.LoadGenesis(c.String("genesis")) + err := basecoinApp.LoadGenesis(c.String("genesis")) if err != nil { return errors.New(cmn.Fmt("%+v", err)) } } - // Start the listener - svr, err := server.NewServer(c.String("address"), "socket", app) + if c.Bool("in-proc") { + 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 { return errors.New("create listener: " + err.Error()) } - // Wait forever cmn.TrapSignal(func() { // Cleanup svr.Stop() }) - 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() + }) } diff --git a/cmd/basecoin/tx.go b/cmd/basecoin/tx.go index 3f902963a..e70d9a9ff 100644 --- a/cmd/basecoin/tx.go +++ b/cmd/basecoin/tx.go @@ -9,7 +9,9 @@ import ( "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" 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")) chainID := c.String("chain_id") + // convert destination address to bytes to, err := hex.DecodeString(toHex) if err != nil { 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) - 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) output := newOutput(to, coin, amount) - - tx := types.SendTx{ + tx := &types.SendTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, Inputs: []types.TxInput{input}, 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))) + // broadcast the transaction to tendermint + if err := broadcastTx(c, tx); err != nil { + return err + } + return nil } @@ -55,6 +73,7 @@ func cmdAppTx(c *cli.Context) error { chainID := c.String("chain_id") dataString := c.String("data") + // convert data to bytes data := []byte(dataString) if cmn.IsHex(dataString) { data, _ = hex.DecodeString(dataString) @@ -62,11 +81,13 @@ func cmdAppTx(c *cli.Context) error { 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) - - tx := types.AppTx{ + tx := &types.AppTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, Name: name, @@ -75,16 +96,74 @@ func cmdAppTx(c *cli.Context) error { } tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) + + fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) + + if err := broadcastTx(c, tx); err != nil { + return err + } + return nil } -func getSeq(c *cli.Context) int { - if c.IsSet("sequence") { - return c.Int("sequence") +// broadcast the transaction to tendermint +func broadcastTx(c *cli.Context, tx types.Tx) error { + 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 - return 0 + res := (*tmResult).(*ctypes.ResultBroadcastTx) + 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 {