diff --git a/client/commands/txs/helpers.go b/client/commands/txs/helpers.go index d58ea342f2..3ac1617cdb 100644 --- a/client/commands/txs/helpers.go +++ b/client/commands/txs/helpers.go @@ -4,6 +4,8 @@ import ( "bufio" "encoding/json" "fmt" + "io" + "io/ioutil" "os" "strings" @@ -15,7 +17,8 @@ import ( crypto "github.com/tendermint/go-crypto" keycmd "github.com/tendermint/go-crypto/cmd" "github.com/tendermint/go-crypto/keys" - lc "github.com/tendermint/light-client" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/go-wire/data" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -49,48 +52,85 @@ func GetSignerAct() (res basecoin.Actor) { return res } -// Sign if it is Signable, otherwise, just convert it to bytes -func Sign(tx interface{}) (packet []byte, err error) { - name := viper.GetString(FlagName) - manager := keycmd.GetKeyManager() - - if sign, ok := tx.(keys.Signable); ok { - if name == "" { - return nil, errors.New("--name is required to sign tx") - } - packet, err = signTx(manager, sign, name) - } else if val, ok := tx.(lc.Value); ok { - packet = val.Bytes() - } else { - err = errors.Errorf("Reader returned invalid tx type: %#v\n", tx) - } - return -} - -// SignAndPostTx does all work once we construct a proper struct -// it validates the data, signs if needed, transforms to bytes, -// and posts to the node. -func SignAndPostTx(tx Validatable) (*ctypes.ResultBroadcastTxCommit, error) { +// SignTx will validate the tx, and signs it if it is wrapping a Signable. +// Modifies tx in place, and returns an error if it should sign but couldn't +func SignTx(tx basecoin.Tx) error { // validate tx client-side err := tx.ValidateBasic() if err != nil { - return nil, err + return err } - // sign the tx if needed - packet, err := Sign(tx) + name := viper.GetString(FlagName) + manager := keycmd.GetKeyManager() + + if sign, ok := tx.Unwrap().(keys.Signable); ok { + // TODO: allow us not to sign? if so then what use? + if name == "" { + return errors.New("--name is required to sign tx") + } + err = signTx(manager, sign, name) + } + return err +} + +// PrepareOrPostTx checks the flags to decide to prepare the tx for future +// multisig, or to post it to the node. Returns error on any failure. +// If no error and the result is nil, it means it already wrote to file, +// no post, no need to do more. +func PrepareOrPostTx(tx basecoin.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + wrote, err := PrepareTx(tx) + // error in prep if err != nil { return nil, err } + // successfully wrote the tx! + if wrote { + return nil, nil + } + // or try to post it + return PostTx(tx) +} +// PrepareTx checks for FlagPrepare and if set, write the tx as json +// to the specified location for later multi-sig. Returns true if it +// handled the tx (no futher work required), false if it did nothing +// (and we should post the tx) +func PrepareTx(tx basecoin.Tx) (bool, error) { + prep := viper.GetString(FlagPrepare) + if prep == "" { + return false, nil + } + + js, err := data.ToJSON(tx) + if err != nil { + return false, err + } + err = writeOutput(prep, js) + if err != nil { + return false, err + } + return true, nil +} + +// PostTx does all work once we construct a proper struct +// it validates the data, signs if needed, transforms to bytes, +// and posts to the node. +func PostTx(tx basecoin.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + packet := wire.BinaryBytes(tx) // post the bytes node := commands.GetNode() return node.BroadcastTxCommit(packet) } -// OutputTx prints the tx result to stdout -// TODO: something other than raw json? +// OutputTx validates if success and prints the tx result to stdout func OutputTx(res *ctypes.ResultBroadcastTxCommit) error { + if res.CheckTx.IsErr() { + return errors.Errorf("CheckTx: (%d): %s", res.CheckTx.Code, res.CheckTx.Log) + } + if res.DeliverTx.IsErr() { + return errors.Errorf("DeliverTx: (%d): %s", res.DeliverTx.Code, res.DeliverTx.Log) + } js, err := json.MarshalIndent(res, "", " ") if err != nil { return err @@ -99,17 +139,13 @@ func OutputTx(res *ctypes.ResultBroadcastTxCommit) error { return nil } -func signTx(manager keys.Manager, tx keys.Signable, name string) ([]byte, error) { +func signTx(manager keys.Manager, tx keys.Signable, name string) error { prompt := fmt.Sprintf("Please enter passphrase for %s: ", name) pass, err := getPassword(prompt) if err != nil { - return nil, err + return err } - err = manager.Sign(name, pass, tx) - if err != nil { - return nil, err - } - return tx.TxBytes() + return manager.Sign(name, pass, tx) } // if we read from non-tty, we just need to init the buffer reader once, @@ -139,3 +175,40 @@ func getPassword(prompt string) (pass string, err error) { } return } + +func writeOutput(file string, d []byte) error { + var writer io.Writer + if file == "-" { + writer = os.Stdout + } else { + f, err := os.Create(file) + if err != nil { + return errors.WithStack(err) + } + defer f.Close() + writer = f + } + + _, err := writer.Write(d) + // this returns nil if err == nil + return errors.WithStack(err) +} + +func readInput(file string) ([]byte, error) { + var reader io.Reader + // get the input stream + if file == "" || file == "-" { + reader = os.Stdin + } else { + f, err := os.Open(file) + if err != nil { + return nil, errors.WithStack(err) + } + defer f.Close() + reader = f + } + + // and read it all! + data, err := ioutil.ReadAll(reader) + return data, errors.WithStack(err) +} diff --git a/client/commands/txs/presenter.go b/client/commands/txs/presenter.go index 79f718a4f5..2886e7bd2f 100644 --- a/client/commands/txs/presenter.go +++ b/client/commands/txs/presenter.go @@ -1,11 +1,8 @@ package txs import ( - "github.com/pkg/errors" - wire "github.com/tendermint/go-wire" "github.com/tendermint/light-client/proofs" - ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/basecoin" ) @@ -21,15 +18,3 @@ func (BaseTxPresenter) ParseData(raw []byte) (interface{}, error) { err := wire.ReadBinaryBytes(raw, &tx) return tx, err } - -// ValidateResult returns an appropriate error if the server rejected the -// tx in CheckTx or DeliverTx -func ValidateResult(res *ctypes.ResultBroadcastTxCommit) error { - if res.CheckTx.IsErr() { - return errors.Errorf("CheckTx: (%d): %s", res.CheckTx.Code, res.CheckTx.Log) - } - if res.DeliverTx.IsErr() { - return errors.Errorf("DeliverTx: (%d): %s", res.DeliverTx.Code, res.DeliverTx.Log) - } - return nil -} diff --git a/client/commands/txs/root.go b/client/commands/txs/root.go index b9e9ea3aca..e2b88a6e68 100644 --- a/client/commands/txs/root.go +++ b/client/commands/txs/root.go @@ -2,9 +2,6 @@ package txs import ( "encoding/json" - "io" - "io/ioutil" - "os" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -47,34 +44,19 @@ func doRawTx(cmd *cobra.Command, args []string) error { return errors.WithStack(err) } - // Sign if needed and post. This it the work-horse - bres, err := SignAndPostTx(tx.Unwrap()) + // sign it + err = SignTx(tx) if err != nil { return err } - if err = ValidateResult(bres); err != nil { + + // otherwise, post it and display response + bres, err := PrepareOrPostTx(tx) + if err != nil { return err } - - // Output result - return OutputTx(bres) -} - -func readInput(file string) ([]byte, error) { - var reader io.Reader - // get the input stream - if file == "" || file == "-" { - reader = os.Stdin - } else { - f, err := os.Open(file) - if err != nil { - return nil, errors.WithStack(err) - } - defer f.Close() - reader = f + if bres == nil { + return nil // successful prep, nothing left to do } - - // and read it all! - data, err := ioutil.ReadAll(reader) - return data, errors.WithStack(err) + return OutputTx(bres) // print response of the post } diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index a8e473276d..a3770c5aaf 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -46,17 +46,19 @@ func counterTx(cmd *cobra.Command, args []string) error { return err } - // Sign if needed and post. This it the work-horse - bres, err := txcmd.SignAndPostTx(tx.Unwrap()) + err = txcmd.SignTx(tx) if err != nil { return err } - if err = txcmd.ValidateResult(bres); err != nil { + + bres, err := txcmd.PrepareOrPostTx(tx) + if err != nil { return err } - - // Output result - return txcmd.OutputTx(bres) + if bres == nil { + return nil // successful prep, nothing left to do + } + return txcmd.OutputTx(bres) // print response of the post } func readCounterTxFlags() (tx basecoin.Tx, err error) { diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index c817c57b69..b651b29e0b 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -33,13 +33,6 @@ func init() { // sendTxCmd is an example of how to make a tx func sendTxCmd(cmd *cobra.Command, args []string) error { - // load data from json or flags - // var tx basecoin.Tx - // found, err := txcmd.LoadJSON(&tx) - // if err != nil { - // return err - // } - tx, err := readSendTxFlags() if err != nil { return err @@ -50,17 +43,20 @@ func sendTxCmd(cmd *cobra.Command, args []string) error { return err } - // Sign if needed and post. This it the work-horse - bres, err := txcmd.SignAndPostTx(tx.Unwrap()) + err = txcmd.SignTx(tx) if err != nil { return err } - if err = txcmd.ValidateResult(bres); err != nil { + + // otherwise, post it and display response + bres, err := txcmd.PrepareOrPostTx(tx) + if err != nil { return err } - - // Output result - return txcmd.OutputTx(bres) + if bres == nil { + return nil // successful prep, nothing left to do + } + return txcmd.OutputTx(bres) // print response of the post } func readSendTxFlags() (tx basecoin.Tx, err error) {