Skip to content

Commit

Permalink
Merge pull request #497 from cosmos/feature/sdk2-gaia-cli
Browse files Browse the repository at this point in the history
REVIEW: Full-fledged gaia cli
  • Loading branch information
ebuchman committed Mar 1, 2018
2 parents 2a3990d + 889551d commit 5494a4e
Show file tree
Hide file tree
Showing 56 changed files with 2,488 additions and 454 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ gaia:

build:
@rm -rf examples/basecoin/vendor/
go build $(BUILD_FLAGS) -o build/basecoind ./examples/basecoin/cmd/basecoind/...
go build $(BUILD_FLAGS) -o build/basecoind ./examples/basecoin/cmd/basecoind
go build $(BUILD_FLAGS) -o build/basecli ./examples/basecoin/cmd/basecli

dist:
@bash publish/dist.sh
Expand Down
20 changes: 11 additions & 9 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var dbHeaderKey = []byte("header")
// The ABCI application
type BaseApp struct {
// initialized on creation
logger log.Logger
Logger log.Logger
name string // application name from abci.Info
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
Expand Down Expand Up @@ -56,7 +56,7 @@ var _ abci.Application = (*BaseApp)(nil)
// Create and name new BaseApp
func NewBaseApp(name string, logger log.Logger, db dbm.DB) *BaseApp {
return &BaseApp{
logger: logger,
Logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
Expand Down Expand Up @@ -361,12 +361,14 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
}

// Run the ante handler.
newCtx, result, abort := app.anteHandler(ctx, tx)
if abort {
return result
}
if !newCtx.IsZero() {
ctx = newCtx
if app.anteHandler != nil {
newCtx, result, abort := app.anteHandler(ctx, tx)
if abort {
return result
}
if !newCtx.IsZero() {
ctx = newCtx
}
}

// Get the correct cache
Expand Down Expand Up @@ -420,7 +422,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
// Write the Deliver state and commit the MultiStore
app.deliverState.ms.Write()
commitID := app.cms.Commit()
app.logger.Debug("Commit synced",
app.Logger.Debug("Commit synced",
"commit", commitID,
)

Expand Down
38 changes: 38 additions & 0 deletions client/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package client

import "github.com/spf13/cobra"

// nolint
const (
FlagChainID = "chain-id"
FlagNode = "node"
FlagHeight = "height"
FlagTrustNode = "trust-node"
FlagName = "name"
)

// LineBreak can be included in a command list to provide a blank line
// to help with readability
var LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}

// GetCommands adds common flags to query commands
func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
// TODO: make this default false when we support proofs
c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for responses")
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
c.Flags().String(FlagNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain")
c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block")
}
return cmds
}

// PostCommands adds common flags for commands to post tx
func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().String(FlagName, "", "Name of private key with which to sign")
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
c.Flags().String(FlagNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain")
}
return cmds
}
71 changes: 71 additions & 0 deletions client/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package client

import (
"fmt"

"github.com/pkg/errors"
"github.com/spf13/viper"

rpcclient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
cmn "github.com/tendermint/tmlibs/common"
)

// GetNode prepares a simple rpc.Client from the flags
func GetNode() (rpcclient.Client, error) {
uri := viper.GetString(FlagNode)
if uri == "" {
return nil, errors.New("Must define node using --node")
}
return rpcclient.NewHTTP(uri, "/websocket"), nil
}

// Broadcast the transaction bytes to Tendermint
func BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {

node, err := GetNode()
if err != nil {
return nil, err
}

res, err := node.BroadcastTxCommit(tx)
if err != nil {
return res, err
}

if res.CheckTx.Code != uint32(0) {
return res, errors.Errorf("CheckTx failed: (%d) %s",
res.CheckTx.Code,
res.CheckTx.Log)
}
if res.DeliverTx.Code != uint32(0) {
return res, errors.Errorf("DeliverTx failed: (%d) %s",
res.DeliverTx.Code,
res.DeliverTx.Log)
}
return res, err
}

// Query from Tendermint with the provided key and storename
func Query(key cmn.HexBytes, storeName string) (res []byte, err error) {

path := fmt.Sprintf("/%s/key", storeName)
node, err := GetNode()
if err != nil {
return res, err
}

opts := rpcclient.ABCIQueryOptions{
Height: viper.GetInt64(FlagHeight),
Trusted: viper.GetBool(FlagTrustNode),
}
result, err := node.ABCIQueryWithOptions(path, key, opts)
if err != nil {
return res, err
}
resp := result.Response
if resp.Code != uint32(0) {
return res, errors.Errorf("Query failed: (%d) %s", resp.Code, resp.Log)
}
return resp.Value, nil
}
92 changes: 92 additions & 0 deletions client/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package client

import (
"bufio"
"fmt"
"os"
"strings"

"github.com/bgentry/speakeasy"
isatty "github.com/mattn/go-isatty"
"github.com/pkg/errors"
)

// MinPassLength is the minimum acceptable password length
const MinPassLength = 8

// BufferStdin is used to allow reading prompts for stdin
// multiple times, when we read from non-tty
func BufferStdin() *bufio.Reader {
return bufio.NewReader(os.Stdin)
}

// GetPassword will prompt for a password one-time (to sign a tx)
// It enforces the password length
func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
if inputIsTty() {
pass, err = speakeasy.Ask(prompt)
} else {
pass, err = readLineFromBuf(buf)
}
if err != nil {
return "", err
}
if len(pass) < MinPassLength {
return "", errors.Errorf("Password must be at least %d characters", MinPassLength)
}
return pass, nil
}

// GetSeed will request a seed phrase from stdin and trims off
// leading/trailing spaces
func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) {
if inputIsTty() {
fmt.Println(prompt)
}
seed, err = readLineFromBuf(buf)
seed = strings.TrimSpace(seed)
return
}

// GetCheckPassword will prompt for a password twice to verify they
// match (for creating a new password).
// It enforces the password length. Only parses password once if
// input is piped in.
func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error) {
// simple read on no-tty
if !inputIsTty() {
return GetPassword(prompt, buf)
}

// TODO: own function???
pass, err := GetPassword(prompt, buf)
if err != nil {
return "", err
}
pass2, err := GetPassword(prompt2, buf)
if err != nil {
return "", err
}
if pass != pass2 {
return "", errors.New("Passphrases don't match")
}
return pass, nil
}

// inputIsTty returns true iff we have an interactive prompt,
// where we can disable echo and request to repeat the password.
// If false, we can optimize for piped input from another command
func inputIsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
}

// readLineFromBuf reads one line from stdin.
// Subsequent calls reuse the same buffer, so we don't lose
// any input when reading a password twice (to verify)
func readLineFromBuf(buf *bufio.Reader) (string, error) {
pass, err := buf.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(pass), nil
}
23 changes: 23 additions & 0 deletions client/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package client

import (
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
dbm "github.com/tendermint/tmlibs/db"
)

// GetKeyBase initializes a keybase based on the configuration
func GetKeyBase(db dbm.DB) keys.Keybase {
keybase := keys.New(
db,
words.MustLoadCodec("english"),
)
return keybase
}

// MockKeyBase generates an in-memory keybase that will be discarded
// useful for --dry-run to generate a seed phrase without
// storing the key
func MockKeyBase() keys.Keybase {
return GetKeyBase(dbm.NewMemDB())
}
Loading

0 comments on commit 5494a4e

Please sign in to comment.