From 734b1073ba56774e9635a1e01e86cc92414ec4ef Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 23 Feb 2018 13:53:49 +0100 Subject: [PATCH] Added tx subcommands and automate manual testing --- client/flags.go | 4 +- client/tx/root.go | 32 ------------ client/tx/search.go | 81 ++++++++++++++++++++++++++++++ client/tx/tx.go | 102 ++++++++++++++++++++++++++++++++++++++ tests/check_basecli.sh | 58 ++++++++++++++++++++++ x/bank/commands/sendtx.go | 2 +- x/bank/handler.go | 1 + 7 files changed, 245 insertions(+), 35 deletions(-) create mode 100644 client/tx/search.go create mode 100644 client/tx/tx.go create mode 100755 tests/check_basecli.sh diff --git a/client/flags.go b/client/flags.go index db35ad06a4..1f55c29217 100644 --- a/client/flags.go +++ b/client/flags.go @@ -20,7 +20,7 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { // TODO: make this default false when we support proofs c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for responses") c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") - c.Flags().String(FlagNode, "", ": to tendermint rpc interface for this chain") + c.Flags().String(FlagNode, "tcp://localhost:46657", ": to tendermint rpc interface for this chain") c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block") } return cmds @@ -31,7 +31,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { c.Flags().String(FlagName, "", "Name of private key with which to sign") c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") - c.Flags().String(FlagNode, "", ": to tendermint rpc interface for this chain") + c.Flags().String(FlagNode, "tcp://localhost:46657", ": to tendermint rpc interface for this chain") } return cmds } diff --git a/client/tx/root.go b/client/tx/root.go index 1d2f33d9e1..e6c6786beb 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -1,21 +1,9 @@ package tx import ( - "errors" - "github.com/spf13/cobra" ) -const ( - flagTags = "tag" - flagAny = "any" -) - -// XXX: remove this when not needed -func todoNotImplemented(_ *cobra.Command, _ []string) error { - return errors.New("TODO: Command not yet implemented") -} - // AddCommands adds a number of tx-query related subcommands func AddCommands(cmd *cobra.Command) { cmd.AddCommand( @@ -23,23 +11,3 @@ func AddCommands(cmd *cobra.Command) { txCommand(), ) } - -func txSearchCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "txs", - Short: "Search for all transactions that match the given tags", - RunE: todoNotImplemented, - } - cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)") - cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL") - return cmd -} - -func txCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "tx ", - Short: "Matches this txhash over all committed blocks", - RunE: todoNotImplemented, - } - return cmd -} diff --git a/client/tx/search.go b/client/tx/search.go new file mode 100644 index 0000000000..51256470a3 --- /dev/null +++ b/client/tx/search.go @@ -0,0 +1,81 @@ +package tx + +import ( + "errors" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +const ( + flagTags = "tag" + flagAny = "any" +) + +func txSearchCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "txs", + Short: "Search for all transactions that match the given tags", + RunE: searchTx, + } + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + // TODO: change this to false when we can + cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)") + cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL") + return cmd +} + +func searchTx(cmd *cobra.Command, args []string) error { + tags := viper.GetStringSlice(flagTags) + if len(tags) == 0 { + return errors.New("Must declare at least one tag to search") + } + // XXX: implement ANY + query := strings.Join(tags, " AND ") + + // get the node + uri := viper.GetString(client.FlagNode) + if uri == "" { + return errors.New("Must define which node to query with --node") + } + node := client.GetNode(uri) + + prove := !viper.GetBool(client.FlagTrustNode) + res, err := node.TxSearch(query, prove) + if err != nil { + return err + } + + info, err := formatTxResults(res) + if err != nil { + return err + } + + cdc := app.MakeTxCodec() + output, err := cdc.MarshalJSON(info) + if err != nil { + return err + } + fmt.Println(string(output)) + + return nil +} + +func formatTxResults(res []*ctypes.ResultTx) ([]txInfo, error) { + var err error + out := make([]txInfo, len(res)) + for i := range res { + out[i], err = formatTxResult(res[i]) + if err != nil { + return nil, err + } + } + return out, nil +} diff --git a/client/tx/tx.go b/client/tx/tx.go new file mode 100644 index 0000000000..5ea3d1995a --- /dev/null +++ b/client/tx/tx.go @@ -0,0 +1,102 @@ +package tx + +import ( + "encoding/hex" + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" // XXX: not good + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/abci/types" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx ", + Short: "Matches this txhash over all committed blocks", + RunE: queryTx, + } + cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + // TODO: change this to false when we can + cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") + return cmd +} + +func queryTx(cmd *cobra.Command, args []string) error { + if len(args) != 1 || len(args[0]) == 0 { + return errors.New("You must provide a tx hash") + } + + // find the key to look up the account + hexStr := args[0] + hash, err := hex.DecodeString(hexStr) + if err != nil { + return err + } + + // get the node + uri := viper.GetString(client.FlagNode) + if uri == "" { + return errors.New("Must define which node to query with --node") + } + node := client.GetNode(uri) + prove := !viper.GetBool(client.FlagTrustNode) + + res, err := node.Tx(hash, prove) + if err != nil { + return err + } + info, err := formatTxResult(res) + if err != nil { + return err + } + + cdc := app.MakeTxCodec() + output, err := cdc.MarshalJSON(info) + if err != nil { + return err + } + fmt.Println(string(output)) + + return nil +} + +func formatTxResult(res *ctypes.ResultTx) (txInfo, error) { + height := res.Height + result := res.TxResult + // TODO: verify the proof if requested + + tx, err := parseTx(res.Tx) + if err != nil { + return txInfo{}, err + } + + info := txInfo{ + Height: height, + Tx: tx, + Result: result, + } + return info, nil +} + +// txInfo is used to prepare info to display +type txInfo struct { + Height int64 `json:"height"` + Tx sdk.Tx `json:"tx"` + Result abci.ResponseDeliverTx `json:"result"` +} + +func parseTx(txBytes []byte) (sdk.Tx, error) { + var tx sdk.StdTx + cdc := app.MakeTxCodec() + err := cdc.UnmarshalBinary(txBytes, &tx) + if err != nil { + return nil, err + } + return tx, nil +} diff --git a/tests/check_basecli.sh b/tests/check_basecli.sh new file mode 100755 index 0000000000..e21cb897c4 --- /dev/null +++ b/tests/check_basecli.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +# Note: Bucky, I know you want to kill bash tests. +# Please show me how to do an alternative to this. +# I would rather get code running before I leave than +# fight trying to invent some new test harness that +# no one else will understand. +# +# Thus, I leave this as an exercise to the reader to +# port into a non-bash version. And I don't do it proper... +# just automate my manual tests + +# WARNING!!! +rm -rf ~/.basecoind ~/.basecli +cd $GOPATH/src/github.com/cosmos/cosmos-sdk +# make get_vendor_deps +make build + +# init stuff +SEED=`./build/basecoind init | tail -1` +PASS='some-silly-123' +(echo $PASS; echo $SEED) | ./build/basecli keys add demo --recover +ADDR=`./build/basecli keys show demo | cut -f3` +echo "Recovered seed for demo:" $ADDR + +# start up server +./build/basecoind start > ~/.basecoind/basecoind.log 2>&1 & +sleep 5 +PID_SERVER=$! + +# query original state +TO='ABCAFE00DEADBEEF00CAFE00DEADBEEF00CAFE00' +echo; echo "My account:" $ADDR +./build/basecli account $ADDR +echo; echo "Empty account:" $TO +./build/basecli account $TO + +# send some money +TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --name=demo --seq=0` +echo; echo "SendTx"; echo $TX +HASH=`echo $TX | cut -d' ' -f6` +echo "tx hash:" $HASH + +# let some blocks come up.... +sleep 2 + +# balances change +echo; echo "My account went down" +./build/basecli account $ADDR +echo; echo "Empty account got some cash" +./build/basecli account $TO + +# query original tx +echo; echo "View tx" +./build/basecli tx $HASH + +# shutdown +kill $PID_SERVER diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 011d2877d8..1cdc77cb01 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" // XXX: not good sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" crypto "github.com/tendermint/go-crypto" diff --git a/x/bank/handler.go b/x/bank/handler.go index e035818cdb..b5d2a67572 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -39,6 +39,7 @@ func handleSendMsg(ctx sdk.Context, ck CoinKeeper, msg SendMsg) sdk.Result { } } + // TODO: add some tags so we can search it! return sdk.Result{} // TODO }