From 7b0934bf9fcf865f9f87ab28b751858183772365 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 20:12:51 +0200 Subject: [PATCH 01/20] Ripped about cmd logic to make middleware modular --- cmd/basecli/commands/cmds.go | 216 +++++------------- cmd/basecli/commands/query.go | 68 +----- cmd/basecli/main.go | 34 ++- .../cmd/countercli/commands/counter.go | 29 +-- docs/guide/counter/cmd/countercli/main.go | 19 +- modules/auth/commands/wrap.go | 35 +++ modules/base/commands/wrap.go | 40 ++++ modules/coin/commands/query.go | 39 ++++ modules/coin/commands/tx.go | 89 ++++++++ modules/fee/commands/wrap.go | 42 ++++ modules/nonce/commands/query.go | 46 ++++ modules/nonce/commands/wrap.go | 41 ++++ 12 files changed, 435 insertions(+), 263 deletions(-) create mode 100644 modules/auth/commands/wrap.go create mode 100644 modules/base/commands/wrap.go create mode 100644 modules/coin/commands/query.go create mode 100644 modules/coin/commands/tx.go create mode 100644 modules/fee/commands/wrap.go create mode 100644 modules/nonce/commands/query.go create mode 100644 modules/nonce/commands/wrap.go diff --git a/cmd/basecli/commands/cmds.go b/cmd/basecli/commands/cmds.go index 3732f38fc..e1b979ee3 100644 --- a/cmd/basecli/commands/cmds.go +++ b/cmd/basecli/commands/cmds.go @@ -6,10 +6,8 @@ import ( "strings" "github.com/pkg/errors" - "github.com/spf13/cobra" - "github.com/spf13/viper" + "github.com/spf13/pflag" - "github.com/tendermint/light-client/commands" txcmd "github.com/tendermint/light-client/commands/txs" cmn "github.com/tendermint/tmlibs/common" @@ -17,85 +15,44 @@ import ( "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/modules/auth" - "github.com/tendermint/basecoin/modules/base" - "github.com/tendermint/basecoin/modules/coin" - "github.com/tendermint/basecoin/modules/fee" - "github.com/tendermint/basecoin/modules/nonce" ) -//------------------------- -// SendTx - -// SendTxCmd is CLI command to send tokens between basecoin accounts -var SendTxCmd = &cobra.Command{ - Use: "send", - Short: "send tokens from one account to another", - RunE: commands.RequireInit(doSendTx), -} - -//nolint -const ( - FlagTo = "to" - FlagAmount = "amount" - FlagFee = "fee" - FlagGas = "gas" - FlagExpires = "expires" - FlagSequence = "sequence" +var ( + // Middleware must be set in main.go to defined the wrappers we should apply + Middleware Wrapper ) -func init() { - flags := SendTxCmd.Flags() - flags.String(FlagTo, "", "Destination address for the bits") - flags.String(FlagAmount, "", "Coins to send in the format ,...") - flags.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format ") - flags.Uint64(FlagGas, 0, "Amount of gas for this transaction") - flags.Uint64(FlagExpires, 0, "Block height at which this tx expires") - flags.Int(FlagSequence, -1, "Sequence number for this transaction") +// Wrapper defines the information needed for each middleware package that +// wraps the data. They should read all configuration out of bounds via viper. +type Wrapper interface { + Wrap(basecoin.Tx) (basecoin.Tx, error) + Register(*pflag.FlagSet) } -// doSendTx is an example of how to make a tx -func doSendTx(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 - } - if !found { - tx, err = readSendTxFlags() - } - if err != nil { - return err - } +// Wrappers combines a list of wrapper middlewares. +// The first one is the inner-most layer, eg. Fee, Nonce, Chain, Auth +type Wrappers []Wrapper - // TODO: make this more flexible for middleware - tx, err = WrapFeeTx(tx) - if err != nil { - return err - } - tx, err = WrapNonceTx(tx) - if err != nil { - return err - } - tx, err = WrapChainTx(tx) - if err != nil { - return err - } +var _ Wrapper = Wrappers{} - // Note: this is single sig (no multi sig yet) - stx := auth.NewSig(tx) - - // Sign if needed and post. This it the work-horse - bres, err := txcmd.SignAndPostTx(stx) - if err != nil { - return err - } - if err = ValidateResult(bres); err != nil { - return err +// Wrap applies the wrappers to the passed in tx in order, +// aborting on the first error +func (ws Wrappers) Wrap(tx basecoin.Tx) (basecoin.Tx, error) { + var err error + for _, w := range ws { + tx, err = w.Wrap(tx) + if err != nil { + break + } } + return tx, err +} - // Output result - return txcmd.OutputTx(bres) +// Register adds any needed flags to the command +func (ws Wrappers) Register(fs *pflag.FlagSet) { + for _, w := range ws { + w.Register(fs) + } } // ValidateResult returns an appropriate error if the server rejected the @@ -110,44 +67,34 @@ func ValidateResult(res *ctypes.ResultBroadcastTxCommit) error { return nil } -// WrapNonceTx grabs the sequence number from the flag and wraps -// the tx with this nonce. Grabs the permission from the signer, -// as we still only support single sig on the cli -func WrapNonceTx(tx basecoin.Tx) (res basecoin.Tx, err error) { - //add the nonce tx layer to the tx - seq := viper.GetInt(FlagSequence) - if seq < 0 { - return res, fmt.Errorf("sequence must be greater than 0") - } - signers := []basecoin.Actor{GetSignerAct()} - res = nonce.NewTx(uint32(seq), signers, tx) - return -} +// ParseAddress parses an address of form: +// [:][:] +// into a basecoin.Actor. +// If app is not specified or "", then assume auth.NameSigs +func ParseAddress(input string) (res basecoin.Actor, err error) { + chain, app := "", auth.NameSigs + spl := strings.SplitN(input, ":", 3) -// WrapFeeTx checks for FlagFee and if present wraps the tx with a -// FeeTx of the given amount, paid by the signer -func WrapFeeTx(tx basecoin.Tx) (res basecoin.Tx, err error) { - //parse the fee and amounts into coin types - toll, err := coin.ParseCoin(viper.GetString(FlagFee)) + if len(spl) == 3 { + chain = spl[0] + spl = spl[1:] + } + if len(spl) == 2 { + if spl[0] != "" { + app = spl[0] + } + spl = spl[1:] + } + + addr, err := hex.DecodeString(cmn.StripHex(spl[0])) if err != nil { - return res, err + return res, errors.Errorf("Address is invalid hex: %v\n", err) } - // if no fee, do nothing, otherwise wrap it - if toll.IsZero() { - return tx, nil + res = basecoin.Actor{ + ChainID: chain, + App: app, + Address: addr, } - res = fee.NewFee(tx, toll, GetSignerAct()) - return -} - -// WrapChainTx will wrap the tx with a ChainTx from the standard flags -func WrapChainTx(tx basecoin.Tx) (res basecoin.Tx, err error) { - expires := viper.GetInt64(FlagExpires) - chain := commands.GetChainID() - if chain == "" { - return res, errors.New("No chain-id provided") - } - res = base.NewChainTx(chain, uint64(expires), tx) return } @@ -161,60 +108,3 @@ func GetSignerAct() (res basecoin.Actor) { } return res } - -func readSendTxFlags() (tx basecoin.Tx, err error) { - // parse to address - chain, to, err := parseChainAddress(viper.GetString(FlagTo)) - if err != nil { - return tx, err - } - toAddr := auth.SigPerm(to) - toAddr.ChainID = chain - - amountCoins, err := coin.ParseCoins(viper.GetString(FlagAmount)) - if err != nil { - return tx, err - } - - // craft the inputs and outputs - ins := []coin.TxInput{{ - Address: GetSignerAct(), - Coins: amountCoins, - }} - outs := []coin.TxOutput{{ - Address: toAddr, - Coins: amountCoins, - }} - - return coin.NewSendTx(ins, outs), nil -} - -func parseChainAddress(toFlag string) (string, []byte, error) { - var toHex string - var chainPrefix string - spl := strings.Split(toFlag, "/") - switch len(spl) { - case 1: - toHex = spl[0] - case 2: - chainPrefix = spl[0] - toHex = spl[1] - default: - return "", nil, errors.Errorf("To address has too many slashes") - } - - // convert destination address to bytes - to, err := hex.DecodeString(cmn.StripHex(toHex)) - if err != nil { - return "", nil, errors.Errorf("To address is invalid hex: %v\n", err) - } - - return chainPrefix, to, nil -} - -/** TODO copied from basecoin cli - put in common somewhere? **/ - -// ParseHexFlag parses a flag string to byte array -func ParseHexFlag(flag string) ([]byte, error) { - return hex.DecodeString(cmn.StripHex(viper.GetString(flag))) -} diff --git a/cmd/basecli/commands/query.go b/cmd/basecli/commands/query.go index ce3fa338c..2fd85a6c7 100644 --- a/cmd/basecli/commands/query.go +++ b/cmd/basecli/commands/query.go @@ -1,78 +1,12 @@ package commands import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/tendermint/basecoin" wire "github.com/tendermint/go-wire" - lc "github.com/tendermint/light-client" - lcmd "github.com/tendermint/light-client/commands" - proofcmd "github.com/tendermint/light-client/commands/proofs" "github.com/tendermint/light-client/proofs" - "github.com/tendermint/basecoin/modules/auth" - "github.com/tendermint/basecoin/modules/coin" - "github.com/tendermint/basecoin/modules/nonce" - "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin" ) -// AccountQueryCmd - command to query an account -var AccountQueryCmd = &cobra.Command{ - Use: "account [address]", - Short: "Get details of an account, with proof", - RunE: lcmd.RequireInit(doAccountQuery), -} - -func doAccountQuery(cmd *cobra.Command, args []string) error { - addr, err := proofcmd.ParseHexKey(args, "address") - if err != nil { - return err - } - key := stack.PrefixedKey(coin.NameCoin, auth.SigPerm(addr).Bytes()) - - acc := coin.Account{} - proof, err := proofcmd.GetAndParseAppProof(key, &acc) - if lc.IsNoDataErr(err) { - return errors.Errorf("Account bytes are empty for address %X ", addr) - } else if err != nil { - return err - } - - return proofcmd.OutputProof(acc, proof.BlockHeight()) -} - -// NonceQueryCmd - command to query an nonce account -var NonceQueryCmd = &cobra.Command{ - Use: "nonce [address]", - Short: "Get details of a nonce sequence number, with proof", - RunE: lcmd.RequireInit(doNonceQuery), -} - -func doNonceQuery(cmd *cobra.Command, args []string) error { - addr, err := proofcmd.ParseHexKey(args, "address") - if err != nil { - return err - } - - act := []basecoin.Actor{basecoin.NewActor( - auth.NameSigs, - addr, - )} - - key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(act)) - - var seq uint32 - proof, err := proofcmd.GetAndParseAppProof(key, &seq) - if lc.IsNoDataErr(err) { - return errors.Errorf("Sequence is empty for address %X ", addr) - } else if err != nil { - return err - } - - return proofcmd.OutputProof(seq, proof.BlockHeight()) -} - // BaseTxPresenter this decodes all basecoin tx type BaseTxPresenter struct { proofs.RawPresenter // this handles MakeKey as hex bytes diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 48dcf1dd5..be87d2d58 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -1,10 +1,12 @@ package main import ( + "fmt" "os" "github.com/spf13/cobra" + "github.com/tendermint/abci/version" keycmd "github.com/tendermint/go-crypto/cmd" "github.com/tendermint/light-client/commands" "github.com/tendermint/light-client/commands/proofs" @@ -15,7 +17,11 @@ import ( "github.com/tendermint/tmlibs/cli" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" - coincmd "github.com/tendermint/basecoin/cmd/basecoin/commands" + authcmd "github.com/tendermint/basecoin/modules/auth/commands" + basecmd "github.com/tendermint/basecoin/modules/base/commands" + coincmd "github.com/tendermint/basecoin/modules/coin/commands" + feecmd "github.com/tendermint/basecoin/modules/fee/commands" + noncecmd "github.com/tendermint/basecoin/modules/nonce/commands" ) // BaseCli - main basecoin client command @@ -30,6 +36,15 @@ tmcli to work for any custom abci app. `, } +// VersionCmd - command to show the application version +var VersionCmd = &cobra.Command{ + Use: "version", + Short: "Show version info", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.Version) + }, +} + func main() { commands.AddBasicFlags(BaseCli) @@ -38,15 +53,24 @@ func main() { // These are default parsers, but optional in your app (you can remove key) proofs.TxCmd, proofs.KeyCmd, - bcmd.AccountQueryCmd, - bcmd.NonceQueryCmd, + coincmd.AccountQueryCmd, + noncecmd.NonceQueryCmd, ) + // set up the middleware + bcmd.Middleware = bcmd.Wrappers{ + feecmd.FeeWrapper{}, + noncecmd.NonceWrapper{}, + basecmd.ChainWrapper{}, + authcmd.SigWrapper{}, + } + bcmd.Middleware.Register(txs.RootCmd.PersistentFlags()) + // you will always want this for the base send command proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{}) txs.RootCmd.AddCommand( // This is the default transaction, optional in your app - bcmd.SendTxCmd, + coincmd.SendTxCmd, ) // Set up the various commands to use @@ -59,7 +83,7 @@ func main() { proofs.RootCmd, txs.RootCmd, proxy.RootCmd, - coincmd.VersionCmd, + VersionCmd, bcmd.AutoCompleteCmd, ) diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index 68fb86700..22e56cf0a 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -9,7 +9,6 @@ import ( "github.com/tendermint/basecoin" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" - "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/coin" ) @@ -34,45 +33,23 @@ func init() { fs := CounterTxCmd.Flags() fs.String(FlagCountFee, "", "Coins to send in the format ,...") fs.Bool(FlagValid, false, "Is count valid?") - - fs.String(bcmd.FlagFee, "0mycoin", "Coins for the transaction fee of the format ") - fs.Int(bcmd.FlagSequence, -1, "Sequence number for this transaction") } // TODO: counterTx is very similar to the sendtx one, // maybe we can pull out some common patterns? func counterTx(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 - } - if !found { - tx, err = readCounterTxFlags() - } + tx, err := readCounterTxFlags() if err != nil { return err } - // TODO: make this more flexible for middleware - tx, err = bcmd.WrapFeeTx(tx) + tx, err = bcmd.Middleware.Wrap(tx) if err != nil { return err } - tx, err = bcmd.WrapNonceTx(tx) - if err != nil { - return err - } - tx, err = bcmd.WrapChainTx(tx) - if err != nil { - return err - } - - stx := auth.NewSig(tx) // Sign if needed and post. This it the work-horse - bres, err := txcmd.SignAndPostTx(stx) + bres, err := txcmd.SignAndPostTx(tx.Unwrap()) if err != nil { return err } diff --git a/docs/guide/counter/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go index 7b543d352..8d5ae608b 100644 --- a/docs/guide/counter/cmd/countercli/main.go +++ b/docs/guide/counter/cmd/countercli/main.go @@ -15,6 +15,11 @@ import ( bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" bcount "github.com/tendermint/basecoin/docs/guide/counter/cmd/countercli/commands" + authcmd "github.com/tendermint/basecoin/modules/auth/commands" + basecmd "github.com/tendermint/basecoin/modules/base/commands" + coincmd "github.com/tendermint/basecoin/modules/coin/commands" + feecmd "github.com/tendermint/basecoin/modules/fee/commands" + noncecmd "github.com/tendermint/basecoin/modules/nonce/commands" ) // BaseCli represents the base command when called without any subcommands @@ -37,17 +42,27 @@ func main() { // These are default parsers, optional in your app proofs.TxCmd, proofs.KeyCmd, - bcmd.AccountQueryCmd, + coincmd.AccountQueryCmd, + noncecmd.NonceQueryCmd, // XXX IMPORTANT: here is how you add custom query commands in your app bcount.CounterQueryCmd, ) + // set up the middleware + bcmd.Middleware = bcmd.Wrappers{ + feecmd.FeeWrapper{}, + noncecmd.NonceWrapper{}, + basecmd.ChainWrapper{}, + authcmd.SigWrapper{}, + } + bcmd.Middleware.Register(txs.RootCmd.PersistentFlags()) + // Prepare transactions proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{}) txs.RootCmd.AddCommand( // This is the default transaction, optional in your app - bcmd.SendTxCmd, + coincmd.SendTxCmd, // XXX IMPORTANT: here is how you add custom tx construction for your app bcount.CounterTxCmd, diff --git a/modules/auth/commands/wrap.go b/modules/auth/commands/wrap.go new file mode 100644 index 000000000..a02033c65 --- /dev/null +++ b/modules/auth/commands/wrap.go @@ -0,0 +1,35 @@ +package commands + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin" + bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/modules/auth" +) + +//nolint +const ( + FlagMulti = "multi" +) + +// SigWrapper wraps a tx with a signature layer to hold pubkey sigs +type SigWrapper struct{} + +var _ bcmd.Wrapper = SigWrapper{} + +// Wrap will wrap the tx with OneSig or MultiSig depending on flags +func (SigWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { + if !viper.GetBool(FlagMulti) { + res = auth.NewSig(tx).Wrap() + } else { + res = auth.NewMulti(tx).Wrap() + } + return +} + +// Register adds the sequence flags to the cli +func (SigWrapper) Register(fs *pflag.FlagSet) { + fs.Bool(FlagMulti, false, "Prepare the tx for multisig") +} diff --git a/modules/base/commands/wrap.go b/modules/base/commands/wrap.go new file mode 100644 index 000000000..a1042eb76 --- /dev/null +++ b/modules/base/commands/wrap.go @@ -0,0 +1,40 @@ +package commands + +import ( + "errors" + + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/light-client/commands" + + "github.com/tendermint/basecoin" + bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/modules/base" +) + +//nolint +const ( + FlagExpires = "expires" +) + +// ChainWrapper wraps a tx with an chain info and optional expiration +type ChainWrapper struct{} + +var _ bcmd.Wrapper = ChainWrapper{} + +// Wrap will wrap the tx with a ChainTx from the standard flags +func (ChainWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { + expires := viper.GetInt64(FlagExpires) + chain := commands.GetChainID() + if chain == "" { + return res, errors.New("No chain-id provided") + } + res = base.NewChainTx(chain, uint64(expires), tx) + return +} + +// Register adds the sequence flags to the cli +func (ChainWrapper) Register(fs *pflag.FlagSet) { + fs.Uint64(FlagExpires, 0, "Block height at which this tx expires") +} diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go new file mode 100644 index 000000000..9a1308b2b --- /dev/null +++ b/modules/coin/commands/query.go @@ -0,0 +1,39 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + lc "github.com/tendermint/light-client" + lcmd "github.com/tendermint/light-client/commands" + proofcmd "github.com/tendermint/light-client/commands/proofs" + + "github.com/tendermint/basecoin/modules/auth" + "github.com/tendermint/basecoin/modules/coin" + "github.com/tendermint/basecoin/stack" +) + +// AccountQueryCmd - command to query an account +var AccountQueryCmd = &cobra.Command{ + Use: "account [address]", + Short: "Get details of an account, with proof", + RunE: lcmd.RequireInit(doAccountQuery), +} + +func doAccountQuery(cmd *cobra.Command, args []string) error { + addr, err := proofcmd.ParseHexKey(args, "address") + if err != nil { + return err + } + key := stack.PrefixedKey(coin.NameCoin, auth.SigPerm(addr).Bytes()) + + acc := coin.Account{} + proof, err := proofcmd.GetAndParseAppProof(key, &acc) + if lc.IsNoDataErr(err) { + return errors.Errorf("Account bytes are empty for address %X ", addr) + } else if err != nil { + return err + } + + return proofcmd.OutputProof(acc, proof.BlockHeight()) +} diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go new file mode 100644 index 000000000..ce1c33a7f --- /dev/null +++ b/modules/coin/commands/tx.go @@ -0,0 +1,89 @@ +package commands + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/light-client/commands" + txcmd "github.com/tendermint/light-client/commands/txs" + + "github.com/tendermint/basecoin" + bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/modules/coin" +) + +// SendTxCmd is CLI command to send tokens between basecoin accounts +var SendTxCmd = &cobra.Command{ + Use: "send", + Short: "send tokens from one account to another", + RunE: commands.RequireInit(doSendTx), +} + +//nolint +const ( + FlagTo = "to" + FlagAmount = "amount" +) + +func init() { + flags := SendTxCmd.Flags() + flags.String(FlagTo, "", "Destination address for the bits") + flags.String(FlagAmount, "", "Coins to send in the format ,...") +} + +// doSendTx is an example of how to make a tx +func doSendTx(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 + } + + tx, err = bcmd.Middleware.Wrap(tx) + if err != nil { + return err + } + + // Sign if needed and post. This it the work-horse + bres, err := txcmd.SignAndPostTx(tx.Unwrap()) + if err != nil { + return err + } + if err = bcmd.ValidateResult(bres); err != nil { + return err + } + + // Output result + return txcmd.OutputTx(bres) +} + +func readSendTxFlags() (tx basecoin.Tx, err error) { + // parse to address + toAddr, err := bcmd.ParseAddress(viper.GetString(FlagTo)) + if err != nil { + return tx, err + } + + amountCoins, err := coin.ParseCoins(viper.GetString(FlagAmount)) + if err != nil { + return tx, err + } + + // craft the inputs and outputs + ins := []coin.TxInput{{ + Address: bcmd.GetSignerAct(), + Coins: amountCoins, + }} + outs := []coin.TxOutput{{ + Address: toAddr, + Coins: amountCoins, + }} + + return coin.NewSendTx(ins, outs), nil +} diff --git a/modules/fee/commands/wrap.go b/modules/fee/commands/wrap.go new file mode 100644 index 000000000..deb4f42ef --- /dev/null +++ b/modules/fee/commands/wrap.go @@ -0,0 +1,42 @@ +package commands + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin" + bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/modules/coin" + "github.com/tendermint/basecoin/modules/fee" +) + +//nolint +const ( + FlagFee = "fee" +) + +// FeeWrapper wraps a tx with an optional fee payment +type FeeWrapper struct{} + +var _ bcmd.Wrapper = FeeWrapper{} + +// Wrap checks for FlagFee and if present wraps the tx with a +// FeeTx of the given amount, paid by the signer +func (FeeWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { + //parse the fee and amounts into coin types + toll, err := coin.ParseCoin(viper.GetString(FlagFee)) + if err != nil { + return res, err + } + // if no fee, do nothing, otherwise wrap it + if toll.IsZero() { + return tx, nil + } + res = fee.NewFee(tx, toll, bcmd.GetSignerAct()) + return +} + +// Register adds the sequence flags to the cli +func (FeeWrapper) Register(fs *pflag.FlagSet) { + fs.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format ") +} diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go new file mode 100644 index 000000000..233de59e2 --- /dev/null +++ b/modules/nonce/commands/query.go @@ -0,0 +1,46 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + lc "github.com/tendermint/light-client" + lcmd "github.com/tendermint/light-client/commands" + proofcmd "github.com/tendermint/light-client/commands/proofs" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/modules/auth" + "github.com/tendermint/basecoin/modules/nonce" + "github.com/tendermint/basecoin/stack" +) + +// NonceQueryCmd - command to query an nonce account +var NonceQueryCmd = &cobra.Command{ + Use: "nonce [address]", + Short: "Get details of a nonce sequence number, with proof", + RunE: lcmd.RequireInit(doNonceQuery), +} + +func doNonceQuery(cmd *cobra.Command, args []string) error { + addr, err := proofcmd.ParseHexKey(args, "address") + if err != nil { + return err + } + + act := []basecoin.Actor{basecoin.NewActor( + auth.NameSigs, + addr, + )} + + key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(act)) + + var seq uint32 + proof, err := proofcmd.GetAndParseAppProof(key, &seq) + if lc.IsNoDataErr(err) { + return errors.Errorf("Sequence is empty for address %X ", addr) + } else if err != nil { + return err + } + + return proofcmd.OutputProof(seq, proof.BlockHeight()) +} diff --git a/modules/nonce/commands/wrap.go b/modules/nonce/commands/wrap.go new file mode 100644 index 000000000..533462b9f --- /dev/null +++ b/modules/nonce/commands/wrap.go @@ -0,0 +1,41 @@ +package commands + +import ( + "fmt" + + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin" + bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/modules/nonce" +) + +// nolint +const ( + FlagSequence = "sequence" +) + +// NonceWrapper wraps a tx with a nonce +type NonceWrapper struct{} + +var _ bcmd.Wrapper = NonceWrapper{} + +// Wrap grabs the sequence number from the flag and wraps +// the tx with this nonce. Grabs the permission from the signer, +// as we still only support single sig on the cli +func (NonceWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { + //add the nonce tx layer to the tx + seq := viper.GetInt(FlagSequence) + if seq < 0 { + return res, fmt.Errorf("sequence must be greater than 0") + } + signers := []basecoin.Actor{bcmd.GetSignerAct()} + res = nonce.NewTx(uint32(seq), signers, tx) + return +} + +// Register adds the sequence flags to the cli +func (NonceWrapper) Register(fs *pflag.FlagSet) { + fs.Int(FlagSequence, -1, "Sequence number for this transaction") +} From a060bde1c4c246d68902907abafc6363c606ed04 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 20:45:48 +0200 Subject: [PATCH 02/20] Add more flags to help with multisig --- cmd/basecli/commands/cmds.go | 1 + modules/coin/commands/tx.go | 17 ++++++++++++++++- modules/fee/commands/wrap.go | 20 ++++++++++++++++++-- modules/nonce/commands/wrap.go | 25 ++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/cmd/basecli/commands/cmds.go b/cmd/basecli/commands/cmds.go index e1b979ee3..5f5325489 100644 --- a/cmd/basecli/commands/cmds.go +++ b/cmd/basecli/commands/cmds.go @@ -73,6 +73,7 @@ func ValidateResult(res *ctypes.ResultBroadcastTxCommit) error { // If app is not specified or "", then assume auth.NameSigs func ParseAddress(input string) (res basecoin.Actor, err error) { chain, app := "", auth.NameSigs + input = strings.TrimSpace(input) spl := strings.SplitN(input, ":", 3) if len(spl) == 3 { diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index ce1c33a7f..81e786d4a 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -23,12 +23,14 @@ var SendTxCmd = &cobra.Command{ const ( FlagTo = "to" FlagAmount = "amount" + FlagFrom = "from" ) func init() { flags := SendTxCmd.Flags() flags.String(FlagTo, "", "Destination address for the bits") flags.String(FlagAmount, "", "Coins to send in the format ,...") + flags.String(FlagFrom, "", "Address sending coins, if not first signer") } // doSendTx is an example of how to make a tx @@ -70,6 +72,11 @@ func readSendTxFlags() (tx basecoin.Tx, err error) { return tx, err } + fromAddr, err := readFromAddr() + if err != nil { + return tx, err + } + amountCoins, err := coin.ParseCoins(viper.GetString(FlagAmount)) if err != nil { return tx, err @@ -77,7 +84,7 @@ func readSendTxFlags() (tx basecoin.Tx, err error) { // craft the inputs and outputs ins := []coin.TxInput{{ - Address: bcmd.GetSignerAct(), + Address: fromAddr, Coins: amountCoins, }} outs := []coin.TxOutput{{ @@ -87,3 +94,11 @@ func readSendTxFlags() (tx basecoin.Tx, err error) { return coin.NewSendTx(ins, outs), nil } + +func readFromAddr() (basecoin.Actor, error) { + from := viper.GetString(FlagFrom) + if from == "" { + return bcmd.GetSignerAct(), nil + } + return bcmd.ParseAddress(from) +} diff --git a/modules/fee/commands/wrap.go b/modules/fee/commands/wrap.go index deb4f42ef..c464c15c9 100644 --- a/modules/fee/commands/wrap.go +++ b/modules/fee/commands/wrap.go @@ -12,7 +12,8 @@ import ( //nolint const ( - FlagFee = "fee" + FlagFee = "fee" + FlagPayer = "payer" ) // FeeWrapper wraps a tx with an optional fee payment @@ -32,11 +33,26 @@ func (FeeWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { if toll.IsZero() { return tx, nil } - res = fee.NewFee(tx, toll, bcmd.GetSignerAct()) + + payer, err := readPayer() + if err != nil { + return res, err + } + + res = fee.NewFee(tx, toll, payer) return } // Register adds the sequence flags to the cli func (FeeWrapper) Register(fs *pflag.FlagSet) { fs.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format ") + fs.String(FlagPayer, "", "Account to pay fee if not current signer (for multisig)") +} + +func readPayer() (basecoin.Actor, error) { + payer := viper.GetString(FlagPayer) + if payer == "" { + return bcmd.GetSignerAct(), nil + } + return bcmd.ParseAddress(payer) } diff --git a/modules/nonce/commands/wrap.go b/modules/nonce/commands/wrap.go index 533462b9f..b63f0e273 100644 --- a/modules/nonce/commands/wrap.go +++ b/modules/nonce/commands/wrap.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "strings" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -14,6 +15,7 @@ import ( // nolint const ( FlagSequence = "sequence" + FlagNonceKey = "nonce-key" ) // NonceWrapper wraps a tx with a nonce @@ -30,7 +32,10 @@ func (NonceWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { if seq < 0 { return res, fmt.Errorf("sequence must be greater than 0") } - signers := []basecoin.Actor{bcmd.GetSignerAct()} + signers, err := readNonceKey() + if err != nil { + return res, err + } res = nonce.NewTx(uint32(seq), signers, tx) return } @@ -38,4 +43,22 @@ func (NonceWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { // Register adds the sequence flags to the cli func (NonceWrapper) Register(fs *pflag.FlagSet) { fs.Int(FlagSequence, -1, "Sequence number for this transaction") + fs.String(FlagNonceKey, "", "Set of comma-separated addresses for the nonce (for multisig)") +} + +func readNonceKey() (signers []basecoin.Actor, err error) { + nonce := viper.GetString(FlagNonceKey) + if nonce == "" { + return []basecoin.Actor{bcmd.GetSignerAct()}, nil + } + + var act basecoin.Actor + for _, k := range strings.Split(nonce, ",") { + act, err = bcmd.ParseAddress(k) + if err != nil { + return + } + signers = append(signers, act) + } + return } From f41aed494528b2c2ef90557d32e9d166e6f66a0a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 20:55:12 +0200 Subject: [PATCH 03/20] Cleanup nonce parsing for multiple keys --- modules/nonce/commands/query.go | 17 ++++++++--------- modules/nonce/commands/wrap.go | 28 +++++++++++++++++++++------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index 233de59e2..4c4b6cff5 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -1,6 +1,8 @@ package commands import ( + "strings" + "github.com/pkg/errors" "github.com/spf13/cobra" @@ -8,8 +10,6 @@ import ( lcmd "github.com/tendermint/light-client/commands" proofcmd "github.com/tendermint/light-client/commands/proofs" - "github.com/tendermint/basecoin" - "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/nonce" "github.com/tendermint/basecoin/stack" ) @@ -22,22 +22,21 @@ var NonceQueryCmd = &cobra.Command{ } func doNonceQuery(cmd *cobra.Command, args []string) error { - addr, err := proofcmd.ParseHexKey(args, "address") + if len(args) == 0 { + return errors.New("Missing required argument [address]") + } + addr := strings.Join(args, ",") + act, err := parseActors(addr) if err != nil { return err } - act := []basecoin.Actor{basecoin.NewActor( - auth.NameSigs, - addr, - )} - key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(act)) var seq uint32 proof, err := proofcmd.GetAndParseAppProof(key, &seq) if lc.IsNoDataErr(err) { - return errors.Errorf("Sequence is empty for address %X ", addr) + return errors.Errorf("Sequence is empty for address %s ", addr) } else if err != nil { return err } diff --git a/modules/nonce/commands/wrap.go b/modules/nonce/commands/wrap.go index b63f0e273..7e29e8312 100644 --- a/modules/nonce/commands/wrap.go +++ b/modules/nonce/commands/wrap.go @@ -27,16 +27,16 @@ var _ bcmd.Wrapper = NonceWrapper{} // the tx with this nonce. Grabs the permission from the signer, // as we still only support single sig on the cli func (NonceWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { - //add the nonce tx layer to the tx - seq := viper.GetInt(FlagSequence) - if seq < 0 { - return res, fmt.Errorf("sequence must be greater than 0") + seq, err := readSequence() + if err != nil { + return res, err } + signers, err := readNonceKey() if err != nil { return res, err } - res = nonce.NewTx(uint32(seq), signers, tx) + res = nonce.NewTx(seq, signers, tx) return } @@ -46,14 +46,17 @@ func (NonceWrapper) Register(fs *pflag.FlagSet) { fs.String(FlagNonceKey, "", "Set of comma-separated addresses for the nonce (for multisig)") } -func readNonceKey() (signers []basecoin.Actor, err error) { +func readNonceKey() ([]basecoin.Actor, error) { nonce := viper.GetString(FlagNonceKey) if nonce == "" { return []basecoin.Actor{bcmd.GetSignerAct()}, nil } + return parseActors(nonce) +} +func parseActors(key string) (signers []basecoin.Actor, err error) { var act basecoin.Actor - for _, k := range strings.Split(nonce, ",") { + for _, k := range strings.Split(key, ",") { act, err = bcmd.ParseAddress(k) if err != nil { return @@ -62,3 +65,14 @@ func readNonceKey() (signers []basecoin.Actor, err error) { } return } + +func readSequence() (uint32, error) { + //add the nonce tx layer to the tx + seq := viper.GetInt(FlagSequence) + if seq > 0 { + return uint32(seq), nil + } + + // TODO: try to download from query.. + return 0, fmt.Errorf("sequence must be greater than 0") +} From a9e4a944028cfbf9749f115a0c88e686e57f648c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 21:23:11 +0200 Subject: [PATCH 04/20] Moved all commands from light-client into basecoin --- cmd/basecli/commands/cmds.go | 2 +- cmd/basecli/main.go | 12 +- commands/common.go | 73 ++++ commands/init.go | 346 ++++++++++++++++++ commands/proofs/get.go | 119 ++++++ commands/proofs/root.go | 23 ++ commands/proofs/state.go | 46 +++ commands/proofs/tx.go | 49 +++ commands/proxy/root.go | 111 ++++++ commands/rpc/helpers.go | 49 +++ commands/rpc/insecure.go | 66 ++++ commands/rpc/root.go | 65 ++++ commands/rpc/secure.go | 75 ++++ commands/seeds/export.go | 43 +++ commands/seeds/import.go | 57 +++ commands/seeds/root.go | 15 + commands/seeds/show.go | 71 ++++ commands/seeds/update.go | 42 +++ commands/txs/helpers.go | 174 +++++++++ commands/txs/root.go | 19 + .../cmd/countercli/commands/counter.go | 2 +- .../counter/cmd/countercli/commands/query.go | 2 +- docs/guide/counter/cmd/countercli/main.go | 10 +- glide.lock | 16 +- glide.yaml | 9 +- modules/base/commands/wrap.go | 2 +- modules/coin/commands/query.go | 4 +- modules/coin/commands/tx.go | 4 +- modules/nonce/commands/query.go | 4 +- 29 files changed, 1474 insertions(+), 36 deletions(-) create mode 100644 commands/common.go create mode 100644 commands/init.go create mode 100644 commands/proofs/get.go create mode 100644 commands/proofs/root.go create mode 100644 commands/proofs/state.go create mode 100644 commands/proofs/tx.go create mode 100644 commands/proxy/root.go create mode 100644 commands/rpc/helpers.go create mode 100644 commands/rpc/insecure.go create mode 100644 commands/rpc/root.go create mode 100644 commands/rpc/secure.go create mode 100644 commands/seeds/export.go create mode 100644 commands/seeds/import.go create mode 100644 commands/seeds/root.go create mode 100644 commands/seeds/show.go create mode 100644 commands/seeds/update.go create mode 100644 commands/txs/helpers.go create mode 100644 commands/txs/root.go diff --git a/cmd/basecli/commands/cmds.go b/cmd/basecli/commands/cmds.go index 5f5325489..19e685e3b 100644 --- a/cmd/basecli/commands/cmds.go +++ b/cmd/basecli/commands/cmds.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/pflag" - txcmd "github.com/tendermint/light-client/commands/txs" + txcmd "github.com/tendermint/basecoin/commands/txs" cmn "github.com/tendermint/tmlibs/common" ctypes "github.com/tendermint/tendermint/rpc/core/types" diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index be87d2d58..acaaa1a7e 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -8,12 +8,12 @@ import ( "github.com/tendermint/abci/version" keycmd "github.com/tendermint/go-crypto/cmd" - "github.com/tendermint/light-client/commands" - "github.com/tendermint/light-client/commands/proofs" - "github.com/tendermint/light-client/commands/proxy" - rpccmd "github.com/tendermint/light-client/commands/rpc" - "github.com/tendermint/light-client/commands/seeds" - "github.com/tendermint/light-client/commands/txs" + "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/commands/proofs" + "github.com/tendermint/basecoin/commands/proxy" + rpccmd "github.com/tendermint/basecoin/commands/rpc" + "github.com/tendermint/basecoin/commands/seeds" + "github.com/tendermint/basecoin/commands/txs" "github.com/tendermint/tmlibs/cli" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/commands/common.go b/commands/common.go new file mode 100644 index 000000000..a022ff408 --- /dev/null +++ b/commands/common.go @@ -0,0 +1,73 @@ +/* +Package commands contains any general setup/helpers valid for all subcommands +*/ +package commands + +import ( + "errors" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/tmlibs/cli" + + rpcclient "github.com/tendermint/tendermint/rpc/client" + + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/light-client/certifiers/client" + "github.com/tendermint/light-client/certifiers/files" +) + +var ( + trustedProv certifiers.Provider + sourceProv certifiers.Provider +) + +const ( + ChainFlag = "chain-id" + NodeFlag = "node" +) + +func AddBasicFlags(cmd *cobra.Command) { + cmd.PersistentFlags().String(ChainFlag, "", "Chain ID of tendermint node") + cmd.PersistentFlags().String(NodeFlag, "", ": to tendermint rpc interface for this chain") +} + +func GetChainID() string { + return viper.GetString(ChainFlag) +} + +func GetNode() rpcclient.Client { + return rpcclient.NewHTTP(viper.GetString(NodeFlag), "/websocket") +} + +func GetProviders() (trusted certifiers.Provider, source certifiers.Provider) { + if trustedProv == nil || sourceProv == nil { + // initialize provider with files stored in homedir + rootDir := viper.GetString(cli.HomeFlag) + trustedProv = certifiers.NewCacheProvider( + certifiers.NewMemStoreProvider(), + files.NewProvider(rootDir), + ) + node := viper.GetString(NodeFlag) + sourceProv = client.NewHTTP(node) + } + return trustedProv, sourceProv +} + +func GetCertifier() (*certifiers.InquiringCertifier, error) { + // load up the latest store.... + trust, source := GetProviders() + + // this gets the most recent verified seed + seed, err := certifiers.LatestSeed(trust) + if certifiers.IsSeedNotFoundErr(err) { + return nil, errors.New("Please run init first to establish a root of trust") + } + if err != nil { + return nil, err + } + cert := certifiers.NewInquiring( + viper.GetString(ChainFlag), seed.Validators, trust, source) + return cert, nil +} diff --git a/commands/init.go b/commands/init.go new file mode 100644 index 000000000..d53b03cab --- /dev/null +++ b/commands/init.go @@ -0,0 +1,346 @@ +package commands + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/tmlibs/cli" + cmn "github.com/tendermint/tmlibs/common" + + "github.com/tendermint/tendermint/types" + + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/light-client/certifiers/files" +) + +var ( + dirPerm = os.FileMode(0700) +) + +const ( + SeedFlag = "seed" + HashFlag = "valhash" + GenesisFlag = "genesis" + + ConfigFile = "config.toml" +) + +// InitCmd will initialize the basecli store +var InitCmd = &cobra.Command{ + Use: "init", + Short: "Initialize the light client for a new chain", + RunE: runInit, +} + +var ResetCmd = &cobra.Command{ + Use: "reset_all", + Short: "DANGEROUS: Wipe out all client data, including keys", + RunE: runResetAll, +} + +func init() { + InitCmd.Flags().Bool("force-reset", false, "Wipe clean an existing client store, except for keys") + InitCmd.Flags().String(SeedFlag, "", "Seed file to import (optional)") + InitCmd.Flags().String(HashFlag, "", "Trusted validator hash (must match to accept)") + InitCmd.Flags().String(GenesisFlag, "", "Genesis file with chainid and validators (optional)") +} + +func runInit(cmd *cobra.Command, args []string) error { + root := viper.GetString(cli.HomeFlag) + if viper.GetBool("force-reset") { + resetRoot(root, true) + } + + // make sure we don't have an existing client initialized + inited, err := WasInited(root) + if err != nil { + return err + } + if inited { + return errors.Errorf("%s already is initialized, --force-reset if you really want to wipe it out", root) + } + + // clean up dir if init fails + err = doInit(cmd, root) + if err != nil { + resetRoot(root, true) + } + return err +} + +// doInit actually creates all the files, on error, we should revert it all +func doInit(cmd *cobra.Command, root string) error { + // read the genesis file if present, and populate --chain-id and --valhash + err := checkGenesis(cmd) + if err != nil { + return err + } + + err = initConfigFile(cmd) + if err != nil { + return err + } + err = initSeed() + return err +} + +func runResetAll(cmd *cobra.Command, args []string) error { + root := viper.GetString(cli.HomeFlag) + resetRoot(root, false) + return nil +} + +func resetRoot(root string, saveKeys bool) { + tmp := filepath.Join(os.TempDir(), cmn.RandStr(16)) + keys := filepath.Join(root, "keys") + if saveKeys { + os.Rename(keys, tmp) + } + os.RemoveAll(root) + if saveKeys { + os.Mkdir(root, 0700) + os.Rename(tmp, keys) + } +} + +type Runable func(cmd *cobra.Command, args []string) error + +// Any commands that require and init'ed basecoin directory +// should wrap their RunE command with RequireInit +// to make sure that the client is initialized. +// +// This cannot be called during PersistentPreRun, +// as they are called from the most specific command first, and root last, +// and the root command sets up viper, which is needed to find the home dir. +func RequireInit(run Runable) Runable { + return func(cmd *cobra.Command, args []string) error { + // first check if we were Init'ed and if not, return an error + root := viper.GetString(cli.HomeFlag) + init, err := WasInited(root) + if err != nil { + return err + } + if !init { + return errors.Errorf("You must run '%s init' first", cmd.Root().Name()) + } + + // otherwise, run the wrappped command + return run(cmd, args) + } +} + +// WasInited returns true if a basecoin was previously initialized +// in this directory. Important to ensure proper behavior. +// +// Returns error if we have filesystem errors +func WasInited(root string) (bool, error) { + // make sure there is a directory here in any case + os.MkdirAll(root, dirPerm) + + // check if there is a config.toml file + cfgFile := filepath.Join(root, "config.toml") + _, err := os.Stat(cfgFile) + if os.IsNotExist(err) { + return false, nil + } + if err != nil { + return false, errors.WithStack(err) + } + + // check if there are non-empty checkpoints and validators dirs + dirs := []string{ + filepath.Join(root, files.CheckDir), + filepath.Join(root, files.ValDir), + } + // if any of these dirs is empty, then we have no data + for _, d := range dirs { + empty, err := isEmpty(d) + if err != nil { + return false, err + } + if empty { + return false, nil + } + } + + // looks like we have everything + return true, nil +} + +func checkGenesis(cmd *cobra.Command) error { + genesis := viper.GetString(GenesisFlag) + if genesis == "" { + return nil + } + + doc, err := types.GenesisDocFromFile(genesis) + if err != nil { + return err + } + + flags := cmd.Flags() + flags.Set(ChainFlag, doc.ChainID) + hash := doc.ValidatorHash() + hexHash := hex.EncodeToString(hash) + flags.Set(HashFlag, hexHash) + + return nil +} + +// isEmpty returns false if we can read files in this dir. +// if it doesn't exist, read issues, etc... return true +// +// TODO: should we handle errors otherwise? +func isEmpty(dir string) (bool, error) { + // check if we can read the directory, missing is fine, other error is not + d, err := os.Open(dir) + if os.IsNotExist(err) { + return true, nil + } + if err != nil { + return false, errors.WithStack(err) + } + defer d.Close() + + // read to see if any (at least one) files here... + files, err := d.Readdirnames(1) + if err == io.EOF { + return true, nil + } + if err != nil { + return false, errors.WithStack(err) + } + empty := len(files) == 0 + return empty, nil +} + +type Config struct { + Chain string `toml:"chain-id,omitempty"` + Node string `toml:"node,omitempty"` + Output string `toml:"output,omitempty"` + Encoding string `toml:"encoding,omitempty"` +} + +func setConfig(flags *pflag.FlagSet, f string, v *string) { + if flags.Changed(f) { + *v = viper.GetString(f) + } +} + +func initConfigFile(cmd *cobra.Command) error { + flags := cmd.Flags() + var cfg Config + + required := []string{ChainFlag, NodeFlag} + for _, f := range required { + if !flags.Changed(f) { + return errors.Errorf(`"--%s" required`, f) + } + } + + setConfig(flags, ChainFlag, &cfg.Chain) + setConfig(flags, NodeFlag, &cfg.Node) + setConfig(flags, cli.OutputFlag, &cfg.Output) + setConfig(flags, cli.EncodingFlag, &cfg.Encoding) + + out, err := os.Create(filepath.Join(viper.GetString(cli.HomeFlag), ConfigFile)) + if err != nil { + return errors.WithStack(err) + } + defer out.Close() + + // save the config file + err = toml.NewEncoder(out).Encode(cfg) + if err != nil { + return errors.WithStack(err) + } + + return nil +} + +func initSeed() (err error) { + // create a provider.... + trust, source := GetProviders() + + // load a seed file, or get data from the provider + var seed certifiers.Seed + seedFile := viper.GetString(SeedFlag) + if seedFile == "" { + fmt.Println("Loading validator set from tendermint rpc...") + seed, err = certifiers.LatestSeed(source) + } else { + fmt.Printf("Loading validators from file %s\n", seedFile) + seed, err = certifiers.LoadSeed(seedFile) + } + // can't load the seed? abort! + if err != nil { + return err + } + + // make sure it is a proper seed + err = seed.ValidateBasic(viper.GetString(ChainFlag)) + if err != nil { + return err + } + + // validate hash interactively or not + hash := viper.GetString(HashFlag) + if hash != "" { + var hashb []byte + hashb, err = hex.DecodeString(hash) + if err == nil && !bytes.Equal(hashb, seed.Hash()) { + err = errors.Errorf("Seed hash doesn't match expectation: %X", seed.Hash()) + } + } else { + err = validateHash(seed) + } + + if err != nil { + return err + } + + // if accepted, store seed as current state + trust.StoreSeed(seed) + return nil +} + +func validateHash(seed certifiers.Seed) error { + // ask the user to verify the validator hash + fmt.Println("\nImportant: if this is incorrect, all interaction with the chain will be insecure!") + fmt.Printf(" Given validator hash valid: %X\n", seed.Hash()) + fmt.Println("Is this valid (y/n)?") + valid := askForConfirmation() + if !valid { + return errors.New("Invalid validator hash, try init with proper seed later") + } + return nil +} + +func askForConfirmation() bool { + var resp string + _, err := fmt.Scanln(&resp) + if err != nil { + fmt.Println("Please type yes or no and then press enter:") + return askForConfirmation() + } + resp = strings.ToLower(resp) + if resp == "y" || resp == "yes" { + return true + } else if resp == "n" || resp == "no" { + return false + } else { + fmt.Println("Please type yes or no and then press enter:") + return askForConfirmation() + } +} diff --git a/commands/proofs/get.go b/commands/proofs/get.go new file mode 100644 index 000000000..846a4e750 --- /dev/null +++ b/commands/proofs/get.go @@ -0,0 +1,119 @@ +package proofs + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/viper" + + wire "github.com/tendermint/go-wire" + "github.com/tendermint/go-wire/data" + + "github.com/tendermint/tendermint/rpc/client" + + lc "github.com/tendermint/light-client" + "github.com/tendermint/basecoin/commands" + "github.com/tendermint/light-client/proofs" +) + +// GetAndParseAppProof does most of the work of the query commands, but is quite +// opinionated, so if you want more control, set up the items and call GetProof +// directly. Notably, it always uses go-wire.ReadBinaryBytes to deserialize, +// and Height and Node from standard flags. +// +// It will try to get the proof for the given key. If it is successful, +// it will return the proof and also unserialize proof.Data into the data +// argument (so pass in a pointer to the appropriate struct) +func GetAndParseAppProof(key []byte, data interface{}) (lc.Proof, error) { + height := GetHeight() + node := commands.GetNode() + prover := proofs.NewAppProver(node) + + proof, err := GetProof(node, prover, key, height) + if err != nil { + return proof, err + } + + err = wire.ReadBinaryBytes(proof.Data(), data) + return proof, err +} + +// GetProof performs the get command directly from the proof (not from the CLI) +func GetProof(node client.Client, prover lc.Prover, key []byte, height int) (proof lc.Proof, err error) { + proof, err = prover.Get(key, uint64(height)) + if err != nil { + return + } + ph := int(proof.BlockHeight()) + // here is the certifier, root of all knowledge + cert, err := commands.GetCertifier() + if err != nil { + return + } + + // get and validate a signed header for this proof + + // FIXME: cannot use cert.GetByHeight for now, as it also requires + // Validators and will fail on querying tendermint for non-current height. + // When this is supported, we should use it instead... + client.WaitForHeight(node, ph, nil) + commit, err := node.Commit(ph) + if err != nil { + return + } + check := lc.Checkpoint{ + Header: commit.Header, + Commit: commit.Commit, + } + err = cert.Certify(check) + if err != nil { + return + } + + // validate the proof against the certified header to ensure data integrity + err = proof.Validate(check) + if err != nil { + return + } + + return proof, err +} + +// ParseHexKey parses the key flag as hex and converts to bytes or returns error +// argname is used to customize the error message +func ParseHexKey(args []string, argname string) ([]byte, error) { + if len(args) == 0 { + return nil, errors.Errorf("Missing required argument [%s]", argname) + } + if len(args) > 1 { + return nil, errors.Errorf("Only accepts one argument [%s]", argname) + } + rawkey := args[0] + if rawkey == "" { + return nil, errors.Errorf("[%s] argument must be non-empty ", argname) + } + // with tx, we always just parse key as hex and use to lookup + return proofs.ParseHexKey(rawkey) +} + +func GetHeight() int { + return viper.GetInt(heightFlag) +} + +type proof struct { + Height uint64 `json:"height"` + Data interface{} `json:"data"` +} + +// OutputProof prints the proof to stdout +// reuse this for printing proofs and we should enhance this for text/json, +// better presentation of height +func OutputProof(info interface{}, height uint64) error { + wrap := proof{height, info} + res, err := data.ToJSON(wrap) + if err != nil { + return err + } + fmt.Println(string(res)) + return nil +} diff --git a/commands/proofs/root.go b/commands/proofs/root.go new file mode 100644 index 000000000..15b49b8c9 --- /dev/null +++ b/commands/proofs/root.go @@ -0,0 +1,23 @@ +package proofs + +import "github.com/spf13/cobra" + +const ( + heightFlag = "height" +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "query", + Short: "Get and store merkle proofs for blockchain data", + Long: `Proofs allows you to validate data and merkle proofs. + +These proofs tie the data to a checkpoint, which is managed by "seeds". +Here we can validate these proofs and import/export them to prove specific +data to other peers as needed. +`, +} + +func init() { + RootCmd.Flags().Int(heightFlag, 0, "Height to query (skip to use latest block)") +} diff --git a/commands/proofs/state.go b/commands/proofs/state.go new file mode 100644 index 000000000..f6dce1dc0 --- /dev/null +++ b/commands/proofs/state.go @@ -0,0 +1,46 @@ +package proofs + +import ( + "github.com/spf13/cobra" + + "github.com/tendermint/go-wire/data" + + "github.com/tendermint/basecoin/commands" + "github.com/tendermint/light-client/proofs" +) + +var KeyCmd = &cobra.Command{ + Use: "key [key]", + Short: "Handle proofs for state of abci app", + Long: `This will look up a given key in the abci app, verify the proof, +and output it as hex. + +If you want json output, use an app-specific command that knows key and value structure.`, + RunE: commands.RequireInit(doKeyQuery), +} + +// Note: we cannot yse GetAndParseAppProof here, as we don't use go-wire to +// parse the object, but rather return the raw bytes +func doKeyQuery(cmd *cobra.Command, args []string) error { + // parse cli + height := GetHeight() + key, err := ParseHexKey(args, "key") + if err != nil { + return err + } + + // get the proof -> this will be used by all prover commands + node := commands.GetNode() + prover := proofs.NewAppProver(node) + proof, err := GetProof(node, prover, key, height) + if err != nil { + return err + } + + // state just returns raw hex.... + info := data.Bytes(proof.Data()) + + // we can reuse this output for other commands for text/json + // unless they do something special like store a file to disk + return OutputProof(info, proof.BlockHeight()) +} diff --git a/commands/proofs/tx.go b/commands/proofs/tx.go new file mode 100644 index 000000000..5009a8149 --- /dev/null +++ b/commands/proofs/tx.go @@ -0,0 +1,49 @@ +package proofs + +import ( + "github.com/spf13/cobra" + + "github.com/tendermint/basecoin/commands" + "github.com/tendermint/light-client/proofs" +) + +var TxPresenters = proofs.NewPresenters() + +var TxCmd = &cobra.Command{ + Use: "tx [txhash]", + Short: "Handle proofs of commited txs", + Long: `Proofs allows you to validate abci state with merkle proofs. + +These proofs tie the data to a checkpoint, which is managed by "seeds". +Here we can validate these proofs and import/export them to prove specific +data to other peers as needed. +`, + RunE: commands.RequireInit(doTxQuery), +} + +func doTxQuery(cmd *cobra.Command, args []string) error { + // parse cli + height := GetHeight() + bkey, err := ParseHexKey(args, "txhash") + if err != nil { + return err + } + + // get the proof -> this will be used by all prover commands + node := commands.GetNode() + prover := proofs.NewTxProver(node) + proof, err := GetProof(node, prover, bkey, height) + if err != nil { + return err + } + + // auto-determine which tx it was, over all registered tx types + info, err := TxPresenters.BruteForce(proof.Data()) + if err != nil { + return err + } + + // we can reuse this output for other commands for text/json + // unless they do something special like store a file to disk + return OutputProof(info, proof.BlockHeight()) +} diff --git a/commands/proxy/root.go b/commands/proxy/root.go new file mode 100644 index 000000000..4897df816 --- /dev/null +++ b/commands/proxy/root.go @@ -0,0 +1,111 @@ +package proxy + +import ( + "net/http" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/rpc/core" + rpc "github.com/tendermint/tendermint/rpc/lib/server" + + certclient "github.com/tendermint/light-client/certifiers/client" + "github.com/tendermint/basecoin/commands" +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "proxy", + Short: "Run proxy server, verifying tendermint rpc", + Long: `This node will run a secure proxy to a tendermint rpc server. + +All calls that can be tracked back to a block header by a proof +will be verified before passing them back to the caller. Other that +that it will present the same interface as a full tendermint node, +just with added trust and running locally.`, + RunE: commands.RequireInit(runProxy), + SilenceUsage: true, +} + +const ( + bindFlag = "serve" + wsEndpoint = "/websocket" +) + +func init() { + RootCmd.Flags().String(bindFlag, ":8888", "Serve the proxy on the given port") +} + +// TODO: pass in a proper logger +var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + +func init() { + logger = logger.With("module", "main") + logger = log.NewFilter(logger, log.AllowInfo()) +} + +func runProxy(cmd *cobra.Command, args []string) error { + // First, connect a client + c := commands.GetNode() + cert, err := commands.GetCertifier() + if err != nil { + return err + } + sc := certclient.Wrap(c, cert) + sc.Start() + r := routes(sc) + + // build the handler... + mux := http.NewServeMux() + rpc.RegisterRPCFuncs(mux, r, logger) + wm := rpc.NewWebsocketManager(r, c) + wm.SetLogger(logger) + core.SetLogger(logger) + mux.HandleFunc(wsEndpoint, wm.WebsocketHandler) + + _, err = rpc.StartHTTPServer(viper.GetString(bindFlag), mux, logger) + if err != nil { + return err + } + + cmn.TrapSignal(func() { + // TODO: close up shop + }) + + return nil +} + +// First step, proxy with no checks.... +func routes(c client.Client) map[string]*rpc.RPCFunc { + + return map[string]*rpc.RPCFunc{ + // Subscribe/unsubscribe are reserved for websocket events. + // We can just use the core tendermint impl, which uses the + // EventSwitch we registered in NewWebsocketManager above + "subscribe": rpc.NewWSRPCFunc(core.Subscribe, "event"), + "unsubscribe": rpc.NewWSRPCFunc(core.Unsubscribe, "event"), + + // info API + "status": rpc.NewRPCFunc(c.Status, ""), + "blockchain": rpc.NewRPCFunc(c.BlockchainInfo, "minHeight,maxHeight"), + "genesis": rpc.NewRPCFunc(c.Genesis, ""), + "block": rpc.NewRPCFunc(c.Block, "height"), + "commit": rpc.NewRPCFunc(c.Commit, "height"), + "tx": rpc.NewRPCFunc(c.Tx, "hash,prove"), + "validators": rpc.NewRPCFunc(c.Validators, ""), + + // broadcast API + "broadcast_tx_commit": rpc.NewRPCFunc(c.BroadcastTxCommit, "tx"), + "broadcast_tx_sync": rpc.NewRPCFunc(c.BroadcastTxSync, "tx"), + "broadcast_tx_async": rpc.NewRPCFunc(c.BroadcastTxAsync, "tx"), + + // abci API + "abci_query": rpc.NewRPCFunc(c.ABCIQuery, "path,data,prove"), + "abci_info": rpc.NewRPCFunc(c.ABCIInfo, ""), + } +} diff --git a/commands/rpc/helpers.go b/commands/rpc/helpers.go new file mode 100644 index 000000000..8fa374916 --- /dev/null +++ b/commands/rpc/helpers.go @@ -0,0 +1,49 @@ +package rpc + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin/commands" + + "github.com/tendermint/tendermint/rpc/client" +) + +var waitCmd = &cobra.Command{ + Use: "wait", + Short: "Wait until a given height, or number of new blocks", + RunE: commands.RequireInit(runWait), +} + +func init() { + waitCmd.Flags().Int(FlagHeight, -1, "wait for block height") + waitCmd.Flags().Int(FlagDelta, -1, "wait for given number of nodes") +} + +func runWait(cmd *cobra.Command, args []string) error { + c := commands.GetNode() + h := viper.GetInt(FlagHeight) + if h == -1 { + // read from delta + d := viper.GetInt(FlagDelta) + if d == -1 { + return errors.New("Must set --height or --delta") + } + status, err := c.Status() + if err != nil { + return err + } + h = status.LatestBlockHeight + d + } + + // now wait + err := client.WaitForHeight(c, h, nil) + if err != nil { + return err + } + fmt.Printf("Chain now at height %d\n", h) + return nil +} diff --git a/commands/rpc/insecure.go b/commands/rpc/insecure.go new file mode 100644 index 000000000..c8054fe1b --- /dev/null +++ b/commands/rpc/insecure.go @@ -0,0 +1,66 @@ +package rpc + +import ( + "github.com/spf13/cobra" + "github.com/tendermint/basecoin/commands" +) + +var statusCmd = &cobra.Command{ + Use: "status", + Short: "Query the status of the node", + RunE: commands.RequireInit(runStatus), +} + +func runStatus(cmd *cobra.Command, args []string) error { + c := commands.GetNode() + status, err := c.Status() + if err != nil { + return err + } + return printResult(status) +} + +var infoCmd = &cobra.Command{ + Use: "info", + Short: "Query info on the abci app", + RunE: commands.RequireInit(runInfo), +} + +func runInfo(cmd *cobra.Command, args []string) error { + c := commands.GetNode() + info, err := c.ABCIInfo() + if err != nil { + return err + } + return printResult(info) +} + +var genesisCmd = &cobra.Command{ + Use: "genesis", + Short: "Query the genesis of the node", + RunE: commands.RequireInit(runGenesis), +} + +func runGenesis(cmd *cobra.Command, args []string) error { + c := commands.GetNode() + genesis, err := c.Genesis() + if err != nil { + return err + } + return printResult(genesis) +} + +var validatorsCmd = &cobra.Command{ + Use: "validators", + Short: "Query the validators of the node", + RunE: commands.RequireInit(runValidators), +} + +func runValidators(cmd *cobra.Command, args []string) error { + c := commands.GetNode() + validators, err := c.Validators() + if err != nil { + return err + } + return printResult(validators) +} diff --git a/commands/rpc/root.go b/commands/rpc/root.go new file mode 100644 index 000000000..2da71392e --- /dev/null +++ b/commands/rpc/root.go @@ -0,0 +1,65 @@ +package rpc + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/tendermint/go-wire/data" + "github.com/tendermint/tendermint/rpc/client" + + certclient "github.com/tendermint/light-client/certifiers/client" + "github.com/tendermint/basecoin/commands" +) + +const ( + FlagDelta = "delta" + FlagHeight = "height" + FlagMax = "max" + FlagMin = "min" +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "rpc", + Short: "Query the tendermint rpc, validating everything with a proof", +} + +// TODO: add support for subscribing to events???? +func init() { + RootCmd.AddCommand( + statusCmd, + infoCmd, + genesisCmd, + validatorsCmd, + blockCmd, + commitCmd, + headersCmd, + waitCmd, + ) +} + +func getSecureNode() (client.Client, error) { + // First, connect a client + c := commands.GetNode() + cert, err := commands.GetCertifier() + if err != nil { + return nil, err + } + sc := certclient.Wrap(c, cert) + return sc, nil +} + +// printResult just writes the struct to the console, returns an error if it can't +func printResult(res interface{}) error { + // TODO: handle text mode + // switch viper.Get(cli.OutputFlag) { + // case "text": + // case "json": + json, err := data.ToJSON(res) + if err != nil { + return err + } + fmt.Println(string(json)) + return nil +} diff --git a/commands/rpc/secure.go b/commands/rpc/secure.go new file mode 100644 index 000000000..da405e78a --- /dev/null +++ b/commands/rpc/secure.go @@ -0,0 +1,75 @@ +package rpc + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/basecoin/commands" +) + +func init() { + blockCmd.Flags().Int(FlagHeight, -1, "block height") + commitCmd.Flags().Int(FlagHeight, -1, "block height") + headersCmd.Flags().Int(FlagMin, -1, "minimum block height") + headersCmd.Flags().Int(FlagMax, -1, "maximum block height") +} + +var blockCmd = &cobra.Command{ + Use: "block", + Short: "Get a validated block at a given height", + RunE: commands.RequireInit(runBlock), +} + +func runBlock(cmd *cobra.Command, args []string) error { + c, err := getSecureNode() + if err != nil { + return err + } + + h := viper.GetInt(FlagHeight) + block, err := c.Block(h) + if err != nil { + return err + } + return printResult(block) +} + +var commitCmd = &cobra.Command{ + Use: "commit", + Short: "Get the header and commit signature at a given height", + RunE: commands.RequireInit(runCommit), +} + +func runCommit(cmd *cobra.Command, args []string) error { + c, err := getSecureNode() + if err != nil { + return err + } + + h := viper.GetInt(FlagHeight) + commit, err := c.Commit(h) + if err != nil { + return err + } + return printResult(commit) +} + +var headersCmd = &cobra.Command{ + Use: "headers", + Short: "Get all headers in the given height range", + RunE: commands.RequireInit(runHeaders), +} + +func runHeaders(cmd *cobra.Command, args []string) error { + c, err := getSecureNode() + if err != nil { + return err + } + + min := viper.GetInt(FlagMin) + max := viper.GetInt(FlagMax) + headers, err := c.BlockchainInfo(min, max) + if err != nil { + return err + } + return printResult(headers) +} diff --git a/commands/seeds/export.go b/commands/seeds/export.go new file mode 100644 index 000000000..14b082bd4 --- /dev/null +++ b/commands/seeds/export.go @@ -0,0 +1,43 @@ +package seeds + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/basecoin/commands" +) + +var exportCmd = &cobra.Command{ + Use: "export ", + Short: "Export selected seeds to given file", + Long: `Exports the most recent seed to a binary file. +If desired, you can select by an older height or validator hash. +`, + RunE: commands.RequireInit(exportSeed), + SilenceUsage: true, +} + +func init() { + exportCmd.Flags().Int(heightFlag, 0, "Show the seed with closest height to this") + exportCmd.Flags().String(hashFlag, "", "Show the seed matching the validator hash") + RootCmd.AddCommand(exportCmd) +} + +func exportSeed(cmd *cobra.Command, args []string) error { + if len(args) != 1 || len(args[0]) == 0 { + return errors.New("You must provide a filepath to output") + } + path := args[0] + + // load the seed as specified + trust, _ := commands.GetProviders() + h := viper.GetInt(heightFlag) + hash := viper.GetString(hashFlag) + seed, err := loadSeed(trust, h, hash, "") + if err != nil { + return err + } + + // now get the output file and write it + return seed.Write(path) +} diff --git a/commands/seeds/import.go b/commands/seeds/import.go new file mode 100644 index 000000000..add67a666 --- /dev/null +++ b/commands/seeds/import.go @@ -0,0 +1,57 @@ +package seeds + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/basecoin/commands" +) + +const ( + dryFlag = "dry-run" +) + +var importCmd = &cobra.Command{ + Use: "import ", + Short: "Imports a new seed from the given file", + Long: `Validate this file and update to the given seed if secure.`, + RunE: commands.RequireInit(importSeed), + SilenceUsage: true, +} + +func init() { + importCmd.Flags().Bool(dryFlag, false, "Test the import fully, but do not import") + RootCmd.AddCommand(importCmd) +} + +func importSeed(cmd *cobra.Command, args []string) error { + if len(args) != 1 || len(args[0]) == 0 { + return errors.New("You must provide an input file") + } + + // prepare the certifier + cert, err := commands.GetCertifier() + if err != nil { + return err + } + + // parse the input file + path := args[0] + seed, err := certifiers.LoadSeed(path) + if err != nil { + return err + } + + // just do simple checks in --dry-run + if viper.GetBool(dryFlag) { + fmt.Printf("Testing seed %d/%X\n", seed.Height(), seed.Hash()) + err = seed.ValidateBasic(cert.ChainID()) + } else { + fmt.Printf("Importing seed %d/%X\n", seed.Height(), seed.Hash()) + err = cert.Update(seed.Checkpoint, seed.Validators) + } + return err +} diff --git a/commands/seeds/root.go b/commands/seeds/root.go new file mode 100644 index 000000000..b4fc9d66f --- /dev/null +++ b/commands/seeds/root.go @@ -0,0 +1,15 @@ +package seeds + +import "github.com/spf13/cobra" + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "seeds", + Short: "Verify seeds from your local store", + Long: `Seeds allows you to inspect and update the validator set for the chain. + +Since all security in a PoS system is based on having the correct validator +set, it is important to inspect the seeds to maintain the security, which +is used to verify all header and merkle proofs. +`, +} diff --git a/commands/seeds/show.go b/commands/seeds/show.go new file mode 100644 index 000000000..2b2c7c716 --- /dev/null +++ b/commands/seeds/show.go @@ -0,0 +1,71 @@ +package seeds + +import ( + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/basecoin/commands" +) + +const ( + heightFlag = "height" + hashFlag = "hash" + fileFlag = "file" +) + +var showCmd = &cobra.Command{ + Use: "show", + Short: "Show the details of one selected seed", + Long: `Shows the most recent downloaded key by default. +If desired, you can select by height, validator hash, or a file. +`, + RunE: commands.RequireInit(showSeed), + SilenceUsage: true, +} + +func init() { + showCmd.Flags().Int(heightFlag, 0, "Show the seed with closest height to this") + showCmd.Flags().String(hashFlag, "", "Show the seed matching the validator hash") + showCmd.Flags().String(fileFlag, "", "Show the seed stored in the given file") + RootCmd.AddCommand(showCmd) +} + +func loadSeed(p certifiers.Provider, h int, hash, file string) (seed certifiers.Seed, err error) { + // load the seed from the proper place + if h != 0 { + seed, err = p.GetByHeight(h) + } else if hash != "" { + var vhash []byte + vhash, err = hex.DecodeString(hash) + if err == nil { + seed, err = p.GetByHash(vhash) + } + } else if file != "" { + seed, err = certifiers.LoadSeed(file) + } else { + // default is latest seed + seed, err = certifiers.LatestSeed(p) + } + return +} + +func showSeed(cmd *cobra.Command, args []string) error { + trust, _ := commands.GetProviders() + + h := viper.GetInt(heightFlag) + hash := viper.GetString(hashFlag) + file := viper.GetString(fileFlag) + seed, err := loadSeed(trust, h, hash, file) + if err != nil { + return err + } + + // now render it! + data, err := json.MarshalIndent(seed, "", " ") + fmt.Println(string(data)) + return err +} diff --git a/commands/seeds/update.go b/commands/seeds/update.go new file mode 100644 index 000000000..ada8fe41f --- /dev/null +++ b/commands/seeds/update.go @@ -0,0 +1,42 @@ +package seeds + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/basecoin/commands" +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update seed to current chain state if possible", + RunE: commands.RequireInit(updateSeed), + SilenceUsage: true, +} + +func init() { + RootCmd.AddCommand(updateCmd) +} + +func updateSeed(cmd *cobra.Command, args []string) error { + cert, err := commands.GetCertifier() + if err != nil { + return err + } + + // get the lastest from our source + seed, err := certifiers.LatestSeed(cert.SeedSource) + if err != nil { + return err + } + fmt.Printf("Trying to update to height: %d...\n", seed.Height()) + + // let the certifier do it's magic to update.... + err = cert.Update(seed.Checkpoint, seed.Validators) + if err != nil { + return err + } + fmt.Println("Success!") + return nil +} diff --git a/commands/txs/helpers.go b/commands/txs/helpers.go new file mode 100644 index 000000000..9426fa768 --- /dev/null +++ b/commands/txs/helpers.go @@ -0,0 +1,174 @@ +package txs + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/bgentry/speakeasy" + "github.com/mattn/go-isatty" + "github.com/pkg/errors" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin/commands" + crypto "github.com/tendermint/go-crypto" + keycmd "github.com/tendermint/go-crypto/cmd" + "github.com/tendermint/go-crypto/keys" + + ctypes "github.com/tendermint/tendermint/rpc/core/types" + + lc "github.com/tendermint/light-client" +) + +type Validatable interface { + ValidateBasic() error +} + +// GetSigner returns the pub key that will sign the tx +// returns empty key if no name provided +func GetSigner() crypto.PubKey { + name := viper.GetString(NameFlag) + manager := keycmd.GetKeyManager() + info, _ := manager.Get(name) // error -> empty pubkey + return info.PubKey +} + +// Sign if it is Signable, otherwise, just convert it to bytes +func Sign(tx interface{}) (packet []byte, err error) { + name := viper.GetString(NameFlag) + 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) { + // validate tx client-side + err := tx.ValidateBasic() + if err != nil { + return nil, err + } + + // sign the tx if needed + packet, err := Sign(tx) + if err != nil { + return nil, err + } + + // post the bytes + node := commands.GetNode() + return node.BroadcastTxCommit(packet) +} + +// LoadJSON will read a json file from disk if --input is passed in +// template is a pointer to a struct that can hold the expected data (&MyTx{}) +// +// If not data is provided, returns (false, nil) +// If data is provided and passes, returns (true, nil) +// If data is provided but not parsable, returns (true, err) +func LoadJSON(template interface{}) (bool, error) { + input := viper.GetString(InputFlag) + if input == "" { + return false, nil + } + + // load the input + raw, err := readInput(input) + if err != nil { + return true, err + } + + // parse the input + err = json.Unmarshal(raw, template) + if err != nil { + return true, err + } + return true, nil +} + +// OutputTx prints the tx result to stdout +// TODO: something other than raw json? +func OutputTx(res *ctypes.ResultBroadcastTxCommit) error { + js, err := json.MarshalIndent(res, "", " ") + if err != nil { + return err + } + fmt.Println(string(js)) + return nil +} + +func signTx(manager keys.Manager, tx keys.Signable, name string) ([]byte, error) { + prompt := fmt.Sprintf("Please enter passphrase for %s: ", name) + pass, err := getPassword(prompt) + if err != nil { + return nil, err + } + err = manager.Sign(name, pass, tx) + if err != nil { + return nil, err + } + return tx.TxBytes() +} + +func readInput(file string) ([]byte, error) { + var reader io.Reader + // get the input stream + if file == "-" { + reader = os.Stdin + } else { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + reader = f + } + + // and read it all! + data, err := ioutil.ReadAll(reader) + return data, errors.WithStack(err) +} + +// if we read from non-tty, we just need to init the buffer reader once, +// in case we try to read multiple passwords +var buf *bufio.Reader + +func inputIsTty() bool { + return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) +} + +func stdinPassword() (string, error) { + if buf == nil { + buf = bufio.NewReader(os.Stdin) + } + pass, err := buf.ReadString('\n') + if err != nil { + return "", err + } + return strings.TrimSpace(pass), nil +} + +func getPassword(prompt string) (pass string, err error) { + if inputIsTty() { + pass, err = speakeasy.Ask(prompt) + } else { + pass, err = stdinPassword() + } + return +} diff --git a/commands/txs/root.go b/commands/txs/root.go new file mode 100644 index 000000000..965f6f259 --- /dev/null +++ b/commands/txs/root.go @@ -0,0 +1,19 @@ +package txs + +import "github.com/spf13/cobra" + +const ( + NameFlag = "name" + InputFlag = "input" +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "tx", + Short: "Create and post transactions to the node", +} + +func init() { + RootCmd.PersistentFlags().String(NameFlag, "", "name to sign the tx") + RootCmd.PersistentFlags().String(InputFlag, "", "file with tx in json format") +} diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index 22e56cf0a..c4336d111 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - txcmd "github.com/tendermint/light-client/commands/txs" + txcmd "github.com/tendermint/basecoin/commands/txs" "github.com/tendermint/basecoin" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/docs/guide/counter/cmd/countercli/commands/query.go b/docs/guide/counter/cmd/countercli/commands/query.go index 489a8a01d..07a8620e8 100644 --- a/docs/guide/counter/cmd/countercli/commands/query.go +++ b/docs/guide/counter/cmd/countercli/commands/query.go @@ -3,7 +3,7 @@ package commands import ( "github.com/spf13/cobra" - proofcmd "github.com/tendermint/light-client/commands/proofs" + proofcmd "github.com/tendermint/basecoin/commands/proofs" "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" "github.com/tendermint/basecoin/stack" diff --git a/docs/guide/counter/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go index 8d5ae608b..b3cd915b0 100644 --- a/docs/guide/counter/cmd/countercli/main.go +++ b/docs/guide/counter/cmd/countercli/main.go @@ -6,11 +6,11 @@ import ( "github.com/spf13/cobra" keycmd "github.com/tendermint/go-crypto/cmd" - "github.com/tendermint/light-client/commands" - "github.com/tendermint/light-client/commands/proofs" - "github.com/tendermint/light-client/commands/proxy" - "github.com/tendermint/light-client/commands/seeds" - "github.com/tendermint/light-client/commands/txs" + "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/commands/proofs" + "github.com/tendermint/basecoin/commands/proxy" + "github.com/tendermint/basecoin/commands/seeds" + "github.com/tendermint/basecoin/commands/txs" "github.com/tendermint/tmlibs/cli" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/glide.lock b/glide.lock index 71ebac8c7..9157e3fc7 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 6eb1119dccf2ab4d0adb870a14cb4408047119be53c8ec4afeaa281bd1d2b457 -updated: 2017-06-28T13:09:42.542992443+02:00 +hash: 2fec08220d5d8cbc791523583b85f3fb68e3d65ead6802198d9c879a9e295b46 +updated: 2017-07-18T21:21:05.336445544+02:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd @@ -111,6 +111,7 @@ imports: - example/dummy - server - types + - version - name: github.com/tendermint/ed25519 version: 1f52c6f8b8a5c7908aff4497c186af344b428925 subpackages: @@ -125,6 +126,7 @@ imports: - keys/server - keys/server/types - keys/storage/filestorage + - keys/storage/memstorage - keys/wordlist - name: github.com/tendermint/go-wire version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb @@ -132,17 +134,11 @@ imports: - data - data/base58 - name: github.com/tendermint/light-client - version: 489b726d8b358dbd9d8f6a15d18e8b9fe0a39269 + version: d63415027075bc5d74a98a718393b59b5c4279a5 subpackages: - certifiers - certifiers/client - certifiers/files - - commands - - commands/proofs - - commands/proxy - - commands/rpc - - commands/seeds - - commands/txs - proofs - name: github.com/tendermint/merkleeyes version: 102aaf5a8ffda1846413fb22805a94def2045b9f @@ -151,7 +147,7 @@ imports: - client - iavl - name: github.com/tendermint/tendermint - version: 3065059da7bb57714f08c7a6fcb97e4b36be0194 + version: 695ad5fe2d70ec7b6fcfe0b46a73cc1b2d55e0ac subpackages: - blockchain - cmd/tendermint/commands diff --git a/glide.yaml b/glide.yaml index 7e7727aac..d9072f482 100644 --- a/glide.yaml +++ b/glide.yaml @@ -22,13 +22,12 @@ import: subpackages: - data - package: github.com/tendermint/light-client - version: develop + version: unstable subpackages: - - commands - - commands/proofs - - commands/seeds - - commands/txs - proofs + - certifiers + - certifiers/client + - certifiers/files - package: github.com/tendermint/merkleeyes version: develop subpackages: diff --git a/modules/base/commands/wrap.go b/modules/base/commands/wrap.go index a1042eb76..3367cc70f 100644 --- a/modules/base/commands/wrap.go +++ b/modules/base/commands/wrap.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/tendermint/light-client/commands" + "github.com/tendermint/basecoin/commands" "github.com/tendermint/basecoin" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go index 9a1308b2b..8edd96c2d 100644 --- a/modules/coin/commands/query.go +++ b/modules/coin/commands/query.go @@ -5,8 +5,8 @@ import ( "github.com/spf13/cobra" lc "github.com/tendermint/light-client" - lcmd "github.com/tendermint/light-client/commands" - proofcmd "github.com/tendermint/light-client/commands/proofs" + lcmd "github.com/tendermint/basecoin/commands" + proofcmd "github.com/tendermint/basecoin/commands/proofs" "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/coin" diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index 81e786d4a..566901f2f 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -4,8 +4,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/light-client/commands" - txcmd "github.com/tendermint/light-client/commands/txs" + "github.com/tendermint/basecoin/commands" + txcmd "github.com/tendermint/basecoin/commands/txs" "github.com/tendermint/basecoin" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index 4c4b6cff5..79e7695c4 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -7,8 +7,8 @@ import ( "github.com/spf13/cobra" lc "github.com/tendermint/light-client" - lcmd "github.com/tendermint/light-client/commands" - proofcmd "github.com/tendermint/light-client/commands/proofs" + lcmd "github.com/tendermint/basecoin/commands" + proofcmd "github.com/tendermint/basecoin/commands/proofs" "github.com/tendermint/basecoin/modules/nonce" "github.com/tendermint/basecoin/stack" From d9c39ff9e6b7bd267a25c909be4570942463f518 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 21:46:13 +0200 Subject: [PATCH 05/20] Bring more cli tests from light-client --- Makefile | 3 ++ tests/cli/init.sh | 108 ++++++++++++++++++++++++++++++++++++++ tests/cli/keys.sh | 29 +++++++++++ tests/cli/rpc.sh | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 270 insertions(+) create mode 100755 tests/cli/init.sh create mode 100755 tests/cli/keys.sh create mode 100755 tests/cli/rpc.sh diff --git a/Makefile b/Makefile index 84c4e11f4..906b3b601 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,9 @@ test_unit: test_cli: tests/cli/shunit2 # sudo apt-get install jq + ./tests/cli/keys.sh + ./tests/cli/rpc.sh + ./tests/cli/init.sh ./tests/cli/basictx.sh ./tests/cli/counter.sh ./tests/cli/restart.sh diff --git a/tests/cli/init.sh b/tests/cli/init.sh new file mode 100755 index 000000000..a5a3bb387 --- /dev/null +++ b/tests/cli/init.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +CLIENT_EXE=basecli +SERVER_EXE=basecoin + +oneTimeSetUp() { + BASE=~/.bc_init_test + rm -rf "$BASE" + mkdir -p "$BASE" + + SERVER="${BASE}/server" + SERVER_LOG="${BASE}/${SERVER_EXE}.log" + + HEX="deadbeef1234deadbeef1234deadbeef1234aaaa" + ${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG" + if ! assertTrue $?; then return 1; fi + + GENESIS_FILE=${SERVER}/genesis.json + CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \") + + printf "starting ${SERVER_EXE}...\n" + ${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 & + sleep 5 + PID_SERVER=$! + disown + if ! ps $PID_SERVER >/dev/null; then + echo "**STARTUP FAILED**" + cat $SERVER_LOG + return 1 + fi +} + +oneTimeTearDown() { + printf "\nstopping ${SERVER_EXE}..." + kill -9 $PID_SERVER >/dev/null 2>&1 + sleep 1 +} + +test01goodInit() { + export BCHOME=${BASE}/client-01 + assertFalse "ls ${BCHOME} 2>/dev/null >&2" + + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null + assertTrue "initialized light-client" $? + checkDir $BCHOME 3 +} + +test02badInit() { + export BCHOME=${BASE}/client-02 + assertFalse "ls ${BCHOME} 2>/dev/null >&2" + + # no node where we go + echo y | ${CLIENT_EXE} init --node=tcp://localhost:9999 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertFalse "invalid init" $? + # dir there, but empty... + checkDir $BCHOME 0 + + # try with invalid chain id + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="bad-chain-id" > /dev/null 2>&1 + assertFalse "invalid init" $? + checkDir $BCHOME 0 + + # reject the response + echo n | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertFalse "invalid init" $? + checkDir $BCHOME 0 +} + +test03noDoubleInit() { + export BCHOME=${BASE}/client-03 + assertFalse "ls ${BCHOME} 2>/dev/null >&2" + + # init properly + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertTrue "initialized light-client" $? + checkDir $BCHOME 3 + + # try again, and we get an error + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertFalse "warning on re-init" $? + checkDir $BCHOME 3 + + # unless we --force-reset + echo y | ${CLIENT_EXE} init --force-reset --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertTrue "re-initialized light-client" $? + checkDir $BCHOME 3 +} + +test04acceptGenesisFile() { + export BCHOME=${BASE}/client-04 + assertFalse "ls ${BCHOME} 2>/dev/null >&2" + + # init properly + ${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1 + assertTrue "initialized light-client" $? + checkDir $BCHOME 3 +} + +# XXX Ex: checkDir $DIR $FILES +# Makes sure directory exists and has the given number of files +checkDir() { + assertTrue "ls ${1} 2>/dev/null >&2" + assertEquals "no files created" "$2" $(ls $1 | wc -l) +} + +# load and run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory +. $DIR/shunit2 diff --git a/tests/cli/keys.sh b/tests/cli/keys.sh new file mode 100755 index 000000000..4cf9e61d1 --- /dev/null +++ b/tests/cli/keys.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +CLIENT_EXE=basecli + + +oneTimeSetUp() { + PASS=qwertyuiop + export BCHOME=$HOME/.bc_keys_test + ${CLIENT_EXE} reset_all + assertTrue $? +} + +newKey(){ + assertNotNull "keyname required" "$1" + KEYPASS=${2:-qwertyuiop} + echo $KEYPASS | ${CLIENT_EXE} keys new $1 >/dev/null 2>&1 + assertTrue "created $1" $? +} + +testMakeKeys() { + USER=demouser + assertFalse "already user $USER" "${CLIENT_EXE} keys get $USER" + newKey $USER + assertTrue "no user $USER" "${CLIENT_EXE} keys get $USER" +} + +# load and run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory +. $DIR/shunit2 diff --git a/tests/cli/rpc.sh b/tests/cli/rpc.sh new file mode 100755 index 000000000..eb764089a --- /dev/null +++ b/tests/cli/rpc.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +CLIENT_EXE=basecli +SERVER_EXE=basecoin + +oneTimeSetUp() { + BASE=~/.bc_init_test + rm -rf "$BASE" + mkdir -p "$BASE" + + SERVER="${BASE}/server" + SERVER_LOG="${BASE}/${SERVER_EXE}.log" + + HEX="deadbeef1234deadbeef1234deadbeef1234aaaa" + ${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG" + if ! assertTrue $?; then return 1; fi + + GENESIS_FILE=${SERVER}/genesis.json + CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \") + + printf "starting ${SERVER_EXE}...\n" + ${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 & + sleep 5 + PID_SERVER=$! + disown + if ! ps $PID_SERVER >/dev/null; then + echo "**STARTUP FAILED**" + cat $SERVER_LOG + return 1 + fi + + # this sets the base for all client queries in the tests + export BCHOME=${BASE}/client + ${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1 + if ! assertTrue "initialized light-client" "$?"; then + return 1 + fi +} + +oneTimeTearDown() { + printf "\nstopping ${SERVER_EXE}..." + kill -9 $PID_SERVER >/dev/null 2>&1 + sleep 1 +} + +test01getInsecure() { + GENESIS=$(${CLIENT_EXE} rpc genesis) + assertTrue "get genesis" "$?" + MYCHAIN=$(echo ${GENESIS} | jq .genesis.chain_id | tr -d \") + assertEquals "genesis chain matches" "${CHAIN_ID}" "${MYCHAIN}" + + STATUS=$(${CLIENT_EXE} rpc status) + assertTrue "get status" "$?" + SHEIGHT=$(echo ${STATUS} | jq .latest_block_height) + assertTrue "parsed status" "$?" + assertNotNull "has a height" "${SHEIGHT}" + + VALS=$(${CLIENT_EXE} rpc validators) + assertTrue "get validators" "$?" + VHEIGHT=$(echo ${VALS} | jq .block_height) + assertTrue "parsed validators" "$?" + assertTrue "sensible heights: $SHEIGHT / $VHEIGHT" "test $VHEIGHT -ge $SHEIGHT" + VCNT=$(echo ${VALS} | jq '.validators | length') + assertEquals "one validator" "1" "$VCNT" + + INFO=$(${CLIENT_EXE} rpc info) + assertTrue "get info" "$?" + DATA=$(echo $INFO | jq .response.data) + assertEquals "basecoin info" '"Basecoin v0.6.1"' "$DATA" +} + +test02getSecure() { + HEIGHT=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertTrue "get status" "$?" + + # check block produces something reasonable + assertFalse "missing height" "${CLIENT_EXE} rpc block" + BLOCK=$(${CLIENT_EXE} rpc block --height=$HEIGHT) + assertTrue "get block" "$?" + MHEIGHT=$(echo $BLOCK | jq .block_meta.header.height) + assertEquals "meta height" "${HEIGHT}" "${MHEIGHT}" + BHEIGHT=$(echo $BLOCK | jq .block.header.height) + assertEquals "meta height" "${HEIGHT}" "${BHEIGHT}" + + # check commit produces something reasonable + assertFalse "missing height" "${CLIENT_EXE} rpc commit" + let "CHEIGHT = $HEIGHT - 1" + COMMIT=$(${CLIENT_EXE} rpc commit --height=$CHEIGHT) + assertTrue "get commit" "$?" + HHEIGHT=$(echo $COMMIT | jq .header.height) + assertEquals "commit height" "${CHEIGHT}" "${HHEIGHT}" + assertEquals "canonical" "true" $(echo $COMMIT | jq .canonical) + BSIG=$(echo $BLOCK | jq .block.last_commit) + CSIG=$(echo $COMMIT | jq .commit) + assertEquals "block and commit" "$BSIG" "$CSIG" + + # now let's get some headers + # assertFalse "missing height" "${CLIENT_EXE} rpc headers" + HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT) + assertTrue "get headers" "$?" + assertEquals "proper height" "$HEIGHT" $(echo $HEADERS | jq '.last_height') + assertEquals "two headers" "2" $(echo $HEADERS | jq '.block_metas | length') + # should we check these headers? + CHEAD=$(echo $COMMIT | jq .header) + # most recent first, so the commit header is second.... + HHEAD=$(echo $HEADERS | jq .block_metas[1].header) + assertEquals "commit and header" "$CHEAD" "$HHEAD" +} + +test03waiting() { + START=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertTrue "get status" "$?" + + let "NEXT = $START + 5" + assertFalse "no args" "${CLIENT_EXE} rpc wait" + assertFalse "too long" "${CLIENT_EXE} rpc wait --height=1234" + assertTrue "normal wait" "${CLIENT_EXE} rpc wait --height=$NEXT" + + STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertEquals "wait until height" "$NEXT" "$STEP" + + let "NEXT = $STEP + 3" + assertTrue "${CLIENT_EXE} rpc wait --delta=3" + STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertEquals "wait for delta" "$NEXT" "$STEP" +} + +# load and run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory +. $DIR/shunit2 From eb495e081b1cc0b690824a9b9f018b7c27f62cd2 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 21:57:37 +0200 Subject: [PATCH 06/20] Move commands to client/commands --- {commands => client/commands}/common.go | 0 {commands => client/commands}/init.go | 0 {commands => client/commands}/proofs/get.go | 2 +- {commands => client/commands}/proofs/root.go | 0 {commands => client/commands}/proofs/state.go | 2 +- {commands => client/commands}/proofs/tx.go | 2 +- {commands => client/commands}/proxy/root.go | 2 +- {commands => client/commands}/rpc/helpers.go | 2 +- {commands => client/commands}/rpc/insecure.go | 2 +- {commands => client/commands}/rpc/root.go | 2 +- {commands => client/commands}/rpc/secure.go | 2 +- {commands => client/commands}/seeds/export.go | 2 +- {commands => client/commands}/seeds/import.go | 2 +- {commands => client/commands}/seeds/root.go | 0 {commands => client/commands}/seeds/show.go | 2 +- {commands => client/commands}/seeds/update.go | 2 +- {commands => client/commands}/txs/helpers.go | 2 +- {commands => client/commands}/txs/root.go | 0 cmd/basecli/commands/cmds.go | 2 +- cmd/basecli/main.go | 12 ++++++------ .../guide/counter/cmd/countercli/commands/counter.go | 2 +- docs/guide/counter/cmd/countercli/commands/query.go | 2 +- docs/guide/counter/cmd/countercli/main.go | 10 +++++----- modules/base/commands/wrap.go | 2 +- modules/coin/commands/query.go | 4 ++-- modules/coin/commands/tx.go | 4 ++-- modules/nonce/commands/query.go | 4 ++-- 27 files changed, 34 insertions(+), 34 deletions(-) rename {commands => client/commands}/common.go (100%) rename {commands => client/commands}/init.go (100%) rename {commands => client/commands}/proofs/get.go (98%) rename {commands => client/commands}/proofs/root.go (100%) rename {commands => client/commands}/proofs/state.go (96%) rename {commands => client/commands}/proofs/tx.go (96%) rename {commands => client/commands}/proxy/root.go (98%) rename {commands => client/commands}/rpc/helpers.go (95%) rename {commands => client/commands}/rpc/insecure.go (96%) rename {commands => client/commands}/rpc/root.go (96%) rename {commands => client/commands}/rpc/secure.go (96%) rename {commands => client/commands}/seeds/export.go (95%) rename {commands => client/commands}/seeds/import.go (96%) rename {commands => client/commands}/seeds/root.go (100%) rename {commands => client/commands}/seeds/show.go (97%) rename {commands => client/commands}/seeds/update.go (94%) rename {commands => client/commands}/txs/helpers.go (98%) rename {commands => client/commands}/txs/root.go (100%) diff --git a/commands/common.go b/client/commands/common.go similarity index 100% rename from commands/common.go rename to client/commands/common.go diff --git a/commands/init.go b/client/commands/init.go similarity index 100% rename from commands/init.go rename to client/commands/init.go diff --git a/commands/proofs/get.go b/client/commands/proofs/get.go similarity index 98% rename from commands/proofs/get.go rename to client/commands/proofs/get.go index 846a4e750..c670e7b3e 100644 --- a/commands/proofs/get.go +++ b/client/commands/proofs/get.go @@ -12,7 +12,7 @@ import ( "github.com/tendermint/tendermint/rpc/client" lc "github.com/tendermint/light-client" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/light-client/proofs" ) diff --git a/commands/proofs/root.go b/client/commands/proofs/root.go similarity index 100% rename from commands/proofs/root.go rename to client/commands/proofs/root.go diff --git a/commands/proofs/state.go b/client/commands/proofs/state.go similarity index 96% rename from commands/proofs/state.go rename to client/commands/proofs/state.go index f6dce1dc0..6f7109d01 100644 --- a/commands/proofs/state.go +++ b/client/commands/proofs/state.go @@ -5,7 +5,7 @@ import ( "github.com/tendermint/go-wire/data" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/light-client/proofs" ) diff --git a/commands/proofs/tx.go b/client/commands/proofs/tx.go similarity index 96% rename from commands/proofs/tx.go rename to client/commands/proofs/tx.go index 5009a8149..7dc21af05 100644 --- a/commands/proofs/tx.go +++ b/client/commands/proofs/tx.go @@ -3,7 +3,7 @@ package proofs import ( "github.com/spf13/cobra" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/light-client/proofs" ) diff --git a/commands/proxy/root.go b/client/commands/proxy/root.go similarity index 98% rename from commands/proxy/root.go rename to client/commands/proxy/root.go index 4897df816..0284f4dbe 100644 --- a/commands/proxy/root.go +++ b/client/commands/proxy/root.go @@ -15,7 +15,7 @@ import ( rpc "github.com/tendermint/tendermint/rpc/lib/server" certclient "github.com/tendermint/light-client/certifiers/client" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) // RootCmd represents the base command when called without any subcommands diff --git a/commands/rpc/helpers.go b/client/commands/rpc/helpers.go similarity index 95% rename from commands/rpc/helpers.go rename to client/commands/rpc/helpers.go index 8fa374916..5ef0f4f3e 100644 --- a/commands/rpc/helpers.go +++ b/client/commands/rpc/helpers.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/tendermint/rpc/client" ) diff --git a/commands/rpc/insecure.go b/client/commands/rpc/insecure.go similarity index 96% rename from commands/rpc/insecure.go rename to client/commands/rpc/insecure.go index c8054fe1b..80432f95e 100644 --- a/commands/rpc/insecure.go +++ b/client/commands/rpc/insecure.go @@ -2,7 +2,7 @@ package rpc import ( "github.com/spf13/cobra" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) var statusCmd = &cobra.Command{ diff --git a/commands/rpc/root.go b/client/commands/rpc/root.go similarity index 96% rename from commands/rpc/root.go rename to client/commands/rpc/root.go index 2da71392e..471744b2f 100644 --- a/commands/rpc/root.go +++ b/client/commands/rpc/root.go @@ -9,7 +9,7 @@ import ( "github.com/tendermint/tendermint/rpc/client" certclient "github.com/tendermint/light-client/certifiers/client" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) const ( diff --git a/commands/rpc/secure.go b/client/commands/rpc/secure.go similarity index 96% rename from commands/rpc/secure.go rename to client/commands/rpc/secure.go index da405e78a..d49504143 100644 --- a/commands/rpc/secure.go +++ b/client/commands/rpc/secure.go @@ -3,7 +3,7 @@ package rpc import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) func init() { diff --git a/commands/seeds/export.go b/client/commands/seeds/export.go similarity index 95% rename from commands/seeds/export.go rename to client/commands/seeds/export.go index 14b082bd4..a4c453526 100644 --- a/commands/seeds/export.go +++ b/client/commands/seeds/export.go @@ -4,7 +4,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) var exportCmd = &cobra.Command{ diff --git a/commands/seeds/import.go b/client/commands/seeds/import.go similarity index 96% rename from commands/seeds/import.go rename to client/commands/seeds/import.go index add67a666..2731324d9 100644 --- a/commands/seeds/import.go +++ b/client/commands/seeds/import.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tendermint/light-client/certifiers" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) const ( diff --git a/commands/seeds/root.go b/client/commands/seeds/root.go similarity index 100% rename from commands/seeds/root.go rename to client/commands/seeds/root.go diff --git a/commands/seeds/show.go b/client/commands/seeds/show.go similarity index 97% rename from commands/seeds/show.go rename to client/commands/seeds/show.go index 2b2c7c716..5ea8287e8 100644 --- a/commands/seeds/show.go +++ b/client/commands/seeds/show.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tendermint/light-client/certifiers" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) const ( diff --git a/commands/seeds/update.go b/client/commands/seeds/update.go similarity index 94% rename from commands/seeds/update.go rename to client/commands/seeds/update.go index ada8fe41f..575ede4ac 100644 --- a/commands/seeds/update.go +++ b/client/commands/seeds/update.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" "github.com/tendermint/light-client/certifiers" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" ) var updateCmd = &cobra.Command{ diff --git a/commands/txs/helpers.go b/client/commands/txs/helpers.go similarity index 98% rename from commands/txs/helpers.go rename to client/commands/txs/helpers.go index 9426fa768..3a592a450 100644 --- a/commands/txs/helpers.go +++ b/client/commands/txs/helpers.go @@ -14,7 +14,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/viper" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" crypto "github.com/tendermint/go-crypto" keycmd "github.com/tendermint/go-crypto/cmd" "github.com/tendermint/go-crypto/keys" diff --git a/commands/txs/root.go b/client/commands/txs/root.go similarity index 100% rename from commands/txs/root.go rename to client/commands/txs/root.go diff --git a/cmd/basecli/commands/cmds.go b/cmd/basecli/commands/cmds.go index 19e685e3b..c0ba9a6e0 100644 --- a/cmd/basecli/commands/cmds.go +++ b/cmd/basecli/commands/cmds.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/pflag" - txcmd "github.com/tendermint/basecoin/commands/txs" + txcmd "github.com/tendermint/basecoin/client/commands/txs" cmn "github.com/tendermint/tmlibs/common" ctypes "github.com/tendermint/tendermint/rpc/core/types" diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index acaaa1a7e..491c5cbee 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -8,12 +8,12 @@ import ( "github.com/tendermint/abci/version" keycmd "github.com/tendermint/go-crypto/cmd" - "github.com/tendermint/basecoin/commands" - "github.com/tendermint/basecoin/commands/proofs" - "github.com/tendermint/basecoin/commands/proxy" - rpccmd "github.com/tendermint/basecoin/commands/rpc" - "github.com/tendermint/basecoin/commands/seeds" - "github.com/tendermint/basecoin/commands/txs" + "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/client/commands/proofs" + "github.com/tendermint/basecoin/client/commands/proxy" + rpccmd "github.com/tendermint/basecoin/client/commands/rpc" + "github.com/tendermint/basecoin/client/commands/seeds" + "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/tmlibs/cli" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index c4336d111..bb66d4c07 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - txcmd "github.com/tendermint/basecoin/commands/txs" + txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/docs/guide/counter/cmd/countercli/commands/query.go b/docs/guide/counter/cmd/countercli/commands/query.go index 07a8620e8..8debaf9c2 100644 --- a/docs/guide/counter/cmd/countercli/commands/query.go +++ b/docs/guide/counter/cmd/countercli/commands/query.go @@ -3,7 +3,7 @@ package commands import ( "github.com/spf13/cobra" - proofcmd "github.com/tendermint/basecoin/commands/proofs" + proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" "github.com/tendermint/basecoin/stack" diff --git a/docs/guide/counter/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go index b3cd915b0..8440372f7 100644 --- a/docs/guide/counter/cmd/countercli/main.go +++ b/docs/guide/counter/cmd/countercli/main.go @@ -6,11 +6,11 @@ import ( "github.com/spf13/cobra" keycmd "github.com/tendermint/go-crypto/cmd" - "github.com/tendermint/basecoin/commands" - "github.com/tendermint/basecoin/commands/proofs" - "github.com/tendermint/basecoin/commands/proxy" - "github.com/tendermint/basecoin/commands/seeds" - "github.com/tendermint/basecoin/commands/txs" + "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/client/commands/proofs" + "github.com/tendermint/basecoin/client/commands/proxy" + "github.com/tendermint/basecoin/client/commands/seeds" + "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/tmlibs/cli" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/modules/base/commands/wrap.go b/modules/base/commands/wrap.go index 3367cc70f..13b6b7f34 100644 --- a/modules/base/commands/wrap.go +++ b/modules/base/commands/wrap.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/tendermint/basecoin/commands" + "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/basecoin" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go index 8edd96c2d..8f5ef7040 100644 --- a/modules/coin/commands/query.go +++ b/modules/coin/commands/query.go @@ -5,8 +5,8 @@ import ( "github.com/spf13/cobra" lc "github.com/tendermint/light-client" - lcmd "github.com/tendermint/basecoin/commands" - proofcmd "github.com/tendermint/basecoin/commands/proofs" + lcmd "github.com/tendermint/basecoin/client/commands" + proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/coin" diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index 566901f2f..4fd7037cf 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -4,8 +4,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/basecoin/commands" - txcmd "github.com/tendermint/basecoin/commands/txs" + "github.com/tendermint/basecoin/client/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index 79e7695c4..2bca14b99 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -7,8 +7,8 @@ import ( "github.com/spf13/cobra" lc "github.com/tendermint/light-client" - lcmd "github.com/tendermint/basecoin/commands" - proofcmd "github.com/tendermint/basecoin/commands/proofs" + lcmd "github.com/tendermint/basecoin/client/commands" + proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/modules/nonce" "github.com/tendermint/basecoin/stack" From 100522069d66953878a99e3375d0f702d2235973 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 22:21:57 +0200 Subject: [PATCH 07/20] Moved cmd/basecli/commands into client/commands --- .../auto.go => client/commands/auto/cmd.go | 2 +- client/commands/common.go | 53 ++++++++- client/commands/txs/helpers.go | 18 ++- client/commands/txs/presenter.go | 34 ++++++ client/commands/txs/wrapper.go | 45 +++++++ cmd/basecli/commands/cmds.go | 111 ------------------ cmd/basecli/commands/query.go | 20 ---- cmd/basecli/main.go | 20 ++-- .../cmd/countercli/commands/counter.go | 8 +- docs/guide/counter/cmd/countercli/main.go | 17 ++- modules/auth/commands/wrap.go | 4 +- modules/base/commands/wrap.go | 4 +- modules/coin/commands/tx.go | 14 +-- modules/fee/commands/wrap.go | 9 +- modules/nonce/commands/wrap.go | 9 +- 15 files changed, 185 insertions(+), 183 deletions(-) rename cmd/basecli/commands/auto.go => client/commands/auto/cmd.go (97%) create mode 100644 client/commands/txs/presenter.go create mode 100644 client/commands/txs/wrapper.go delete mode 100644 cmd/basecli/commands/cmds.go delete mode 100644 cmd/basecli/commands/query.go diff --git a/cmd/basecli/commands/auto.go b/client/commands/auto/cmd.go similarity index 97% rename from cmd/basecli/commands/auto.go rename to client/commands/auto/cmd.go index 040eb6ac3..704ba1785 100644 --- a/cmd/basecli/commands/auto.go +++ b/client/commands/auto/cmd.go @@ -1,4 +1,4 @@ -package commands +package auto import ( "os" diff --git a/client/commands/common.go b/client/commands/common.go index a022ff408..f14a11f38 100644 --- a/client/commands/common.go +++ b/client/commands/common.go @@ -4,18 +4,23 @@ Package commands contains any general setup/helpers valid for all subcommands package commands import ( - "errors" + "encoding/hex" + "strings" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/tmlibs/cli" - - rpcclient "github.com/tendermint/tendermint/rpc/client" - "github.com/tendermint/light-client/certifiers" "github.com/tendermint/light-client/certifiers/client" "github.com/tendermint/light-client/certifiers/files" + "github.com/tendermint/tmlibs/cli" + cmn "github.com/tendermint/tmlibs/common" + + rpcclient "github.com/tendermint/tendermint/rpc/client" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/modules/auth" ) var ( @@ -28,19 +33,24 @@ const ( NodeFlag = "node" ) +// AddBasicFlags adds --node and --chain-id, which we need for everything func AddBasicFlags(cmd *cobra.Command) { cmd.PersistentFlags().String(ChainFlag, "", "Chain ID of tendermint node") cmd.PersistentFlags().String(NodeFlag, "", ": to tendermint rpc interface for this chain") } +// GetChainID reads ChainID from the flags func GetChainID() string { return viper.GetString(ChainFlag) } +// GetNode prepares a simple rpc.Client from the flags func GetNode() rpcclient.Client { return rpcclient.NewHTTP(viper.GetString(NodeFlag), "/websocket") } +// GetProviders creates a trusted (local) seed provider and a remote +// provider based on configuration. func GetProviders() (trusted certifiers.Provider, source certifiers.Provider) { if trustedProv == nil || sourceProv == nil { // initialize provider with files stored in homedir @@ -55,6 +65,7 @@ func GetProviders() (trusted certifiers.Provider, source certifiers.Provider) { return trustedProv, sourceProv } +// GetCertifier constructs a dynamic certifier from the config info func GetCertifier() (*certifiers.InquiringCertifier, error) { // load up the latest store.... trust, source := GetProviders() @@ -71,3 +82,35 @@ func GetCertifier() (*certifiers.InquiringCertifier, error) { viper.GetString(ChainFlag), seed.Validators, trust, source) return cert, nil } + +// ParseAddress parses an address of form: +// [:][:] +// into a basecoin.Actor. +// If app is not specified or "", then assume auth.NameSigs +func ParseAddress(input string) (res basecoin.Actor, err error) { + chain, app := "", auth.NameSigs + input = strings.TrimSpace(input) + spl := strings.SplitN(input, ":", 3) + + if len(spl) == 3 { + chain = spl[0] + spl = spl[1:] + } + if len(spl) == 2 { + if spl[0] != "" { + app = spl[0] + } + spl = spl[1:] + } + + addr, err := hex.DecodeString(cmn.StripHex(spl[0])) + if err != nil { + return res, errors.Errorf("Address is invalid hex: %v\n", err) + } + res = basecoin.Actor{ + ChainID: chain, + App: app, + Address: addr, + } + return +} diff --git a/client/commands/txs/helpers.go b/client/commands/txs/helpers.go index 3a592a450..dd7999dd7 100644 --- a/client/commands/txs/helpers.go +++ b/client/commands/txs/helpers.go @@ -14,16 +14,19 @@ import ( "github.com/pkg/errors" "github.com/spf13/viper" - "github.com/tendermint/basecoin/client/commands" 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" ctypes "github.com/tendermint/tendermint/rpc/core/types" - lc "github.com/tendermint/light-client" + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/modules/auth" ) +// Validatable represents anything that can be Validated type Validatable interface { ValidateBasic() error } @@ -37,6 +40,17 @@ func GetSigner() crypto.PubKey { return info.PubKey } +// GetSignerAct returns the address of the signer of the tx +// (as we still only support single sig) +func GetSignerAct() (res basecoin.Actor) { + // this could be much cooler with multisig... + signer := GetSigner() + if !signer.Empty() { + res = auth.SigPerm(signer.Address()) + } + return res +} + // Sign if it is Signable, otherwise, just convert it to bytes func Sign(tx interface{}) (packet []byte, err error) { name := viper.GetString(NameFlag) diff --git a/client/commands/txs/presenter.go b/client/commands/txs/presenter.go new file mode 100644 index 000000000..b13f694e5 --- /dev/null +++ b/client/commands/txs/presenter.go @@ -0,0 +1,34 @@ +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" +) + +// BaseTxPresenter this decodes all basecoin tx +type BaseTxPresenter struct { + proofs.RawPresenter // this handles MakeKey as hex bytes +} + +// ParseData - unmarshal raw bytes to a basecoin tx +func (BaseTxPresenter) ParseData(raw []byte) (interface{}, error) { + var tx basecoin.Tx + 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/wrapper.go b/client/commands/txs/wrapper.go new file mode 100644 index 000000000..3d711f948 --- /dev/null +++ b/client/commands/txs/wrapper.go @@ -0,0 +1,45 @@ +package txs + +import ( + "github.com/spf13/pflag" + + "github.com/tendermint/basecoin" +) + +var ( + // Middleware must be set in main.go to defined the wrappers we should apply + Middleware Wrapper +) + +// Wrapper defines the information needed for each middleware package that +// wraps the data. They should read all configuration out of bounds via viper. +type Wrapper interface { + Wrap(basecoin.Tx) (basecoin.Tx, error) + Register(*pflag.FlagSet) +} + +// Wrappers combines a list of wrapper middlewares. +// The first one is the inner-most layer, eg. Fee, Nonce, Chain, Auth +type Wrappers []Wrapper + +var _ Wrapper = Wrappers{} + +// Wrap applies the wrappers to the passed in tx in order, +// aborting on the first error +func (ws Wrappers) Wrap(tx basecoin.Tx) (basecoin.Tx, error) { + var err error + for _, w := range ws { + tx, err = w.Wrap(tx) + if err != nil { + break + } + } + return tx, err +} + +// Register adds any needed flags to the command +func (ws Wrappers) Register(fs *pflag.FlagSet) { + for _, w := range ws { + w.Register(fs) + } +} diff --git a/cmd/basecli/commands/cmds.go b/cmd/basecli/commands/cmds.go deleted file mode 100644 index c0ba9a6e0..000000000 --- a/cmd/basecli/commands/cmds.go +++ /dev/null @@ -1,111 +0,0 @@ -package commands - -import ( - "encoding/hex" - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/pflag" - - txcmd "github.com/tendermint/basecoin/client/commands/txs" - cmn "github.com/tendermint/tmlibs/common" - - ctypes "github.com/tendermint/tendermint/rpc/core/types" - - "github.com/tendermint/basecoin" - "github.com/tendermint/basecoin/modules/auth" -) - -var ( - // Middleware must be set in main.go to defined the wrappers we should apply - Middleware Wrapper -) - -// Wrapper defines the information needed for each middleware package that -// wraps the data. They should read all configuration out of bounds via viper. -type Wrapper interface { - Wrap(basecoin.Tx) (basecoin.Tx, error) - Register(*pflag.FlagSet) -} - -// Wrappers combines a list of wrapper middlewares. -// The first one is the inner-most layer, eg. Fee, Nonce, Chain, Auth -type Wrappers []Wrapper - -var _ Wrapper = Wrappers{} - -// Wrap applies the wrappers to the passed in tx in order, -// aborting on the first error -func (ws Wrappers) Wrap(tx basecoin.Tx) (basecoin.Tx, error) { - var err error - for _, w := range ws { - tx, err = w.Wrap(tx) - if err != nil { - break - } - } - return tx, err -} - -// Register adds any needed flags to the command -func (ws Wrappers) Register(fs *pflag.FlagSet) { - for _, w := range ws { - w.Register(fs) - } -} - -// 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 fmt.Errorf("CheckTx: (%d): %s", res.CheckTx.Code, res.CheckTx.Log) - } - if res.DeliverTx.IsErr() { - return fmt.Errorf("DeliverTx: (%d): %s", res.DeliverTx.Code, res.DeliverTx.Log) - } - return nil -} - -// ParseAddress parses an address of form: -// [:][:] -// into a basecoin.Actor. -// If app is not specified or "", then assume auth.NameSigs -func ParseAddress(input string) (res basecoin.Actor, err error) { - chain, app := "", auth.NameSigs - input = strings.TrimSpace(input) - spl := strings.SplitN(input, ":", 3) - - if len(spl) == 3 { - chain = spl[0] - spl = spl[1:] - } - if len(spl) == 2 { - if spl[0] != "" { - app = spl[0] - } - spl = spl[1:] - } - - addr, err := hex.DecodeString(cmn.StripHex(spl[0])) - if err != nil { - return res, errors.Errorf("Address is invalid hex: %v\n", err) - } - res = basecoin.Actor{ - ChainID: chain, - App: app, - Address: addr, - } - return -} - -// GetSignerAct returns the address of the signer of the tx -// (as we still only support single sig) -func GetSignerAct() (res basecoin.Actor) { - // this could be much cooler with multisig... - signer := txcmd.GetSigner() - if !signer.Empty() { - res = auth.SigPerm(signer.Address()) - } - return res -} diff --git a/cmd/basecli/commands/query.go b/cmd/basecli/commands/query.go deleted file mode 100644 index 2fd85a6c7..000000000 --- a/cmd/basecli/commands/query.go +++ /dev/null @@ -1,20 +0,0 @@ -package commands - -import ( - wire "github.com/tendermint/go-wire" - "github.com/tendermint/light-client/proofs" - - "github.com/tendermint/basecoin" -) - -// BaseTxPresenter this decodes all basecoin tx -type BaseTxPresenter struct { - proofs.RawPresenter // this handles MakeKey as hex bytes -} - -// ParseData - unmarshal raw bytes to a basecoin tx -func (BaseTxPresenter) ParseData(raw []byte) (interface{}, error) { - var tx basecoin.Tx - err := wire.ReadBinaryBytes(raw, &tx) - return tx, err -} diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 491c5cbee..b827f7ffb 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -8,15 +8,15 @@ import ( "github.com/tendermint/abci/version" keycmd "github.com/tendermint/go-crypto/cmd" + "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/client/commands/auto" "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/client/commands/proxy" rpccmd "github.com/tendermint/basecoin/client/commands/rpc" "github.com/tendermint/basecoin/client/commands/seeds" - "github.com/tendermint/basecoin/client/commands/txs" - "github.com/tendermint/tmlibs/cli" - - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" authcmd "github.com/tendermint/basecoin/modules/auth/commands" basecmd "github.com/tendermint/basecoin/modules/base/commands" coincmd "github.com/tendermint/basecoin/modules/coin/commands" @@ -56,19 +56,19 @@ func main() { coincmd.AccountQueryCmd, noncecmd.NonceQueryCmd, ) + proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{}) // set up the middleware - bcmd.Middleware = bcmd.Wrappers{ + txcmd.Middleware = txcmd.Wrappers{ feecmd.FeeWrapper{}, noncecmd.NonceWrapper{}, basecmd.ChainWrapper{}, authcmd.SigWrapper{}, } - bcmd.Middleware.Register(txs.RootCmd.PersistentFlags()) + txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags()) // you will always want this for the base send command - proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{}) - txs.RootCmd.AddCommand( + txcmd.RootCmd.AddCommand( // This is the default transaction, optional in your app coincmd.SendTxCmd, ) @@ -81,10 +81,10 @@ func main() { seeds.RootCmd, rpccmd.RootCmd, proofs.RootCmd, - txs.RootCmd, + txcmd.RootCmd, proxy.RootCmd, VersionCmd, - bcmd.AutoCompleteCmd, + auto.AutoCompleteCmd, ) cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli")) diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index bb66d4c07..a8e473276 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -4,10 +4,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - txcmd "github.com/tendermint/basecoin/client/commands/txs" - "github.com/tendermint/basecoin" - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" "github.com/tendermint/basecoin/modules/coin" ) @@ -43,7 +41,7 @@ func counterTx(cmd *cobra.Command, args []string) error { return err } - tx, err = bcmd.Middleware.Wrap(tx) + tx, err = txcmd.Middleware.Wrap(tx) if err != nil { return err } @@ -53,7 +51,7 @@ func counterTx(cmd *cobra.Command, args []string) error { if err != nil { return err } - if err = bcmd.ValidateResult(bres); err != nil { + if err = txcmd.ValidateResult(bres); err != nil { return err } diff --git a/docs/guide/counter/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go index 8440372f7..8cc95f667 100644 --- a/docs/guide/counter/cmd/countercli/main.go +++ b/docs/guide/counter/cmd/countercli/main.go @@ -6,14 +6,13 @@ import ( "github.com/spf13/cobra" keycmd "github.com/tendermint/go-crypto/cmd" + "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/client/commands/proxy" "github.com/tendermint/basecoin/client/commands/seeds" - "github.com/tendermint/basecoin/client/commands/txs" - "github.com/tendermint/tmlibs/cli" - - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" bcount "github.com/tendermint/basecoin/docs/guide/counter/cmd/countercli/commands" authcmd "github.com/tendermint/basecoin/modules/auth/commands" basecmd "github.com/tendermint/basecoin/modules/base/commands" @@ -50,17 +49,17 @@ func main() { ) // set up the middleware - bcmd.Middleware = bcmd.Wrappers{ + txcmd.Middleware = txcmd.Wrappers{ feecmd.FeeWrapper{}, noncecmd.NonceWrapper{}, basecmd.ChainWrapper{}, authcmd.SigWrapper{}, } - bcmd.Middleware.Register(txs.RootCmd.PersistentFlags()) + txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags()) // Prepare transactions - proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{}) - txs.RootCmd.AddCommand( + proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{}) + txcmd.RootCmd.AddCommand( // This is the default transaction, optional in your app coincmd.SendTxCmd, @@ -75,7 +74,7 @@ func main() { keycmd.RootCmd, seeds.RootCmd, proofs.RootCmd, - txs.RootCmd, + txcmd.RootCmd, proxy.RootCmd, ) diff --git a/modules/auth/commands/wrap.go b/modules/auth/commands/wrap.go index a02033c65..c93eecb70 100644 --- a/modules/auth/commands/wrap.go +++ b/modules/auth/commands/wrap.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/viper" "github.com/tendermint/basecoin" - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin/modules/auth" ) @@ -17,7 +17,7 @@ const ( // SigWrapper wraps a tx with a signature layer to hold pubkey sigs type SigWrapper struct{} -var _ bcmd.Wrapper = SigWrapper{} +var _ txcmd.Wrapper = SigWrapper{} // Wrap will wrap the tx with OneSig or MultiSig depending on flags func (SigWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { diff --git a/modules/base/commands/wrap.go b/modules/base/commands/wrap.go index 13b6b7f34..e199c4ed0 100644 --- a/modules/base/commands/wrap.go +++ b/modules/base/commands/wrap.go @@ -9,7 +9,7 @@ import ( "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/basecoin" - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin/modules/base" ) @@ -21,7 +21,7 @@ const ( // ChainWrapper wraps a tx with an chain info and optional expiration type ChainWrapper struct{} -var _ bcmd.Wrapper = ChainWrapper{} +var _ txcmd.Wrapper = ChainWrapper{} // Wrap will wrap the tx with a ChainTx from the standard flags func (ChainWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index 4fd7037cf..9a338582c 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -4,11 +4,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/client/commands" txcmd "github.com/tendermint/basecoin/client/commands/txs" - - "github.com/tendermint/basecoin" - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" "github.com/tendermint/basecoin/modules/coin" ) @@ -47,7 +45,7 @@ func doSendTx(cmd *cobra.Command, args []string) error { return err } - tx, err = bcmd.Middleware.Wrap(tx) + tx, err = txcmd.Middleware.Wrap(tx) if err != nil { return err } @@ -57,7 +55,7 @@ func doSendTx(cmd *cobra.Command, args []string) error { if err != nil { return err } - if err = bcmd.ValidateResult(bres); err != nil { + if err = txcmd.ValidateResult(bres); err != nil { return err } @@ -67,7 +65,7 @@ func doSendTx(cmd *cobra.Command, args []string) error { func readSendTxFlags() (tx basecoin.Tx, err error) { // parse to address - toAddr, err := bcmd.ParseAddress(viper.GetString(FlagTo)) + toAddr, err := commands.ParseAddress(viper.GetString(FlagTo)) if err != nil { return tx, err } @@ -98,7 +96,7 @@ func readSendTxFlags() (tx basecoin.Tx, err error) { func readFromAddr() (basecoin.Actor, error) { from := viper.GetString(FlagFrom) if from == "" { - return bcmd.GetSignerAct(), nil + return txcmd.GetSignerAct(), nil } - return bcmd.ParseAddress(from) + return commands.ParseAddress(from) } diff --git a/modules/fee/commands/wrap.go b/modules/fee/commands/wrap.go index c464c15c9..6f8c082f8 100644 --- a/modules/fee/commands/wrap.go +++ b/modules/fee/commands/wrap.go @@ -5,7 +5,8 @@ import ( "github.com/spf13/viper" "github.com/tendermint/basecoin" - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/client/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin/modules/coin" "github.com/tendermint/basecoin/modules/fee" ) @@ -19,7 +20,7 @@ const ( // FeeWrapper wraps a tx with an optional fee payment type FeeWrapper struct{} -var _ bcmd.Wrapper = FeeWrapper{} +var _ txcmd.Wrapper = FeeWrapper{} // Wrap checks for FlagFee and if present wraps the tx with a // FeeTx of the given amount, paid by the signer @@ -52,7 +53,7 @@ func (FeeWrapper) Register(fs *pflag.FlagSet) { func readPayer() (basecoin.Actor, error) { payer := viper.GetString(FlagPayer) if payer == "" { - return bcmd.GetSignerAct(), nil + return txcmd.GetSignerAct(), nil } - return bcmd.ParseAddress(payer) + return commands.ParseAddress(payer) } diff --git a/modules/nonce/commands/wrap.go b/modules/nonce/commands/wrap.go index 7e29e8312..bd3cc9999 100644 --- a/modules/nonce/commands/wrap.go +++ b/modules/nonce/commands/wrap.go @@ -8,7 +8,8 @@ import ( "github.com/spf13/viper" "github.com/tendermint/basecoin" - bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/client/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin/modules/nonce" ) @@ -21,7 +22,7 @@ const ( // NonceWrapper wraps a tx with a nonce type NonceWrapper struct{} -var _ bcmd.Wrapper = NonceWrapper{} +var _ txcmd.Wrapper = NonceWrapper{} // Wrap grabs the sequence number from the flag and wraps // the tx with this nonce. Grabs the permission from the signer, @@ -49,7 +50,7 @@ func (NonceWrapper) Register(fs *pflag.FlagSet) { func readNonceKey() ([]basecoin.Actor, error) { nonce := viper.GetString(FlagNonceKey) if nonce == "" { - return []basecoin.Actor{bcmd.GetSignerAct()}, nil + return []basecoin.Actor{txcmd.GetSignerAct()}, nil } return parseActors(nonce) } @@ -57,7 +58,7 @@ func readNonceKey() ([]basecoin.Actor, error) { func parseActors(key string) (signers []basecoin.Actor, err error) { var act basecoin.Actor for _, k := range strings.Split(key, ",") { - act, err = bcmd.ParseAddress(k) + act, err = commands.ParseAddress(k) if err != nil { return } From 942506c21a9252ad9f220bcc5d0b5773aefab702 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 22:40:04 +0200 Subject: [PATCH 08/20] basecli tx handles json input --- Makefile | 3 +- client/commands/txs/helpers.go | 51 +----------------------- client/commands/txs/root.go | 72 +++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index 906b3b601..66e51f817 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,8 @@ dist: benchmark: @go test -bench=. ./modules/... -test: test_unit test_cli test_tutorial +#test: test_unit test_cli test_tutorial +test: test_unit test_cli test_unit: @go test `glide novendor` diff --git a/client/commands/txs/helpers.go b/client/commands/txs/helpers.go index dd7999dd7..d58ea342f 100644 --- a/client/commands/txs/helpers.go +++ b/client/commands/txs/helpers.go @@ -4,8 +4,6 @@ import ( "bufio" "encoding/json" "fmt" - "io" - "io/ioutil" "os" "strings" @@ -34,7 +32,7 @@ type Validatable interface { // GetSigner returns the pub key that will sign the tx // returns empty key if no name provided func GetSigner() crypto.PubKey { - name := viper.GetString(NameFlag) + name := viper.GetString(FlagName) manager := keycmd.GetKeyManager() info, _ := manager.Get(name) // error -> empty pubkey return info.PubKey @@ -53,7 +51,7 @@ func GetSignerAct() (res basecoin.Actor) { // Sign if it is Signable, otherwise, just convert it to bytes func Sign(tx interface{}) (packet []byte, err error) { - name := viper.GetString(NameFlag) + name := viper.GetString(FlagName) manager := keycmd.GetKeyManager() if sign, ok := tx.(keys.Signable); ok { @@ -90,32 +88,6 @@ func SignAndPostTx(tx Validatable) (*ctypes.ResultBroadcastTxCommit, error) { return node.BroadcastTxCommit(packet) } -// LoadJSON will read a json file from disk if --input is passed in -// template is a pointer to a struct that can hold the expected data (&MyTx{}) -// -// If not data is provided, returns (false, nil) -// If data is provided and passes, returns (true, nil) -// If data is provided but not parsable, returns (true, err) -func LoadJSON(template interface{}) (bool, error) { - input := viper.GetString(InputFlag) - if input == "" { - return false, nil - } - - // load the input - raw, err := readInput(input) - if err != nil { - return true, err - } - - // parse the input - err = json.Unmarshal(raw, template) - if err != nil { - return true, err - } - return true, nil -} - // OutputTx prints the tx result to stdout // TODO: something other than raw json? func OutputTx(res *ctypes.ResultBroadcastTxCommit) error { @@ -140,25 +112,6 @@ func signTx(manager keys.Manager, tx keys.Signable, name string) ([]byte, error) return tx.TxBytes() } -func readInput(file string) ([]byte, error) { - var reader io.Reader - // get the input stream - if file == "-" { - reader = os.Stdin - } else { - f, err := os.Open(file) - if err != nil { - return nil, err - } - defer f.Close() - reader = f - } - - // and read it all! - data, err := ioutil.ReadAll(reader) - return data, errors.WithStack(err) -} - // if we read from non-tty, we just need to init the buffer reader once, // in case we try to read multiple passwords var buf *bufio.Reader diff --git a/client/commands/txs/root.go b/client/commands/txs/root.go index 965f6f259..6a1080477 100644 --- a/client/commands/txs/root.go +++ b/client/commands/txs/root.go @@ -1,19 +1,79 @@ package txs -import "github.com/spf13/cobra" +import ( + "encoding/json" + "io" + "io/ioutil" + "os" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/basecoin" +) + +// nolint const ( - NameFlag = "name" - InputFlag = "input" + FlagName = "name" + FlagIn = "in" + FlagPrepare = "prepare" ) // RootCmd represents the base command when called without any subcommands var RootCmd = &cobra.Command{ Use: "tx", - Short: "Create and post transactions to the node", + Short: "Post tx from json input", + RunE: doRawTx, } func init() { - RootCmd.PersistentFlags().String(NameFlag, "", "name to sign the tx") - RootCmd.PersistentFlags().String(InputFlag, "", "file with tx in json format") + RootCmd.PersistentFlags().String(FlagName, "", "name to sign the tx") + // TODO: prepare needs to override the SignAndPost somehow to SignAndSave + RootCmd.PersistentFlags().String(FlagPrepare, "", "file to store prepared tx") + RootCmd.Flags().String(FlagIn, "", "file with tx in json format") +} + +func doRawTx(cmd *cobra.Command, args []string) error { + raw, err := readInput(viper.GetString(FlagIn)) + if err != nil { + return err + } + + // parse the input + var tx basecoin.Tx + err = json.Unmarshal(raw, &tx) + if err != nil { + return errors.WithStack(err) + } + + // Sign if needed and post. This it the work-horse + bres, err := SignAndPostTx(tx.Unwrap()) + if err != nil { + return err + } + if err = ValidateResult(bres); 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 + } + + // and read it all! + data, err := ioutil.ReadAll(reader) + return data, errors.WithStack(err) } From 0a9460dc93d799dd9a1f2cb425ab9e7ec8a4b07c Mon Sep 17 00:00:00 2001 From: rigel rozanski Date: Wed, 19 Jul 2017 00:13:39 -0400 Subject: [PATCH 09/20] auto-sequencing --- Makefile | 2 +- modules/coin/errors.go | 1 - modules/nonce/commands/query.go | 32 ++++-- modules/nonce/commands/wrap.go | 37 ++++-- tests/cli/basictx.sh | 15 +-- tests/cli/common.sh | 4 +- tests/cli/counter.sh | 2 +- tests/cli/ibc.sh | 14 +-- tests/cli/init.sh | 130 ++++++++++----------- tests/cli/keys.sh | 24 ++-- tests/cli/rpc.sh | 192 ++++++++++++++++---------------- 11 files changed, 240 insertions(+), 213 deletions(-) diff --git a/Makefile b/Makefile index 66e51f817..aaee262c3 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ test_unit: test_cli: tests/cli/shunit2 # sudo apt-get install jq ./tests/cli/keys.sh - ./tests/cli/rpc.sh + #./tests/cli/rpc.sh ./tests/cli/init.sh ./tests/cli/basictx.sh ./tests/cli/counter.sh diff --git a/modules/coin/errors.go b/modules/coin/errors.go index d245354bf..0bf3bde9e 100644 --- a/modules/coin/errors.go +++ b/modules/coin/errors.go @@ -15,7 +15,6 @@ var ( errNoOutputs = fmt.Errorf("No Output Coins") errInvalidAddress = fmt.Errorf("Invalid Address") errInvalidCoins = fmt.Errorf("Invalid Coins") - errInvalidSequence = fmt.Errorf("Invalid Sequence") ) var ( diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index 2bca14b99..ccc94b937 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -6,9 +6,10 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - lc "github.com/tendermint/light-client" + "github.com/tendermint/basecoin" lcmd "github.com/tendermint/basecoin/client/commands" proofcmd "github.com/tendermint/basecoin/client/commands/proofs" + lc "github.com/tendermint/light-client" "github.com/tendermint/basecoin/modules/nonce" "github.com/tendermint/basecoin/stack" @@ -18,28 +19,37 @@ import ( var NonceQueryCmd = &cobra.Command{ Use: "nonce [address]", Short: "Get details of a nonce sequence number, with proof", - RunE: lcmd.RequireInit(doNonceQuery), + RunE: lcmd.RequireInit(nonceQueryCmd), } -func doNonceQuery(cmd *cobra.Command, args []string) error { +func nonceQueryCmd(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("Missing required argument [address]") } addr := strings.Join(args, ",") - act, err := parseActors(addr) + + var signers []basecoin.Actor + signers, err := parseActors(addr) if err != nil { return err } - key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(act)) - - var seq uint32 - proof, err := proofcmd.GetAndParseAppProof(key, &seq) - if lc.IsNoDataErr(err) { - return errors.Errorf("Sequence is empty for address %s ", addr) - } else if err != nil { + seq, proof, err := doNonceQuery(signers) + if err != nil { return err } return proofcmd.OutputProof(seq, proof.BlockHeight()) } + +func doNonceQuery(signers []basecoin.Actor) (sequence uint32, proof lc.Proof, err error) { + + key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(signers)) + + proof, err = proofcmd.GetAndParseAppProof(key, &sequence) + if lc.IsNoDataErr(err) { + err = errors.Errorf("Sequence is empty for key %s ", key) + } + + return +} diff --git a/modules/nonce/commands/wrap.go b/modules/nonce/commands/wrap.go index bd3cc9999..ea42fe483 100644 --- a/modules/nonce/commands/wrap.go +++ b/modules/nonce/commands/wrap.go @@ -28,15 +28,17 @@ var _ txcmd.Wrapper = NonceWrapper{} // the tx with this nonce. Grabs the permission from the signer, // as we still only support single sig on the cli func (NonceWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) { - seq, err := readSequence() - if err != nil { - return res, err - } signers, err := readNonceKey() if err != nil { return res, err } + + seq, err := readSequence(signers) + if err != nil { + return res, err + } + res = nonce.NewTx(seq, signers, tx) return } @@ -67,13 +69,28 @@ func parseActors(key string) (signers []basecoin.Actor, err error) { return } -func readSequence() (uint32, error) { +// read the sequence from the flag or query for it if flag is -1 +func readSequence(signers []basecoin.Actor) (seq uint32, err error) { //add the nonce tx layer to the tx - seq := viper.GetInt(FlagSequence) - if seq > 0 { - return uint32(seq), nil + seqFlag := viper.GetInt(FlagSequence) + + switch { + case seqFlag > 0: + seq = uint32(seqFlag) + + case seqFlag == -1: + //autocalculation for default sequence + seq, _, err = doNonceQuery(signers) + if err != nil { + return + } + + //increase the sequence by 1! + seq++ + + default: + err = fmt.Errorf("sequence must be either greater than 0, or -1 for autocalculation") } - // TODO: try to download from query.. - return 0, fmt.Errorf("sequence must be greater than 0") + return } diff --git a/tests/cli/basictx.sh b/tests/cli/basictx.sh index 009e1ddc0..640d6ba0b 100755 --- a/tests/cli/basictx.sh +++ b/tests/cli/basictx.sh @@ -21,20 +21,20 @@ test00GetAccount() { SENDER=$(getAddr $RICH) RECV=$(getAddr $POOR) - assertFalse "requires arg" "${CLIENT_EXE} query account" + assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account" checkAccount $SENDER "9007199254740992" ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null) - assertFalse "has no genesis account" $? + assertFalse "line=${LINENO}, has no genesis account" $? } test01SendTx() { SENDER=$(getAddr $RICH) RECV=$(getAddr $POOR) - assertFalse "missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1" - assertFalse "bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH" + assertFalse "line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1" + assertFalse "line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH" TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH) txSucceeded $? "$TX" "$RECV" HASH=$(echo $TX | jq .hash | tr -d \") @@ -53,7 +53,8 @@ test02SendTxWithFee() { SENDER=$(getAddr $RICH) RECV=$(getAddr $POOR) - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH) + # Test to see if the auto-sequencing works, the sequence here should be calculated to be 2 + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=-1 --to=$RECV --name=$RICH) txSucceeded $? "$TX" "$RECV" HASH=$(echo $TX | jq .hash | tr -d \") TX_HEIGHT=$(echo $TX | jq .height) @@ -67,7 +68,7 @@ test02SendTxWithFee() { # assert replay protection TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH 2>/dev/null) - assertFalse "replay: $TX" $? + assertFalse "line=${LINENO}, replay: $TX" $? checkAccount $SENDER "9007199254739900" checkAccount $RECV "1082" @@ -76,7 +77,7 @@ test02SendTxWithFee() { if [ -n "$DEBUG" ]; then echo $NONCE; echo; fi # TODO: note that cobra returns error code 0 on parse failure, # so currently this check passes even if there is no nonce query command - if assertTrue "no nonce query" $?; then + if assertTrue "line=${LINENO}, no nonce query" $?; then assertEquals "line=${LINENO}, proper nonce" "2" $(echo $NONCE | jq .data) fi } diff --git a/tests/cli/common.sh b/tests/cli/common.sh index b195184b4..c68a80c56 100644 --- a/tests/cli/common.sh +++ b/tests/cli/common.sh @@ -141,7 +141,7 @@ checkAccount() { # XXX Ex Usage: txSucceeded $? "$TX" "$RECIEVER" # Desc: Must be called right after the `tx` command, makes sure it got a success response txSucceeded() { - if (assertTrue "sent tx ($3): $2" $1); then + if (assertTrue "line=${LINENO}, sent tx ($3): $2" $1); then TX=$2 assertEquals "line=${LINENO}, good check ($3): $TX" "0" $(echo $TX | jq .check_tx.code) assertEquals "line=${LINENO}, good deliver ($3): $TX" "0" $(echo $TX | jq .deliver_tx.code) @@ -177,7 +177,7 @@ checkSendTx() { # and that the first input was from this sender for this amount checkSendFeeTx() { TX=$(${CLIENT_EXE} query tx $1) - assertTrue "found tx" $? + assertTrue "line=${LINENO}, found tx" $? if [ -n "$DEBUG" ]; then echo $TX; echo; fi assertEquals "line=${LINENO}, proper height" $2 $(echo $TX | jq .height) diff --git a/tests/cli/counter.sh b/tests/cli/counter.sh index f21c9066d..7e17fc4f5 100755 --- a/tests/cli/counter.sh +++ b/tests/cli/counter.sh @@ -104,7 +104,7 @@ test03AddCount() { # make sure we cannot replay the counter, no state change TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --countfee=10mycoin --sequence=2 --name=${RICH} --valid 2>/dev/null) - assertFalse "replay: $TX" $? + assertFalse "line=${LINENO}, replay: $TX" $? checkCounter "2" "17" checkAccount $SENDER "9007199254739979" } diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index aea38fba5..36f380ded 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -66,21 +66,21 @@ test00GetAccount() { RECV_1=$(BC_HOME=${CLIENT_1} getAddr $POOR) export BC_HOME=${CLIENT_1} - assertFalse "requires arg" "${CLIENT_EXE} query account 2>/dev/null" - assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null" + assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null" + assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null" checkAccount $SENDER_1 "0" "9007199254740992" export BC_HOME=${CLIENT_2} SENDER_2=$(getAddr $RICH) RECV_2=$(getAddr $POOR) - assertFalse "requires arg" "${CLIENT_EXE} query account 2>/dev/null" - assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null" + assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null" + assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null" checkAccount $SENDER_2 "0" "9007199254740992" # Make sure that they have different addresses on both chains (they are random keys) - assertNotEquals "sender keys must be different" "$SENDER_1" "$SENDER_2" - assertNotEquals "recipient keys must be different" "$RECV_1" "$RECV_2" + assertNotEquals "line=${LINENO}, sender keys must be different" "$SENDER_1" "$SENDER_2" + assertNotEquals "line=${LINENO}, recipient keys must be different" "$RECV_1" "$RECV_2" } test01SendIBCTx() { @@ -105,7 +105,7 @@ test01SendIBCTx() { # Make sure nothing arrived - yet waitForBlock ${PORT_1} - assertFalse "no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" + assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" # Start the relay and wait a few blocks... # (already sent a tx on chain1, so use higher sequence) diff --git a/tests/cli/init.sh b/tests/cli/init.sh index a5a3bb387..9de8057c4 100755 --- a/tests/cli/init.sh +++ b/tests/cli/init.sh @@ -4,103 +4,103 @@ CLIENT_EXE=basecli SERVER_EXE=basecoin oneTimeSetUp() { - BASE=~/.bc_init_test - rm -rf "$BASE" - mkdir -p "$BASE" + BASE=~/.bc_init_test + rm -rf "$BASE" + mkdir -p "$BASE" - SERVER="${BASE}/server" - SERVER_LOG="${BASE}/${SERVER_EXE}.log" + SERVER="${BASE}/server" + SERVER_LOG="${BASE}/${SERVER_EXE}.log" - HEX="deadbeef1234deadbeef1234deadbeef1234aaaa" - ${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG" - if ! assertTrue $?; then return 1; fi + HEX="deadbeef1234deadbeef1234deadbeef1234aaaa" + ${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG" + if ! assertTrue "line=${LINENO}" $?; then return 1; fi - GENESIS_FILE=${SERVER}/genesis.json - CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \") + GENESIS_FILE=${SERVER}/genesis.json + CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \") - printf "starting ${SERVER_EXE}...\n" - ${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 & - sleep 5 - PID_SERVER=$! - disown - if ! ps $PID_SERVER >/dev/null; then - echo "**STARTUP FAILED**" - cat $SERVER_LOG - return 1 - fi + printf "starting ${SERVER_EXE}...\n" + ${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 & + sleep 5 + PID_SERVER=$! + disown + if ! ps $PID_SERVER >/dev/null; then + echo "**STARTUP FAILED**" + cat $SERVER_LOG + return 1 + fi } oneTimeTearDown() { - printf "\nstopping ${SERVER_EXE}..." - kill -9 $PID_SERVER >/dev/null 2>&1 - sleep 1 + printf "\nstopping ${SERVER_EXE}..." + kill -9 $PID_SERVER >/dev/null 2>&1 + sleep 1 } test01goodInit() { - export BCHOME=${BASE}/client-01 - assertFalse "ls ${BCHOME} 2>/dev/null >&2" + export BCHOME=${BASE}/client-01 + assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2" - echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null - assertTrue "initialized light-client" $? - checkDir $BCHOME 3 + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null + assertTrue "line=${LINENO}, initialized light-client" $? + checkDir $BCHOME 3 } test02badInit() { - export BCHOME=${BASE}/client-02 - assertFalse "ls ${BCHOME} 2>/dev/null >&2" + export BCHOME=${BASE}/client-02 + assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2" - # no node where we go - echo y | ${CLIENT_EXE} init --node=tcp://localhost:9999 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 - assertFalse "invalid init" $? - # dir there, but empty... - checkDir $BCHOME 0 + # no node where we go + echo y | ${CLIENT_EXE} init --node=tcp://localhost:9999 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertFalse "line=${LINENO}, invalid init" $? + # dir there, but empty... + checkDir $BCHOME 0 - # try with invalid chain id - echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="bad-chain-id" > /dev/null 2>&1 - assertFalse "invalid init" $? - checkDir $BCHOME 0 + # try with invalid chain id + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="bad-chain-id" > /dev/null 2>&1 + assertFalse "line=${LINENO}, invalid init" $? + checkDir $BCHOME 0 - # reject the response - echo n | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 - assertFalse "invalid init" $? - checkDir $BCHOME 0 + # reject the response + echo n | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertFalse "line=${LINENO}, invalid init" $? + checkDir $BCHOME 0 } test03noDoubleInit() { - export BCHOME=${BASE}/client-03 - assertFalse "ls ${BCHOME} 2>/dev/null >&2" + export BCHOME=${BASE}/client-03 + assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2" - # init properly - echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 - assertTrue "initialized light-client" $? - checkDir $BCHOME 3 + # init properly + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertTrue "line=${LINENO}, initialized light-client" $? + checkDir $BCHOME 3 - # try again, and we get an error - echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 - assertFalse "warning on re-init" $? - checkDir $BCHOME 3 + # try again, and we get an error + echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertFalse "line=${LINENO}, warning on re-init" $? + checkDir $BCHOME 3 - # unless we --force-reset - echo y | ${CLIENT_EXE} init --force-reset --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 - assertTrue "re-initialized light-client" $? - checkDir $BCHOME 3 + # unless we --force-reset + echo y | ${CLIENT_EXE} init --force-reset --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1 + assertTrue "line=${LINENO}, re-initialized light-client" $? + checkDir $BCHOME 3 } test04acceptGenesisFile() { - export BCHOME=${BASE}/client-04 - assertFalse "ls ${BCHOME} 2>/dev/null >&2" + export BCHOME=${BASE}/client-04 + assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2" - # init properly - ${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1 - assertTrue "initialized light-client" $? - checkDir $BCHOME 3 + # init properly + ${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1 + assertTrue "line=${LINENO}, initialized light-client" $? + checkDir $BCHOME 3 } # XXX Ex: checkDir $DIR $FILES # Makes sure directory exists and has the given number of files checkDir() { - assertTrue "ls ${1} 2>/dev/null >&2" - assertEquals "no files created" "$2" $(ls $1 | wc -l) + assertTrue "line=${LINENO}" "ls ${1} 2>/dev/null >&2" + assertEquals "line=${LINENO}, no files created" "$2" $(ls $1 | wc -l) } # load and run these tests with shunit2! diff --git a/tests/cli/keys.sh b/tests/cli/keys.sh index 4cf9e61d1..89e1ee538 100755 --- a/tests/cli/keys.sh +++ b/tests/cli/keys.sh @@ -4,24 +4,24 @@ CLIENT_EXE=basecli oneTimeSetUp() { - PASS=qwertyuiop - export BCHOME=$HOME/.bc_keys_test - ${CLIENT_EXE} reset_all - assertTrue $? + PASS=qwertyuiop + export BCHOME=$HOME/.bc_keys_test + ${CLIENT_EXE} reset_all + assertTrue "line ${LINENO}" $? } newKey(){ - assertNotNull "keyname required" "$1" - KEYPASS=${2:-qwertyuiop} - echo $KEYPASS | ${CLIENT_EXE} keys new $1 >/dev/null 2>&1 - assertTrue "created $1" $? + assertNotNull "keyname required" "$1" + KEYPASS=${2:-qwertyuiop} + echo $KEYPASS | ${CLIENT_EXE} keys new $1 >/dev/null 2>&1 + assertTrue "line ${LINENO}, created $1" $? } testMakeKeys() { - USER=demouser - assertFalse "already user $USER" "${CLIENT_EXE} keys get $USER" - newKey $USER - assertTrue "no user $USER" "${CLIENT_EXE} keys get $USER" + USER=demouser + assertFalse "line ${LINENO}, already user $USER" "${CLIENT_EXE} keys get $USER" + newKey $USER + assertTrue "line ${LINENO}, no user $USER" "${CLIENT_EXE} keys get $USER" } # load and run these tests with shunit2! diff --git a/tests/cli/rpc.sh b/tests/cli/rpc.sh index eb764089a..1026ee890 100755 --- a/tests/cli/rpc.sh +++ b/tests/cli/rpc.sh @@ -4,125 +4,125 @@ CLIENT_EXE=basecli SERVER_EXE=basecoin oneTimeSetUp() { - BASE=~/.bc_init_test - rm -rf "$BASE" - mkdir -p "$BASE" + BASE=~/.bc_init_test + rm -rf "$BASE" + mkdir -p "$BASE" - SERVER="${BASE}/server" - SERVER_LOG="${BASE}/${SERVER_EXE}.log" + SERVER="${BASE}/server" + SERVER_LOG="${BASE}/${SERVER_EXE}.log" - HEX="deadbeef1234deadbeef1234deadbeef1234aaaa" - ${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG" - if ! assertTrue $?; then return 1; fi + HEX="deadbeef1234deadbeef1234deadbeef1234aaaa" + ${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG" + if ! assertTrue "line=${LINENO}" $?; then return 1; fi - GENESIS_FILE=${SERVER}/genesis.json - CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \") + GENESIS_FILE=${SERVER}/genesis.json + CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \") - printf "starting ${SERVER_EXE}...\n" - ${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 & - sleep 5 - PID_SERVER=$! - disown - if ! ps $PID_SERVER >/dev/null; then - echo "**STARTUP FAILED**" - cat $SERVER_LOG - return 1 - fi + printf "starting ${SERVER_EXE}...\n" + ${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 & + sleep 5 + PID_SERVER=$! + disown + if ! ps $PID_SERVER >/dev/null; then + echo "**STARTUP FAILED**" + cat $SERVER_LOG + return 1 + fi - # this sets the base for all client queries in the tests - export BCHOME=${BASE}/client - ${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1 - if ! assertTrue "initialized light-client" "$?"; then - return 1 - fi + # this sets the base for all client queries in the tests + export BCHOME=${BASE}/client + ${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1 + if ! assertTrue "line=${LINENO}, initialized light-client" "$?"; then + return 1 + fi } oneTimeTearDown() { - printf "\nstopping ${SERVER_EXE}..." - kill -9 $PID_SERVER >/dev/null 2>&1 - sleep 1 + printf "\nstopping ${SERVER_EXE}..." + kill -9 $PID_SERVER >/dev/null 2>&1 + sleep 1 } -test01getInsecure() { - GENESIS=$(${CLIENT_EXE} rpc genesis) - assertTrue "get genesis" "$?" - MYCHAIN=$(echo ${GENESIS} | jq .genesis.chain_id | tr -d \") - assertEquals "genesis chain matches" "${CHAIN_ID}" "${MYCHAIN}" +test01GetInsecure() { + GENESIS=$(${CLIENT_EXE} rpc genesis) + assertTrue "line=${LINENO}, get genesis" "$?" + MYCHAIN=$(echo ${GENESIS} | jq .genesis.chain_id | tr -d \") + assertEquals "line=${LINENO}, genesis chain matches" "${CHAIN_ID}" "${MYCHAIN}" - STATUS=$(${CLIENT_EXE} rpc status) - assertTrue "get status" "$?" - SHEIGHT=$(echo ${STATUS} | jq .latest_block_height) - assertTrue "parsed status" "$?" - assertNotNull "has a height" "${SHEIGHT}" + STATUS=$(${CLIENT_EXE} rpc status) + assertTrue "line=${LINENO}, get status" "$?" + SHEIGHT=$(echo ${STATUS} | jq .latest_block_height) + assertTrue "line=${LINENO}, parsed status" "$?" + assertNotNull "line=${LINENO}, has a height" "${SHEIGHT}" - VALS=$(${CLIENT_EXE} rpc validators) - assertTrue "get validators" "$?" - VHEIGHT=$(echo ${VALS} | jq .block_height) - assertTrue "parsed validators" "$?" - assertTrue "sensible heights: $SHEIGHT / $VHEIGHT" "test $VHEIGHT -ge $SHEIGHT" - VCNT=$(echo ${VALS} | jq '.validators | length') - assertEquals "one validator" "1" "$VCNT" + VALS=$(${CLIENT_EXE} rpc validators) + assertTrue "line=${LINENO}, get validators" "$?" + VHEIGHT=$(echo ${VALS} | jq .block_height) + assertTrue "line=${LINENO}, parsed validators" "$?" + assertTrue "line=${LINENO}, sensible heights: $SHEIGHT / $VHEIGHT" "test $VHEIGHT -ge $SHEIGHT" + VCNT=$(echo ${VALS} | jq '.validators | length') + assertEquals "line=${LINENO}, one validator" "1" "$VCNT" - INFO=$(${CLIENT_EXE} rpc info) - assertTrue "get info" "$?" - DATA=$(echo $INFO | jq .response.data) - assertEquals "basecoin info" '"Basecoin v0.6.1"' "$DATA" + INFO=$(${CLIENT_EXE} rpc info) + assertTrue "line=${LINENO}, get info" "$?" + DATA=$(echo $INFO | jq .response.data) + assertEquals "line=${LINENO}, basecoin info" '"Basecoin v0.6.1"' "$DATA" } -test02getSecure() { - HEIGHT=$(${CLIENT_EXE} rpc status | jq .latest_block_height) - assertTrue "get status" "$?" +test02GetSecure() { + HEIGHT=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertTrue "line=${LINENO}, get status" "$?" - # check block produces something reasonable - assertFalse "missing height" "${CLIENT_EXE} rpc block" - BLOCK=$(${CLIENT_EXE} rpc block --height=$HEIGHT) - assertTrue "get block" "$?" - MHEIGHT=$(echo $BLOCK | jq .block_meta.header.height) - assertEquals "meta height" "${HEIGHT}" "${MHEIGHT}" - BHEIGHT=$(echo $BLOCK | jq .block.header.height) - assertEquals "meta height" "${HEIGHT}" "${BHEIGHT}" + # check block produces something reasonable + assertFalse "line=${LINENO}, missing height" "${CLIENT_EXE} rpc block" + BLOCK=$(${CLIENT_EXE} rpc block --height=$HEIGHT) + assertTrue "line=${LINENO}, get block" "$?" + MHEIGHT=$(echo $BLOCK | jq .block_meta.header.height) + assertEquals "line=${LINENO}, meta height" "${HEIGHT}" "${MHEIGHT}" + BHEIGHT=$(echo $BLOCK | jq .block.header.height) + assertEquals "line=${LINENO}, meta height" "${HEIGHT}" "${BHEIGHT}" - # check commit produces something reasonable - assertFalse "missing height" "${CLIENT_EXE} rpc commit" - let "CHEIGHT = $HEIGHT - 1" - COMMIT=$(${CLIENT_EXE} rpc commit --height=$CHEIGHT) - assertTrue "get commit" "$?" - HHEIGHT=$(echo $COMMIT | jq .header.height) - assertEquals "commit height" "${CHEIGHT}" "${HHEIGHT}" - assertEquals "canonical" "true" $(echo $COMMIT | jq .canonical) - BSIG=$(echo $BLOCK | jq .block.last_commit) - CSIG=$(echo $COMMIT | jq .commit) - assertEquals "block and commit" "$BSIG" "$CSIG" + # check commit produces something reasonable + assertFalse "line=${LINENO}, missing height" "${CLIENT_EXE} rpc commit" + let "CHEIGHT = $HEIGHT - 1" + COMMIT=$(${CLIENT_EXE} rpc commit --height=$CHEIGHT) + assertTrue "line=${LINENO}, get commit" "$?" + HHEIGHT=$(echo $COMMIT | jq .header.height) + assertEquals "line=${LINENO}, commit height" "${CHEIGHT}" "${HHEIGHT}" + assertEquals "line=${LINENO}, canonical" "true" $(echo $COMMIT | jq .canonical) + BSIG=$(echo $BLOCK | jq .block.last_commit) + CSIG=$(echo $COMMIT | jq .commit) + assertEquals "line=${LINENO}, block and commit" "$BSIG" "$CSIG" - # now let's get some headers - # assertFalse "missing height" "${CLIENT_EXE} rpc headers" - HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT) - assertTrue "get headers" "$?" - assertEquals "proper height" "$HEIGHT" $(echo $HEADERS | jq '.last_height') - assertEquals "two headers" "2" $(echo $HEADERS | jq '.block_metas | length') - # should we check these headers? - CHEAD=$(echo $COMMIT | jq .header) - # most recent first, so the commit header is second.... - HHEAD=$(echo $HEADERS | jq .block_metas[1].header) - assertEquals "commit and header" "$CHEAD" "$HHEAD" + # now let's get some headers + # assertFalse "missing height" "${CLIENT_EXE} rpc headers" + HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT) + assertTrue "line=${LINENO}, get headers" "$?" + assertEquals "line=${LINENO}, proper height" "$HEIGHT" $(echo $HEADERS | jq '.last_height') + assertEquals "line=${LINENO}, two headers" "2" $(echo $HEADERS | jq '.block_metas | length') + # should we check these headers? + CHEAD=$(echo $COMMIT | jq .header) + # most recent first, so the commit header is second.... + HHEAD=$(echo $HEADERS | jq .block_metas[1].header) + assertEquals "line=${LINENO}, commit and header" "$CHEAD" "$HHEAD" } -test03waiting() { - START=$(${CLIENT_EXE} rpc status | jq .latest_block_height) - assertTrue "get status" "$?" +test03Waiting() { + START=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertTrue "line=${LINENO}, get status" "$?" - let "NEXT = $START + 5" - assertFalse "no args" "${CLIENT_EXE} rpc wait" - assertFalse "too long" "${CLIENT_EXE} rpc wait --height=1234" - assertTrue "normal wait" "${CLIENT_EXE} rpc wait --height=$NEXT" + let "NEXT = $START + 5" + assertFalse "line=${LINENO}, no args" "${CLIENT_EXE} rpc wait" + assertFalse "line=${LINENO}, too long" "${CLIENT_EXE} rpc wait --height=1234" + assertTrue "line=${LINENO}, normal wait" "${CLIENT_EXE} rpc wait --height=$NEXT" - STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height) - assertEquals "wait until height" "$NEXT" "$STEP" + STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertEquals "line=${LINENO}, wait until height" "$NEXT" "$STEP" - let "NEXT = $STEP + 3" - assertTrue "${CLIENT_EXE} rpc wait --delta=3" - STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height) - assertEquals "wait for delta" "$NEXT" "$STEP" + let "NEXT = $STEP + 3" + assertTrue "line=${LINENO}, ${CLIENT_EXE} rpc wait --delta=3" + STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height) + assertEquals "line=${LINENO}, wait for delta" "$NEXT" "$STEP" } # load and run these tests with shunit2! From 86178412967d8220238ba79ede821128a06e814e Mon Sep 17 00:00:00 2001 From: rigel rozanski Date: Wed, 19 Jul 2017 01:23:13 -0400 Subject: [PATCH 10/20] errors cleanup --- Makefile | 2 +- errors/common.go | 76 ++++------------------------------ errors/common_test.go | 8 ---- modules/auth/errors.go | 30 ++++++++++++++ modules/auth/errors_test.go | 29 +++++++++++++ modules/auth/tx.go | 6 +-- modules/base/chain.go | 7 ++-- modules/base/errors.go | 40 ++++++++++++++++++ modules/base/errors_test.go | 45 ++++++++++++++++++++ modules/base/tx.go | 4 +- modules/coin/commands/query.go | 6 +-- modules/coin/commands/tx.go | 6 +-- modules/coin/errors.go | 24 +++++++---- modules/coin/handler.go | 2 +- modules/fee/errors.go | 8 ++-- modules/nonce/errors.go | 19 ++++++--- modules/nonce/tx.go | 5 +-- modules/roles/error.go | 16 +++---- 18 files changed, 214 insertions(+), 119 deletions(-) create mode 100644 modules/auth/errors.go create mode 100644 modules/auth/errors_test.go create mode 100644 modules/base/errors.go create mode 100644 modules/base/errors_test.go diff --git a/Makefile b/Makefile index aaee262c3..66e51f817 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ test_unit: test_cli: tests/cli/shunit2 # sudo apt-get install jq ./tests/cli/keys.sh - #./tests/cli/rpc.sh + ./tests/cli/rpc.sh ./tests/cli/init.sh ./tests/cli/basictx.sh ./tests/cli/counter.sh diff --git a/errors/common.go b/errors/common.go index 1ef42177f..f4fd66deb 100644 --- a/errors/common.go +++ b/errors/common.go @@ -5,27 +5,17 @@ import ( "fmt" "reflect" - "github.com/pkg/errors" - abci "github.com/tendermint/abci/types" ) var ( - errDecoding = fmt.Errorf("Error decoding input") - errUnauthorized = fmt.Errorf("Unauthorized") - errInvalidSignature = fmt.Errorf("Invalid Signature") - errTooLarge = fmt.Errorf("Input size too large") - errNoSigners = fmt.Errorf("There are no signers") - errMissingSignature = fmt.Errorf("Signature missing") - errTooManySignatures = fmt.Errorf("Too many signatures") - errNoChain = fmt.Errorf("No chain id provided") - errTxEmpty = fmt.Errorf("The provided Tx is empty") - errWrongChain = fmt.Errorf("Wrong chain for tx") - errUnknownTxType = fmt.Errorf("Tx type unknown") - errInvalidFormat = fmt.Errorf("Invalid format") - errUnknownModule = fmt.Errorf("Unknown module") - errExpired = fmt.Errorf("Tx expired") - errUnknownKey = fmt.Errorf("Unknown key") + errDecoding = fmt.Errorf("Error decoding input") + errUnauthorized = fmt.Errorf("Unauthorized") + errTooLarge = fmt.Errorf("Input size too large") + errMissingSignature = fmt.Errorf("Signature missing") + errUnknownTxType = fmt.Errorf("Tx type unknown") + errInvalidFormat = fmt.Errorf("Invalid format") + errUnknownModule = fmt.Errorf("Unknown module") internalErr = abci.CodeType_InternalError encodingErr = abci.CodeType_EncodingError @@ -70,14 +60,6 @@ func IsUnknownModuleErr(err error) bool { return IsSameError(errUnknownModule, err) } -func ErrUnknownKey(mod string) TMError { - w := errors.Wrap(errUnknownKey, mod) - return WithCode(w, abci.CodeType_UnknownRequest) -} -func IsUnknownKeyErr(err error) bool { - return IsSameError(errUnknownKey, err) -} - func ErrInternal(msg string) TMError { return New(msg, internalErr) } @@ -104,10 +86,6 @@ func IsUnauthorizedErr(err error) bool { return HasErrorCode(err, unauthorized) } -func ErrNoSigners() TMError { - return WithCode(errNoSigners, unauthorized) -} - func ErrMissingSignature() TMError { return WithCode(errMissingSignature, unauthorized) } @@ -115,49 +93,9 @@ func IsMissingSignatureErr(err error) bool { return IsSameError(errMissingSignature, err) } -func ErrTooManySignatures() TMError { - return WithCode(errTooManySignatures, unauthorized) -} -func IsTooManySignaturesErr(err error) bool { - return IsSameError(errTooManySignatures, err) -} - -func ErrInvalidSignature() TMError { - return WithCode(errInvalidSignature, unauthorized) -} -func IsInvalidSignatureErr(err error) bool { - return IsSameError(errInvalidSignature, err) -} - -func ErrNoChain() TMError { - return WithCode(errNoChain, unauthorized) -} -func IsNoChainErr(err error) bool { - return IsSameError(errNoChain, err) -} - -func ErrTxEmpty() TMError { - return WithCode(errTxEmpty, unauthorized) -} - -func ErrWrongChain(chain string) TMError { - msg := errors.Wrap(errWrongChain, chain) - return WithCode(msg, unauthorized) -} -func IsWrongChainErr(err error) bool { - return IsSameError(errWrongChain, err) -} - func ErrTooLarge() TMError { return WithCode(errTooLarge, encodingErr) } func IsTooLargeErr(err error) bool { return IsSameError(errTooLarge, err) } - -func ErrExpired() TMError { - return WithCode(errExpired, unauthorized) -} -func IsExpiredErr(err error) bool { - return IsSameError(errExpired, err) -} diff --git a/errors/common_test.go b/errors/common_test.go index 59d5fc626..f16933df4 100644 --- a/errors/common_test.go +++ b/errors/common_test.go @@ -42,7 +42,6 @@ func TestErrorMatches(t *testing.T) { {errUnauthorized, ErrUnauthorized(), true}, {errMissingSignature, ErrUnauthorized(), false}, {errMissingSignature, ErrMissingSignature(), true}, - {errWrongChain, ErrWrongChain("hakz"), true}, {errUnknownTxType, ErrUnknownTxType(holder{}), true}, {errUnknownTxType, ErrUnknownTxType("some text here..."), true}, {errUnknownTxType, ErrUnknownTxType(demoTx{5}.Wrap()), true}, @@ -66,13 +65,6 @@ func TestChecks(t *testing.T) { {ErrDecoding(), IsDecodingErr, true}, {ErrUnauthorized(), IsDecodingErr, false}, {ErrUnauthorized(), IsUnauthorizedErr, true}, - {ErrInvalidSignature(), IsInvalidSignatureErr, true}, - // unauthorized includes InvalidSignature, but not visa versa - {ErrInvalidSignature(), IsUnauthorizedErr, true}, - {ErrUnauthorized(), IsInvalidSignatureErr, false}, - // make sure WrongChain works properly - {ErrWrongChain("fooz"), IsUnauthorizedErr, true}, - {ErrWrongChain("barz"), IsWrongChainErr, true}, // make sure lots of things match InternalErr, but not everything {ErrInternal("bad db connection"), IsInternalErr, true}, {Wrap(errors.New("wrapped")), IsInternalErr, true}, diff --git a/modules/auth/errors.go b/modules/auth/errors.go new file mode 100644 index 000000000..8de87b20f --- /dev/null +++ b/modules/auth/errors.go @@ -0,0 +1,30 @@ +//nolint +package auth + +import ( + "fmt" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/errors" +) + +var ( + errInvalidSignature = fmt.Errorf("Invalid Signature") //move auth + errTooManySignatures = fmt.Errorf("Too many signatures") //move auth + + unauthorized = abci.CodeType_Unauthorized +) + +func ErrTooManySignatures() errors.TMError { + return errors.WithCode(errTooManySignatures, unauthorized) +} +func IsTooManySignaturesErr(err error) bool { + return errors.IsSameError(errTooManySignatures, err) +} + +func ErrInvalidSignature() errors.TMError { + return errors.WithCode(errInvalidSignature, unauthorized) +} +func IsInvalidSignatureErr(err error) bool { + return errors.IsSameError(errInvalidSignature, err) +} diff --git a/modules/auth/errors_test.go b/modules/auth/errors_test.go new file mode 100644 index 000000000..887eae1e5 --- /dev/null +++ b/modules/auth/errors_test.go @@ -0,0 +1,29 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/tendermint/basecoin/errors" +) + +func TestChecks(t *testing.T) { + // TODO: make sure the Is and Err methods match + assert := assert.New(t) + + cases := []struct { + err error + check func(error) bool + match bool + }{ + // unauthorized includes InvalidSignature, but not visa versa + {ErrInvalidSignature(), IsInvalidSignatureErr, true}, + {ErrInvalidSignature(), errors.IsUnauthorizedErr, true}, + } + + for i, tc := range cases { + match := tc.check(tc.err) + assert.Equal(tc.match, match, "%d", i) + } +} diff --git a/modules/auth/tx.go b/modules/auth/tx.go index 26e2b94b6..970f321dc 100644 --- a/modules/auth/tx.go +++ b/modules/auth/tx.go @@ -106,7 +106,7 @@ func (s *OneSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error { return errors.ErrMissingSignature() } if !s.Empty() { - return errors.ErrTooManySignatures() + return ErrTooManySignatures() } // set the value once we are happy s.Signed = signed @@ -121,7 +121,7 @@ func (s *OneSig) Signers() ([]crypto.PubKey, error) { return nil, errors.ErrMissingSignature() } if !s.Pubkey.VerifyBytes(s.SignBytes(), s.Sig) { - return nil, errors.ErrInvalidSignature() + return nil, ErrInvalidSignature() } return []crypto.PubKey{s.Pubkey}, nil } @@ -194,7 +194,7 @@ func (s *MultiSig) Signers() ([]crypto.PubKey, error) { for i := range s.Sigs { ms := s.Sigs[i] if !ms.Pubkey.VerifyBytes(data, ms.Sig) { - return nil, errors.ErrInvalidSignature() + return nil, ErrInvalidSignature() } keys[i] = ms.Pubkey } diff --git a/modules/base/chain.go b/modules/base/chain.go index 65513679d..9dc704c36 100644 --- a/modules/base/chain.go +++ b/modules/base/chain.go @@ -2,7 +2,6 @@ package base import ( "github.com/tendermint/basecoin" - "github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) @@ -48,7 +47,7 @@ func (c Chain) checkChainTx(chainID string, height uint64, tx basecoin.Tx) (base // make sure it is a chaintx ctx, ok := tx.Unwrap().(ChainTx) if !ok { - return tx, errors.ErrNoChain() + return tx, ErrNoChain() } // basic validation @@ -59,10 +58,10 @@ func (c Chain) checkChainTx(chainID string, height uint64, tx basecoin.Tx) (base // compare against state if ctx.ChainID != chainID { - return tx, errors.ErrWrongChain(ctx.ChainID) + return tx, ErrWrongChain(ctx.ChainID) } if ctx.ExpiresAt != 0 && ctx.ExpiresAt <= height { - return tx, errors.ErrExpired() + return tx, ErrExpired() } return ctx.Tx, nil } diff --git a/modules/base/errors.go b/modules/base/errors.go new file mode 100644 index 000000000..bd55b1ffe --- /dev/null +++ b/modules/base/errors.go @@ -0,0 +1,40 @@ +//nolint +package base + +import ( + "fmt" + + pkgerrors "github.com/pkg/errors" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/errors" +) + +var ( + errNoChain = fmt.Errorf("No chain id provided") //move base + errWrongChain = fmt.Errorf("Wrong chain for tx") //move base + errExpired = fmt.Errorf("Tx expired") //move base + + unauthorized = abci.CodeType_Unauthorized +) + +func ErrNoChain() errors.TMError { + return errors.WithCode(errNoChain, unauthorized) +} +func IsNoChainErr(err error) bool { + return errors.IsSameError(errNoChain, err) +} +func ErrWrongChain(chain string) errors.TMError { + msg := pkgerrors.Wrap(errWrongChain, chain) + return errors.WithCode(msg, unauthorized) +} +func IsWrongChainErr(err error) bool { + return errors.IsSameError(errWrongChain, err) +} + +func ErrExpired() errors.TMError { + return errors.WithCode(errExpired, unauthorized) +} +func IsExpiredErr(err error) bool { + return errors.IsSameError(errExpired, err) +} diff --git a/modules/base/errors_test.go b/modules/base/errors_test.go new file mode 100644 index 000000000..64fda60f8 --- /dev/null +++ b/modules/base/errors_test.go @@ -0,0 +1,45 @@ +package base + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/tendermint/basecoin/errors" +) + +func TestErrorMatches(t *testing.T) { + assert := assert.New(t) + + cases := []struct { + pattern, err error + match bool + }{ + {errWrongChain, ErrWrongChain("hakz"), true}, + } + + for i, tc := range cases { + same := errors.IsSameError(tc.pattern, tc.err) + assert.Equal(tc.match, same, "%d: %#v / %#v", i, tc.pattern, tc.err) + } +} + +func TestChecks(t *testing.T) { + // TODO: make sure the Is and Err methods match + assert := assert.New(t) + + cases := []struct { + err error + check func(error) bool + match bool + }{ + // make sure WrongChain works properly + {ErrWrongChain("fooz"), errors.IsUnauthorizedErr, true}, + {ErrWrongChain("barz"), IsWrongChainErr, true}, + } + + for i, tc := range cases { + match := tc.check(tc.err) + assert.Equal(tc.match, match, "%d", i) + } +} diff --git a/modules/base/tx.go b/modules/base/tx.go index fd66fad88..d365c1b7b 100644 --- a/modules/base/tx.go +++ b/modules/base/tx.go @@ -86,10 +86,10 @@ func (c ChainTx) Wrap() basecoin.Tx { } func (c ChainTx) ValidateBasic() error { if c.ChainID == "" { - return errors.ErrNoChain() + return ErrNoChain() } if !chainPattern.MatchString(c.ChainID) { - return errors.ErrWrongChain(c.ChainID) + return ErrWrongChain(c.ChainID) } if c.Tx.Empty() { return errors.ErrUnknownTxType(c.Tx) diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go index 8f5ef7040..55e7a060f 100644 --- a/modules/coin/commands/query.go +++ b/modules/coin/commands/query.go @@ -4,9 +4,9 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - lc "github.com/tendermint/light-client" lcmd "github.com/tendermint/basecoin/client/commands" proofcmd "github.com/tendermint/basecoin/client/commands/proofs" + lc "github.com/tendermint/light-client" "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/coin" @@ -17,10 +17,10 @@ import ( var AccountQueryCmd = &cobra.Command{ Use: "account [address]", Short: "Get details of an account, with proof", - RunE: lcmd.RequireInit(doAccountQuery), + RunE: lcmd.RequireInit(accountQueryCmd), } -func doAccountQuery(cmd *cobra.Command, args []string) error { +func accountQueryCmd(cmd *cobra.Command, args []string) error { addr, err := proofcmd.ParseHexKey(args, "address") if err != nil { return err diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index 9a338582c..c817c57b6 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -14,7 +14,7 @@ import ( var SendTxCmd = &cobra.Command{ Use: "send", Short: "send tokens from one account to another", - RunE: commands.RequireInit(doSendTx), + RunE: commands.RequireInit(sendTxCmd), } //nolint @@ -31,8 +31,8 @@ func init() { flags.String(FlagFrom, "", "Address sending coins, if not first signer") } -// doSendTx is an example of how to make a tx -func doSendTx(cmd *cobra.Command, args []string) error { +// 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) diff --git a/modules/coin/errors.go b/modules/coin/errors.go index 0bf3bde9e..1f045adbb 100644 --- a/modules/coin/errors.go +++ b/modules/coin/errors.go @@ -4,23 +4,25 @@ package coin import ( "fmt" + pkgerrors "github.com/pkg/errors" + abci "github.com/tendermint/abci/types" "github.com/tendermint/basecoin/errors" ) var ( errNoAccount = fmt.Errorf("No such account") - errInsufficientFunds = fmt.Errorf("Insufficient Funds") - errNoInputs = fmt.Errorf("No Input Coins") - errNoOutputs = fmt.Errorf("No Output Coins") - errInvalidAddress = fmt.Errorf("Invalid Address") - errInvalidCoins = fmt.Errorf("Invalid Coins") -) + errInsufficientFunds = fmt.Errorf("Insufficient funds") + errNoInputs = fmt.Errorf("No input coins") + errNoOutputs = fmt.Errorf("No output coins") + errInvalidAddress = fmt.Errorf("Invalid address") + errInvalidCoins = fmt.Errorf("Invalid coins") + errUnknownKey = fmt.Errorf("Unknown key") -var ( invalidInput = abci.CodeType_BaseInvalidInput invalidOutput = abci.CodeType_BaseInvalidOutput unknownAddress = abci.CodeType_BaseUnknownAddress + unknownRequest = abci.CodeType_UnknownRequest ) // here are some generic handlers to grab classes of errors based on code @@ -79,3 +81,11 @@ func ErrNoOutputs() errors.TMError { func IsNoOutputsErr(err error) bool { return errors.IsSameError(errNoOutputs, err) } + +func ErrUnknownKey(mod string) errors.TMError { + w := pkgerrors.Wrap(errUnknownKey, mod) + return errors.WithCode(w, unknownRequest) +} +func IsUnknownKeyErr(err error) bool { + return errors.IsSameError(errUnknownKey, err) +} diff --git a/modules/coin/handler.go b/modules/coin/handler.go index 0e3378db7..ef3f3ae0f 100644 --- a/modules/coin/handler.go +++ b/modules/coin/handler.go @@ -99,7 +99,7 @@ func (h Handler) SetOption(l log.Logger, store state.KVStore, module, key, value return "Success", nil } - return "", errors.ErrUnknownKey(key) + return "", ErrUnknownKey(key) } func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) { diff --git a/modules/fee/errors.go b/modules/fee/errors.go index 06cdd10c5..5edc84786 100644 --- a/modules/fee/errors.go +++ b/modules/fee/errors.go @@ -10,19 +10,21 @@ import ( ) var ( - errInsufficientFees = fmt.Errorf("Insufficient Fees") + errInsufficientFees = fmt.Errorf("Insufficient fees") errWrongFeeDenom = fmt.Errorf("Required fee denomination") + + invalidInput = abci.CodeType_BaseInvalidInput ) func ErrInsufficientFees() errors.TMError { - return errors.WithCode(errInsufficientFees, abci.CodeType_BaseInvalidInput) + return errors.WithCode(errInsufficientFees, invalidInput) } func IsInsufficientFeesErr(err error) bool { return errors.IsSameError(errInsufficientFees, err) } func ErrWrongFeeDenom(denom string) errors.TMError { - return errors.WithMessage(denom, errWrongFeeDenom, abci.CodeType_BaseInvalidInput) + return errors.WithMessage(denom, errWrongFeeDenom, invalidInput) } func IsWrongFeeDenomErr(err error) bool { return errors.IsSameError(errWrongFeeDenom, err) diff --git a/modules/nonce/errors.go b/modules/nonce/errors.go index f88629b45..832ea8ce2 100644 --- a/modules/nonce/errors.go +++ b/modules/nonce/errors.go @@ -11,22 +11,31 @@ import ( var ( errNoNonce = fmt.Errorf("Tx doesn't contain nonce") - errNotMember = fmt.Errorf("nonce contains non-permissioned member") + errNotMember = fmt.Errorf("Nonce contains non-permissioned member") errZeroSequence = fmt.Errorf("Sequence number cannot be zero") + errNoSigners = fmt.Errorf("There are no signers") + errTxEmpty = fmt.Errorf("The provided Tx is empty") unauthorized = abci.CodeType_Unauthorized + badNonce = abci.CodeType_BadNonce + invalidInput = abci.CodeType_BaseInvalidInput ) func ErrBadNonce(got, expected uint32) errors.TMError { - return errors.WithCode(fmt.Errorf("Bad nonce sequence, got %d, expected %d", got, expected), unauthorized) + return errors.WithCode(fmt.Errorf("Bad nonce sequence, got %d, expected %d", got, expected), badNonce) } - func ErrNoNonce() errors.TMError { - return errors.WithCode(errNoNonce, unauthorized) + return errors.WithCode(errNoNonce, badNonce) } func ErrNotMember() errors.TMError { return errors.WithCode(errNotMember, unauthorized) } func ErrZeroSequence() errors.TMError { - return errors.WithCode(errZeroSequence, unauthorized) + return errors.WithCode(errZeroSequence, invalidInput) +} +func ErrNoSigners() errors.TMError { + return errors.WithCode(errNoSigners, invalidInput) +} +func ErrTxEmpty() errors.TMError { + return errors.WithCode(errTxEmpty, invalidInput) } diff --git a/modules/nonce/tx.go b/modules/nonce/tx.go index 7a70878b2..6fb5ce7fd 100644 --- a/modules/nonce/tx.go +++ b/modules/nonce/tx.go @@ -11,7 +11,6 @@ import ( "sort" "github.com/tendermint/basecoin" - "github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/state" ) @@ -50,11 +49,11 @@ func (n Tx) Wrap() basecoin.Tx { func (n Tx) ValidateBasic() error { switch { case n.Tx.Empty(): - return errors.ErrTxEmpty() + return ErrTxEmpty() case n.Sequence == 0: return ErrZeroSequence() case len(n.Signers) == 0: - return errors.ErrNoSigners() + return ErrNoSigners() } return n.Tx.ValidateBasic() } diff --git a/modules/roles/error.go b/modules/roles/error.go index 1fb8333a2..02b9e0637 100644 --- a/modules/roles/error.go +++ b/modules/roles/error.go @@ -16,54 +16,56 @@ var ( errNoMembers = fmt.Errorf("No members specified") errTooManyMembers = fmt.Errorf("Too many members specified") errNotEnoughMembers = fmt.Errorf("Not enough members specified") + + unauthorized = abci.CodeType_Unauthorized ) // TODO: codegen? // ex: err-gen NoRole,"No such role",CodeType_Unauthorized func ErrNoRole() errors.TMError { - return errors.WithCode(errNoRole, abci.CodeType_Unauthorized) + return errors.WithCode(errNoRole, unauthorized) } func IsNoRoleErr(err error) bool { return errors.IsSameError(errNoRole, err) } func ErrRoleExists() errors.TMError { - return errors.WithCode(errRoleExists, abci.CodeType_Unauthorized) + return errors.WithCode(errRoleExists, unauthorized) } func IsRoleExistsErr(err error) bool { return errors.IsSameError(errRoleExists, err) } func ErrNotMember() errors.TMError { - return errors.WithCode(errNotMember, abci.CodeType_Unauthorized) + return errors.WithCode(errNotMember, unauthorized) } func IsNotMemberErr(err error) bool { return errors.IsSameError(errNotMember, err) } func ErrInsufficientSigs() errors.TMError { - return errors.WithCode(errInsufficientSigs, abci.CodeType_Unauthorized) + return errors.WithCode(errInsufficientSigs, unauthorized) } func IsInsufficientSigsErr(err error) bool { return errors.IsSameError(errInsufficientSigs, err) } func ErrNoMembers() errors.TMError { - return errors.WithCode(errNoMembers, abci.CodeType_Unauthorized) + return errors.WithCode(errNoMembers, unauthorized) } func IsNoMembersErr(err error) bool { return errors.IsSameError(errNoMembers, err) } func ErrTooManyMembers() errors.TMError { - return errors.WithCode(errTooManyMembers, abci.CodeType_Unauthorized) + return errors.WithCode(errTooManyMembers, unauthorized) } func IsTooManyMembersErr(err error) bool { return errors.IsSameError(errTooManyMembers, err) } func ErrNotEnoughMembers() errors.TMError { - return errors.WithCode(errNotEnoughMembers, abci.CodeType_Unauthorized) + return errors.WithCode(errNotEnoughMembers, unauthorized) } func IsNotEnoughMembersErr(err error) bool { return errors.IsSameError(errNotEnoughMembers, err) From ac1ecc10c442941e84475d7698017c28875ca00e Mon Sep 17 00:00:00 2001 From: rigel rozanski Date: Wed, 19 Jul 2017 04:51:36 -0400 Subject: [PATCH 11/20] new library orders --- app/genesis_test.go | 4 +++- client/commands/init.go | 6 +++--- client/commands/proofs/get.go | 5 ++--- client/commands/proofs/state.go | 9 +++++---- client/commands/proofs/tx.go | 11 +++++++---- client/commands/proxy/root.go | 7 +++---- client/commands/rpc/insecure.go | 1 + client/commands/rpc/root.go | 2 +- client/commands/rpc/secure.go | 1 + client/commands/seeds/export.go | 1 + client/commands/seeds/import.go | 2 ++ client/commands/seeds/show.go | 2 ++ client/commands/seeds/update.go | 2 ++ client/commands/txs/presenter.go | 1 + client/commands/txs/root.go | 1 + cmd/basecoin/commands/key.go | 2 -- cmd/basecoin/main.go | 3 ++- modules/auth/errors.go | 1 + modules/auth/tx_test.go | 2 +- modules/base/commands/wrap.go | 3 +-- modules/base/errors.go | 7 ++----- modules/base/tx_test.go | 2 +- modules/coin/commands/query.go | 4 ++-- modules/coin/errors.go | 6 ++---- modules/coin/helper.go | 2 +- modules/nonce/commands/query.go | 4 ++-- modules/roles/error.go | 1 + modules/roles/handler_test.go | 1 + modules/roles/store.go | 3 ++- stack/state_space_test.go | 2 +- 30 files changed, 55 insertions(+), 43 deletions(-) diff --git a/app/genesis_test.go b/app/genesis_test.go index 95e1d788e..57c895c8d 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -7,10 +7,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/basecoin/modules/coin" + eyescli "github.com/tendermint/merkleeyes/client" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/basecoin/modules/coin" ) const genesisFilepath = "./testdata/genesis.json" diff --git a/client/commands/init.go b/client/commands/init.go index d53b03cab..fe0109372 100644 --- a/client/commands/init.go +++ b/client/commands/init.go @@ -15,19 +15,19 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/light-client/certifiers/files" "github.com/tendermint/tmlibs/cli" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tendermint/types" - - "github.com/tendermint/light-client/certifiers" - "github.com/tendermint/light-client/certifiers/files" ) var ( dirPerm = os.FileMode(0700) ) +//nolint const ( SeedFlag = "seed" HashFlag = "valhash" diff --git a/client/commands/proofs/get.go b/client/commands/proofs/get.go index c670e7b3e..34638698c 100644 --- a/client/commands/proofs/get.go +++ b/client/commands/proofs/get.go @@ -8,12 +8,11 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" - + lc "github.com/tendermint/light-client" + "github.com/tendermint/light-client/proofs" "github.com/tendermint/tendermint/rpc/client" - lc "github.com/tendermint/light-client" "github.com/tendermint/basecoin/client/commands" - "github.com/tendermint/light-client/proofs" ) // GetAndParseAppProof does most of the work of the query commands, but is quite diff --git a/client/commands/proofs/state.go b/client/commands/proofs/state.go index 6f7109d01..e55b5b581 100644 --- a/client/commands/proofs/state.go +++ b/client/commands/proofs/state.go @@ -4,24 +4,25 @@ import ( "github.com/spf13/cobra" "github.com/tendermint/go-wire/data" + "github.com/tendermint/light-client/proofs" "github.com/tendermint/basecoin/client/commands" - "github.com/tendermint/light-client/proofs" ) -var KeyCmd = &cobra.Command{ +// KeyQueryCmd - CLI command to query a state by key with proof +var KeyQueryCmd = &cobra.Command{ Use: "key [key]", Short: "Handle proofs for state of abci app", Long: `This will look up a given key in the abci app, verify the proof, and output it as hex. If you want json output, use an app-specific command that knows key and value structure.`, - RunE: commands.RequireInit(doKeyQuery), + RunE: commands.RequireInit(keyQueryCmd), } // Note: we cannot yse GetAndParseAppProof here, as we don't use go-wire to // parse the object, but rather return the raw bytes -func doKeyQuery(cmd *cobra.Command, args []string) error { +func keyQueryCmd(cmd *cobra.Command, args []string) error { // parse cli height := GetHeight() key, err := ParseHexKey(args, "key") diff --git a/client/commands/proofs/tx.go b/client/commands/proofs/tx.go index 7dc21af05..cffc4dfc1 100644 --- a/client/commands/proofs/tx.go +++ b/client/commands/proofs/tx.go @@ -3,13 +3,16 @@ package proofs import ( "github.com/spf13/cobra" - "github.com/tendermint/basecoin/client/commands" "github.com/tendermint/light-client/proofs" + + "github.com/tendermint/basecoin/client/commands" ) +//nolint TODO add description var TxPresenters = proofs.NewPresenters() -var TxCmd = &cobra.Command{ +// TxQueryCmd - CLI command to query a transaction with proof +var TxQueryCmd = &cobra.Command{ Use: "tx [txhash]", Short: "Handle proofs of commited txs", Long: `Proofs allows you to validate abci state with merkle proofs. @@ -18,10 +21,10 @@ These proofs tie the data to a checkpoint, which is managed by "seeds". Here we can validate these proofs and import/export them to prove specific data to other peers as needed. `, - RunE: commands.RequireInit(doTxQuery), + RunE: commands.RequireInit(txQueryCmd), } -func doTxQuery(cmd *cobra.Command, args []string) error { +func txQueryCmd(cmd *cobra.Command, args []string) error { // parse cli height := GetHeight() bkey, err := ParseHexKey(args, "txhash") diff --git a/client/commands/proxy/root.go b/client/commands/proxy/root.go index 0284f4dbe..4845773da 100644 --- a/client/commands/proxy/root.go +++ b/client/commands/proxy/root.go @@ -7,14 +7,13 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - cmn "github.com/tendermint/tmlibs/common" - "github.com/tendermint/tmlibs/log" - + certclient "github.com/tendermint/light-client/certifiers/client" "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/core" rpc "github.com/tendermint/tendermint/rpc/lib/server" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" - certclient "github.com/tendermint/light-client/certifiers/client" "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/rpc/insecure.go b/client/commands/rpc/insecure.go index 80432f95e..5587a5914 100644 --- a/client/commands/rpc/insecure.go +++ b/client/commands/rpc/insecure.go @@ -2,6 +2,7 @@ package rpc import ( "github.com/spf13/cobra" + "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/rpc/root.go b/client/commands/rpc/root.go index 471744b2f..2a1d0e532 100644 --- a/client/commands/rpc/root.go +++ b/client/commands/rpc/root.go @@ -6,9 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/tendermint/go-wire/data" + certclient "github.com/tendermint/light-client/certifiers/client" "github.com/tendermint/tendermint/rpc/client" - certclient "github.com/tendermint/light-client/certifiers/client" "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/rpc/secure.go b/client/commands/rpc/secure.go index d49504143..717c20c98 100644 --- a/client/commands/rpc/secure.go +++ b/client/commands/rpc/secure.go @@ -3,6 +3,7 @@ package rpc import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/seeds/export.go b/client/commands/seeds/export.go index a4c453526..1ac3ac42a 100644 --- a/client/commands/seeds/export.go +++ b/client/commands/seeds/export.go @@ -4,6 +4,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/seeds/import.go b/client/commands/seeds/import.go index 2731324d9..4a63ef7d7 100644 --- a/client/commands/seeds/import.go +++ b/client/commands/seeds/import.go @@ -6,7 +6,9 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/seeds/show.go b/client/commands/seeds/show.go index 5ea8287e8..100201b63 100644 --- a/client/commands/seeds/show.go +++ b/client/commands/seeds/show.go @@ -7,7 +7,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/seeds/update.go b/client/commands/seeds/update.go index 575ede4ac..153f090f8 100644 --- a/client/commands/seeds/update.go +++ b/client/commands/seeds/update.go @@ -4,7 +4,9 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/basecoin/client/commands" ) diff --git a/client/commands/txs/presenter.go b/client/commands/txs/presenter.go index b13f694e5..79f718a4f 100644 --- a/client/commands/txs/presenter.go +++ b/client/commands/txs/presenter.go @@ -2,6 +2,7 @@ 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" diff --git a/client/commands/txs/root.go b/client/commands/txs/root.go index 6a1080477..b9e9ea3ac 100644 --- a/client/commands/txs/root.go +++ b/client/commands/txs/root.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/basecoin" ) diff --git a/cmd/basecoin/commands/key.go b/cmd/basecoin/commands/key.go index b5b03e3fb..72620072f 100644 --- a/cmd/basecoin/commands/key.go +++ b/cmd/basecoin/commands/key.go @@ -8,8 +8,6 @@ import ( "path" "strings" - //"github.com/pkg/errors" - "github.com/spf13/viper" "github.com/tendermint/go-crypto" diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 8b4f5764c..33ba8c1d4 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -3,9 +3,10 @@ package main import ( "os" + "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/basecoin/app" "github.com/tendermint/basecoin/cmd/basecoin/commands" - "github.com/tendermint/tmlibs/cli" ) func main() { diff --git a/modules/auth/errors.go b/modules/auth/errors.go index 8de87b20f..28352ce2d 100644 --- a/modules/auth/errors.go +++ b/modules/auth/errors.go @@ -5,6 +5,7 @@ import ( "fmt" abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/errors" ) diff --git a/modules/auth/tx_test.go b/modules/auth/tx_test.go index 745e4466e..03ef42c12 100644 --- a/modules/auth/tx_test.go +++ b/modules/auth/tx_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/basecoin/stack" crypto "github.com/tendermint/go-crypto" keys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/go-crypto/keys/cryptostore" @@ -14,6 +13,7 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/stack" ) func checkSignBytes(t *testing.T, bytes []byte, expected string) { diff --git a/modules/base/commands/wrap.go b/modules/base/commands/wrap.go index e199c4ed0..92e994fd0 100644 --- a/modules/base/commands/wrap.go +++ b/modules/base/commands/wrap.go @@ -6,9 +6,8 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/tendermint/basecoin/client/commands" - "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/client/commands" txcmd "github.com/tendermint/basecoin/client/commands/txs" "github.com/tendermint/basecoin/modules/base" ) diff --git a/modules/base/errors.go b/modules/base/errors.go index bd55b1ffe..8279bc434 100644 --- a/modules/base/errors.go +++ b/modules/base/errors.go @@ -4,9 +4,8 @@ package base import ( "fmt" - pkgerrors "github.com/pkg/errors" - abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/errors" ) @@ -25,13 +24,11 @@ func IsNoChainErr(err error) bool { return errors.IsSameError(errNoChain, err) } func ErrWrongChain(chain string) errors.TMError { - msg := pkgerrors.Wrap(errWrongChain, chain) - return errors.WithCode(msg, unauthorized) + return errors.WithMessage(chain, errWrongChain, unauthorized) } func IsWrongChainErr(err error) bool { return errors.IsSameError(errWrongChain, err) } - func ErrExpired() errors.TMError { return errors.WithCode(errExpired, unauthorized) } diff --git a/modules/base/tx_test.go b/modules/base/tx_test.go index 00bd4ea79..1830c469d 100644 --- a/modules/base/tx_test.go +++ b/modules/base/tx_test.go @@ -7,10 +7,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/basecoin/stack" "github.com/tendermint/go-wire/data" "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/stack" ) func TestEncoding(t *testing.T) { diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go index 55e7a060f..5bea51478 100644 --- a/modules/coin/commands/query.go +++ b/modules/coin/commands/query.go @@ -4,10 +4,10 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - lcmd "github.com/tendermint/basecoin/client/commands" - proofcmd "github.com/tendermint/basecoin/client/commands/proofs" lc "github.com/tendermint/light-client" + lcmd "github.com/tendermint/basecoin/client/commands" + proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/coin" "github.com/tendermint/basecoin/stack" diff --git a/modules/coin/errors.go b/modules/coin/errors.go index 1f045adbb..49513f433 100644 --- a/modules/coin/errors.go +++ b/modules/coin/errors.go @@ -4,9 +4,8 @@ package coin import ( "fmt" - pkgerrors "github.com/pkg/errors" - abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/errors" ) @@ -83,8 +82,7 @@ func IsNoOutputsErr(err error) bool { } func ErrUnknownKey(mod string) errors.TMError { - w := pkgerrors.Wrap(errUnknownKey, mod) - return errors.WithCode(w, unknownRequest) + return errors.WithMessage(mod, errUnknownKey, unknownRequest) } func IsUnknownKeyErr(err error) bool { return errors.IsSameError(errUnknownKey, err) diff --git a/modules/coin/helper.go b/modules/coin/helper.go index 21c9ea782..4672e85d3 100644 --- a/modules/coin/helper.go +++ b/modules/coin/helper.go @@ -1,11 +1,11 @@ package coin import ( - "github.com/tendermint/basecoin/modules/auth" crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire/data" "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/modules/auth" ) // AccountWithKey is a helper for tests, that includes and account diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index ccc94b937..2570c88d9 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -6,11 +6,11 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + lc "github.com/tendermint/light-client" + "github.com/tendermint/basecoin" lcmd "github.com/tendermint/basecoin/client/commands" proofcmd "github.com/tendermint/basecoin/client/commands/proofs" - lc "github.com/tendermint/light-client" - "github.com/tendermint/basecoin/modules/nonce" "github.com/tendermint/basecoin/stack" ) diff --git a/modules/roles/error.go b/modules/roles/error.go index 02b9e0637..62ff77226 100644 --- a/modules/roles/error.go +++ b/modules/roles/error.go @@ -5,6 +5,7 @@ import ( "fmt" abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/errors" ) diff --git a/modules/roles/handler_test.go b/modules/roles/handler_test.go index e5c744bbc..6d1c5dfa6 100644 --- a/modules/roles/handler_test.go +++ b/modules/roles/handler_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/modules/roles" "github.com/tendermint/basecoin/stack" diff --git a/modules/roles/store.go b/modules/roles/store.go index c29d93f0f..eb114ff6f 100644 --- a/modules/roles/store.go +++ b/modules/roles/store.go @@ -3,10 +3,11 @@ package roles import ( "fmt" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/state" - wire "github.com/tendermint/go-wire" ) // NewPerm creates a role permission with the given label diff --git a/stack/state_space_test.go b/stack/state_space_test.go index 2722ad6a3..071b32660 100644 --- a/stack/state_space_test.go +++ b/stack/state_space_test.go @@ -6,11 +6,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/go-wire/data" "github.com/tendermint/tmlibs/log" "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/state" - "github.com/tendermint/go-wire/data" ) // writerMid is a middleware that writes the given bytes on CheckTx and DeliverTx From f7fb30fbb6127f8f254d2a3a97dd91efd4d168f0 Mon Sep 17 00:00:00 2001 From: rigel rozanski Date: Wed, 19 Jul 2017 04:56:44 -0400 Subject: [PATCH 12/20] final PR update --- modules/nonce/commands/query.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index 2570c88d9..1ce287618 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -28,7 +28,6 @@ func nonceQueryCmd(cmd *cobra.Command, args []string) error { } addr := strings.Join(args, ",") - var signers []basecoin.Actor signers, err := parseActors(addr) if err != nil { return err From d712d6ffd1b61d9ba5584bbcd6aa094f1ca6305a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 12:22:01 +0200 Subject: [PATCH 13/20] Fixed imports in main.go --- cmd/basecli/main.go | 4 ++-- docs/guide/counter/cmd/countercli/main.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index b827f7ffb..cf9e95f9f 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -51,8 +51,8 @@ func main() { // Prepare queries proofs.RootCmd.AddCommand( // These are default parsers, but optional in your app (you can remove key) - proofs.TxCmd, - proofs.KeyCmd, + proofs.TxQueryCmd, + proofs.KeyQueryCmd, coincmd.AccountQueryCmd, noncecmd.NonceQueryCmd, ) diff --git a/docs/guide/counter/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go index 8cc95f667..6e930cd7d 100644 --- a/docs/guide/counter/cmd/countercli/main.go +++ b/docs/guide/counter/cmd/countercli/main.go @@ -39,8 +39,8 @@ func main() { // Prepare queries proofs.RootCmd.AddCommand( // These are default parsers, optional in your app - proofs.TxCmd, - proofs.KeyCmd, + proofs.TxQueryCmd, + proofs.KeyQueryCmd, coincmd.AccountQueryCmd, noncecmd.NonceQueryCmd, From e7da4c2d3a966da77fcd43fe03d43c1f464915cd Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 13:22:53 +0200 Subject: [PATCH 14/20] Add support for --prepare to store tx for multisig --- client/commands/txs/helpers.go | 143 +++++++++++++----- client/commands/txs/presenter.go | 15 -- client/commands/txs/root.go | 36 ++--- .../cmd/countercli/commands/counter.go | 14 +- modules/coin/commands/tx.go | 22 ++- 5 files changed, 134 insertions(+), 96 deletions(-) diff --git a/client/commands/txs/helpers.go b/client/commands/txs/helpers.go index d58ea342f..3ac1617cd 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 79f718a4f..2886e7bd2 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 b9e9ea3ac..e2b88a6e6 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 a8e473276..a3770c5aa 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 c817c57b6..b651b29e0 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) { From a12d866ed1c57fa74240faf3546a72d201874ffe Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 13:27:29 +0200 Subject: [PATCH 15/20] --sequence=-1 broken if no sequence yet --- tests/cli/basictx.sh | 2 +- tests/cli/counter.sh | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/cli/basictx.sh b/tests/cli/basictx.sh index 640d6ba0b..d6ab7ea9e 100755 --- a/tests/cli/basictx.sh +++ b/tests/cli/basictx.sh @@ -54,7 +54,7 @@ test02SendTxWithFee() { RECV=$(getAddr $POOR) # Test to see if the auto-sequencing works, the sequence here should be calculated to be 2 - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=-1 --to=$RECV --name=$RICH) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --to=$RECV --name=$RICH) txSucceeded $? "$TX" "$RECV" HASH=$(echo $TX | jq .hash | tr -d \") TX_HEIGHT=$(echo $TX | jq .height) diff --git a/tests/cli/counter.sh b/tests/cli/counter.sh index 7e17fc4f5..f57827a96 100755 --- a/tests/cli/counter.sh +++ b/tests/cli/counter.sh @@ -33,9 +33,10 @@ test01SendTx() { SENDER=$(getAddr $RICH) RECV=$(getAddr $POOR) - assertFalse "Line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 2>/dev/null" - assertFalse "Line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null" - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH) + # sequence should work well for first time also + assertFalse "Line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin 2>/dev/null" + assertFalse "Line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --to=$RECV --name=$RICH 2>/dev/null" + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --to=$RECV --name=$RICH) txSucceeded $? "$TX" "$RECV" HASH=$(echo $TX | jq .hash | tr -d \") TX_HEIGHT=$(echo $TX | jq .height) From 737e3740ddad8ba7a0de4db5dc8d0da7ddfdd4a5 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 13:32:55 +0200 Subject: [PATCH 16/20] rigel: Handle auto-sequence when nonce=0 --- modules/nonce/commands/query.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index 1ce287618..e92a8d596 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -47,7 +47,8 @@ func doNonceQuery(signers []basecoin.Actor) (sequence uint32, proof lc.Proof, er proof, err = proofcmd.GetAndParseAppProof(key, &sequence) if lc.IsNoDataErr(err) { - err = errors.Errorf("Sequence is empty for key %s ", key) + // no data, return sequence 0 + return 0, proof, nil } return From 911dd1423e165c600dfe51663a368d576fa5dec8 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 14:25:17 +0200 Subject: [PATCH 17/20] Add roles wrapper/tx/query command to basecoin --- app/app.go | 10 ++- client/commands/common.go | 18 ++++- client/commands/txs/helpers.go | 31 +++++++++ cmd/basecli/main.go | 2 + .../cmd/countercli/commands/counter.go | 22 +------ modules/coin/commands/tx.go | 25 +------ modules/fee/commands/wrap.go | 2 +- modules/nonce/commands/query.go | 6 +- modules/nonce/commands/wrap.go | 15 +---- modules/roles/commands/query.go | 40 ++++++++++++ modules/roles/commands/tx.go | 65 +++++++++++++++++++ modules/roles/commands/wrap.go | 48 ++++++++++++++ 12 files changed, 219 insertions(+), 65 deletions(-) create mode 100644 modules/roles/commands/query.go create mode 100644 modules/roles/commands/tx.go create mode 100644 modules/roles/commands/wrap.go diff --git a/app/app.go b/app/app.go index bdcdbbbd8..c750a7a3a 100644 --- a/app/app.go +++ b/app/app.go @@ -16,6 +16,7 @@ import ( "github.com/tendermint/basecoin/modules/coin" "github.com/tendermint/basecoin/modules/fee" "github.com/tendermint/basecoin/modules/nonce" + "github.com/tendermint/basecoin/modules/roles" "github.com/tendermint/basecoin/stack" sm "github.com/tendermint/basecoin/state" "github.com/tendermint/basecoin/version" @@ -56,14 +57,19 @@ func NewBasecoin(handler basecoin.Handler, eyesCli *eyes.Client, logger log.Logg // DefaultHandler - placeholder to just handle sendtx func DefaultHandler(feeDenom string) basecoin.Handler { // use the default stack - h := coin.NewHandler() - d := stack.NewDispatcher(stack.WrapHandler(h)) + c := coin.NewHandler() + r := roles.NewHandler() + d := stack.NewDispatcher( + stack.WrapHandler(c), + stack.WrapHandler(r), + ) return stack.New( base.Logger{}, stack.Recovery{}, auth.Signatures{}, base.Chain{}, nonce.ReplayCheck{}, + roles.NewMiddleware(), fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank), ).Use(d) } diff --git a/client/commands/common.go b/client/commands/common.go index f14a11f38..b6781bc3f 100644 --- a/client/commands/common.go +++ b/client/commands/common.go @@ -83,11 +83,11 @@ func GetCertifier() (*certifiers.InquiringCertifier, error) { return cert, nil } -// ParseAddress parses an address of form: +// ParseActor parses an address of form: // [:][:] // into a basecoin.Actor. // If app is not specified or "", then assume auth.NameSigs -func ParseAddress(input string) (res basecoin.Actor, err error) { +func ParseActor(input string) (res basecoin.Actor, err error) { chain, app := "", auth.NameSigs input = strings.TrimSpace(input) spl := strings.SplitN(input, ":", 3) @@ -114,3 +114,17 @@ func ParseAddress(input string) (res basecoin.Actor, err error) { } return } + +// ParseActors takes a comma-separated list of actors and parses them into +// a slice +func ParseActors(key string) (signers []basecoin.Actor, err error) { + var act basecoin.Actor + for _, k := range strings.Split(key, ",") { + act, err = ParseActor(k) + if err != nil { + return + } + signers = append(signers, act) + } + return +} diff --git a/client/commands/txs/helpers.go b/client/commands/txs/helpers.go index 3ac1617cd..68e53915f 100644 --- a/client/commands/txs/helpers.go +++ b/client/commands/txs/helpers.go @@ -52,6 +52,37 @@ func GetSignerAct() (res basecoin.Actor) { return res } +// DoTx is a helper function for the lazy :) +// +// It uses only public functions and goes through the standard sequence of +// wrapping the tx with middleware layers, signing it, either preparing it, +// or posting it and displaying the result. +// +// If you want a non-standard flow, just call the various functions directly. +// eg. if you already set the middleware layers in your code, or want to +// output in another format. +func DoTx(tx basecoin.Tx) (err error) { + tx, err = Middleware.Wrap(tx) + if err != nil { + return err + } + + err = SignTx(tx) + if err != nil { + return err + } + + bres, err := PrepareOrPostTx(tx) + if err != nil { + return err + } + if bres == nil { + return nil // successful prep, nothing left to do + } + return OutputTx(bres) // print response of the post + +} + // 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 { diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index cf9e95f9f..0a3e8e997 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -22,6 +22,7 @@ import ( coincmd "github.com/tendermint/basecoin/modules/coin/commands" feecmd "github.com/tendermint/basecoin/modules/fee/commands" noncecmd "github.com/tendermint/basecoin/modules/nonce/commands" + rolecmd "github.com/tendermint/basecoin/modules/roles/commands" ) // BaseCli - main basecoin client command @@ -61,6 +62,7 @@ func main() { // set up the middleware txcmd.Middleware = txcmd.Wrappers{ feecmd.FeeWrapper{}, + rolecmd.RoleWrapper{}, noncecmd.NonceWrapper{}, basecmd.ChainWrapper{}, authcmd.SigWrapper{}, diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index a3770c5aa..cf4dbfe28 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -33,32 +33,12 @@ func init() { fs.Bool(FlagValid, false, "Is count valid?") } -// TODO: counterTx is very similar to the sendtx one, -// maybe we can pull out some common patterns? func counterTx(cmd *cobra.Command, args []string) error { tx, err := readCounterTxFlags() if err != nil { return err } - - tx, err = txcmd.Middleware.Wrap(tx) - if err != nil { - return err - } - - err = txcmd.SignTx(tx) - if err != nil { - return err - } - - bres, err := txcmd.PrepareOrPostTx(tx) - if err != nil { - return err - } - if bres == nil { - return nil // successful prep, nothing left to do - } - return txcmd.OutputTx(bres) // print response of the post + return txcmd.DoTx(tx) } func readCounterTxFlags() (tx basecoin.Tx, err error) { diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index b651b29e0..cff4a51ac 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -37,31 +37,12 @@ func sendTxCmd(cmd *cobra.Command, args []string) error { if err != nil { return err } - - tx, err = txcmd.Middleware.Wrap(tx) - if err != nil { - return err - } - - err = txcmd.SignTx(tx) - if err != nil { - return err - } - - // otherwise, post it and display response - bres, err := txcmd.PrepareOrPostTx(tx) - if err != nil { - return err - } - if bres == nil { - return nil // successful prep, nothing left to do - } - return txcmd.OutputTx(bres) // print response of the post + return txcmd.DoTx(tx) } func readSendTxFlags() (tx basecoin.Tx, err error) { // parse to address - toAddr, err := commands.ParseAddress(viper.GetString(FlagTo)) + toAddr, err := commands.ParseActor(viper.GetString(FlagTo)) if err != nil { return tx, err } @@ -94,5 +75,5 @@ func readFromAddr() (basecoin.Actor, error) { if from == "" { return txcmd.GetSignerAct(), nil } - return commands.ParseAddress(from) + return commands.ParseActor(from) } diff --git a/modules/fee/commands/wrap.go b/modules/fee/commands/wrap.go index 6f8c082f8..32fb98483 100644 --- a/modules/fee/commands/wrap.go +++ b/modules/fee/commands/wrap.go @@ -55,5 +55,5 @@ func readPayer() (basecoin.Actor, error) { if payer == "" { return txcmd.GetSignerAct(), nil } - return commands.ParseAddress(payer) + return commands.ParseActor(payer) } diff --git a/modules/nonce/commands/query.go b/modules/nonce/commands/query.go index e92a8d596..405b0ea94 100644 --- a/modules/nonce/commands/query.go +++ b/modules/nonce/commands/query.go @@ -9,7 +9,7 @@ import ( lc "github.com/tendermint/light-client" "github.com/tendermint/basecoin" - lcmd "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/client/commands" proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/modules/nonce" "github.com/tendermint/basecoin/stack" @@ -19,7 +19,7 @@ import ( var NonceQueryCmd = &cobra.Command{ Use: "nonce [address]", Short: "Get details of a nonce sequence number, with proof", - RunE: lcmd.RequireInit(nonceQueryCmd), + RunE: commands.RequireInit(nonceQueryCmd), } func nonceQueryCmd(cmd *cobra.Command, args []string) error { @@ -28,7 +28,7 @@ func nonceQueryCmd(cmd *cobra.Command, args []string) error { } addr := strings.Join(args, ",") - signers, err := parseActors(addr) + signers, err := commands.ParseActors(addr) if err != nil { return err } diff --git a/modules/nonce/commands/wrap.go b/modules/nonce/commands/wrap.go index ea42fe483..508294665 100644 --- a/modules/nonce/commands/wrap.go +++ b/modules/nonce/commands/wrap.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "strings" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -54,19 +53,7 @@ func readNonceKey() ([]basecoin.Actor, error) { if nonce == "" { return []basecoin.Actor{txcmd.GetSignerAct()}, nil } - return parseActors(nonce) -} - -func parseActors(key string) (signers []basecoin.Actor, err error) { - var act basecoin.Actor - for _, k := range strings.Split(key, ",") { - act, err = commands.ParseAddress(k) - if err != nil { - return - } - signers = append(signers, act) - } - return + return commands.ParseActors(nonce) } // read the sequence from the flag or query for it if flag is -1 diff --git a/modules/roles/commands/query.go b/modules/roles/commands/query.go new file mode 100644 index 000000000..d7c97a8e2 --- /dev/null +++ b/modules/roles/commands/query.go @@ -0,0 +1,40 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + lcmd "github.com/tendermint/basecoin/client/commands" + proofcmd "github.com/tendermint/basecoin/client/commands/proofs" + "github.com/tendermint/basecoin/modules/roles" + "github.com/tendermint/basecoin/stack" +) + +// RoleQueryCmd - command to query a role +var RoleQueryCmd = &cobra.Command{ + Use: "role [name]", + Short: "Get details of a role, with proof", + RunE: lcmd.RequireInit(roleQueryCmd), +} + +func roleQueryCmd(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("Missing required argument [name]") + } else if len(args) > 1 { + return errors.New("Command only supports one name") + } + + role, err := parseRole(args[0]) + if err != nil { + return err + } + + var res roles.Role + key := stack.PrefixedKey(roles.NameRole, role) + proof, err := proofcmd.GetAndParseAppProof(key, &res) + if err != nil { + return err + } + + return proofcmd.OutputProof(res, proof.BlockHeight()) +} diff --git a/modules/roles/commands/tx.go b/modules/roles/commands/tx.go new file mode 100644 index 000000000..b9c39043c --- /dev/null +++ b/modules/roles/commands/tx.go @@ -0,0 +1,65 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/client/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" + "github.com/tendermint/basecoin/modules/roles" +) + +// CreateRoleTxCmd is CLI command to send tokens between basecoin accounts +var CreateRoleTxCmd = &cobra.Command{ + Use: "send", + Short: "send tokens from one account to another", + RunE: commands.RequireInit(createRoleTxCmd), +} + +//nolint +const ( + FlagRole = "role" + FlagMembers = "members" + FlagMinSigs = "min-sigs" +) + +func init() { + flags := CreateRoleTxCmd.Flags() + flags.String(FlagRole, "", "Name of the role to create") + flags.String(FlagMembers, "", "Set of comma-separated addresses for this role") + flags.Int(FlagMinSigs, 0, "Minimum number of signatures needed to assume this role") +} + +// createRoleTxCmd is an example of how to make a tx +func createRoleTxCmd(cmd *cobra.Command, args []string) error { + tx, err := readCreateRoleTxFlags() + if err != nil { + return err + } + return txcmd.DoTx(tx) +} + +func readCreateRoleTxFlags() (tx basecoin.Tx, err error) { + role, err := parseRole(viper.GetString(FlagRole)) + if err != nil { + return tx, err + } + + sigs := viper.GetInt(FlagMinSigs) + if sigs < 1 { + return tx, errors.Errorf("--%s must be at least 1", FlagMinSigs) + } + + signers, err := commands.ParseActors(viper.GetString(FlagMembers)) + if err != nil { + return tx, err + } + if len(signers) == 0 { + return tx, errors.New("must specify at least one member") + } + + tx = roles.NewCreateRoleTx(role, uint32(sigs), signers) + return tx, nil +} diff --git a/modules/roles/commands/wrap.go b/modules/roles/commands/wrap.go new file mode 100644 index 000000000..48d85a2e7 --- /dev/null +++ b/modules/roles/commands/wrap.go @@ -0,0 +1,48 @@ +package commands + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin" + txcmd "github.com/tendermint/basecoin/client/commands/txs" + "github.com/tendermint/basecoin/modules/roles" +) + +// nolint +const ( + FlagAssumeRole = "assume-role" +) + +// RoleWrapper wraps a tx with 0, 1, or more roles +type RoleWrapper struct{} + +var _ txcmd.Wrapper = RoleWrapper{} + +// Wrap grabs the sequence number from the flag and wraps +// the tx with this nonce. Grabs the permission from the signer, +// as we still only support single sig on the cli +func (RoleWrapper) Wrap(tx basecoin.Tx) (basecoin.Tx, error) { + assume := viper.GetStringSlice(FlagAssumeRole) + + // we wrap from inside-out, so we must wrap them in the reverse order, + // so they are applied in the order the user intended + for i := len(assume) - 1; i >= 0; i-- { + r, err := parseRole(assume[i]) + if err != nil { + return tx, err + } + tx = roles.NewAssumeRoleTx(r, tx) + } + return tx, nil +} + +// Register adds the sequence flags to the cli +func (RoleWrapper) Register(fs *pflag.FlagSet) { + fs.StringSlice(FlagAssumeRole, nil, "Roles to assume (can use multiple times)") +} + +// parse role turns the string->byte... todo: support hex? +func parseRole(role string) ([]byte, error) { + return []byte(role), nil +} From 63fc25e74e00133cde24b4067a4135221c7d1892 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 16:14:26 +0200 Subject: [PATCH 18/20] Add roles cli test, coin query supports multiple apps in actor --- client/commands/common.go | 11 ++++ client/commands/version.go | 17 ++++++ cmd/basecli/main.go | 16 ++---- modules/coin/commands/query.go | 13 +++-- modules/roles/commands/tx.go | 4 +- tests/cli/common.sh | 47 +++++++++++++++++ tests/cli/roles.sh | 95 ++++++++++++++++++++++++++++++++++ 7 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 client/commands/version.go create mode 100755 tests/cli/roles.sh diff --git a/client/commands/common.go b/client/commands/common.go index b6781bc3f..a7ea8da7d 100644 --- a/client/commands/common.go +++ b/client/commands/common.go @@ -128,3 +128,14 @@ func ParseActors(key string) (signers []basecoin.Actor, err error) { } return } + +// GetOneArg makes sure there is exactly one positional argument +func GetOneArg(args []string, argname string) (string, error) { + if len(args) == 0 { + return "", errors.Errorf("Missing required argument [%s]", argname) + } + if len(args) > 1 { + return "", errors.Errorf("Only accepts one argument [%s]", argname) + } + return args[0], nil +} diff --git a/client/commands/version.go b/client/commands/version.go new file mode 100644 index 000000000..9ffa6b36d --- /dev/null +++ b/client/commands/version.go @@ -0,0 +1,17 @@ +package commands + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/tendermint/basecoin/version" +) + +// VersionCmd - command to show the application version +var VersionCmd = &cobra.Command{ + Use: "version", + Short: "Show version info", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.Version) + }, +} diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 0a3e8e997..02335e390 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -1,12 +1,10 @@ package main import ( - "fmt" "os" "github.com/spf13/cobra" - "github.com/tendermint/abci/version" keycmd "github.com/tendermint/go-crypto/cmd" "github.com/tendermint/tmlibs/cli" @@ -37,15 +35,6 @@ tmcli to work for any custom abci app. `, } -// VersionCmd - command to show the application version -var VersionCmd = &cobra.Command{ - Use: "version", - Short: "Show version info", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(version.Version) - }, -} - func main() { commands.AddBasicFlags(BaseCli) @@ -56,6 +45,7 @@ func main() { proofs.KeyQueryCmd, coincmd.AccountQueryCmd, noncecmd.NonceQueryCmd, + rolecmd.RoleQueryCmd, ) proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{}) @@ -73,6 +63,8 @@ func main() { txcmd.RootCmd.AddCommand( // This is the default transaction, optional in your app coincmd.SendTxCmd, + // this enables creating roles + rolecmd.CreateRoleTxCmd, ) // Set up the various commands to use @@ -85,7 +77,7 @@ func main() { proofs.RootCmd, txcmd.RootCmd, proxy.RootCmd, - VersionCmd, + commands.VersionCmd, auto.AutoCompleteCmd, ) diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go index 5bea51478..7a7d4805a 100644 --- a/modules/coin/commands/query.go +++ b/modules/coin/commands/query.go @@ -6,9 +6,8 @@ import ( lc "github.com/tendermint/light-client" - lcmd "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/client/commands" proofcmd "github.com/tendermint/basecoin/client/commands/proofs" - "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/coin" "github.com/tendermint/basecoin/stack" ) @@ -17,15 +16,19 @@ import ( var AccountQueryCmd = &cobra.Command{ Use: "account [address]", Short: "Get details of an account, with proof", - RunE: lcmd.RequireInit(accountQueryCmd), + RunE: commands.RequireInit(accountQueryCmd), } func accountQueryCmd(cmd *cobra.Command, args []string) error { - addr, err := proofcmd.ParseHexKey(args, "address") + addr, err := commands.GetOneArg(args, "address") if err != nil { return err } - key := stack.PrefixedKey(coin.NameCoin, auth.SigPerm(addr).Bytes()) + act, err := commands.ParseActor(addr) + if err != nil { + return err + } + key := stack.PrefixedKey(coin.NameCoin, act.Bytes()) acc := coin.Account{} proof, err := proofcmd.GetAndParseAppProof(key, &acc) diff --git a/modules/roles/commands/tx.go b/modules/roles/commands/tx.go index b9c39043c..710413142 100644 --- a/modules/roles/commands/tx.go +++ b/modules/roles/commands/tx.go @@ -13,8 +13,8 @@ import ( // CreateRoleTxCmd is CLI command to send tokens between basecoin accounts var CreateRoleTxCmd = &cobra.Command{ - Use: "send", - Short: "send tokens from one account to another", + Use: "create-role", + Short: "Create a new role", RunE: commands.RequireInit(createRoleTxCmd), } diff --git a/tests/cli/common.sh b/tests/cli/common.sh index c68a80c56..f50ef6b9d 100644 --- a/tests/cli/common.sh +++ b/tests/cli/common.sh @@ -138,6 +138,23 @@ checkAccount() { return $? } +# XXX Ex Usage: checkRole $ROLE $SIGS $NUM_SIGNERS +# Desc: Ensures this named role exists, and has the number of members and required signatures as above +checkRole() { + # make sure sender goes down + ROLE=$(${CLIENT_EXE} query role $1) + if ! assertTrue "line=${LINENO}, role must exist" $?; then + return 1 + fi + + if [ -n "$DEBUG" ]; then echo $ROLE; echo; fi + assertEquals "line=${LINENO}, proper sigs" "$2" $(echo $ROLE | jq .data.min_sigs) + assertEquals "line=${LINENO}, proper app" '"sigs"' $(echo $ROLE | jq '.data.signers[0].app' ) + assertEquals "line=${LINENO}, proper signers" "$3" $(echo $ROLE | jq '.data.signers | length') + return $? +} + + # XXX Ex Usage: txSucceeded $? "$TX" "$RECIEVER" # Desc: Must be called right after the `tx` command, makes sure it got a success response txSucceeded() { @@ -171,6 +188,36 @@ checkSendTx() { return $? } +# XXX Ex Usage: toHex "my-name" +# converts the string into the hex representation of the bytes +toHex() { + echo -n $1 | od -A n -t x1 | sed 's/ //g' | tr 'a-f' 'A-F' +} + +# XXX Ex Usage: checkRoleTx $HASH $HEIGHT $NAME $NUM_SIGNERS +# Desc: This looks up the tx by hash, and makes sure the height and type match +# and that the it refers to the proper role +checkRoleTx() { + TX=$(${CLIENT_EXE} query tx $1) + assertTrue "line=${LINENO}, found tx" $? + if [ -n "$DEBUG" ]; then echo $TX; echo; fi + + + assertEquals "line=${LINENO}, proper height" $2 $(echo $TX | jq .height) + assertEquals "line=${LINENO}, type=sigs/one" '"sigs/one"' $(echo $TX | jq .data.type) + CTX=$(echo $TX | jq .data.data.tx) + assertEquals "line=${LINENO}, type=chain/tx" '"chain/tx"' $(echo $CTX | jq .type) + NTX=$(echo $CTX | jq .data.tx) + assertEquals "line=${LINENO}, type=nonce" '"nonce"' $(echo $NTX | jq .type) + RTX=$(echo $NTX | jq .data.tx) + assertEquals "line=${LINENO}, type=role/create" '"role/create"' $(echo $RTX | jq .type) + HEXNAME=$(toHex $3) + assertEquals "line=${LINENO}, proper name" "\"$HEXNAME\"" $(echo $RTX | jq .data.role) + assertEquals "line=${LINENO}, proper num signers" "$4" $(echo $RTX | jq '.data.signers | length') + return $? +} + + # XXX Ex Usage: checkSendFeeTx $HASH $HEIGHT $SENDER $AMOUNT $FEE # Desc: This is like checkSendTx, but asserts a feetx wrapper with $FEE value. # This looks up the tx by hash, and makes sure the height and type match diff --git a/tests/cli/roles.sh b/tests/cli/roles.sh new file mode 100755 index 000000000..0932ab7f9 --- /dev/null +++ b/tests/cli/roles.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# These global variables are required for common.sh +SERVER_EXE=basecoin +CLIENT_EXE=basecli +ACCOUNTS=(jae ethan bucky rigel igor) +RICH=${ACCOUNTS[0]} +POOR=${ACCOUNTS[4]} +DUDE=${ACCOUNTS[2]} + +oneTimeSetUp() { + if ! quickSetup .basecoin_test_roles roles-chain; then + exit 1; + fi +} + +oneTimeTearDown() { + quickTearDown +} + +test01SetupRole() { + ONE=$(getAddr $RICH) + TWO=$(getAddr $POOR) + THREE=$(getAddr $DUDE) + MEMBERS=${ONE},${TWO},${THREE} + + assertFalse "line=${LINENO}, missing min-sigs" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --members=${MEMBERS} --sequence=1 --name=$RICH" + assertFalse "line=${LINENO}, missing members" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=2 --sequence=1 --name=$RICH" + assertFalse "line=${LINENO}, missing role" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --min-sigs=2 --members=${MEMBERS} --sequence=1 --name=$RICH" + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=2 --members=${MEMBERS} --sequence=1 --name=$RICH) + txSucceeded $? "$TX" "bank" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + checkRole bank 2 3 + + # Make sure tx is indexed + checkRoleTx $HASH $TX_HEIGHT "bank" 3 +} + +test02SendTxToRole() { + SENDER=$(getAddr $RICH) + HEXROLE=$(toHex bank) + RECV="role:$HEXROLE" + + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --fee=90mycoin --amount=10000mycoin --to=$RECV --sequence=2 --name=$RICH) + txSucceeded $? "$TX" "bank" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + # reduce by 10090 + checkAccount $SENDER "9007199254730902" + checkAccount $RECV "10000" + + checkSendFeeTx $HASH $TX_HEIGHT $SENDER "10000" "90" +} + +# test02SendTxWithFee() { +# SENDER=$(getAddr $RICH) +# RECV=$(getAddr $POOR) + +# # Test to see if the auto-sequencing works, the sequence here should be calculated to be 2 +# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --to=$RECV --name=$RICH) +# txSucceeded $? "$TX" "$RECV" +# HASH=$(echo $TX | jq .hash | tr -d \") +# TX_HEIGHT=$(echo $TX | jq .height) + +# # deduct 100 from sender, add 90 to receiver... fees "vanish" +# checkAccount $SENDER "9007199254739900" +# checkAccount $RECV "1082" + +# # Make sure tx is indexed +# checkSendFeeTx $HASH $TX_HEIGHT $SENDER "90" "10" + +# # assert replay protection +# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH 2>/dev/null) +# assertFalse "line=${LINENO}, replay: $TX" $? +# checkAccount $SENDER "9007199254739900" +# checkAccount $RECV "1082" + +# # make sure we can query the proper nonce +# NONCE=$(${CLIENT_EXE} query nonce $SENDER) +# if [ -n "$DEBUG" ]; then echo $NONCE; echo; fi +# # TODO: note that cobra returns error code 0 on parse failure, +# # so currently this check passes even if there is no nonce query command +# if assertTrue "line=${LINENO}, no nonce query" $?; then +# assertEquals "line=${LINENO}, proper nonce" "2" $(echo $NONCE | jq .data) +# fi +# } + + +# Load common then run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory +. $DIR/common.sh +. $DIR/shunit2 From 5f1d98ba0f5efd4a256a96f9b2ddec58fbd0085f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 16:36:35 +0200 Subject: [PATCH 19/20] Tested sending 1 sig from role --- Makefile | 1 + app/app.go | 7 +++++-- tests/cli/roles.sh | 32 ++++++++++++++++++++++++++++---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 66e51f817..0d3eac4ce 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ test_cli: tests/cli/shunit2 ./tests/cli/rpc.sh ./tests/cli/init.sh ./tests/cli/basictx.sh + ./tests/cli/roles.sh ./tests/cli/counter.sh ./tests/cli/restart.sh # @./tests/cli/ibc.sh diff --git a/app/app.go b/app/app.go index c750a7a3a..378b36e0b 100644 --- a/app/app.go +++ b/app/app.go @@ -145,7 +145,9 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { return errors.Result(err) } - // TODO: can we abstract this setup and commit logic?? + // we also need to discard error changes, so we don't increment checktx + // sequence on error, but not delivertx + cache := app.cacheState.CacheWrap() ctx := stack.NewContext( app.state.GetChainID(), app.height, @@ -153,11 +155,12 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { ) // checktx generally shouldn't touch the state, but we don't care // here on the framework level, since the cacheState is thrown away next block - res, err := app.handler.CheckTx(ctx, app.cacheState, tx) + res, err := app.handler.CheckTx(ctx, cache, tx) if err != nil { return errors.Result(err) } + cache.CacheSync() return res.ToABCI() } diff --git a/tests/cli/roles.sh b/tests/cli/roles.sh index 0932ab7f9..e78f0d90d 100755 --- a/tests/cli/roles.sh +++ b/tests/cli/roles.sh @@ -24,15 +24,17 @@ test01SetupRole() { THREE=$(getAddr $DUDE) MEMBERS=${ONE},${TWO},${THREE} + SIGS=1 + assertFalse "line=${LINENO}, missing min-sigs" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --members=${MEMBERS} --sequence=1 --name=$RICH" assertFalse "line=${LINENO}, missing members" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=2 --sequence=1 --name=$RICH" assertFalse "line=${LINENO}, missing role" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --min-sigs=2 --members=${MEMBERS} --sequence=1 --name=$RICH" - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=2 --members=${MEMBERS} --sequence=1 --name=$RICH) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=$SIGS --members=${MEMBERS} --sequence=1 --name=$RICH) txSucceeded $? "$TX" "bank" HASH=$(echo $TX | jq .hash | tr -d \") TX_HEIGHT=$(echo $TX | jq .height) - checkRole bank 2 3 + checkRole bank $SIGS 3 # Make sure tx is indexed checkRoleTx $HASH $TX_HEIGHT "bank" 3 @@ -40,8 +42,10 @@ test01SetupRole() { test02SendTxToRole() { SENDER=$(getAddr $RICH) - HEXROLE=$(toHex bank) - RECV="role:$HEXROLE" + RECV=role:$(toHex bank) + + # HEXROLE=$(toHex bank) + # RECV="role:$HEXROLE" TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --fee=90mycoin --amount=10000mycoin --to=$RECV --sequence=2 --name=$RICH) txSucceeded $? "$TX" "bank" @@ -55,6 +59,26 @@ test02SendTxToRole() { checkSendFeeTx $HASH $TX_HEIGHT $SENDER "10000" "90" } +test03SendMultiFromRole() { + ONE=$(getAddr $RICH) + TWO=$(getAddr $POOR) + THREE=$(getAddr $DUDE) + BANK=role:$(toHex bank) + + # no money to start mr. poor... + assertFalse "line=${LINENO}, has no money yet" "${CLIENT_EXE} query account $TWO 2>/dev/null" + + # let's try to send money from the role directly without multisig + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --name=$POOR 2>/dev/null) + assertFalse "need to assume role" $? + # echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR --prepare=- + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR) + txSucceeded $? "$TX" "from-bank" + + checkAccount $TWO "6000" + checkAccount $BANK "4000" +} + # test02SendTxWithFee() { # SENDER=$(getAddr $RICH) # RECV=$(getAddr $POOR) From 44f6696f112d30f126efc8c79096d804ec732b08 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 16:45:03 +0200 Subject: [PATCH 20/20] Proper tests for accessing a multi-sig account --- tests/cli/roles.sh | 56 +++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/tests/cli/roles.sh b/tests/cli/roles.sh index e78f0d90d..5457090b9 100755 --- a/tests/cli/roles.sh +++ b/tests/cli/roles.sh @@ -24,7 +24,7 @@ test01SetupRole() { THREE=$(getAddr $DUDE) MEMBERS=${ONE},${TWO},${THREE} - SIGS=1 + SIGS=2 assertFalse "line=${LINENO}, missing min-sigs" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --members=${MEMBERS} --sequence=1 --name=$RICH" assertFalse "line=${LINENO}, missing members" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=2 --sequence=1 --name=$RICH" @@ -44,9 +44,6 @@ test02SendTxToRole() { SENDER=$(getAddr $RICH) RECV=role:$(toHex bank) - # HEXROLE=$(toHex bank) - # RECV="role:$HEXROLE" - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --fee=90mycoin --amount=10000mycoin --to=$RECV --sequence=2 --name=$RICH) txSucceeded $? "$TX" "bank" HASH=$(echo $TX | jq .hash | tr -d \") @@ -69,49 +66,26 @@ test03SendMultiFromRole() { assertFalse "line=${LINENO}, has no money yet" "${CLIENT_EXE} query account $TWO 2>/dev/null" # let's try to send money from the role directly without multisig - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --name=$POOR 2>/dev/null) + FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --name=$POOR 2>/dev/null) assertFalse "need to assume role" $? - # echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR --prepare=- - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR) - txSucceeded $? "$TX" "from-bank" + FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR 2>/dev/null) + assertFalse "need two signatures" $? + + # okay, begin a multisig transaction mr. poor... + TX_FILE=$BASE_DIR/tx.json + echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR --multi --prepare=$TX_FILE + assertTrue "line=${LINENO}, successfully prepare tx" $? + # and get some dude to sign it + FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$POOR 2>/dev/null) + assertFalse "line=${LINENO}, double signing doesn't get bank" $? + # and get some dude to sign it for the full access + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$DUDE) + txSucceeded $? "$TX" "multi-bank" checkAccount $TWO "6000" checkAccount $BANK "4000" } -# test02SendTxWithFee() { -# SENDER=$(getAddr $RICH) -# RECV=$(getAddr $POOR) - -# # Test to see if the auto-sequencing works, the sequence here should be calculated to be 2 -# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --to=$RECV --name=$RICH) -# txSucceeded $? "$TX" "$RECV" -# HASH=$(echo $TX | jq .hash | tr -d \") -# TX_HEIGHT=$(echo $TX | jq .height) - -# # deduct 100 from sender, add 90 to receiver... fees "vanish" -# checkAccount $SENDER "9007199254739900" -# checkAccount $RECV "1082" - -# # Make sure tx is indexed -# checkSendFeeTx $HASH $TX_HEIGHT $SENDER "90" "10" - -# # assert replay protection -# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH 2>/dev/null) -# assertFalse "line=${LINENO}, replay: $TX" $? -# checkAccount $SENDER "9007199254739900" -# checkAccount $RECV "1082" - -# # make sure we can query the proper nonce -# NONCE=$(${CLIENT_EXE} query nonce $SENDER) -# if [ -n "$DEBUG" ]; then echo $NONCE; echo; fi -# # TODO: note that cobra returns error code 0 on parse failure, -# # so currently this check passes even if there is no nonce query command -# if assertTrue "line=${LINENO}, no nonce query" $?; then -# assertEquals "line=${LINENO}, proper nonce" "2" $(echo $NONCE | jq .data) -# fi -# } - # Load common then run these tests with shunit2! DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory